@bitkyc08/opencodex 2.7.30 → 2.7.31
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.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +479 -0
- package/README.zh-CN.md +1 -1
- package/gui/dist/assets/index-BPa0R6EN.js +46 -0
- package/gui/dist/assets/{index-B-UauL1p.css → index-BY7KvJRB.css} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/cursor/discovery.ts +2 -2
- package/src/adapters/google.ts +7 -0
- package/src/codex/auth-api.ts +18 -0
- package/src/codex/catalog.ts +33 -10
- package/src/codex/routing.ts +85 -4
- package/src/combos/index.ts +1 -1
- package/src/combos/request.ts +26 -4
- package/src/combos/types.ts +4 -4
- package/src/lib/upstream-retry.ts +21 -0
- package/src/lib/winsw.ts +7 -1
- package/src/oauth/anthropic.ts +23 -1
- package/src/oauth/index.ts +83 -4
- package/src/oauth/local-token-detect.ts +3 -0
- package/src/oauth/store.ts +32 -0
- package/src/providers/antigravity-models.ts +23 -12
- package/src/providers/registry.ts +38 -16
- package/src/server/index.ts +3 -3
- package/src/server/relay.ts +31 -5
- package/src/server/request-log.ts +12 -0
- package/src/server/responses.ts +111 -27
- package/src/service.ts +12 -3
- package/src/types.ts +2 -2
- package/src/usage/expected-prices.ts +12 -9
- package/gui/dist/assets/index-avcinRsG.js +0 -40
|
@@ -196,36 +196,42 @@ const ALIBABA_TOKEN_PLAN_QWEN_MODELS = [
|
|
|
196
196
|
];
|
|
197
197
|
const ALIBABA_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
|
|
198
198
|
"qwen3.8-max-preview": ["text", "image"],
|
|
199
|
-
"qwen3.7-max": ["text"],
|
|
199
|
+
"qwen3.7-max": ["text", "image"],
|
|
200
200
|
"qwen3.7-plus": ["text", "image"],
|
|
201
201
|
"qwen3.6-flash": ["text", "image"],
|
|
202
202
|
"glm-5.2": ["text"],
|
|
203
203
|
"deepseek-v4-pro": ["text"],
|
|
204
204
|
};
|
|
205
205
|
|
|
206
|
-
// 260721 Alibaba Token Plan International (ap-southeast-1 / Singapore).
|
|
206
|
+
// 260721 Alibaba Token Plan International (ap-southeast-1 / Singapore, hardened 260721).
|
|
207
207
|
// Multi-vendor lineup distinct from Beijing — includes DeepSeek V4 flash, Kimi K2.7, MiniMax.
|
|
208
208
|
// Evidence: https://www.alibabacloud.com/help/en/model-studio/token-plan-overview
|
|
209
|
+
// https://qwencloud.com/pricing/token-plan (qwen3.8 metadata)
|
|
209
210
|
const ALIBABA_INTL_TOKEN_PLAN_MODELS = [
|
|
210
|
-
"qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
|
|
211
|
+
"qwen3.8-max-preview", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
|
|
211
212
|
"deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v3.2",
|
|
212
|
-
"kimi-k2.7-code",
|
|
213
|
-
"glm-5.2",
|
|
213
|
+
"kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5",
|
|
214
|
+
"glm-5.2", "glm-5.1", "glm-5",
|
|
214
215
|
"MiniMax-M2.5",
|
|
215
216
|
];
|
|
216
217
|
const ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS = [
|
|
217
|
-
"qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
|
|
218
|
+
"qwen3.8-max-preview", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
|
|
218
219
|
];
|
|
219
220
|
const ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
|
|
220
|
-
"qwen3.
|
|
221
|
+
"qwen3.8-max-preview": ["text", "image"],
|
|
222
|
+
"qwen3.7-max": ["text", "image"],
|
|
221
223
|
"qwen3.7-plus": ["text", "image"],
|
|
222
224
|
"qwen3.6-plus": ["text", "image"],
|
|
223
225
|
"qwen3.6-flash": ["text", "image"],
|
|
224
226
|
"deepseek-v4-pro": ["text"],
|
|
225
227
|
"deepseek-v4-flash": ["text"],
|
|
226
228
|
"deepseek-v3.2": ["text"],
|
|
227
|
-
"kimi-k2.7-code": ["text"],
|
|
229
|
+
"kimi-k2.7-code": ["text", "image"],
|
|
230
|
+
"kimi-k2.6": ["text", "image"],
|
|
231
|
+
"kimi-k2.5": ["text", "image"],
|
|
228
232
|
"glm-5.2": ["text"],
|
|
233
|
+
"glm-5.1": ["text"],
|
|
234
|
+
"glm-5": ["text"],
|
|
229
235
|
"MiniMax-M2.5": ["text"],
|
|
230
236
|
};
|
|
231
237
|
|
|
@@ -632,9 +638,11 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
632
638
|
// devlog/_plan/260710_provider_hardening/001_research_frontier.md.
|
|
633
639
|
{
|
|
634
640
|
id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true,
|
|
635
|
-
dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.5-flash", "gemini-3.1-pro-preview"],
|
|
636
|
-
modelContextWindows: { "gemini-3.5-flash": 1_000_000 },
|
|
641
|
+
dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.1-pro-preview"],
|
|
642
|
+
modelContextWindows: { "gemini-3.6-flash": 1_048_576, "gemini-3.5-flash": 1_000_000 },
|
|
643
|
+
modelInputModalities: { "gemini-3.6-flash": ["text", "image"] },
|
|
637
644
|
modelReasoningEfforts: {
|
|
645
|
+
"gemini-3.6-flash": ["minimal", "low", "medium", "high"],
|
|
638
646
|
"gemini-3.5-flash": ["minimal", "low", "medium", "high"],
|
|
639
647
|
"gemini-3.1-pro-preview": ["low", "medium", "high"],
|
|
640
648
|
},
|
|
@@ -643,7 +651,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
643
651
|
// 2026-07-10: defaultModel is frozen pending Vertex-specific Tier-2 evidence; Gemini API
|
|
644
652
|
// evidence from ai.google.dev does not establish Vertex publisher availability.
|
|
645
653
|
{ id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
|
|
646
|
-
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, defaultModel: "gemini-3.
|
|
654
|
+
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, defaultModel: "gemini-3.6-flash-medium", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
|
|
647
655
|
{ id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
|
|
648
656
|
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
649
657
|
{ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
@@ -762,6 +770,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
762
770
|
liveModels: false,
|
|
763
771
|
note: "Token Plan Personal Edition · China (Beijing)",
|
|
764
772
|
modelInputModalities: ALIBABA_TOKEN_PLAN_INPUT_MODALITIES,
|
|
773
|
+
modelContextWindows: {
|
|
774
|
+
"qwen3.8-max-preview": 983_616, "qwen3.7-max": 1_000_000, "qwen3.7-plus": 1_000_000,
|
|
775
|
+
"qwen3.6-flash": 1_000_000, "glm-5.2": 1_000_000, "deepseek-v4-pro": 1_000_000,
|
|
776
|
+
},
|
|
765
777
|
modelReasoningEfforts: {
|
|
766
778
|
...Object.fromEntries(ALIBABA_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
|
|
767
779
|
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
@@ -769,7 +781,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
769
781
|
},
|
|
770
782
|
modelReasoningEffortMap: { "deepseek-v4-pro": DEEPSEEK_THINKING_REASONING_MAP },
|
|
771
783
|
thinkingBudgetModels: ALIBABA_TOKEN_PLAN_QWEN_MODELS,
|
|
772
|
-
preserveReasoningContentModels: ["glm-5.2", "deepseek-v4-pro", "qwen3.8-max-preview"],
|
|
784
|
+
preserveReasoningContentModels: ["glm-5.2", "deepseek-v4-pro", "qwen3.8-max-preview", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash"],
|
|
785
|
+
noVisionModels: ["glm-5.2", "deepseek-v4-pro"],
|
|
773
786
|
},
|
|
774
787
|
{
|
|
775
788
|
id: "alibaba-token-plan-intl",
|
|
@@ -786,9 +799,17 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
786
799
|
note: "Token Plan Team Edition · Singapore (ap-southeast-1)",
|
|
787
800
|
metadataModelIdNormalize: "case-insensitive",
|
|
788
801
|
modelInputModalities: ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES,
|
|
789
|
-
modelContextWindows: {
|
|
802
|
+
modelContextWindows: {
|
|
803
|
+
"qwen3.8-max-preview": 983_616,
|
|
804
|
+
"qwen3.7-max": 1_000_000, "qwen3.7-plus": 1_000_000, "qwen3.6-plus": 1_000_000, "qwen3.6-flash": 1_000_000,
|
|
805
|
+
"deepseek-v4-pro": 1_000_000, "deepseek-v4-flash": 1_000_000, "deepseek-v3.2": 131_072,
|
|
806
|
+
"kimi-k2.7-code": 262_144, "kimi-k2.6": 262_144, "kimi-k2.5": 262_144,
|
|
807
|
+
"glm-5.2": 1_000_000, "glm-5.1": 1_000_000, "glm-5": 1_000_000,
|
|
808
|
+
"MiniMax-M2.5": 204_800,
|
|
809
|
+
},
|
|
790
810
|
modelReasoningEfforts: {
|
|
791
811
|
...Object.fromEntries(ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
|
|
812
|
+
"qwen3.8-max-preview": ["low", "high", "xhigh"],
|
|
792
813
|
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
793
814
|
"deepseek-v4-pro": DEEPSEEK_THINKING_EFFORTS,
|
|
794
815
|
"deepseek-v4-flash": DEEPSEEK_THINKING_EFFORTS,
|
|
@@ -798,9 +819,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
798
819
|
"deepseek-v4-flash": DEEPSEEK_THINKING_REASONING_MAP,
|
|
799
820
|
},
|
|
800
821
|
thinkingBudgetModels: ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS,
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
noReasoningModels: ["kimi-k2.7-code", "deepseek-v3.2", "MiniMax-M2.5"],
|
|
822
|
+
preserveReasoningContentModels: ["glm-5.2", "deepseek-v4-pro", "deepseek-v4-flash", "qwen3.8-max-preview", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash"],
|
|
823
|
+
noVisionModels: ["deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v3.2", "glm-5.2", "glm-5.1", "glm-5", "MiniMax-M2.5"],
|
|
824
|
+
noReasoningModels: ["kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5", "deepseek-v3.2", "glm-5.1", "glm-5", "MiniMax-M2.5"],
|
|
825
|
+
modelDefaultReasoningEfforts: { "qwen3.8-max-preview": "xhigh" },
|
|
804
826
|
},
|
|
805
827
|
// NEEDS_HUMAN 2026-07-10: kept for config compatibility, but this is a dashboard URL,
|
|
806
828
|
// no /models endpoint is documented, and tools are silently ignored upstream per docs.parallel.ai.
|
package/src/server/index.ts
CHANGED
|
@@ -537,7 +537,7 @@ export function startServer(port?: number) {
|
|
|
537
537
|
void (async () => {
|
|
538
538
|
const start = Date.now();
|
|
539
539
|
const requestId = nextRequestLogId(start);
|
|
540
|
-
const logCtx = { model: "unknown", provider: "unknown" };
|
|
540
|
+
const logCtx: RequestLogContext = { model: "unknown", provider: "unknown" };
|
|
541
541
|
let logged = false;
|
|
542
542
|
const finalizeLog = (
|
|
543
543
|
status: number,
|
|
@@ -556,7 +556,7 @@ export function startServer(port?: number) {
|
|
|
556
556
|
body: JSON.stringify({ ...payload, stream: true }),
|
|
557
557
|
});
|
|
558
558
|
try {
|
|
559
|
-
let terminalRecorder: ((status: ResponsesTerminalStatus) => void) | undefined;
|
|
559
|
+
let terminalRecorder: ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined;
|
|
560
560
|
const response = await handleResponses(req, config, logCtx, {
|
|
561
561
|
forceEmptyResponseId: true,
|
|
562
562
|
abortSignal: turnAbort.signal,
|
|
@@ -570,7 +570,7 @@ export function startServer(port?: number) {
|
|
|
570
570
|
await sendResponseToWebSocket(ws, response, isCurrent, {
|
|
571
571
|
onSsePayload: payload => inspectResponseLogSsePayload(logCtx, payload),
|
|
572
572
|
onTerminal: status => {
|
|
573
|
-
terminalRecorder?.(status);
|
|
573
|
+
terminalRecorder?.(status, logCtx.terminalHttpStatus);
|
|
574
574
|
finalizeLog(httpStatusForRequestLogTerminal(status, logCtx), {
|
|
575
575
|
terminalStatus: status,
|
|
576
576
|
closeReason: "terminal",
|
package/src/server/relay.ts
CHANGED
|
@@ -406,7 +406,7 @@ export function relaySseWithHeartbeat(
|
|
|
406
406
|
*/
|
|
407
407
|
export function consumeForInspection(
|
|
408
408
|
body: ReadableStream<Uint8Array>,
|
|
409
|
-
onTerminal: (status: ResponsesTerminalStatus) => void,
|
|
409
|
+
onTerminal: (status: ResponsesTerminalStatus, httpStatusOverride?: number) => void,
|
|
410
410
|
signal?: AbortSignal,
|
|
411
411
|
onDone?: () => void,
|
|
412
412
|
logCtx?: RequestLogContext,
|
|
@@ -451,14 +451,24 @@ export function consumeForInspection(
|
|
|
451
451
|
reportFirstOutput(payload);
|
|
452
452
|
if (payload) {
|
|
453
453
|
const status = terminalStatusFromSsePayload(payload);
|
|
454
|
-
if (status) {
|
|
454
|
+
if (status) {
|
|
455
|
+
reported = true;
|
|
456
|
+
if (logCtx) {
|
|
457
|
+
logCtx.transportPhase = "terminal_sse";
|
|
458
|
+
logCtx.terminalSource = "upstream";
|
|
459
|
+
}
|
|
460
|
+
onTerminal(status);
|
|
461
|
+
}
|
|
455
462
|
if (onCompletedResponse) {
|
|
456
463
|
const response = completedResponseFromSsePayload(payload);
|
|
457
464
|
if (response) onCompletedResponse(response);
|
|
458
465
|
}
|
|
459
466
|
}
|
|
460
467
|
}
|
|
461
|
-
if (!reported && !cancelled)
|
|
468
|
+
if (!reported && !cancelled) {
|
|
469
|
+
if (logCtx) logCtx.terminalSource = "synthetic";
|
|
470
|
+
onTerminal("incomplete");
|
|
471
|
+
}
|
|
462
472
|
return;
|
|
463
473
|
}
|
|
464
474
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -472,7 +482,14 @@ export function consumeForInspection(
|
|
|
472
482
|
if (!payload) continue;
|
|
473
483
|
if (!reported) {
|
|
474
484
|
const status = terminalStatusFromSsePayload(payload);
|
|
475
|
-
if (status) {
|
|
485
|
+
if (status) {
|
|
486
|
+
reported = true;
|
|
487
|
+
if (logCtx) {
|
|
488
|
+
logCtx.transportPhase = "terminal_sse";
|
|
489
|
+
logCtx.terminalSource = "upstream";
|
|
490
|
+
}
|
|
491
|
+
onTerminal(status);
|
|
492
|
+
}
|
|
476
493
|
}
|
|
477
494
|
if (onCompletedResponse) {
|
|
478
495
|
const response = completedResponseFromSsePayload(payload);
|
|
@@ -481,7 +498,16 @@ export function consumeForInspection(
|
|
|
481
498
|
}
|
|
482
499
|
}
|
|
483
500
|
} catch {
|
|
484
|
-
|
|
501
|
+
// Upstream read failure after HTTP 200 (mid-stream socket reset) is not a
|
|
502
|
+
// protocol `response.incomplete` terminal. Report a synthetic 502 so account
|
|
503
|
+
// health treats it as transient; abort-driven client cancellation still wins.
|
|
504
|
+
if (!reported && !cancelled) {
|
|
505
|
+
if (logCtx) {
|
|
506
|
+
logCtx.transportPhase = "mid_stream";
|
|
507
|
+
logCtx.terminalSource = "synthetic";
|
|
508
|
+
}
|
|
509
|
+
onTerminal("failed", 502);
|
|
510
|
+
}
|
|
485
511
|
} finally {
|
|
486
512
|
onDone?.();
|
|
487
513
|
}
|
|
@@ -62,6 +62,9 @@ export interface RequestLogContext {
|
|
|
62
62
|
upstreamError?: string;
|
|
63
63
|
/** HTTP status derived from a terminal `response.failed` SSE payload (429/401/503/etc.). */
|
|
64
64
|
terminalHttpStatus?: number;
|
|
65
|
+
affinity?: "reused" | "new_bind" | "rebound" | "cleared";
|
|
66
|
+
transportPhase?: "pre_headers" | "mid_stream" | "terminal_sse";
|
|
67
|
+
terminalSource?: "upstream" | "synthetic";
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
export interface RequestLogEntry {
|
|
@@ -92,6 +95,12 @@ export interface RequestLogEntry {
|
|
|
92
95
|
usage?: OcxUsage;
|
|
93
96
|
totalTokens?: number;
|
|
94
97
|
attempts?: PersistedUsageAttempt[];
|
|
98
|
+
/** Codex pool affinity decision for this request (diagnostics for #186). */
|
|
99
|
+
affinity?: "reused" | "new_bind" | "rebound" | "cleared";
|
|
100
|
+
/** Where the upstream terminal/failure was observed. */
|
|
101
|
+
transportPhase?: "pre_headers" | "mid_stream" | "terminal_sse";
|
|
102
|
+
/** Whether the terminal came from a real upstream SSE event or a proxy synthetic tail. */
|
|
103
|
+
terminalSource?: "upstream" | "synthetic";
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
const requestLog: RequestLogEntry[] = [];
|
|
@@ -587,6 +596,9 @@ export function addFinalRequestLog(
|
|
|
587
596
|
...(loggedUsage ? { usage: loggedUsage } : {}),
|
|
588
597
|
...(totalTokens !== undefined ? { totalTokens } : {}),
|
|
589
598
|
...(attempts?.length ? { attempts } : {}),
|
|
599
|
+
...(logCtx.affinity ? { affinity: logCtx.affinity } : {}),
|
|
600
|
+
...(logCtx.transportPhase ? { transportPhase: logCtx.transportPhase } : {}),
|
|
601
|
+
...(logCtx.terminalSource ? { terminalSource: logCtx.terminalSource } : {}),
|
|
590
602
|
});
|
|
591
603
|
if (isUsageDebugEnabled()) {
|
|
592
604
|
appendUsageDebug({
|
package/src/server/responses.ts
CHANGED
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
recordCodexUpstreamOutcome,
|
|
56
56
|
type CodexUpstreamOutcome,
|
|
57
57
|
} from "../codex/routing";
|
|
58
|
-
import { fetchWithResetRetry, fetchWithTransientRetry } from "../lib/upstream-retry";
|
|
58
|
+
import { fetchWithResetRetry, fetchWithTransientRetry, applyUpstreamRecoveryInit } from "../lib/upstream-retry";
|
|
59
59
|
import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "./auth-cors";
|
|
60
60
|
import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../providers/openai-sidecar";
|
|
61
61
|
import { applyOpenAiVirtualModel, resolveOpenAiCompactModel } from "../providers/openai-virtual-models";
|
|
@@ -69,6 +69,7 @@ import type { WsData } from "./ws-bridge";
|
|
|
69
69
|
import { registerTurn, trackStreamLifetime, unregisterTurn } from "./lifecycle";
|
|
70
70
|
import { redactSecretString } from "../lib/redact";
|
|
71
71
|
import { readBoundedResponseBody } from "../lib/bounded-body";
|
|
72
|
+
import { supportedLadderFor } from "./effort-policy";
|
|
72
73
|
import {
|
|
73
74
|
beginRequestAttempt,
|
|
74
75
|
catalogModelSupportsServiceTier,
|
|
@@ -395,9 +396,13 @@ export function sanitizeEncryptedContentInPlace(input: unknown): number {
|
|
|
395
396
|
return rewritten;
|
|
396
397
|
}
|
|
397
398
|
|
|
398
|
-
export function sidecarOutcomeRecorder(
|
|
399
|
+
export function sidecarOutcomeRecorder(
|
|
400
|
+
config: OcxConfig,
|
|
401
|
+
authCtx: CodexAuthContext,
|
|
402
|
+
threadId?: string | null,
|
|
403
|
+
): ((outcome: CodexUpstreamOutcome) => void) | undefined {
|
|
399
404
|
return authCtx.kind === "pool" || authCtx.kind === "main-pool"
|
|
400
|
-
? outcome => recordCodexUpstreamOutcome(config, authCtx.accountId, outcome)
|
|
405
|
+
? outcome => recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, { threadId })
|
|
401
406
|
: undefined;
|
|
402
407
|
}
|
|
403
408
|
|
|
@@ -418,9 +423,32 @@ export function codexForwardTerminalOutcomeRecorder(
|
|
|
418
423
|
config: OcxConfig,
|
|
419
424
|
authCtx: CodexAuthContext,
|
|
420
425
|
provider: OcxProviderConfig,
|
|
421
|
-
|
|
426
|
+
logCtx?: RequestLogContext,
|
|
427
|
+
threadId?: string | null,
|
|
428
|
+
): ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined {
|
|
422
429
|
if (!usesCodexForwardPoolAuth(authCtx, provider)) return undefined;
|
|
423
|
-
return status
|
|
430
|
+
return (status, httpStatusOverride) => {
|
|
431
|
+
if (status === "incomplete") {
|
|
432
|
+
// Normal limit/content-filter/stall terminal — the account served the
|
|
433
|
+
// request. Don't penalize account health; record success to clear any
|
|
434
|
+
// prior soft-avoid so a healthy account isn't stuck avoided.
|
|
435
|
+
recordCodexUpstreamOutcome(config, authCtx.accountId, 200, { threadId });
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
// status === "completed" or "failed": use the semantic HTTP status derived
|
|
439
|
+
// from the terminal SSE error payload (httpStatusFromTerminalError in
|
|
440
|
+
// request-log inspection) instead of collapsing every non-completed terminal
|
|
441
|
+
// to 502. A 400 invalid_request_error must not soft-avoid the account or
|
|
442
|
+
// rebind threads — only genuine transport/5xx failures should trigger
|
|
443
|
+
// transient health recording.
|
|
444
|
+
// httpStatusOverride: the combo WS path inspects SSE payloads into the parent
|
|
445
|
+
// logCtx, but this recorder closes over the child logCtx. The caller passes
|
|
446
|
+
// the parent's terminalHttpStatus so the semantic status is not lost.
|
|
447
|
+
const outcome = status === "completed"
|
|
448
|
+
? 200
|
|
449
|
+
: (httpStatusOverride ?? logCtx?.terminalHttpStatus ?? 502);
|
|
450
|
+
recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, { threadId });
|
|
451
|
+
};
|
|
424
452
|
}
|
|
425
453
|
|
|
426
454
|
/**
|
|
@@ -466,7 +494,7 @@ interface HandleResponsesOptions {
|
|
|
466
494
|
onFirstOutput?: () => void;
|
|
467
495
|
onCodexAuthContextResolved?: (context: CodexAuthContext | undefined) => void;
|
|
468
496
|
recordTerminalOutcomes?: boolean;
|
|
469
|
-
setTerminalOutcomeRecorder?: (recorder: ((status: ResponsesTerminalStatus) => void) | undefined) => void;
|
|
497
|
+
setTerminalOutcomeRecorder?: (recorder: ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined) => void;
|
|
470
498
|
onNativePassthroughTerminal?: (status: ResponsesTerminalStatus) => void;
|
|
471
499
|
onNativePassthroughCancel?: () => void;
|
|
472
500
|
/** Internal recursion guard; callers outside this module must not set it. */
|
|
@@ -595,10 +623,12 @@ async function handleComboResponses(
|
|
|
595
623
|
model: pick.target.model,
|
|
596
624
|
provider: pick.target.provider,
|
|
597
625
|
};
|
|
626
|
+
const targetRoute = routeModel(config, `${pick.target.provider}/${pick.target.model}`);
|
|
598
627
|
const childBody = concreteComboRequestBody(
|
|
599
628
|
rawBody,
|
|
600
629
|
pick.target,
|
|
601
630
|
comboDefaultEffort(config, comboId),
|
|
631
|
+
supportedLadderFor({ provider: targetRoute.provider, modelId: targetRoute.modelId }),
|
|
602
632
|
);
|
|
603
633
|
const childHeaders = new Headers(req.headers);
|
|
604
634
|
childHeaders.delete("content-length");
|
|
@@ -608,7 +638,7 @@ async function handleComboResponses(
|
|
|
608
638
|
body: JSON.stringify(childBody),
|
|
609
639
|
});
|
|
610
640
|
let resolvedAuth: CodexAuthContext | undefined;
|
|
611
|
-
let terminalRecorder: ((status: ResponsesTerminalStatus) => void) | undefined;
|
|
641
|
+
let terminalRecorder: ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined;
|
|
612
642
|
const started = Date.now();
|
|
613
643
|
const attempt = beginRequestAttempt(
|
|
614
644
|
(logCtx.attempts?.length ?? 0) + 1,
|
|
@@ -1092,11 +1122,11 @@ export async function handleResponses(
|
|
|
1092
1122
|
upstreamResponse = await fetchWithTransientRetry(
|
|
1093
1123
|
recovery => {
|
|
1094
1124
|
noteAttemptSend(logCtx.activeAttempt, passthroughEstimate, recovery);
|
|
1095
|
-
return fetchWithHeaderTimeout(request.url, {
|
|
1125
|
+
return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
|
|
1096
1126
|
method: request.method,
|
|
1097
1127
|
headers: request.headers,
|
|
1098
1128
|
body: request.body,
|
|
1099
|
-
}, upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
|
|
1129
|
+
}, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
|
|
1100
1130
|
},
|
|
1101
1131
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url) },
|
|
1102
1132
|
);
|
|
@@ -1104,7 +1134,11 @@ export async function handleResponses(
|
|
|
1104
1134
|
upstream.abort();
|
|
1105
1135
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
1106
1136
|
const outcome = err instanceof Error && err.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
1107
|
-
if (usesCodexForwardPoolAuth(authCtx, route.provider))
|
|
1137
|
+
if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
|
|
1138
|
+
recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
|
|
1139
|
+
threadId: req.headers.get("x-codex-parent-thread-id"),
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1108
1142
|
const msg = outcome === "timeout"
|
|
1109
1143
|
? `Provider connect timeout after ${connectMs}ms`
|
|
1110
1144
|
: `Provider unreachable: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -1122,7 +1156,13 @@ export async function handleResponses(
|
|
|
1122
1156
|
const passthroughCt = headers.get("content-type")?.toLowerCase();
|
|
1123
1157
|
const isEventStream = passthroughCt?.includes("text/event-stream")
|
|
1124
1158
|
|| (upstreamResponse.ok && !!upstreamResponse.body && !passthroughCt && parsed.stream);
|
|
1125
|
-
const terminalRecorder = codexForwardTerminalOutcomeRecorder(
|
|
1159
|
+
const terminalRecorder = codexForwardTerminalOutcomeRecorder(
|
|
1160
|
+
config,
|
|
1161
|
+
authCtx,
|
|
1162
|
+
route.provider,
|
|
1163
|
+
logCtx,
|
|
1164
|
+
req.headers.get("x-codex-parent-thread-id"),
|
|
1165
|
+
);
|
|
1126
1166
|
const terminalBodyWillRecord = !!terminalRecorder && upstreamResponse.ok && isEventStream;
|
|
1127
1167
|
// Capture quota from upstream response for multi-account tracking
|
|
1128
1168
|
if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
|
|
@@ -1148,14 +1188,15 @@ export async function handleResponses(
|
|
|
1148
1188
|
);
|
|
1149
1189
|
}
|
|
1150
1190
|
if (terminalBodyWillRecord) {
|
|
1151
|
-
options.setTerminalOutcomeRecorder?.(status => {
|
|
1152
|
-
terminalRecorder(status);
|
|
1191
|
+
options.setTerminalOutcomeRecorder?.((status, httpStatusOverride) => {
|
|
1192
|
+
terminalRecorder(status, httpStatusOverride);
|
|
1153
1193
|
options.onNativePassthroughTerminal?.(status);
|
|
1154
1194
|
});
|
|
1155
1195
|
} else {
|
|
1156
1196
|
recordCodexUpstreamOutcome(config, authCtx.accountId, upstreamResponse.status, {
|
|
1157
1197
|
retryAfter: retryAfterRaw,
|
|
1158
1198
|
resetAt: [primaryResetRaw, secondaryResetRaw, monthlyResetRaw].filter(Boolean),
|
|
1199
|
+
threadId: req.headers.get("x-codex-parent-thread-id"),
|
|
1159
1200
|
});
|
|
1160
1201
|
}
|
|
1161
1202
|
}
|
|
@@ -1174,8 +1215,8 @@ export async function handleResponses(
|
|
|
1174
1215
|
// even if the client has already disconnected: the turn genuinely reached that terminal, so
|
|
1175
1216
|
// it must log as completed/failed, not be dropped or downgraded to a cancel (#44). A pure
|
|
1176
1217
|
// client-cancel (no terminal seen) is finalized separately via consumeForInspection's onCancel.
|
|
1177
|
-
const reportNativeTerminal = (status: ResponsesTerminalStatus) => {
|
|
1178
|
-
terminalRecorder?.(status);
|
|
1218
|
+
const reportNativeTerminal = (status: ResponsesTerminalStatus, httpStatusOverride?: number) => {
|
|
1219
|
+
terminalRecorder?.(status, httpStatusOverride);
|
|
1179
1220
|
options.onNativePassthroughTerminal?.(status);
|
|
1180
1221
|
};
|
|
1181
1222
|
consumeForInspection(
|
|
@@ -1390,9 +1431,11 @@ export async function handleResponses(
|
|
|
1390
1431
|
upstreamResponse = await fetchWithResetRetry(
|
|
1391
1432
|
recovery => {
|
|
1392
1433
|
noteAttemptSend(logCtx.activeAttempt, inputTokenEstimate, recovery);
|
|
1393
|
-
return fetchWithHeaderTimeout(request.url, {
|
|
1394
|
-
method: request.method,
|
|
1395
|
-
|
|
1434
|
+
return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
|
|
1435
|
+
method: request.method,
|
|
1436
|
+
headers: request.headers,
|
|
1437
|
+
body: request.body,
|
|
1438
|
+
}, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
|
|
1396
1439
|
},
|
|
1397
1440
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url) },
|
|
1398
1441
|
);
|
|
@@ -1710,10 +1753,11 @@ export async function handleResponsesCompact(
|
|
|
1710
1753
|
// headers would run compaction on the wrong account (or 401) whenever a pool account is
|
|
1711
1754
|
// active for this thread while normal turns succeed.
|
|
1712
1755
|
let compactProvider = route.provider;
|
|
1756
|
+
let authCtx: CodexAuthContext = { kind: "main", accountId: null };
|
|
1713
1757
|
const headers = new Headers({ "content-type": "application/json" });
|
|
1714
1758
|
try {
|
|
1715
1759
|
if (route.codexAccountMode) {
|
|
1716
|
-
|
|
1760
|
+
authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode);
|
|
1717
1761
|
const selected = headersForCodexAuthContext(req.headers, authCtx);
|
|
1718
1762
|
compactProvider = applyCodexAuthContextToProvider(route.provider, authCtx, route.codexAccountMode);
|
|
1719
1763
|
for (const name of FORWARD_HEADERS) {
|
|
@@ -1744,19 +1788,59 @@ export async function handleResponsesCompact(
|
|
|
1744
1788
|
const base = (compactProvider.baseUrl ?? "").replace(/\/$/, "");
|
|
1745
1789
|
if (compactProvider.apiKey) headers.set("authorization", `Bearer ${resolveEnvValue(compactProvider.apiKey)}`);
|
|
1746
1790
|
const { reasoning: _reasoning, ...compactBody } = raw as typeof raw & { reasoning?: unknown };
|
|
1791
|
+
const compactUrl = `${base}/responses/compact`;
|
|
1792
|
+
const compactThreadId = req.headers.get("x-codex-parent-thread-id");
|
|
1793
|
+
const connectMs = config.connectTimeoutMs ?? 200_000;
|
|
1794
|
+
const recordCompactPoolOutcome = (outcome: CodexUpstreamOutcome, meta: { retryAfter?: string | null } = {}) => {
|
|
1795
|
+
if (!usesCodexForwardPoolAuth(authCtx, route.provider)) return;
|
|
1796
|
+
recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
|
|
1797
|
+
...meta,
|
|
1798
|
+
threadId: compactThreadId,
|
|
1799
|
+
});
|
|
1800
|
+
};
|
|
1747
1801
|
let upstream: Response;
|
|
1748
1802
|
try {
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1803
|
+
// Same connect timeout + keep-alive reset + transient-5xx recovery as /v1/responses —
|
|
1804
|
+
// compact hits the same ChatGPT host and must soft-avoid / clear affinity (#186).
|
|
1805
|
+
upstream = await fetchWithTransientRetry(
|
|
1806
|
+
recovery => fetchWithHeaderTimeout(
|
|
1807
|
+
compactUrl,
|
|
1808
|
+
applyUpstreamRecoveryInit({
|
|
1809
|
+
method: "POST",
|
|
1810
|
+
headers,
|
|
1811
|
+
body: JSON.stringify({ ...compactBody, model: route.modelId }),
|
|
1812
|
+
}, recovery),
|
|
1813
|
+
req.signal,
|
|
1814
|
+
connectMs,
|
|
1815
|
+
false,
|
|
1816
|
+
providerFetch(compactProvider),
|
|
1817
|
+
),
|
|
1818
|
+
{ abortSignal: req.signal, label: safeHostLabel(compactUrl) },
|
|
1819
|
+
);
|
|
1820
|
+
} catch (err) {
|
|
1756
1821
|
if (req.signal.aborted) return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
|
|
1822
|
+
const outcome = err instanceof Error && err.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
1823
|
+
recordCompactPoolOutcome(outcome);
|
|
1757
1824
|
return formatErrorResponse(502, "upstream_error", "Failed to connect to compact upstream");
|
|
1758
1825
|
}
|
|
1759
|
-
|
|
1826
|
+
const retryAfter = upstream.headers.get("retry-after");
|
|
1827
|
+
const buffered = await bufferCompactResponse(upstream, req.signal);
|
|
1828
|
+
// Record pool health only after the body is fully delivered (or definitively failed).
|
|
1829
|
+
// A premature 200 would clear soft-avoid while the client still sees a buffer 502.
|
|
1830
|
+
if (buffered.status === 499) {
|
|
1831
|
+
return buffered;
|
|
1832
|
+
}
|
|
1833
|
+
if (upstream.ok && buffered.status >= 500) {
|
|
1834
|
+
// The upstream account returned 200 — it is healthy. The buffering failure
|
|
1835
|
+
// (oversized body exceeding COMPACT_RESPONSE_MAX_BYTES, or a rare mid-read
|
|
1836
|
+
// reset on a small JSON payload) is a local proxy issue, not account flakiness.
|
|
1837
|
+
// Record the upstream status so a deterministic payload-size limit does not
|
|
1838
|
+
// soft-avoid a healthy account and rotate a thread for 30s.
|
|
1839
|
+
recordCompactPoolOutcome(upstream.status, { retryAfter });
|
|
1840
|
+
} else {
|
|
1841
|
+
recordCompactPoolOutcome(upstream.status, { retryAfter });
|
|
1842
|
+
}
|
|
1843
|
+
return buffered;
|
|
1760
1844
|
}
|
|
1761
1845
|
|
|
1762
1846
|
// ROUTED model: run the v2 synthetic-compaction turn internally (appends COMPACT_PROMPT, no
|
package/src/service.ts
CHANGED
|
@@ -503,7 +503,7 @@ function installWindows(): void {
|
|
|
503
503
|
throw new Error(`Cannot remove the native service before switching to Task Scheduler: ${err instanceof Error ? err.message : String(err)}. Remove it manually with 'sc delete ${WINSW_SERVICE_ID}' or retry.`);
|
|
504
504
|
}
|
|
505
505
|
if (statusWinswRaw() !== "nonexistent") {
|
|
506
|
-
throw new Error(
|
|
506
|
+
throw new Error(`Native service registration could not be re-verified after the removal attempt — aborting switch. Check 'sc.exe query ${WINSW_SERVICE_ID}' and remove it manually if present.`);
|
|
507
507
|
}
|
|
508
508
|
}
|
|
509
509
|
// End a running task BEFORE rewriting the assets it is executing — cmd.exe reading the
|
|
@@ -761,7 +761,9 @@ export function stopServiceIfInstalled(): boolean {
|
|
|
761
761
|
const q = schtasks(["/query", "/tn", TASK]);
|
|
762
762
|
if (q.includes(TASK)) { stopWindows(); stopped = true; }
|
|
763
763
|
} catch { /* task not found */ }
|
|
764
|
-
if (statusWinswRaw() !== "nonexistent") {
|
|
764
|
+
if (statusWinswRaw() !== "nonexistent") {
|
|
765
|
+
try { stopWinswService(); stopped = true; } catch { /* best-effort */ }
|
|
766
|
+
}
|
|
765
767
|
if (stopped) return true;
|
|
766
768
|
} else if (process.platform === "linux" && isSystemd() && existsSync(unitPath())) {
|
|
767
769
|
try { stopSystemd(); return true; } catch { return false; }
|
|
@@ -793,7 +795,14 @@ export function uninstallServiceIfInstalled(): boolean {
|
|
|
793
795
|
const q = schtasks(["/query", "/tn", TASK]);
|
|
794
796
|
if (q.includes(TASK)) { uninstallWindows(); removed = true; }
|
|
795
797
|
} catch { /* task not found */ }
|
|
796
|
-
if (statusWinswRaw() !== "nonexistent") {
|
|
798
|
+
if (statusWinswRaw() !== "nonexistent") {
|
|
799
|
+
try {
|
|
800
|
+
uninstallWinswService();
|
|
801
|
+
removed = true;
|
|
802
|
+
} catch (err) {
|
|
803
|
+
console.warn(`⚠️ Failed to remove native service: ${err instanceof Error ? err.message : String(err)}. Check 'sc.exe query ${WINSW_SERVICE_ID}'.`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
797
806
|
if (removed) { removeServiceInstallState(); return true; }
|
|
798
807
|
} else if (process.platform === "linux" && existsSync(unitPath())) {
|
|
799
808
|
try { uninstallSystemd(); removeServiceInstallState(); return true; } catch {
|
package/src/types.ts
CHANGED
|
@@ -498,8 +498,8 @@ export interface OcxComboConfig {
|
|
|
498
498
|
strategy?: OcxComboStrategy;
|
|
499
499
|
/** Successful requests retained on one RR selection batch. Default 1; range 1..100. */
|
|
500
500
|
stickyLimit?: number;
|
|
501
|
-
/** Used when the client omits reasoning.effort.
|
|
502
|
-
defaultEffort?: OcxComboDefaultEffort;
|
|
501
|
+
/** Used when the client omits reasoning.effort. null/omitted leaves the target default unchanged. */
|
|
502
|
+
defaultEffort?: OcxComboDefaultEffort | null;
|
|
503
503
|
}
|
|
504
504
|
|
|
505
505
|
/**
|
|
@@ -30,8 +30,7 @@ export interface ExpectedPriceOverlay {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const GEMINI_31_PRO: Cost4 = { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 };
|
|
33
|
-
const
|
|
34
|
-
const GEMINI_3_FLASH: Cost4 = { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 };
|
|
33
|
+
const GEMINI_36_FLASH: Cost4 = { input: 1.5, output: 7.5, cacheRead: 0.15, cacheWrite: 0 };
|
|
35
34
|
const MINIMAX_M21_HIGHSPEED: Cost4 = { input: 0.6, output: 2.4, cacheRead: 0.03, cacheWrite: 0.375 };
|
|
36
35
|
const KIMI_K3: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3 };
|
|
37
36
|
const KIMI_K27_CODE: Cost4 = { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0.95 };
|
|
@@ -39,7 +38,7 @@ const KIMI_K27_CODE_HIGHSPEED: Cost4 = { input: 1.9, output: 8, cacheRead: 0.38,
|
|
|
39
38
|
const KIMI_K26: Cost4 = { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0.95 };
|
|
40
39
|
const KIMI_K25: Cost4 = { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0.6 };
|
|
41
40
|
|
|
42
|
-
const GEMINI_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-
|
|
41
|
+
const GEMINI_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-07-22); cacheWrite=0: storage is billed per-hour, not per-token";
|
|
43
42
|
const MINIMAX_PRICING = "https://platform.minimax.io/docs/guides/pricing-paygo";
|
|
44
43
|
const DEEPSEEK_PRICING = "https://api-docs.deepseek.com/quick_start/pricing-details-usd; V4 Flash alias transition scheduled 2026-07-24 — re-verify after";
|
|
45
44
|
// Kimi official tables publish input/output/cache-hit only; cacheWrite is mapped to the
|
|
@@ -58,12 +57,16 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
58
57
|
// base model's standard rate per the official Billing FAQ).
|
|
59
58
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-low", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
60
59
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-high", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
61
|
-
{ provider: "google-antigravity", modelId: "gemini-3.
|
|
62
|
-
{ provider: "google-antigravity", modelId: "gemini-3.
|
|
63
|
-
{ provider: "google-antigravity", modelId: "gemini-3.
|
|
64
|
-
{ provider: "google-antigravity", modelId: "gemini-3.5-flash-
|
|
65
|
-
{ provider: "google-antigravity", modelId: "gemini-3-flash-
|
|
66
|
-
|
|
60
|
+
{ provider: "google-antigravity", modelId: "gemini-3.6-flash-low", cost4: GEMINI_36_FLASH, source: `derived: gemini-3.6-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
61
|
+
{ provider: "google-antigravity", modelId: "gemini-3.6-flash-medium", cost4: GEMINI_36_FLASH, source: `derived: gemini-3.6-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
62
|
+
{ provider: "google-antigravity", modelId: "gemini-3.6-flash-high", cost4: GEMINI_36_FLASH, source: `derived: gemini-3.6-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
63
|
+
{ provider: "google-antigravity", modelId: "gemini-3.5-flash-extra-low", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-low ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
64
|
+
{ provider: "google-antigravity", modelId: "gemini-3.5-flash-low", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-medium ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
65
|
+
{ provider: "google-antigravity", modelId: "gemini-3.5-flash-mid", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-medium ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
66
|
+
{ provider: "google-antigravity", modelId: "gemini-3.5-flash-high", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-high ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
67
|
+
{ provider: "google-antigravity", modelId: "gemini-3-flash-agent", cost4: GEMINI_36_FLASH, source: `compat alias -> gemini-3.6-flash-high ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
68
|
+
// Direct Google Gemini API current model (verified — published table).
|
|
69
|
+
{ provider: "google", modelId: "gemini-3.6-flash", cost4: GEMINI_36_FLASH, source: GEMINI_PRICING, verifiedAt: "2026-07-22", status: "verified" },
|
|
67
70
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-preview", cost4: GEMINI_31_PRO, source: GEMINI_PRICING, verifiedAt: "2026-07-20", status: "verified" },
|
|
68
71
|
// Antigravity-bundled third-party models — derived from the underlying vendor's
|
|
69
72
|
// official API price (Antigravity itself bills via subscription quota).
|