@bitkyc08/opencodex 2.40.0 → 2.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/gui/dist/assets/index-BU1tE0sr.js +112 -0
- package/gui/dist/assets/index-DL9-iS6J.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/meta.svg +1 -0
- package/package.json +4 -3
- package/src/adapters/cursor/catalog.ts +71 -29
- package/src/adapters/cursor/claude-id.ts +76 -0
- package/src/adapters/cursor/discovery.ts +16 -3
- package/src/adapters/cursor/effort-map.ts +27 -12
- package/src/adapters/cursor/protobuf-request.ts +41 -21
- package/src/adapters/google.ts +39 -2
- package/src/adapters/identity.ts +8 -2
- package/src/adapters/openai-responses.ts +57 -4
- package/src/bridge.ts +25 -3
- package/src/cli/account-auth.ts +28 -3
- package/src/cli/account-extended.ts +7 -1
- package/src/cli/capabilities.ts +2 -2
- package/src/cli/claude.ts +11 -2
- package/src/cli/connect.ts +7 -1
- package/src/cli/observe.ts +3 -1
- package/src/cli/registry.ts +1 -1
- package/src/cli/status.ts +19 -4
- package/src/client/connect.ts +5 -1
- package/src/client/hub-client.ts +29 -5
- package/src/clients/config-export.ts +12 -2
- package/src/codex/auth-api.ts +102 -9
- package/src/codex/catalog/aggregation.ts +8 -0
- package/src/codex/catalog/effort.ts +15 -2
- package/src/codex/catalog/metadata.ts +119 -9
- package/src/codex/catalog/native-models.ts +71 -0
- package/src/codex/catalog/parsing.ts +5 -3
- package/src/codex/catalog/provider-fetch.ts +166 -28
- package/src/codex/catalog.ts +1 -1
- package/src/codex/convergence-types.ts +1 -0
- package/src/codex/data/upstream-models.json +169 -0
- package/src/codex/desired-state.ts +18 -11
- package/src/codex/inject.ts +96 -6
- package/src/codex/injected-marker.ts +30 -4
- package/src/codex/journal.ts +14 -0
- package/src/combos/failover.ts +185 -6
- package/src/combos/index.ts +6 -0
- package/src/combos/resolve.ts +43 -6
- package/src/config.ts +5 -1
- package/src/generated/compatibility-version.json +115 -83
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/sync.ts +10 -2
- package/src/integrations/cursor-effort-table.ts +143 -0
- package/src/integrations/state.ts +1 -1
- package/src/integrations/writer.ts +2 -2
- package/src/lib/app-owned-memory-stores.ts +27 -8
- package/src/lib/bounded-body.ts +16 -1
- package/src/oauth/account-quota-rank.ts +40 -1
- package/src/oauth/chatgpt-device.ts +187 -0
- package/src/oauth/chatgpt.ts +31 -4
- package/src/oauth/generic-account-failover.ts +2 -2
- package/src/oauth/index.ts +24 -3
- package/src/oauth/log.ts +3 -0
- package/src/oauth/meta-muse.ts +235 -0
- package/src/providers/antigravity-models.ts +71 -13
- package/src/providers/command-code-efforts.ts +15 -0
- package/src/providers/free-directory.ts +4 -1
- package/src/providers/muse-subscription-usage.ts +95 -0
- package/src/providers/quota.ts +96 -0
- package/src/providers/registry.ts +116 -8
- package/src/responses/code-mode-helper-compat.ts +4 -1
- package/src/responses/state.ts +5 -4
- package/src/server/auth-cors.ts +241 -56
- package/src/server/chat-completions.ts +11 -2
- package/src/server/chat-native.ts +30 -4
- package/src/server/claude-messages.ts +17 -3
- package/src/server/effort-row.ts +131 -0
- package/src/server/index.ts +82 -45
- package/src/server/live.ts +18 -4
- package/src/server/management/api-key-rotation.ts +2 -1
- package/src/server/management/api-key-usage.ts +97 -43
- package/src/server/management/context.ts +3 -0
- package/src/server/management/cursor-integration-routes.ts +36 -7
- package/src/server/management/logs-usage-routes.ts +64 -87
- package/src/server/management/oauth-account-routes.ts +10 -3
- package/src/server/management/provider-routes.ts +218 -1
- package/src/server/management/route-registry.ts +1 -0
- package/src/server/management/usage-aggregate-cache.ts +464 -0
- package/src/server/management/usage-summary-cache.ts +4 -0
- package/src/server/models-capabilities.ts +60 -5
- package/src/server/responses/core.ts +95 -7
- package/src/server/responses/empty-completion-guard.ts +4 -0
- package/src/types/config.ts +10 -1
- package/src/types/request.ts +8 -0
- package/src/types/tools.ts +12 -9
- package/src/usage/expected-prices.ts +43 -7
- package/src/usage/ledger-scanner.ts +448 -0
- package/src/usage/log.ts +1 -1
- package/src/usage/summary.ts +915 -655
- package/src/web-search/index.ts +1 -1
- package/gui/dist/assets/index-BHe2rl_C.js +0 -112
- package/gui/dist/assets/index-CJSb3HPe.css +0 -1
|
@@ -68,12 +68,14 @@ import { resolvePassiveRouteSubjectId } from "../passive-route-linker";
|
|
|
68
68
|
import {
|
|
69
69
|
advanceComboAfterFailure,
|
|
70
70
|
comboDefaultEffort,
|
|
71
|
+
comboFailureCooldownScope,
|
|
71
72
|
comboFailureDecision,
|
|
72
73
|
comboIdFromRawBody,
|
|
73
74
|
comboRequestHasImageInput,
|
|
74
75
|
concreteComboRequestBody,
|
|
75
76
|
getCombo,
|
|
76
77
|
isComboTargetInCooldown,
|
|
78
|
+
comboCooldownRetryAfterSeconds,
|
|
77
79
|
NoAvailableComboTargetsError,
|
|
78
80
|
noteComboSuccess,
|
|
79
81
|
parseRetryAfterMs,
|
|
@@ -217,6 +219,9 @@ import {
|
|
|
217
219
|
waitForProviderRequestSlot,
|
|
218
220
|
} from "../../providers/request-pacing";
|
|
219
221
|
import { slugsEquivalent } from "../../providers/slug-codec";
|
|
222
|
+
import { isMuseSubscriptionUsagePayload, parseMuseSubscriptionUsage } from "../../providers/muse-subscription-usage";
|
|
223
|
+
import { hasPassiveAccountQuota, recordPassiveAccountQuota } from "../../providers/quota";
|
|
224
|
+
import { captureConfigGeneration } from "../../lib/state-store-sweeper";
|
|
220
225
|
import { applyOpenAiVirtualModel, resolveOpenAiCompactModel } from "../../providers/openai-virtual-models";
|
|
221
226
|
import { isUsageDebugEnabled } from "../../usage/debug";
|
|
222
227
|
import { readJsonRequestBody, DecompressedBodyTooLargeError, UnsupportedContentEncodingError } from "../request-decompress";
|
|
@@ -326,6 +331,7 @@ import {
|
|
|
326
331
|
restoreImageGenCallsInJson,
|
|
327
332
|
} from "../responses-image-gen-repair";
|
|
328
333
|
import { createResponsesModelPayloadRewrite, rewriteResponsesModelJson } from "../responses-model-rewrite";
|
|
334
|
+
import { parseRequestEffortRowId } from "../effort-row";
|
|
329
335
|
import {
|
|
330
336
|
collectSelfNamedNamespaceScrubAuthorization,
|
|
331
337
|
createSelfNamedToolCallNamespaceScrubRewrite,
|
|
@@ -1434,15 +1440,29 @@ export function decodeRequestErrorResponse(err: unknown, label: string): Respons
|
|
|
1434
1440
|
|
|
1435
1441
|
|
|
1436
1442
|
|
|
1437
|
-
export function comboUnavailableResponse(
|
|
1443
|
+
export function comboUnavailableResponse(
|
|
1444
|
+
message: string,
|
|
1445
|
+
options?: { retryAfter?: string | null },
|
|
1446
|
+
): Response {
|
|
1447
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1448
|
+
const retryAfter = options?.retryAfter?.trim();
|
|
1449
|
+
if (retryAfter && retryAfter.length > 0 && retryAfter.length <= 128) {
|
|
1450
|
+
headers.set("Retry-After", retryAfter);
|
|
1451
|
+
}
|
|
1438
1452
|
return new Response(
|
|
1439
1453
|
JSON.stringify({
|
|
1440
1454
|
error: { message, type: "server_error", code: "combo_unavailable" },
|
|
1441
1455
|
}),
|
|
1442
|
-
{ status: 503, headers
|
|
1456
|
+
{ status: 503, headers },
|
|
1443
1457
|
);
|
|
1444
1458
|
}
|
|
1445
1459
|
|
|
1460
|
+
function comboUnavailable(comboId: string, now = Date.now()): Response {
|
|
1461
|
+
return comboUnavailableResponse(`No available targets for combo: ${comboId}`, {
|
|
1462
|
+
retryAfter: comboCooldownRetryAfterSeconds(comboId, now),
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1446
1466
|
|
|
1447
1467
|
|
|
1448
1468
|
export interface ConsumedComboFailure {
|
|
@@ -2297,7 +2317,7 @@ export async function handleComboResponses(
|
|
|
2297
2317
|
config,
|
|
2298
2318
|
{ parentThreadId: inboundClientThreadId },
|
|
2299
2319
|
);
|
|
2300
|
-
return
|
|
2320
|
+
return comboUnavailable(comboId);
|
|
2301
2321
|
}
|
|
2302
2322
|
let recovered = false;
|
|
2303
2323
|
try {
|
|
@@ -2334,7 +2354,7 @@ export async function handleComboResponses(
|
|
|
2334
2354
|
}
|
|
2335
2355
|
|
|
2336
2356
|
if (!pick) {
|
|
2337
|
-
return
|
|
2357
|
+
return comboUnavailable(comboId);
|
|
2338
2358
|
}
|
|
2339
2359
|
// One immutable combo selection trace, before any child dispatch; child
|
|
2340
2360
|
// adoption below must never replace it with a concrete child route trace.
|
|
@@ -2541,7 +2561,13 @@ export async function handleComboResponses(
|
|
|
2541
2561
|
const nextPick = advanceComboAfterFailure(config, pick, {
|
|
2542
2562
|
retryAfter: failure.retryAfter,
|
|
2543
2563
|
now: Date.now(),
|
|
2564
|
+
cooldownScope: comboFailureCooldownScope(failure.response.status, failure.classificationText, {
|
|
2565
|
+
code: failure.upstreamCode,
|
|
2566
|
+
}),
|
|
2544
2567
|
eligible: payloadEligible,
|
|
2568
|
+
status: failure.response.status,
|
|
2569
|
+
code: failure.upstreamCode,
|
|
2570
|
+
message: failure.classificationText,
|
|
2545
2571
|
});
|
|
2546
2572
|
if (!nextPick) adoptFailedChildLog(childLog);
|
|
2547
2573
|
pick = nextPick;
|
|
@@ -2697,6 +2723,23 @@ async function handleResponsesInner(
|
|
|
2697
2723
|
}
|
|
2698
2724
|
return decodeRequestErrorResponse(err, "responses");
|
|
2699
2725
|
}
|
|
2726
|
+
// An effort row naming a table-less combo (`combo/x--high`) must reach the combo dispatcher
|
|
2727
|
+
// as its base id, so the selector is normalized here, before comboIdFromRawBody reads model.
|
|
2728
|
+
const comboEffortRow = !options.comboAttempt && body && typeof body === "object" && !Array.isArray(body)
|
|
2729
|
+
&& typeof (body as { model?: unknown }).model === "string"
|
|
2730
|
+
? parseRequestEffortRowId((body as { model: string }).model, config)
|
|
2731
|
+
: null;
|
|
2732
|
+
if (comboEffortRow) {
|
|
2733
|
+
const raw = body as Record<string, unknown>;
|
|
2734
|
+
raw.model = comboEffortRow.baseId;
|
|
2735
|
+
const rawReasoning = raw.reasoning;
|
|
2736
|
+
raw.reasoning = {
|
|
2737
|
+
...(rawReasoning && typeof rawReasoning === "object" && !Array.isArray(rawReasoning)
|
|
2738
|
+
? rawReasoning as Record<string, unknown>
|
|
2739
|
+
: {}),
|
|
2740
|
+
effort: comboEffortRow.effort,
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2700
2743
|
const comboId = !options.comboAttempt ? comboIdFromRawBody(body, config) : null;
|
|
2701
2744
|
if (comboId && Object.hasOwn(config.combos ?? {}, comboId)) {
|
|
2702
2745
|
options.onRequestBodyRead?.();
|
|
@@ -2751,6 +2794,20 @@ async function handleResponsesInner(
|
|
|
2751
2794
|
let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
|
|
2752
2795
|
try {
|
|
2753
2796
|
parsed = parseRequest(body);
|
|
2797
|
+
const effortRow = parseRequestEffortRowId(parsed.modelId, config);
|
|
2798
|
+
if (effortRow) {
|
|
2799
|
+
parsed.modelId = effortRow.baseId;
|
|
2800
|
+
parsed.options.reasoning = effortRow.effort;
|
|
2801
|
+
const raw = parsed._rawBody as Record<string, unknown>;
|
|
2802
|
+
const rawReasoning = raw.reasoning;
|
|
2803
|
+
raw.model = effortRow.baseId;
|
|
2804
|
+
raw.reasoning = {
|
|
2805
|
+
...(rawReasoning && typeof rawReasoning === "object" && !Array.isArray(rawReasoning)
|
|
2806
|
+
? rawReasoning as Record<string, unknown>
|
|
2807
|
+
: {}),
|
|
2808
|
+
effort: effortRow.effort,
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2754
2811
|
if (options.comboReplaySnapshot?.recoveredPlaintext) {
|
|
2755
2812
|
markBodyNonPersistable(parsed._rawBody);
|
|
2756
2813
|
}
|
|
@@ -2881,7 +2938,7 @@ async function handleResponsesInner(
|
|
|
2881
2938
|
logCtx.routeDecision = route.routeDecision;
|
|
2882
2939
|
} catch (err) {
|
|
2883
2940
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
2884
|
-
return
|
|
2941
|
+
return comboUnavailable(err.comboId);
|
|
2885
2942
|
}
|
|
2886
2943
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
2887
2944
|
// Persist the evaluation trace (per-candidate exclusions + the
|
|
@@ -2991,7 +3048,7 @@ async function handleResponsesInner(
|
|
|
2991
3048
|
logCtx.routeDecision = route.routeDecision;
|
|
2992
3049
|
} catch (err) {
|
|
2993
3050
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
2994
|
-
return
|
|
3051
|
+
return comboUnavailable(err.comboId);
|
|
2995
3052
|
}
|
|
2996
3053
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
2997
3054
|
logCtx.routeDecision = err.trace;
|
|
@@ -3115,7 +3172,7 @@ async function handleResponsesInner(
|
|
|
3115
3172
|
logCtx.routeDecision = route.routeDecision;
|
|
3116
3173
|
} catch (err) {
|
|
3117
3174
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
3118
|
-
return
|
|
3175
|
+
return comboUnavailable(err.comboId);
|
|
3119
3176
|
}
|
|
3120
3177
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
3121
3178
|
logCtx.routeDecision = err.trace;
|
|
@@ -3254,6 +3311,13 @@ async function handleResponsesInner(
|
|
|
3254
3311
|
// the request actually used, so a concurrent rotation cannot cool an innocent replacement.
|
|
3255
3312
|
let genericFailoverAccountId: string | null = null;
|
|
3256
3313
|
let genericFailovers = 0;
|
|
3314
|
+
/**
|
|
3315
|
+
* Config generation captured where the serving credential is RESOLVED, not where the
|
|
3316
|
+
* quota is written. A streaming turn is a long await, so a generation captured at write
|
|
3317
|
+
* time cannot see a config or account change that happened earlier in the same turn —
|
|
3318
|
+
* the case the fence exists for. Stays 0 for every provider without a passive quota.
|
|
3319
|
+
*/
|
|
3320
|
+
let passiveQuotaWriterGeneration = 0;
|
|
3257
3321
|
/**
|
|
3258
3322
|
* Apply a rotated account's FULL credential snapshot to the live route (#2568d).
|
|
3259
3323
|
*
|
|
@@ -3389,6 +3453,10 @@ async function handleResponsesInner(
|
|
|
3389
3453
|
if (isGenericFailoverProvider(route.providerName, route.provider)) {
|
|
3390
3454
|
genericFailoverAccountId = resolved.accountId;
|
|
3391
3455
|
}
|
|
3456
|
+
// Captured beside the account it fences, so the two can never disagree.
|
|
3457
|
+
if (hasPassiveAccountQuota(route.providerName)) {
|
|
3458
|
+
passiveQuotaWriterGeneration = captureConfigGeneration();
|
|
3459
|
+
}
|
|
3392
3460
|
if (route.providerName === "kiro") {
|
|
3393
3461
|
// `{}` is intentional: this is an account-scoped request with no stored routing metadata.
|
|
3394
3462
|
// Only genuinely accountless adapter calls leave the context undefined and use local/env fallback.
|
|
@@ -3834,7 +3902,27 @@ async function handleResponsesInner(
|
|
|
3834
3902
|
// check sees nothing undeclared, and the refused turn enters continuation state anyway. So the
|
|
3835
3903
|
// rejection is sticky for the whole turn, set from every parsed payload on the inspection side.
|
|
3836
3904
|
let inspectionSawUndeclaredTool = false;
|
|
3905
|
+
const passiveQuotaObserved = hasPassiveAccountQuota(route.providerName)
|
|
3906
|
+
&& route.provider.authMode === "oauth";
|
|
3837
3907
|
const noteInspectedPayload = (payload: unknown) => {
|
|
3908
|
+
// Meta reports subscription usage ONLY as an in-stream event; there is no endpoint
|
|
3909
|
+
// to poll (003 §E probed 17 paths, all 404). Observed here rather than behind a
|
|
3910
|
+
// dedicated inspector handler because onParsedPayload already reaches every
|
|
3911
|
+
// passthrough shape -- eager relay and both tee consumers -- through this one
|
|
3912
|
+
// function.
|
|
3913
|
+
//
|
|
3914
|
+
// Placed BEFORE the undeclared-tool early return below, which is load-bearing: that
|
|
3915
|
+
// guard latches for the rest of the turn once it fires, and a turn that tripped it
|
|
3916
|
+
// still legitimately reports usage.
|
|
3917
|
+
if (passiveQuotaObserved && isMuseSubscriptionUsagePayload(payload)) {
|
|
3918
|
+
const quota = parseMuseSubscriptionUsage(payload);
|
|
3919
|
+
// Read at EVENT time, not at handler construction: failover rebinds this, and the
|
|
3920
|
+
// quota belongs to the account that actually served the turn.
|
|
3921
|
+
const servingAccountId = genericFailoverAccountId;
|
|
3922
|
+
if (quota && servingAccountId) {
|
|
3923
|
+
recordPassiveAccountQuota(route.providerName, servingAccountId, quota, passiveQuotaWriterGeneration);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3838
3926
|
// Gated on the same flag as the guard itself: with no readable catalog (or a forward-auth
|
|
3839
3927
|
// provider) every name looks undeclared, and flipping this would stop recording continuation
|
|
3840
3928
|
// state for exactly the passthrough traffic the guard deliberately stands down for.
|
|
@@ -176,6 +176,9 @@ export function mergeUsage(
|
|
|
176
176
|
const contextTotalTokens = second.contextTotalTokens ?? first.contextTotalTokens;
|
|
177
177
|
const inputTokens = first.inputTokens + second.inputTokens;
|
|
178
178
|
const outputTokens = first.outputTokens + second.outputTokens;
|
|
179
|
+
// The attempt that produced the content owns the raw wire usage (openai/codex#41980);
|
|
180
|
+
// an empty first attempt may still be the only one that saw it.
|
|
181
|
+
const rawUsage = second.rawUsage ?? first.rawUsage;
|
|
179
182
|
return {
|
|
180
183
|
inputTokens,
|
|
181
184
|
outputTokens,
|
|
@@ -186,6 +189,7 @@ export function mergeUsage(
|
|
|
186
189
|
...(cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens } : {}),
|
|
187
190
|
...(reasoningOutputTokens !== undefined ? { reasoningOutputTokens } : {}),
|
|
188
191
|
...(first.estimated || second.estimated ? { estimated: true } : {}),
|
|
192
|
+
...(rawUsage !== undefined ? { rawUsage } : {}),
|
|
189
193
|
};
|
|
190
194
|
}
|
|
191
195
|
|
package/src/types/config.ts
CHANGED
|
@@ -348,7 +348,10 @@ export interface OcxConfig {
|
|
|
348
348
|
* the guess is wrong.
|
|
349
349
|
*/
|
|
350
350
|
oauthOpenBrowser?: boolean;
|
|
351
|
-
/**
|
|
351
|
+
/**
|
|
352
|
+
* @deprecated Compatibility-only limit for bounded legacy usage readers.
|
|
353
|
+
* `GET /api/usage` always aggregates the complete ledger.
|
|
354
|
+
*/
|
|
352
355
|
managementUsageMaxReadBytes?: number;
|
|
353
356
|
providers: Record<string, OcxProviderConfig>;
|
|
354
357
|
defaultProvider: string;
|
|
@@ -366,6 +369,12 @@ export interface OcxConfig {
|
|
|
366
369
|
};
|
|
367
370
|
/** Enable the shipped model alias patterns for providers without an override. */
|
|
368
371
|
defaultModelAliases?: boolean;
|
|
372
|
+
/**
|
|
373
|
+
* Opt-in Cursor Private Inference compatibility rows. When true, `/v1/models`
|
|
374
|
+
* adds `<base-id>--<effort>` selectors for reasoning-capable model ids absent
|
|
375
|
+
* from Cursor's built-in effort table. Omitted/false preserves discovery output.
|
|
376
|
+
*/
|
|
377
|
+
cursorEffortRows?: boolean;
|
|
369
378
|
/** Explicit top-level deletion intent used by stale whole-config rebases. */
|
|
370
379
|
configRebaseProvenance?: OcxConfigRebaseProvenance | Record<string, unknown>;
|
|
371
380
|
/** OpenAI provider-contract migration marker (v2 = single `openai` provider with account mode). */
|
package/src/types/request.ts
CHANGED
|
@@ -396,4 +396,12 @@ export interface OcxUsage {
|
|
|
396
396
|
cacheCreationInputTokens?: number;
|
|
397
397
|
reasoningOutputTokens?: number;
|
|
398
398
|
estimated?: boolean;
|
|
399
|
+
/**
|
|
400
|
+
* The raw upstream usage object for Responses-shaped upstreams (openai/codex#41980 parity):
|
|
401
|
+
* codex-rs preserves the complete `response.usage` object through its own pipeline, so fields
|
|
402
|
+
* the proxy does not model (subscription metadata, future counters) must survive the bridged /
|
|
403
|
+
* rebuilt `response.completed` too. Accounting paths read only the canonical fields above; the
|
|
404
|
+
* wire rebuild merges this object's unknown keys back under the normalized values.
|
|
405
|
+
*/
|
|
406
|
+
rawUsage?: Record<string, unknown>;
|
|
399
407
|
}
|
package/src/types/tools.ts
CHANGED
|
@@ -35,20 +35,23 @@ export function namespacedToolName(namespace: string | undefined, name: string):
|
|
|
35
35
|
* Codex unified-exec name normalization.
|
|
36
36
|
*
|
|
37
37
|
* Codex's code-mode shell tool is declared as `exec` (a freeform custom tool whose own
|
|
38
|
-
* description mentions the nested `await tools.exec_command(...)` helper).
|
|
39
|
-
*
|
|
40
|
-
* `
|
|
41
|
-
*
|
|
42
|
-
*
|
|
38
|
+
* description mentions the nested `await tools.exec_command(...)` helper). Some routed providers
|
|
39
|
+
* echo that helper name as the tool-call name, emitting `exec_command`, `write_stdin`, or
|
|
40
|
+
* `apply_patch` instead of the declared `exec`. Accept these nested helper names only when the
|
|
41
|
+
* request catalog actually declares `exec` and does not itself declare the emitted name (an MCP
|
|
42
|
+
* server may legitimately advertise one under its own namespace).
|
|
43
43
|
*/
|
|
44
44
|
const LEGACY_SHELL_BRIDGE_TOOL_NAMES = ["exec_command", "shell_command"] as const;
|
|
45
|
-
const CODE_MODE_HELPER_TOOL_NAMES = [
|
|
45
|
+
const CODE_MODE_HELPER_TOOL_NAMES = [
|
|
46
|
+
...LEGACY_SHELL_BRIDGE_TOOL_NAMES,
|
|
47
|
+
"write_stdin",
|
|
48
|
+
"apply_patch",
|
|
49
|
+
] as const;
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
52
|
* The one declared name that turns nested-helper normalization on. Declaring it is not just a
|
|
49
|
-
* name: it also decides whether an emitted
|
|
50
|
-
*
|
|
51
|
-
* genuine bare declaration.
|
|
53
|
+
* name: it also decides whether an emitted helper name is accepted as that shell tool, so callers
|
|
54
|
+
* that build declared-name sets must add it only for a genuine bare declaration.
|
|
52
55
|
*/
|
|
53
56
|
export const CODE_MODE_EXEC_TOOL_NAME = "exec";
|
|
54
57
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeCursorClaudeId } from "../adapters/cursor/claude-id";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Expected-price overlay for models whose jawcode cost rows are missing or all-zero
|
|
3
5
|
* (subscription/OAuth surfaces). Sourced from official pricing pages only
|
|
@@ -56,6 +58,10 @@ const GEMINI_36_FLASH: Cost4 = { input: 1.5, output: 7.5, cacheRead: 0.15, cache
|
|
|
56
58
|
// through 2026-12-31, stepping up to $1.50 / $7.50 on 2027-01-01. Revisit this row
|
|
57
59
|
// then — the promotional rate is dated on the pricing page, not open-ended.
|
|
58
60
|
const GEMINI_37_FLASH: Cost4 = { input: 0.75, output: 3.75, cacheRead: 0.075, cacheWrite: 0 };
|
|
61
|
+
// Gemini 3.8 Flash carries the same published promotional shape as 3.7 through 2026-12-31,
|
|
62
|
+
// rising to $1.50 / $7.50 on 2027-01-01. A SEPARATE constant on purpose: equal today, but
|
|
63
|
+
// aliasing them would silently drag 3.8 along if 3.7's row is ever re-verified differently.
|
|
64
|
+
const GEMINI_38_FLASH: Cost4 = { input: 0.75, output: 3.75, cacheRead: 0.075, cacheWrite: 0 };
|
|
59
65
|
const MINIMAX_M21_HIGHSPEED: Cost4 = { input: 0.6, output: 2.4, cacheRead: 0.03, cacheWrite: 0.375 };
|
|
60
66
|
const KIMI_K3: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3 };
|
|
61
67
|
const KIMI_K27_CODE: Cost4 = { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0.95 };
|
|
@@ -79,8 +85,19 @@ const ANTHROPIC_PRICING = "https://platform.claude.com/docs/en/about-claude/pric
|
|
|
79
85
|
|
|
80
86
|
const GEMINI_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-07-22); cacheWrite=0: storage is billed per-hour, not per-token";
|
|
81
87
|
const GEMINI_37_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-08-14); promotional rate through 2026-12-31, rises to 1.50/7.50 on 2027-01-01; cacheWrite=0: storage is billed per-hour, not per-token";
|
|
88
|
+
const GEMINI_38_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-09-03); promotional rate through 2026-12-31, rises to 1.50/7.50 on 2027-01-01; cacheWrite=0: storage is billed per-hour, not per-token";
|
|
82
89
|
const MINIMAX_PRICING = "https://platform.minimax.io/docs/guides/pricing-paygo";
|
|
83
90
|
const OPENAI_GPT56_PRICING = "https://developers.openai.com/api/docs/pricing";
|
|
91
|
+
const META_MODEL_PRICING = "https://dev.meta.ai/docs/pricing-rate-limits";
|
|
92
|
+
/*
|
|
93
|
+
* Shared by both Meta providers. Overlays resolve by EXACT provider id, so `meta-muse`
|
|
94
|
+
* cannot inherit `meta-model`'s rows — and an unpriced provider whose whole warning is
|
|
95
|
+
* "treat every call as billable" would report no cost at all.
|
|
96
|
+
*/
|
|
97
|
+
const META_MUSE_SPARK_13: Cost4 = { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 };
|
|
98
|
+
const META_MUSE_SPARK_13_CONTRIBUTOR: Cost4 = { input: 0.1, output: 0.2, cacheRead: 0.002, cacheWrite: 0 };
|
|
99
|
+
const META_SPARK_SOURCE = `Meta Model API published price ${META_MODEL_PRICING}`;
|
|
100
|
+
const META_SPARK_CONTRIBUTOR_SOURCE = `Meta Model API published Contributor-tier price ${META_MODEL_PRICING}; data-sharing discount tier`;
|
|
84
101
|
const DEEPSEEK_PRICING = "https://api-docs.deepseek.com/quick_start/pricing-details-usd; V4 Flash alias transition scheduled 2026-07-24 — re-verify after";
|
|
85
102
|
// Kimi official tables publish input/output/cache-hit only; cacheWrite is mapped to the
|
|
86
103
|
// cache-miss input price (Kimi auto-caches with no separate write billing). 2026-07-20 re-verified.
|
|
@@ -100,13 +117,8 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
100
117
|
// bundle collapses anthropic-apikey onto anthropic).
|
|
101
118
|
{ provider: "anthropic", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input`, verifiedAt: "2026-09-02", status: "verified" },
|
|
102
119
|
{ provider: "anthropic-apikey", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input`, verifiedAt: "2026-09-02", status: "verified" },
|
|
103
|
-
// Cursor
|
|
104
|
-
// the model-level vendor fallback only searches jawcode metadata, which has no Fable 5.1
|
|
105
|
-
// row yet, so each Cursor spelling needs its own overlay. Vendor list price, like the
|
|
106
|
-
// cursor/claude-opus-5 row.
|
|
120
|
+
// Cursor canonicalizes every Fable 5.1 spelling onto this sole overlay row.
|
|
107
121
|
{ provider: "cursor", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input; vendor list price applied to the Cursor surface`, verifiedAt: "2026-09-02", status: "verified-derived" },
|
|
108
|
-
{ provider: "cursor", modelId: "claude-fable-5.1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input; vendor list price applied to the Cursor surface`, verifiedAt: "2026-09-02", status: "verified-derived" },
|
|
109
|
-
{ provider: "cursor", modelId: "claude-5.1-fable", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input; vendor list price applied to the Cursor surface`, verifiedAt: "2026-09-02", status: "verified-derived" },
|
|
110
122
|
// claude-opus-5 is exposed by three providers but absent from the jawcode bundle, so
|
|
111
123
|
// cost resolution returned null and the Logs `~$` column rendered an em dash. The
|
|
112
124
|
// model-level vendor fallback only searches jawcode metadata, never overlays, so one
|
|
@@ -126,6 +138,10 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
126
138
|
// 3.7 Flash rides CCA, whose billing equivalence to the Developer API list price is
|
|
127
139
|
// not published, so this is `verified-derived` rather than `verified`: the number is
|
|
128
140
|
// proven, the claim that Antigravity charges it is inferred.
|
|
141
|
+
{ provider: "google-antigravity", modelId: "gemini-3.8-flash", cost4: GEMINI_38_FLASH, source: `derived: Gemini 3.8 Flash promotional rate through 2026-12-31 ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
|
|
142
|
+
{ provider: "google-antigravity", modelId: "gemini-3.8-flash-low", cost4: GEMINI_38_FLASH, source: `derived: gemini-3.8-flash ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
|
|
143
|
+
{ provider: "google-antigravity", modelId: "gemini-3.8-flash-medium", cost4: GEMINI_38_FLASH, source: `derived: gemini-3.8-flash ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
|
|
144
|
+
{ provider: "google-antigravity", modelId: "gemini-3.8-flash-high", cost4: GEMINI_38_FLASH, source: `derived: gemini-3.8-flash ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
|
|
129
145
|
{ provider: "google-antigravity", modelId: "gemini-3.7-flash", cost4: GEMINI_37_FLASH, source: `derived: Gemini 3.7 Flash promotional rate through 2026-12-31 ${GEMINI_37_PRICING}`, verifiedAt: "2026-08-14", status: "verified-derived" },
|
|
130
146
|
// Retained after the 3.6 retirement: historical usage.jsonl rows still carry these
|
|
131
147
|
// ids, and dropping the row would silently zero the cost of requests already made.
|
|
@@ -139,6 +155,19 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
139
155
|
{ provider: "openai-apikey", modelId: "gpt-5.6-sol-pro", cost4: GPT56_SOL, source: `collapsed base ID ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-03", status: "verified-derived" },
|
|
140
156
|
{ provider: "openai-apikey", modelId: "gpt-5.6-terra-pro", cost4: GPT56_TERRA, source: `collapsed base ID ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-03", status: "verified-derived" },
|
|
141
157
|
{ provider: "openai-apikey", modelId: "gpt-5.6-luna-pro", cost4: GPT56_LUNA, source: `collapsed base ID ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-03", status: "verified-derived" },
|
|
158
|
+
// Meta Model API direct provider. `meta-model` has no jawcode metadata alias, so an
|
|
159
|
+
// unpriced row falls through the whole resolution chain and the Logs cost column
|
|
160
|
+
// renders nothing — these exact overlays are the only source. Both are Meta's own
|
|
161
|
+
// published list prices for Meta's own endpoint (hence "verified", not derived), and
|
|
162
|
+
// they match the figures Command Code republishes for the same two models.
|
|
163
|
+
// cacheWrite=0: Meta publishes a cached-input price but no cache-write charge.
|
|
164
|
+
{ provider: "meta-model", modelId: "muse-spark-1.3", cost4: META_MUSE_SPARK_13, source: META_SPARK_SOURCE, verifiedAt: "2026-09-03", status: "verified" },
|
|
165
|
+
{ provider: "meta-model", modelId: "muse-spark-1.3-contributor", cost4: META_MUSE_SPARK_13_CONTRIBUTOR, source: META_SPARK_CONTRIBUTOR_SOURCE, verifiedAt: "2026-09-03", status: "verified" },
|
|
166
|
+
// Same endpoint, same list price, different credential. Meta does not authorize this
|
|
167
|
+
// reuse and settlement is not observable, so these are the public Model API rates as a
|
|
168
|
+
// conservative estimate — not evidence of how the call is actually billed.
|
|
169
|
+
{ provider: "meta-muse", modelId: "muse-spark-1.3", cost4: META_MUSE_SPARK_13, source: META_SPARK_SOURCE, verifiedAt: "2026-09-03", status: "verified-derived" },
|
|
170
|
+
{ provider: "meta-muse", modelId: "muse-spark-1.3-contributor", cost4: META_MUSE_SPARK_13_CONTRIBUTOR, source: META_SPARK_CONTRIBUTOR_SOURCE, verifiedAt: "2026-09-03", status: "verified-derived" },
|
|
142
171
|
// Daybreak aliases: priced as their current snapshots (red -> gpt-5.6-cyber,
|
|
143
172
|
// blue -> gpt-5.6-sol). The alias ids carry no rows of their own upstream, hence
|
|
144
173
|
// verified-derived. Blue deliberately reuses GPT56_SOL rather than duplicating the tuple.
|
|
@@ -163,6 +192,7 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
163
192
|
{ provider: "google", modelId: "gemini-3.6-flash", cost4: GEMINI_36_FLASH, source: GEMINI_PRICING, verifiedAt: "2026-07-22", status: "verified" },
|
|
164
193
|
// Developer API row: the price IS published for this surface, so `verified`.
|
|
165
194
|
{ provider: "google", modelId: "gemini-3.7-flash", cost4: GEMINI_37_FLASH, source: GEMINI_37_PRICING, verifiedAt: "2026-08-14", status: "verified" },
|
|
195
|
+
{ provider: "google", modelId: "gemini-3.8-flash", cost4: GEMINI_38_FLASH, source: GEMINI_38_PRICING, verifiedAt: "2026-09-03", status: "verified" },
|
|
166
196
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-preview", cost4: GEMINI_31_PRO, source: GEMINI_PRICING, verifiedAt: "2026-07-20", status: "verified" },
|
|
167
197
|
// Antigravity-bundled third-party models — derived from the underlying vendor's
|
|
168
198
|
// official API price (Antigravity itself bills via subscription quota).
|
|
@@ -236,8 +266,14 @@ export function findExpectedPriceOverlay(
|
|
|
236
266
|
overlays: readonly ExpectedPriceOverlay[] = EXPECTED_PRICE_OVERLAYS,
|
|
237
267
|
): ExpectedPriceOverlay | undefined {
|
|
238
268
|
const exact = overlays.filter(row => row.provider === provider && row.modelId === modelId);
|
|
239
|
-
|
|
269
|
+
const match = exact.find(row => row.status === "verified")
|
|
240
270
|
?? exact.find(row => row.status === "verified-derived");
|
|
271
|
+
if (match || provider !== "cursor") return match;
|
|
272
|
+
const canonicalBaseId = normalizeCursorClaudeId(modelId)?.canonicalBaseId;
|
|
273
|
+
if (!canonicalBaseId) return undefined;
|
|
274
|
+
const canonical = overlays.filter(row => row.provider === provider && row.modelId === canonicalBaseId);
|
|
275
|
+
return canonical.find(row => row.status === "verified")
|
|
276
|
+
?? canonical.find(row => row.status === "verified-derived");
|
|
241
277
|
}
|
|
242
278
|
|
|
243
279
|
/** OpenAI Fast price multipliers retained as a compatibility export. */
|