feat(providers): refresh Fable, Cursor, and Devin catalogs (#12367)

Validado em lote numa worktree combinada com #12524, #12538, #12277 e #12367 sobre o tip de release/v3.8.51: os quatro boardaram sem conflito (áreas disjuntas — zai-web, nvidia, clova, cursor/devin/fable). typecheck:core limpo, check:provider-consistency OK (272 entradas REGISTRY, 355 providers canônicos), check:known-symbols OK, e 305/305 nos testes tocados pelos quatro PRs. Os IDs de modelo adicionados foram conferidos individualmente. Obrigado, @backryun.
This commit is contained in:
backryun
2026-09-03 19:50:41 +09:00
committed by GitHub
parent 82c64d76d3
commit 6795783228
42 changed files with 1835 additions and 921 deletions

View File

@@ -1,5 +1,6 @@
import { providerUsesAuthoritativeLiveCatalog } from "@omniroute/open-sse/config/providerRegistry";
import { PROVIDER_ID_TO_ALIAS } from "@omniroute/open-sse/config/providerModels.ts";
import { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog";
import {
getSyncedAvailableModels,
getSyncedAvailableModelsByConnection,
@@ -76,6 +77,19 @@ function collectModelsForConnections(
return Array.from(models.values());
}
function enrichCursorCatalog(
providerId: string,
models: SyncedAvailableModel[]
): SyncedAvailableModel[] {
// An empty sync means discovery has not completed (or failed). Do not let the
// synthetic Cursor auto-router rows turn that empty state into an authoritative
// catalog, otherwise every built-in model is incorrectly marked unavailable.
if (models.length === 0) return models;
return providerId === "cursor" || providerId === "cursor-api"
? ensureCursorAutoCatalogEntry(models)
: models;
}
/**
* Return the unioned synced catalog belonging only to active connections.
*
@@ -105,7 +119,10 @@ export async function getActiveSyncedCatalog(providerId: string): Promise<Active
.filter((connection): connection is ProviderConnectionRef => connection !== null)
.map((connection) => connection.id);
const models = collectModelsForConnections(modelsByConnection, activeConnectionIds);
const models = enrichCursorCatalog(
storedProviderId,
collectModelsForConnections(modelsByConnection, activeConnectionIds)
);
if (models.length > 0) {
return {
authoritative: providerUsesAuthoritativeLiveCatalog(providerId),
@@ -125,7 +142,13 @@ export async function getActiveSyncedCatalog(providerId: string): Promise<Active
// NON-authoritative: #9294's live-catalog gating is about what an active
// connection actually serves, so a node-backed catalog must inform metadata
// without ever being used to reject a model as unavailable.
return { authoritative: false, models: await getSyncedAvailableModels(storedProviderId) };
return {
authoritative: false,
models: enrichCursorCatalog(
storedProviderId,
await getSyncedAvailableModels(storedProviderId)
),
};
} catch {
return { authoritative: false, models: [] };
}
@@ -161,7 +184,10 @@ export async function getAllActiveSyncedModels(): Promise<Record<string, SyncedA
Array.from(connectionIdsByProvider.entries()).map(async ([providerId, connectionIds]) => {
const modelsByConnection = await getSyncedAvailableModelsByConnection(providerId);
const models = collectModelsForConnections(modelsByConnection, connectionIds);
const models = enrichCursorCatalog(
providerId,
collectModelsForConnections(modelsByConnection, connectionIds)
);
if (models.length > 0) {
result[providerId] = models;

View File

@@ -8,7 +8,6 @@ export type CursorAutoCatalogEntry = {
id: string;
name: string;
owned_by?: string;
[key: string]: unknown;
};
export const CURSOR_AUTO_ROUTER_VARIANT_IDS = [
@@ -26,10 +25,56 @@ const CURSOR_AUTO_ROUTER_VARIANT_NAMES: Record<
"auto-intelligence": "Auto (intelligence)",
};
const CURSOR_ONE_MILLION_CONTEXT = 1_000_000;
const CURSOR_CONTEXT_EFFORT = "(?:low|medium|high|xhigh|max)";
const CURSOR_ONE_MILLION_MODEL_PATTERNS = [
new RegExp(`^claude-fable-5-1-thinking-${CURSOR_CONTEXT_EFFORT}$`),
new RegExp(`^claude-opus-5-(?:thinking-)?${CURSOR_CONTEXT_EFFORT}(?:-fast)?$`),
new RegExp(`^claude-opus-4-8-(?:thinking-)?${CURSOR_CONTEXT_EFFORT}(?:-fast)?$`),
new RegExp(`^claude-sonnet-5-(?:thinking-)?${CURSOR_CONTEXT_EFFORT}$`),
new RegExp(`^claude-4\\.6-sonnet-${CURSOR_CONTEXT_EFFORT}(?:-thinking)?$`),
new RegExp(`^gpt-5\\.6-(?:sol|terra|luna)-(?:none|${CURSOR_CONTEXT_EFFORT})$`),
] as const;
const CURSOR_CONTEXT_FAMILY_NAMES = [
"Claude Fable 5.1",
"Claude Opus 5",
"Claude Opus 4.8",
"Claude Sonnet 5",
"Claude Sonnet 4.6",
"GPT-5.6 Sol",
"GPT-5.6 Terra",
"GPT-5.6 Luna",
] as const;
function supportsCursorOneMillionContext(id: string): boolean {
return CURSOR_ONE_MILLION_MODEL_PATTERNS.some((pattern) => pattern.test(id));
}
function oneMillionDisplayName(name: string): string {
const family = CURSOR_CONTEXT_FAMILY_NAMES.find((candidate) => name.startsWith(candidate));
return family ? `${family} 1M${name.slice(family.length)}` : `${name} 1M`;
}
/** Cursor auto-router: catalog id `auto`, wire id `default`. Always keep `auto` visible. */
export function ensureCursorAutoCatalogEntry<T extends CursorAutoCatalogEntry>(models: T[]): T[] {
const byId = new Map(models.map((m) => [m.id, m]));
const out = [...models];
const out: T[] = [];
for (const model of models) {
const oneMillionId = `${model.id}-1m`;
if (supportsCursorOneMillionContext(model.id) && !byId.has(oneMillionId)) {
const oneMillionEntry = {
...model,
id: oneMillionId,
name: oneMillionDisplayName(model.name),
contextLength: CURSOR_ONE_MILLION_CONTEXT,
} as T;
out.push(oneMillionEntry);
byId.set(oneMillionId, oneMillionEntry);
}
out.push(model);
}
if (!byId.has("auto")) {
const defaultEntry = byId.get("default");

View File

@@ -9,8 +9,11 @@ import {
humanizeCursorModelId,
type CursorAgentModelEntry,
} from "@/lib/providerModels/cursorAgent";
import { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog";
import { getConsistentMachineId } from "@/shared/utils/machineId";
export { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog";
export type FetchCursorAvailableModelsOptions = {
accessToken: string;
machineId?: string | null;
@@ -40,6 +43,45 @@ function pickModelName(entry: Record<string, unknown>, id: string): string {
return humanizeCursorModelId(id);
}
function collectArrays(record: Record<string, unknown>, keys: string[]): unknown[] {
return keys.flatMap((key) => (Array.isArray(record[key]) ? record[key] : []));
}
function collectModelCandidates(payload: unknown): unknown[] {
const root = asRecord(payload) ?? {};
const candidates = collectArrays(root, [
"models",
"availableModels",
"available_models",
"model",
]);
const nestedModels = asRecord(root.models);
if (nestedModels) candidates.push(...collectArrays(nestedModels, ["models", "items", "list"]));
if (Array.isArray(payload)) candidates.push(...payload);
return candidates;
}
function isUnavailableModel(entry: Record<string, unknown>): boolean {
return (
entry.disabled === true ||
entry.isDisabled === true ||
entry.usable === false ||
entry.isUsable === false
);
}
function normalizeModelCandidate(item: unknown): CursorAgentModelEntry | null {
if (typeof item === "string") {
const id = item.trim();
return id ? { id, name: humanizeCursorModelId(id), owned_by: "cursor" } : null;
}
const entry = asRecord(item);
if (!entry || isUnavailableModel(entry)) return null;
const id = pickModelId(entry);
return id ? { id, name: pickModelName(entry, id), owned_by: "cursor" } : null;
}
/**
* Normalize AvailableModels JSON (Connect JSON or protobuf-json) into catalog rows.
* Exported for unit tests.
@@ -48,99 +90,18 @@ function pickModelName(entry: Record<string, unknown>, id: string): string {
* only). OmniRoute clients request `cu/auto`; resolveRequestedModel maps it to `default`.
*/
export function normalizeCursorAvailableModelsPayload(payload: unknown): CursorAgentModelEntry[] {
const root = asRecord(payload) ?? {};
const candidates: unknown[] = [];
for (const key of ["models", "availableModels", "available_models", "model"]) {
const v = root[key];
if (Array.isArray(v)) candidates.push(...v);
}
// Some Connect JSON responses nest under `models.models` or similar
const nestedModels = asRecord(root.models);
if (nestedModels) {
for (const key of ["models", "items", "list"]) {
const v = nestedModels[key];
if (Array.isArray(v)) candidates.push(...v);
}
}
if (Array.isArray(payload)) candidates.push(...payload);
const seen = new Set<string>();
const out: CursorAgentModelEntry[] = [];
for (const item of candidates) {
if (typeof item === "string" && item.trim()) {
const id = item.trim();
if (seen.has(id)) continue;
seen.add(id);
out.push({ id, name: humanizeCursorModelId(id), owned_by: "cursor" });
continue;
}
const rec = asRecord(item);
if (!rec) continue;
const id = pickModelId(rec);
if (!id || seen.has(id)) continue;
// Prefer usable / non-disabled when flags exist
if (rec.disabled === true || rec.isDisabled === true) continue;
if (rec.usable === false || rec.isUsable === false) continue;
seen.add(id);
out.push({ id, name: pickModelName(rec, id), owned_by: "cursor" });
for (const item of collectModelCandidates(payload)) {
const model = normalizeModelCandidate(item);
if (!model || seen.has(model.id)) continue;
seen.add(model.id);
out.push(model);
}
return ensureCursorAutoCatalogEntry(out);
}
/** OpenCodex-style Cursor Router optimization modes (catalog ids). */
export const CURSOR_AUTO_ROUTER_VARIANT_IDS = [
"auto-cost",
"auto-balance",
"auto-intelligence",
] as const;
const CURSOR_AUTO_ROUTER_VARIANT_NAMES: Record<
(typeof CURSOR_AUTO_ROUTER_VARIANT_IDS)[number],
string
> = {
"auto-cost": "Auto (cost)",
"auto-balance": "Auto (balance)",
"auto-intelligence": "Auto (intelligence)",
};
/** Cursor auto-router: catalog id `auto`, wire id `default`. Always keep `auto` visible. */
export function ensureCursorAutoCatalogEntry(
models: CursorAgentModelEntry[]
): CursorAgentModelEntry[] {
const byId = new Map(models.map((m) => [m.id, m]));
const out = [...models];
if (!byId.has("auto")) {
const defaultEntry = byId.get("default");
const autoEntry: CursorAgentModelEntry = {
id: "auto",
name: defaultEntry?.name || "Auto (current, default)",
owned_by: "cursor",
};
// Prefer `auto` as the public id; keep `default` for wire-compat listings.
out.unshift(autoEntry);
byId.set("auto", autoEntry);
}
// Always expose Cost/Balance/Intelligence router modes (OpenCodex CURSOR_ROUTER_MODEL_IDS).
for (const id of CURSOR_AUTO_ROUTER_VARIANT_IDS) {
if (byId.has(id)) continue;
const entry: CursorAgentModelEntry = {
id,
name: CURSOR_AUTO_ROUTER_VARIANT_NAMES[id],
owned_by: "cursor",
};
out.push(entry);
byId.set(id, entry);
}
return out;
}
export async function fetchCursorAvailableModels(
options: FetchCursorAvailableModelsOptions
): Promise<CursorAgentModelEntry[]> {

View File

@@ -35,6 +35,7 @@ const STATIC_MODEL_PROVIDERS: Record<string, () => Array<{ id: string; name: str
],
antigravity: () => ANTIGRAVITY_PUBLIC_MODELS.map((model) => ({ ...model })),
claude: () => [
{ id: "claude-fable-5-1", name: "Claude Fable 5.1" },
{ id: "claude-fable-5", name: "Claude Fable 5" },
{ id: "claude-opus-5", name: "Claude Opus 5" },
{ id: "claude-opus-4-8", name: "Claude Opus 4.8" },

View File

@@ -45,7 +45,7 @@ export const CLI_TOOLS: Record<string, CliCatalogEntry> = {
name: "Claude Fable",
alias: "fable",
envKey: "ANTHROPIC_DEFAULT_FABLE_MODEL",
defaultValue: _cc.fable ? `cc/${_cc.fable}` : "cc/claude-fable-5",
defaultValue: _cc.fable ? `cc/${_cc.fable}` : "cc/claude-fable-5-1",
isTopLevel: true,
},
{

View File

@@ -24,9 +24,13 @@ export interface ModelSpec {
// Model ONLY supports adaptive thinking: manual extended thinking was removed. Sending
// `thinking.type:"enabled"` or any `thinking.budget_tokens` returns HTTP 400; reasoning
// is steered exclusively by `output_config.effort` (low/medium/high/xhigh/max). True for
// Claude Opus 4.7 and later (Opus 4.7/4.8/5, Fable 5). Per Anthropic's migration guide,
// Claude Opus 4.7 and later (Opus 4.7/4.8/5, Fable 5/5.1). Per Anthropic's migration guide,
// any request that tries to set a fixed thinking budget gets a 400 error.
adaptiveThinkingOnly?: boolean;
// The model rejects tool_choice values that require a tool call. Keep tools available,
// but normalize a forced choice to the default auto behavior before dispatch. Fable 5.1 always runs
// adaptive thinking, so forced tool use cannot be combined with any valid request.
rejectsForcedToolChoice?: boolean;
// Highest effort accepted while `thinking.type:"disabled"` is present. Claude Opus 5
// rejects disabled thinking with xhigh/max, while accepting it through high.
maxEffortWhenThinkingDisabled?: "high";
@@ -371,6 +375,21 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
aliases: BEDROCK_CLAUDE_ALIASES("claude-opus-4-7", "claude-opus-4.7"),
},
// ── Claude Fable 5.1 ────────────────────────────────────────────
"claude-fable-5-1": {
maxOutputTokens: 128000,
contextWindow: 1000000,
defaultThinkingBudget: 32000,
thinkingBudgetCap: 120000,
supportsThinking: true,
supportsTools: true,
supportsVision: true,
rejectsThinkingDisabled: true,
adaptiveThinkingOnly: true,
rejectsForcedToolChoice: true,
aliases: BEDROCK_CLAUDE_ALIASES("claude-fable-5-1"),
},
// ── Claude Fable 5 ──────────────────────────────────────────────
"claude-fable-5": {
maxOutputTokens: 128000,
@@ -849,9 +868,39 @@ export function normalizeThinkingForModel<T extends Record<string, unknown>>(
getModelSpec(modelId)?.rejectsThinkingDisabled
) {
const { thinking: _omitted, ...rest } = body as Record<string, unknown>;
return rest as T;
return normalizeForcedToolChoiceForModel(rest as T, modelId);
}
return body;
return normalizeForcedToolChoiceForModel(body, modelId);
}
/**
* Normalize tool-choice constraints that a resolved model cannot accept.
*
* Claude Fable 5.1 always uses adaptive thinking and rejects tool choices that force
* either any tool or one named tool. Preserve the declared tools and every unrelated
* request field, but drop the choice to select the default `auto` behavior so routing a
* request to Fable 5.1 does not turn a recoverable preference into an upstream 400.
*/
export function normalizeForcedToolChoiceForModel<T extends Record<string, unknown>>(
body: T,
modelId: string
): T {
if (!getModelSpec(modelId)?.rejectsForcedToolChoice) return body;
const toolChoice = body.tool_choice;
const forced =
toolChoice === "required" ||
toolChoice === "any" ||
(toolChoice !== null &&
typeof toolChoice === "object" &&
!Array.isArray(toolChoice) &&
["any", "tool", "function"].includes(
String((toolChoice as Record<string, unknown>).type || "").toLowerCase()
));
if (!forced) return body;
const { tool_choice: _omitted, ...rest } = body;
return rest as T;
}
export function capMaxOutputTokens(modelId: string, requested?: number): number | undefined {

View File

@@ -7,10 +7,12 @@ import { DEFAULT_PRICING_OAUTH } from "./oauth-subscriptions";
import { DEFAULT_PRICING_FRONTIER } from "./frontier-labs";
import { DEFAULT_PRICING_INFERENCE } from "./inference-hosts";
import { DEFAULT_PRICING_REGIONAL } from "./regional";
import { DEFAULT_PRICING_DEVIN } from "./devin";
export const DEFAULT_PRICING = {
...DEFAULT_PRICING_OAUTH,
...DEFAULT_PRICING_FRONTIER,
...DEFAULT_PRICING_INFERENCE,
...DEFAULT_PRICING_REGIONAL,
...DEFAULT_PRICING_DEVIN,
};

View File

@@ -0,0 +1,142 @@
type DevinTokenPricing = {
input: number;
cached: number;
output: number;
};
const QUALITY_EFFORTS = ["max", "xhigh", "high", "medium", "low"] as const;
const GPT_EFFORTS = ["max", "xhigh", "high", "medium", "low", "none"] as const;
function variantIds(base: string, efforts: readonly string[]): string[] {
return efforts.map((effort) => `${base}-${effort}`);
}
function fastVariantIds(base: string): string[] {
return QUALITY_EFFORTS.map((effort) => `${base}-${effort}-fast`);
}
function priorityVariantIds(base: string): string[] {
return GPT_EFFORTS.map((effort) => `${base}-${effort}-priority`);
}
function priced(ids: readonly string[], pricing: DevinTokenPricing) {
return Object.fromEntries(ids.map((id) => [id, pricing]));
}
const CLAUDE_FABLE_5_1 = { input: 10, cached: 0.25, output: 50 };
const CLAUDE_OPUS = { input: 5, cached: 0.5, output: 25 };
const CLAUDE_OPUS_FAST = { input: 10, cached: 1, output: 50 };
const CLAUDE_SONNET_5 = { input: 2, cached: 0.2, output: 10 };
const CLAUDE_SONNET_4_6 = { input: 3, cached: 0.3, output: 15 };
const CLAUDE_HAIKU_4_5 = { input: 1, cached: 0.1, output: 5 };
const GPT_5_6_SOL = { input: 4, cached: 0.4, output: 20 };
const GPT_5_6_SOL_FAST = { input: 8, cached: 0.8, output: 40 };
const GPT_5_6_TERRA = { input: 2, cached: 0.2, output: 12 };
const GPT_5_6_TERRA_FAST = { input: 4, cached: 0.4, output: 24 };
const GPT_5_6_LUNA = { input: 0.2, cached: 0.02, output: 1.2 };
const GPT_5_6_LUNA_FAST = { input: 0.4, cached: 0.04, output: 2.4 };
/**
* Exact per-UID rates returned by Devin's authenticated live catalog on
* 2026-09-02. Rates are USD per one million tokens.
*/
export const DEVIN_MODEL_PRICING: Record<string, DevinTokenPricing> = {
...priced(variantIds("claude-fable-5-1", QUALITY_EFFORTS), CLAUDE_FABLE_5_1),
...priced(variantIds("claude-opus-5", QUALITY_EFFORTS), CLAUDE_OPUS),
...priced(fastVariantIds("claude-opus-5"), CLAUDE_OPUS_FAST),
...priced(variantIds("claude-opus-4-8", QUALITY_EFFORTS), CLAUDE_OPUS),
...priced(fastVariantIds("claude-opus-4-8"), CLAUDE_OPUS_FAST),
...priced(variantIds("claude-sonnet-5", QUALITY_EFFORTS), CLAUDE_SONNET_5),
...priced(
[
"claude-sonnet-4-6",
"claude-sonnet-4-6-thinking",
"claude-sonnet-4-6-1m",
"claude-sonnet-4-6-thinking-1m",
],
CLAUDE_SONNET_4_6
),
MODEL_PRIVATE_11: CLAUDE_HAIKU_4_5,
...priced(variantIds("gpt-5-6-sol", GPT_EFFORTS), GPT_5_6_SOL),
...priced(priorityVariantIds("gpt-5-6-sol"), GPT_5_6_SOL_FAST),
...priced(variantIds("gpt-5-6-terra", GPT_EFFORTS), GPT_5_6_TERRA),
...priced(priorityVariantIds("gpt-5-6-terra"), GPT_5_6_TERRA_FAST),
...priced(variantIds("gpt-5-6-luna", GPT_EFFORTS), GPT_5_6_LUNA),
...priced(priorityVariantIds("gpt-5-6-luna"), GPT_5_6_LUNA_FAST),
...priced(variantIds("kimi-k3", ["max", "high", "low"]), {
input: 3,
cached: 0.3,
output: 15,
}),
"kimi-k2-7": { input: 0.95, cached: 0.19, output: 4 },
...priced(variantIds("glm-5-3", ["max", "high", "low"]), {
input: 1.4,
cached: 0.26,
output: 4.4,
}),
...priced(variantIds("glm-5-3-flash", ["max", "high", "low"]), {
input: 0.15,
cached: 0.03,
output: 0.5,
}),
...priced(["swe-1-7", "swe-1-7-medium"], {
input: 0.5,
cached: 0.2,
output: 2.5,
}),
...priced(["swe-1-7-lightning", "swe-1-7-lightning-medium"], {
input: 2.5,
cached: 1,
output: 12.5,
}),
adaptive: { input: 0.5, cached: 0.1, output: 2 },
...priced(variantIds("grok-4-6", ["xhigh", "high", "medium", "low"]), {
input: 2,
cached: 0.3,
output: 6,
}),
...priced(variantIds("inkling", ["max", "xhigh", "high", "medium", "low", "none"]), {
input: 1.4,
cached: 0.26,
output: 4.4,
}),
...priced(variantIds("deepseek-v4-flash", ["max", "high", "low"]), {
input: 0.14,
cached: 0.03,
output: 0.28,
}),
...priced(variantIds("nemotron-3-ultra", ["high", "medium", "none"]), {
input: 0.6,
cached: 0.12,
output: 2.4,
}),
...priced(variantIds("gemini-3-7-flash", ["high", "medium", "low"]), {
input: 1.5,
cached: 0.15,
output: 7.5,
}),
...priced(variantIds("gemini-3-1-pro", ["high", "low"]), {
input: 2,
cached: 0.2,
output: 12,
}),
...priced(variantIds("deepseek-v4-pro", ["max", "high", "low"]), {
input: 1.32,
cached: 0.04,
output: 3.96,
}),
};
// Each transport gets its own provider namespace. They share today's upstream
// rate snapshot, but can diverge independently if Devin changes one channel.
export const DEFAULT_PRICING_DEVIN = {
"devin-cli": { ...DEVIN_MODEL_PRICING },
dv: { ...DEVIN_MODEL_PRICING },
"devin-desktop": { ...DEVIN_MODEL_PRICING },
"devin-cli-agentic": { ...DEVIN_MODEL_PRICING },
dva: { ...DEVIN_MODEL_PRICING },
};

View File

@@ -8,6 +8,7 @@ import {
GPT_5_6_LUNA_PRICING,
GPT_5_6_SOL_PRICING,
GPT_5_6_TERRA_PRICING,
CLAUDE_FABLE_5_1_PRICING,
CLAUDE_FABLE_5_PRICING,
CLAUDE_OPUS_5_PRICING,
CLAUDE_OPUS_4_PRICING,
@@ -213,6 +214,7 @@ export const DEFAULT_PRICING_FRONTIER = {
// Common model IDs (without dates) used across providers
// Intentional duplicates of dot-notation variants (e.g. claude-opus-4.6)
// to cover hyphen-notation IDs (claude-opus-4-6) used by some clients
"claude-fable-5-1": CLAUDE_FABLE_5_1_PRICING,
"claude-fable-5": CLAUDE_FABLE_5_PRICING,
"claude-opus-5": CLAUDE_OPUS_5_PRICING,
"claude-sonnet-5": CLAUDE_SONNET_5_PRICING,

View File

@@ -3,6 +3,7 @@
* Pure data; merged by default-pricing.ts via spread (god-file decomposition; semantic split).
*/
import {
CLAUDE_FABLE_5_1_PRICING,
CLAUDE_OPUS_5_PRICING,
GEMINI_3_7_FLASH_PROMO_PRICING,
GPT_5_3_CODEX_PRICING,
@@ -20,6 +21,7 @@ const ANTIGRAVITY_GEMINI_3_7_PRICING = {
export const DEFAULT_PRICING_OAUTH = {
cc: {
"claude-fable-5-1": CLAUDE_FABLE_5_1_PRICING,
"claude-fable-5": {
input: 10.0,
output: 50.0,

View File

@@ -60,6 +60,14 @@ export const CLAUDE_FABLE_5_PRICING = {
cache_creation: 15.0,
};
export const CLAUDE_FABLE_5_1_PRICING = {
input: 10.0,
output: 50.0,
cached: 0.25,
reasoning: 50.0,
cache_creation: 12.5,
};
export const CLAUDE_OPUS_5_PRICING = {
input: 5.0,
output: 25.0,