@bitkyc08/opencodex 2.88.0 → 2.89.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 +11 -1
- package/bin/ocx.mjs +15 -4
- package/gui/dist/assets/App-BnRSujOq.js +58 -0
- package/gui/dist/assets/App-dnkJ85te.css +1 -0
- package/gui/dist/assets/{Tray-B5bC7AWL.js → Tray-7z1COnmz.js} +1 -1
- package/gui/dist/assets/{index-B5bsvxor.js → index-uchD-ngf.js} +13 -13
- package/gui/dist/assets/{quota-summary-D8y2wTsB.js → quota-summary-BY_10IEz.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic/cache-retention.ts +117 -0
- package/src/adapters/anthropic/passthrough.ts +7 -4
- package/src/adapters/anthropic.ts +10 -3
- package/src/adapters/coding-agent/protocol.ts +6 -0
- package/src/adapters/google-http.ts +3 -2
- package/src/adapters/google-vertex-endpoint.ts +67 -0
- package/src/adapters/google.ts +35 -29
- package/src/adapters/openai-responses/compaction-search-history.ts +27 -0
- package/src/adapters/openai-responses/passthrough.ts +17 -12
- package/src/adapters/openai-responses/reasoning.ts +68 -1
- package/src/adapters/registry.ts +1 -1
- package/src/adapters/upstream-http-error.ts +3 -2
- package/src/adapters/zed.ts +7 -4
- package/src/bridge/internal.ts +11 -1
- package/src/bridge/response-json.ts +2 -2
- package/src/bridge/sse.ts +10 -4
- package/src/claude/inbound.ts +12 -26
- package/src/claude/outbound.ts +5 -0
- package/src/cli/account-api.ts +13 -1
- package/src/cli/account-extended.ts +17 -5
- package/src/cli/account-key-quota.ts +4 -1
- package/src/cli/account.ts +1 -1
- package/src/cli/capabilities-agents-routing.ts +45 -8
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-provider-models.ts +5 -4
- package/src/cli/combo-result.ts +8 -2
- package/src/cli/combo.ts +17 -0
- package/src/cli/help.ts +1 -1
- package/src/cli/message-command.ts +4 -1
- package/src/cli/message-remote-args.ts +69 -0
- package/src/cli/message-remote-command.ts +77 -0
- package/src/cli/provider-runtime.ts +69 -1
- package/src/cli/registry.ts +3 -3
- package/src/codex/account-pause.ts +6 -1
- package/src/codex/account-runtime-state.ts +5 -0
- package/src/codex/account-store.ts +30 -3
- package/src/codex/auth-api/account-list.ts +3 -0
- package/src/codex/auth-api/main-account-probe.ts +3 -2
- package/src/codex/auth-context.ts +9 -4
- package/src/codex/catalog/aggregation.ts +15 -0
- package/src/codex/catalog/auto-review.ts +2 -1
- package/src/codex/catalog/build-entries.ts +17 -7
- package/src/codex/catalog/derive-entry.ts +3 -1
- package/src/codex/catalog/metadata.ts +2 -0
- package/src/codex/catalog/model-visibility.ts +0 -2
- package/src/codex/catalog/ownership.ts +71 -0
- package/src/codex/catalog/parsing.ts +12 -3
- package/src/codex/catalog/retained-sync.ts +2 -1
- package/src/codex/catalog/routed-removal.ts +4 -4
- package/src/codex/convergence.ts +2 -1
- package/src/codex/low-quota-desktop-notifier.ts +125 -0
- package/src/codex/low-quota-protection.ts +57 -31
- package/src/codex/main-account-cache.ts +77 -3
- package/src/codex/main-account.ts +6 -2
- package/src/codex/native-residue.ts +2 -3
- package/src/codex/quota.ts +26 -2
- package/src/combos/failover.ts +2 -1
- package/src/combos/jev-model-backend.ts +30 -7
- package/src/combos/jev-model-request.ts +20 -0
- package/src/combos/jev-quota-activation.ts +153 -0
- package/src/combos/jev-quota-config.ts +62 -0
- package/src/combos/jev-quota-route.ts +26 -0
- package/src/combos/jev-quota.ts +88 -0
- package/src/combos/jev.ts +25 -12
- package/src/combos/types.ts +27 -0
- package/src/compatibility/index.ts +3 -0
- package/src/compatibility/openai-responses.ts +8 -1
- package/src/compatibility/opengateway-responses.ts +61 -0
- package/src/config/account-auto-pause-revisions.ts +136 -0
- package/src/config/diagnostics.ts +13 -0
- package/src/config/live-reconcile.ts +9 -0
- package/src/config/persist-unlocked.ts +10 -1
- package/src/config/removed-opencode-free.ts +135 -0
- package/src/config/schema/account-auto-pause.ts +14 -0
- package/src/config/schema/config-schema.ts +15 -0
- package/src/config/schema/leaf-validators.ts +7 -0
- package/src/config.ts +10 -3
- package/src/generated/compatibility-version.json +344 -144
- package/src/images/loop.ts +32 -9
- package/src/lib/account-auto-pause-response.ts +16 -0
- package/src/lib/bun-runtime.ts +18 -1
- package/src/lib/bun-transpiler-cache-scope.d.mts +8 -0
- package/src/lib/bun-transpiler-cache-scope.mjs +68 -0
- package/src/lib/decision-quota-sink.ts +76 -0
- package/src/lib/state-store-registrations.ts +25 -0
- package/src/link/ssh-argv.ts +3 -0
- package/src/messaging/envelope.ts +6 -3
- package/src/messaging/remote-auth.ts +83 -0
- package/src/messaging/remote-bridge.ts +270 -0
- package/src/messaging/remote-contract.ts +108 -0
- package/src/messaging/remote-enrollment-recovery.ts +59 -0
- package/src/messaging/remote-enrollment.ts +244 -0
- package/src/messaging/remote-envelope.ts +40 -0
- package/src/messaging/remote-files.ts +117 -0
- package/src/messaging/remote-owner.ts +169 -0
- package/src/messaging/remote-ports.ts +56 -0
- package/src/messaging/remote-process.ts +131 -0
- package/src/messaging/remote-replies.ts +98 -0
- package/src/messaging/remote-rpc-admission.ts +32 -0
- package/src/messaging/remote-send.ts +72 -0
- package/src/messaging/remote-store.ts +111 -0
- package/src/messaging/remote-tunnels.ts +65 -0
- package/src/messaging/rpc.ts +13 -1
- package/src/oauth/account-auto-pause-policy.ts +66 -0
- package/src/oauth/account-auto-pause.ts +171 -0
- package/src/oauth/account-quota-rank.ts +2 -1
- package/src/oauth/anthropic-account-refusal.ts +6 -0
- package/src/oauth/anthropic-model-family.ts +8 -0
- package/src/oauth/anthropic-model-quota.ts +2 -6
- package/src/oauth/api-key-pool-preserve.ts +37 -0
- package/src/oauth/index.ts +5 -35
- package/src/oauth/store.ts +35 -8
- package/src/oauth/types.ts +2 -0
- package/src/plugins/upstream-hooks.ts +14 -1
- package/src/providers/api-key-pause.ts +32 -0
- package/src/providers/api-key-resolve.ts +10 -4
- package/src/providers/api-key-selection.ts +6 -1
- package/src/providers/api-keys.ts +8 -4
- package/src/providers/claude-cli-usage.ts +5 -0
- package/src/providers/key-failover.ts +11 -11
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +6 -72
- package/src/providers/quota/account-cache.ts +15 -1
- package/src/providers/quota/anthropic-account-quota.ts +2 -1
- package/src/providers/quota/anthropic-cooldown-recovery.ts +22 -4
- package/src/providers/quota/anthropic-decision-windows.ts +47 -0
- package/src/providers/quota/antigravity.ts +39 -6
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota-decision-publication.ts +77 -0
- package/src/providers/quota-decision-snapshot.ts +145 -0
- package/src/providers/quota-routing-cache.ts +3 -0
- package/src/providers/quota-types.ts +6 -1
- package/src/providers/quota.ts +3 -2
- package/src/providers/registry/entries-extended.ts +8 -52
- package/src/providers/registry/model-seeds.ts +1 -1
- package/src/providers/registry.ts +1 -2
- package/src/providers/stale-vision-classification-migration.ts +1 -1
- package/src/responses/parser.ts +18 -10
- package/src/responses/web-search-replay.ts +138 -0
- package/src/routing/compatibility/behavior.ts +4 -1
- package/src/server/auth-cors.ts +7 -0
- package/src/server/background-lifecycle.ts +2 -1
- package/src/server/chat-native.ts +28 -13
- package/src/server/claude-messages.ts +2 -1
- package/src/server/index/optional-listeners.ts +6 -0
- package/src/server/management/combo-routes.ts +17 -2
- package/src/server/management/oauth-account-routes.ts +10 -1
- package/src/server/management/provider-completion-test.ts +289 -0
- package/src/server/management/provider-routes.ts +9 -1
- package/src/server/management/route-registry.ts +2 -0
- package/src/server/messages-native.ts +18 -4
- package/src/server/messages-shared.ts +4 -1
- package/src/server/request-log.ts +7 -2
- package/src/server/responses/account-auto-pause-recovery.ts +26 -0
- package/src/server/responses/adapter-dispatch.ts +77 -28
- package/src/server/responses/combo-session-recall.ts +25 -0
- package/src/server/responses/combo-stream-preflight.ts +3 -1
- package/src/server/responses/combo-tool-loop-pin.ts +70 -0
- package/src/server/responses/compact.ts +8 -5
- package/src/server/responses/context-overflow.ts +141 -2
- package/src/server/responses/core-codex-account.ts +7 -1
- package/src/server/responses/core-combo.ts +31 -5
- package/src/server/responses/core.ts +12 -9
- package/src/server/responses/empty-completion-guard.ts +2 -0
- package/src/server/responses/fetch-helpers.ts +35 -4
- package/src/server/responses/jev-model-invoke.ts +13 -14
- package/src/server/responses/native-steering-log.ts +2 -2
- package/src/server/responses/passthrough-delivery.ts +3 -2
- package/src/server/responses/passthrough-dispatch.ts +30 -33
- package/src/server/responses/passthrough-observations.ts +38 -0
- package/src/server/responses/request-compression.ts +98 -0
- package/src/server/responses/request-transport.ts +32 -3
- package/src/server/responses/run-turn-execution.ts +2 -0
- package/src/server/responses/sidecar-execution.ts +16 -3
- package/src/server/responses/terminal-guard.ts +2 -0
- package/src/server/startup-action-control.ts +2 -2
- package/src/server/startup-health-cache.ts +2 -2
- package/src/server/windows-tray-control.ts +2 -2
- package/src/service/definition.ts +9 -1
- package/src/types/account-auto-pause.ts +15 -0
- package/src/types/config.ts +23 -1
- package/src/types/provider.ts +9 -5
- package/src/types/request.ts +20 -2
- package/src/types.ts +1 -0
- package/src/usage/cost.ts +17 -1
- package/src/usage/expected-prices.ts +3 -1
- package/src/usage/jev-stats.ts +4 -0
- package/src/usage/log.ts +2 -0
- package/src/usage/summary.ts +7 -0
- package/src/web-search/loop.ts +37 -11
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -3
- package/src/web-search/sidecar-search-replay.ts +96 -0
- package/gui/dist/assets/App-CHC89EXQ.js +0 -58
- package/gui/dist/assets/App-wT3mXqFz.css +0 -1
package/src/adapters/google.ts
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
observeAntigravityReplay,
|
|
37
37
|
} from "./google-antigravity-replay";
|
|
38
38
|
import { canonicalAntigravityUsageModel, resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
39
|
-
import {
|
|
39
|
+
import { selectVertexEndpoint, vertexInferenceUrl, vertexReplayModelKey } from "./google-vertex-endpoint";
|
|
40
40
|
import { forgetThoughtSignatureForReplay, lookupReplayThoughtSignature } from "../responses/thought-signature-replay";
|
|
41
41
|
import {
|
|
42
42
|
isTranslatorBudgetExceededError,
|
|
@@ -333,13 +333,20 @@ function geminiToolResultParts(
|
|
|
333
333
|
msg: OcxToolResultMessage,
|
|
334
334
|
wireName: string,
|
|
335
335
|
wireCallId: string,
|
|
336
|
+
nestToolResultImages: boolean,
|
|
336
337
|
): unknown[] {
|
|
337
338
|
const functionResponse: Record<string, unknown> = {
|
|
338
339
|
name: wireName,
|
|
339
340
|
response: { result: geminiToolResultText(msg.content) },
|
|
340
341
|
id: wireCallId,
|
|
341
342
|
};
|
|
342
|
-
|
|
343
|
+
const images = toolResultImageParts(msg.content);
|
|
344
|
+
// Vertex Argon rejects image siblings beside a functionResponse as an unsupported model tail.
|
|
345
|
+
if (nestToolResultImages && images.length > 0) {
|
|
346
|
+
functionResponse.parts = images;
|
|
347
|
+
return [{ functionResponse }];
|
|
348
|
+
}
|
|
349
|
+
return [{ functionResponse }, ...images];
|
|
343
350
|
}
|
|
344
351
|
|
|
345
352
|
function geminiMissingToolResultPart(wireName: string, wireCallId: string): unknown {
|
|
@@ -370,6 +377,7 @@ function messagesToGeminiFormat(
|
|
|
370
377
|
identityModelId: string,
|
|
371
378
|
stripRejectedClaudeSdkParagraph = false,
|
|
372
379
|
isCloudCodeAssist = false,
|
|
380
|
+
nestToolResultImages = false,
|
|
373
381
|
): { systemInstruction?: unknown; contents: unknown[]; replayedCallIds: string[] } {
|
|
374
382
|
// Neutralize Codex's GPT-5 identity line (Gemini/Antigravity share this path) so a routed model
|
|
375
383
|
// never misreports as GPT-5/OpenAI, and never leaks the proxy identity upstream.
|
|
@@ -551,13 +559,17 @@ function messagesToGeminiFormat(
|
|
|
551
559
|
const responseParts: unknown[] = [];
|
|
552
560
|
for (const call of toolCalls) {
|
|
553
561
|
const result = resultsById.get(call.wireCallId);
|
|
554
|
-
if (result) responseParts.push(...geminiToolResultParts(result, call.wireName, call.wireCallId));
|
|
562
|
+
if (result) responseParts.push(...geminiToolResultParts(result, call.wireName, call.wireCallId, nestToolResultImages));
|
|
555
563
|
else responseParts.push(geminiMissingToolResultPart(call.wireName, call.wireCallId));
|
|
556
564
|
}
|
|
557
|
-
|
|
558
|
-
responseParts.push(...geminiOrphanToolResultParts(orphan));
|
|
559
|
-
}
|
|
565
|
+
const orphanParts = orphanResults.flatMap(geminiOrphanToolResultParts);
|
|
560
566
|
contents.push({ role: "user", parts: responseParts });
|
|
567
|
+
// Orphan images cannot share Argon's response batch or claim another call's ownership.
|
|
568
|
+
if (nestToolResultImages && orphanParts.some(part => typeof part === "object" && part !== null && "inline_data" in part)) {
|
|
569
|
+
contents.push({ role: "user", parts: orphanParts });
|
|
570
|
+
} else {
|
|
571
|
+
responseParts.push(...orphanParts);
|
|
572
|
+
}
|
|
561
573
|
i = j - 1;
|
|
562
574
|
}
|
|
563
575
|
break;
|
|
@@ -1026,6 +1038,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1026
1038
|
identityModelId,
|
|
1027
1039
|
stripRejectedClaudeSdkParagraph,
|
|
1028
1040
|
isCloudCodeAssist,
|
|
1041
|
+
provider.googleMode === "vertex" && parsed.modelId === "gemini-4-argon",
|
|
1029
1042
|
);
|
|
1030
1043
|
lastInjectedCallIds = [...replayedCallIds];
|
|
1031
1044
|
lastReasoningReplayScope = parsed._reasoningReplayScope;
|
|
@@ -1192,9 +1205,10 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1192
1205
|
const compiled = compileGoogleWireBody(body, toolSchemaProfile, toolSchemaPolicy);
|
|
1193
1206
|
reportToolSchemaLoss(compiled.toolSchemaLossReport);
|
|
1194
1207
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
1195
|
-
const
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1208
|
+
const apiKey = resolveVertexApiKey(provider.apiKey);
|
|
1209
|
+
const vertexEndpoint = selectVertexEndpoint(provider, Boolean(apiKey));
|
|
1210
|
+
const url = vertexInferenceUrl(vertexEndpoint, parsed.modelId, method, streamParam);
|
|
1211
|
+
vertexReplayModel = vertexReplayModelKey(vertexEndpoint, parsed.modelId);
|
|
1198
1212
|
vertexReplaySession = vertexReplaySessionId(parsed);
|
|
1199
1213
|
// Compile names before replay so the cache matches the exact provider-visible
|
|
1200
1214
|
// functionCall identity. This is the same bounded TTL/LRU store used by CCA, with the
|
|
@@ -1210,27 +1224,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1210
1224
|
(compiled.body as { contents: unknown[] }).contents,
|
|
1211
1225
|
);
|
|
1212
1226
|
}
|
|
1213
|
-
//
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1227
|
+
// Endpoint, location and model path were validated above, before any credential is attached.
|
|
1228
|
+
if (apiKey) headers["x-goog-api-key"] = apiKey;
|
|
1229
|
+
else {
|
|
1230
|
+
// Resolve each marker per request; never share a process-global active account.
|
|
1231
|
+
const markerAccount = gcpCredentialMarkerAccount(provider.apiKey);
|
|
1232
|
+
headers["Authorization"] = `Bearer ${await getVertexAccessToken({ markerAccount })}`;
|
|
1219
1233
|
}
|
|
1220
|
-
const project = provider.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
|
|
1221
|
-
if (!project) throw new Error("Vertex AI requires a project id (provider.project or GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT).");
|
|
1222
|
-
const location = provider.location || process.env.GOOGLE_CLOUD_LOCATION;
|
|
1223
|
-
if (!location) throw new Error("Vertex AI requires a location (provider.location or GOOGLE_CLOUD_LOCATION).");
|
|
1224
|
-
const locationError = googleVertexLocationConfigError(location);
|
|
1225
|
-
if (locationError) throw new Error(locationError);
|
|
1226
|
-
const host = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`;
|
|
1227
|
-
const url = `https://${host}/v1/projects/${project}/locations/${location}/publishers/google/models/${parsed.modelId}:${method}${streamParam}`;
|
|
1228
|
-
// The marker account travels per-request as an argument (never process-global state): a
|
|
1229
|
-
// module-level "active marker" written at routing time would race across concurrent
|
|
1230
|
-
// requests for different providers and could resolve one call with another's credential.
|
|
1231
|
-
const markerAccount = gcpCredentialMarkerAccount(provider.apiKey);
|
|
1232
|
-
const token = await getVertexAccessToken({ markerAccount });
|
|
1233
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
1234
1234
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
1235
1235
|
}
|
|
1236
1236
|
|
|
@@ -1254,6 +1254,12 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1254
1254
|
const compiled = compileGoogleWireBody(body, toolSchemaProfile, toolSchemaPolicy);
|
|
1255
1255
|
reportToolSchemaLoss(compiled.toolSchemaLossReport);
|
|
1256
1256
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
1257
|
+
// A functionCall issued by another model (a combo hop, #7298) has no Gemini signature, and
|
|
1258
|
+
// Gemini 3 refuses the turn without one. The same Gemini-only sentinel CCA and Vertex
|
|
1259
|
+
// apply; a real signature carried by history always wins.
|
|
1260
|
+
if (Array.isArray((compiled.body as { contents?: unknown[] }).contents)) {
|
|
1261
|
+
applyAntigravityThoughtSignatureFallback(routedModelId, (compiled.body as { contents: unknown[] }).contents);
|
|
1262
|
+
}
|
|
1257
1263
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
1258
1264
|
},
|
|
1259
1265
|
|
|
@@ -114,3 +114,30 @@ export function renderCompactionSearchHistoryItems(items: readonly unknown[], re
|
|
|
114
114
|
if (omissionIndex >= 0) out[omissionIndex] = omissionNote(omitted);
|
|
115
115
|
return out;
|
|
116
116
|
}
|
|
117
|
+
|
|
118
|
+
function isHostedSearchTool(tool: unknown): boolean {
|
|
119
|
+
return isPlainObject(tool) && typeof tool.type === "string" && tool.type.startsWith("web_search");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* True when the outbound body declares a hosted search tool, either top-level or inside a Lite
|
|
124
|
+
* `additional_tools` input wrapper. Read on the final body, after unsupported hosted tools were
|
|
125
|
+
* stripped, so a declaration the destination never sees does not count.
|
|
126
|
+
*/
|
|
127
|
+
export function declaresHostedWebSearch(body: Record<string, unknown>): boolean {
|
|
128
|
+
if (Array.isArray(body.tools) && body.tools.some(isHostedSearchTool)) return true;
|
|
129
|
+
return Array.isArray(body.input) && body.input.some(item => isPlainObject(item)
|
|
130
|
+
&& item.type === "additional_tools" && Array.isArray(item.tools) && item.tools.some(isHostedSearchTool));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Project unreferenced hosted `web_search_call` history into reference notes. A same-input
|
|
135
|
+
* `item_reference` to a hosted cell keeps that cell structural. Returns the original body when
|
|
136
|
+
* there is nothing to project.
|
|
137
|
+
*/
|
|
138
|
+
export function projectHostedSearchHistory(body: Record<string, unknown>): Record<string, unknown> {
|
|
139
|
+
if (!Array.isArray(body.input) || !body.input.some(item => isPlainObject(item) && item.type === "web_search_call")) return body;
|
|
140
|
+
const referencedIds = new Set(body.input.flatMap(item => isPlainObject(item)
|
|
141
|
+
&& item.type === "item_reference" && typeof item.id === "string" ? [item.id] : []));
|
|
142
|
+
return { ...body, input: renderCompactionSearchHistoryItems(body.input, referencedIds) };
|
|
143
|
+
}
|
|
@@ -53,7 +53,7 @@ import { stripMuseSparkUnsupportedWebSearchFields, stripOpenAiOnlyWebSearchField
|
|
|
53
53
|
import { observeOutbound } from "../../usage/cache-diagnostic";
|
|
54
54
|
|
|
55
55
|
import { normalizeMuseToolChoice } from "./muse-tool-choice";
|
|
56
|
-
import { renderCompactionSearchHistoryItems } from "./compaction-search-history";
|
|
56
|
+
import { declaresHostedWebSearch, projectHostedSearchHistory, renderCompactionSearchHistoryItems } from "./compaction-search-history";
|
|
57
57
|
import { stripKimiEmptyAssistantMessages } from "./empty-assistant";
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -135,7 +135,8 @@ function usageFromResponsesPayload(payload: unknown): OcxUsage | undefined {
|
|
|
135
135
|
const knownKeys = new Set(["input_tokens", "output_tokens", "total_tokens", "input_tokens_details", "output_tokens_details"]);
|
|
136
136
|
const hasExtras = Object.keys(usage).some(key => !knownKeys.has(key))
|
|
137
137
|
|| (isPlainObject(usage.input_tokens_details)
|
|
138
|
-
&& Object.keys(usage.input_tokens_details).some(key => key !== "cached_tokens" && key !== "cache_write_tokens"
|
|
138
|
+
&& Object.keys(usage.input_tokens_details).some(key => key !== "cached_tokens" && key !== "cache_write_tokens"
|
|
139
|
+
&& key !== "cache_write_1h_tokens"))
|
|
139
140
|
|| (isPlainObject(usage.output_tokens_details)
|
|
140
141
|
&& Object.keys(usage.output_tokens_details).some(key => key !== "reasoning_tokens"));
|
|
141
142
|
if (inputTokens === 0 && outputTokens === 0 && !hasExtras) return undefined;
|
|
@@ -147,6 +148,8 @@ function usageFromResponsesPayload(payload: unknown): OcxUsage | undefined {
|
|
|
147
148
|
...(typeof usage.total_tokens === "number" ? { totalTokens: usage.total_tokens } : {}),
|
|
148
149
|
...(typeof inputDetails?.cached_tokens === "number" ? { cachedInputTokens: inputDetails.cached_tokens } : {}),
|
|
149
150
|
...(typeof inputDetails?.cache_write_tokens === "number" ? { cacheCreationInputTokens: inputDetails.cache_write_tokens } : {}),
|
|
151
|
+
...(typeof inputDetails?.cache_write_1h_tokens === "number"
|
|
152
|
+
? { cacheCreation1hInputTokens: inputDetails.cache_write_1h_tokens } : {}),
|
|
150
153
|
...(typeof outputDetails?.reasoning_tokens === "number" ? { reasoningOutputTokens: outputDetails.reasoning_tokens } : {}),
|
|
151
154
|
...(hasExtras ? { rawUsage: { ...usage } } : {}),
|
|
152
155
|
};
|
|
@@ -492,16 +495,18 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
492
495
|
// here, on the serialized body, not on the parsed selector. One place covers both the
|
|
493
496
|
// HTTP and the WebSocket outbound, because the WS path transports this same request
|
|
494
497
|
// instead of rebuilding it.
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
498
|
+
// Hosted-search history projection, after bridge restoration and tool validation. Two
|
|
499
|
+
// triggers share one bounded renderer, and tool declarations and other input stay intact:
|
|
500
|
+
// 1. The opt-in Codex Lite policy covers ordinary turns and native v2 compaction triggers
|
|
501
|
+
// (#7268). Portable compaction was already projected by the summary builder above.
|
|
502
|
+
// 2. Any request whose final tool surface declares no hosted search. Native Sol answers a
|
|
503
|
+
// replayed web_search_call without a matching declaration with a generic server_error
|
|
504
|
+
// (#7250), so the cells become untrusted reference notes instead of a doomed request.
|
|
505
|
+
if (isPlainObject(finalBody)
|
|
506
|
+
&& ((provider.projectResponsesLiteHostedSearchHistory === true && url === `${CODEX_FORWARD_BASE_URL}/responses`
|
|
507
|
+
&& !parsed._portableCompaction && codexResponsesLiteSelected(headers, finalBody))
|
|
508
|
+
|| !declaresHostedWebSearch(finalBody))) {
|
|
509
|
+
finalBody = projectHostedSearchHistory(finalBody);
|
|
505
510
|
}
|
|
506
511
|
observeOutbound(parsed._rawBody, finalBody, headers);
|
|
507
512
|
if (!isCanonicalOpenAiForwardProvider(provider)) {
|
|
@@ -163,7 +163,74 @@ export function sanitizeReasoningInputContent(
|
|
|
163
163
|
});
|
|
164
164
|
|
|
165
165
|
if (blankedPlaintext) opts?.onPlaintextReasoningBlanked?.();
|
|
166
|
-
|
|
166
|
+
// Runs after the per-item pass so an item it emptied has already been backfilled, and a
|
|
167
|
+
// placeholder is only invented for a turn that carries no reasoning item at all.
|
|
168
|
+
const withTurnReasoning = opts?.requirePlaintextReasoning === true
|
|
169
|
+
? backfillBareToolCallTurnReasoning(input)
|
|
170
|
+
: input;
|
|
171
|
+
if (withTurnReasoning !== input) changed = true;
|
|
172
|
+
return changed ? { ...raw, input: withTurnReasoning } : body;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Assistant-authored history items: what DeepSeek merges into one assistant message. */
|
|
176
|
+
function isAssistantTurnItem(item: unknown): boolean {
|
|
177
|
+
if (!isPlainObject(item) || typeof item.type !== "string") return false;
|
|
178
|
+
if (item.type === "reasoning") return true;
|
|
179
|
+
if (item.type === "message") return item.role === "assistant";
|
|
180
|
+
return item.type.endsWith("_call");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isClientToolCallItem(item: unknown): boolean {
|
|
184
|
+
return isPlainObject(item)
|
|
185
|
+
&& (item.type === "function_call" || item.type === "custom_tool_call" || item.type === "local_shell_call");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Give every assistant tool-call turn that has NO reasoning item a minimal plaintext one (#7298).
|
|
190
|
+
*
|
|
191
|
+
* A Combo can serve the turn that issued a tool call on one model and the continuation on
|
|
192
|
+
* another. When the issuing model was a GPT turn that produced no reasoning item (or the item
|
|
193
|
+
* was dropped upstream), the strict destination sees a bare `function_call` and DeepSeek's
|
|
194
|
+
* thinking mode refuses the whole request with `The reasoning_text in the thinking mode must be
|
|
195
|
+
* passed back to the API`. The per-item backfill above cannot help, because there is no item to
|
|
196
|
+
* backfill. This is the Responses-wire twin of the Chat adapter's `reasoning_content: " "`
|
|
197
|
+
* placeholder (#5421): the same single-space text, never fabricated reasoning, inserted at the
|
|
198
|
+
* head of the turn so it precedes the call exactly where a native turn's reasoning would be.
|
|
199
|
+
*
|
|
200
|
+
* A turn is a maximal run of assistant-authored items; any user message or tool output ends it.
|
|
201
|
+
* Turns that already carry a reasoning item, and turns with no client tool call, are untouched.
|
|
202
|
+
*/
|
|
203
|
+
export function backfillBareToolCallTurnReasoning(input: unknown[]): unknown[] {
|
|
204
|
+
const bareTurnStarts = new Set<number>();
|
|
205
|
+
let turnStart = -1;
|
|
206
|
+
let turnHasReasoning = false;
|
|
207
|
+
let turnHasToolCall = false;
|
|
208
|
+
const closeTurn = (): void => {
|
|
209
|
+
if (turnStart >= 0 && turnHasToolCall && !turnHasReasoning) bareTurnStarts.add(turnStart);
|
|
210
|
+
turnStart = -1;
|
|
211
|
+
turnHasReasoning = false;
|
|
212
|
+
turnHasToolCall = false;
|
|
213
|
+
};
|
|
214
|
+
for (let index = 0; index < input.length; index++) {
|
|
215
|
+
const item = input[index];
|
|
216
|
+
if (!isAssistantTurnItem(item)) {
|
|
217
|
+
closeTurn();
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (turnStart < 0) turnStart = index;
|
|
221
|
+
if ((item as Record<string, unknown>).type === "reasoning") turnHasReasoning = true;
|
|
222
|
+
if (isClientToolCallItem(item)) turnHasToolCall = true;
|
|
223
|
+
}
|
|
224
|
+
closeTurn();
|
|
225
|
+
if (bareTurnStarts.size === 0) return input;
|
|
226
|
+
const result: unknown[] = [];
|
|
227
|
+
for (let index = 0; index < input.length; index++) {
|
|
228
|
+
if (bareTurnStarts.has(index)) {
|
|
229
|
+
result.push({ type: "reasoning", summary: [], content: [{ type: "reasoning_text", text: " " }] });
|
|
230
|
+
}
|
|
231
|
+
result.push(input[index]);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
167
234
|
}
|
|
168
235
|
|
|
169
236
|
/**
|
package/src/adapters/registry.ts
CHANGED
|
@@ -138,7 +138,7 @@ export const ADAPTER_REGISTRY = {
|
|
|
138
138
|
zed: {
|
|
139
139
|
wire: "zed",
|
|
140
140
|
mutation: "codex-owned",
|
|
141
|
-
create: (provider: OcxProviderConfig,
|
|
141
|
+
create: (provider: OcxProviderConfig, context: AdapterFactoryContext) => createZedAdapter(provider, context.cacheRetention),
|
|
142
142
|
},
|
|
143
143
|
"mimo-free": {
|
|
144
144
|
contractParent: "openai-chat",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { inheritAccountAutoPauseResponse } from "../lib/account-auto-pause-response";
|
|
1
2
|
import { readBoundedResponseBody } from "../lib/bounded-body";
|
|
2
3
|
import { redactSecretString } from "../lib/redact";
|
|
3
4
|
|
|
@@ -40,9 +41,9 @@ export async function normalizeUpstreamHttpErrorResponse(
|
|
|
40
41
|
const headers = new Headers(res.headers);
|
|
41
42
|
headers.delete("content-encoding");
|
|
42
43
|
headers.delete("content-length");
|
|
43
|
-
return new Response(await opts.formatMessage(payloadText), {
|
|
44
|
+
return inheritAccountAutoPauseResponse(res, new Response(await opts.formatMessage(payloadText), {
|
|
44
45
|
status: res.status,
|
|
45
46
|
statusText: res.statusText,
|
|
46
47
|
headers,
|
|
47
|
-
});
|
|
48
|
+
}));
|
|
48
49
|
}
|
package/src/adapters/zed.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { AdapterRequest, IncomingMeta, ProviderAdapter } from "./base";
|
|
3
3
|
import { createAnthropicAdapter } from "./anthropic";
|
|
4
|
+
import type { AnthropicCacheRetention } from "./anthropic/cache-retention";
|
|
4
5
|
import { createGoogleAdapter } from "./google";
|
|
5
6
|
import { createOpenAIChatAdapter } from "./openai-chat";
|
|
6
7
|
import { createResponsesPassthroughAdapter } from "./openai-responses";
|
|
@@ -218,15 +219,17 @@ export function zedEventStream(
|
|
|
218
219
|
return body.pipeThrough(output);
|
|
219
220
|
}
|
|
220
221
|
|
|
221
|
-
function createDelegate(provider: OcxProviderConfig, zedProvider: ZedProvider): ProviderAdapter {
|
|
222
|
+
function createDelegate(provider: OcxProviderConfig, zedProvider: ZedProvider, cacheRetention?: AnthropicCacheRetention): ProviderAdapter {
|
|
222
223
|
const config = delegateProvider(provider, zedProvider);
|
|
223
|
-
|
|
224
|
+
// The delegate only borrows the api.anthropic.com body builder; the wire goes to Zed's
|
|
225
|
+
// gateway, which does not document the 1h TTL, so an omitted retention stays 5m here.
|
|
226
|
+
if (zedProvider === "anthropic") return createAnthropicAdapter(config, cacheRetention ?? "short");
|
|
224
227
|
if (zedProvider === "google") return createGoogleAdapter(config);
|
|
225
228
|
if (zedProvider === "open_ai") return createResponsesPassthroughAdapter(config);
|
|
226
229
|
return createOpenAIChatAdapter(config);
|
|
227
230
|
}
|
|
228
231
|
|
|
229
|
-
export function createZedAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
232
|
+
export function createZedAdapter(provider: OcxProviderConfig, cacheRetention?: AnthropicCacheRetention): ProviderAdapter {
|
|
230
233
|
let delegate: ZedDelegate | undefined;
|
|
231
234
|
let credentials: ZedCredentials | undefined;
|
|
232
235
|
|
|
@@ -246,7 +249,7 @@ export function createZedAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
|
246
249
|
/* Model inference fallback below; a transient catalog outage must not block passthrough. */
|
|
247
250
|
}
|
|
248
251
|
const zedProvider = providerFromCatalog(catalog, parsed.modelId);
|
|
249
|
-
const selected = createDelegate(provider, zedProvider);
|
|
252
|
+
const selected = createDelegate(provider, zedProvider, cacheRetention);
|
|
250
253
|
const safeIncoming: IncomingMeta = {
|
|
251
254
|
headers: incoming?.headers ?? new Headers(),
|
|
252
255
|
translatorBudget: incoming?.translatorBudget ?? createTranslatorBudget(),
|
package/src/bridge/internal.ts
CHANGED
|
@@ -23,6 +23,11 @@ export function uuid(): string {
|
|
|
23
23
|
return crypto.randomUUID().replace(/-/g, "");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** The client-visible id of a hosted search cell: the producer's `ws_` id when well-formed, else a fresh one. */
|
|
27
|
+
export function webSearchCellItemId(candidate: string | undefined): string {
|
|
28
|
+
return typeof candidate === "string" && /^ws_[A-Za-z0-9_-]{1,128}$/.test(candidate) ? candidate : `ws_${uuid()}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
/** Test-only: bound the abandoned-owned-budget watchdog delay (null restores). */
|
|
27
32
|
export let ownedBudgetAbandonedMs = 10 * 60 * 1000;
|
|
28
33
|
const OWNED_BUDGET_ABANDONED_DEFAULT_MS = ownedBudgetAbandonedMs;
|
|
@@ -64,7 +69,7 @@ export function responsesUsage(usage: OcxUsage | undefined): Record<string, unkn
|
|
|
64
69
|
// normalized contract).
|
|
65
70
|
const rawInputDetails = isRecord(raw.input_tokens_details)
|
|
66
71
|
? Object.fromEntries(Object.entries(raw.input_tokens_details as Record<string, unknown>)
|
|
67
|
-
.filter(([key]) => key !== "cache_write_tokens"))
|
|
72
|
+
.filter(([key]) => key !== "cache_write_tokens" && key !== "cache_write_1h_tokens"))
|
|
68
73
|
: {} as Record<string, unknown>;
|
|
69
74
|
const rawOutputDetails = isRecord(raw.output_tokens_details)
|
|
70
75
|
? raw.output_tokens_details as Record<string, unknown>
|
|
@@ -92,6 +97,11 @@ export function responsesUsage(usage: OcxUsage | undefined): Record<string, unkn
|
|
|
92
97
|
usage.cacheCreationInputTokens,
|
|
93
98
|
Math.max(0, inputTokens - cacheRead),
|
|
94
99
|
);
|
|
100
|
+
// The 1-hour-TTL share of the write (Anthropic cache_creation split), only when reported,
|
|
101
|
+
// so a translated Messages client can price it at the 1h rate.
|
|
102
|
+
if (usage.cacheCreation1hInputTokens !== undefined) {
|
|
103
|
+
inputDetails.cache_write_1h_tokens = Math.min(usage.cacheCreation1hInputTokens, inputDetails.cache_write_tokens as number);
|
|
104
|
+
}
|
|
95
105
|
}
|
|
96
106
|
out.input_tokens_details = inputDetails;
|
|
97
107
|
out.output_tokens_details = { ...rawOutputDetails, reasoning_tokens: usage.reasoningOutputTokens ?? 0 };
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
type TranslatorBudget,
|
|
43
43
|
type TranslatorBufferKind,
|
|
44
44
|
} from "../lib/translator-budget";
|
|
45
|
-
import { adapterFailureFromEvent, emptyChunks, joinChunks, responsesUsage, toolCallArgumentsUsable, uuid, webSearchAction } from "./internal";
|
|
45
|
+
import { adapterFailureFromEvent, emptyChunks, joinChunks, responsesUsage, toolCallArgumentsUsable, uuid, webSearchAction, webSearchCellItemId } from "./internal";
|
|
46
46
|
import type { OutputItem, StringChunks } from "./internal";
|
|
47
47
|
import { bridgeToResponsesSSE } from "./sse";
|
|
48
48
|
|
|
@@ -546,7 +546,7 @@ function buildResponseJSONWithBudget(
|
|
|
546
546
|
flushToolCall();
|
|
547
547
|
const safeSources = safeWebSearchSources(e.sources);
|
|
548
548
|
pushOutput({
|
|
549
|
-
type: "web_search_call", id:
|
|
549
|
+
type: "web_search_call", id: webSearchCellItemId(e.itemId), status: e.status ?? "completed",
|
|
550
550
|
action: webSearchAction(e.queries, e.anthropic),
|
|
551
551
|
...(safeSources.length > 0 ? { sources: safeSources } : {}),
|
|
552
552
|
...(e.anthropic ? { anthropic_blocks: e.anthropic } : {}),
|
package/src/bridge/sse.ts
CHANGED
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
type TranslatorBudget,
|
|
51
51
|
type TranslatorBufferKind,
|
|
52
52
|
} from "../lib/translator-budget";
|
|
53
|
-
import { adapterFailureFromEvent, emptyChunks, joinChunks, ownedBudgetAbandonedMs, responsesUsage, toolCallArgumentsCouldBeJson, toolCallArgumentsUsable, uuid, webSearchAction } from "./internal";
|
|
53
|
+
import { adapterFailureFromEvent, emptyChunks, joinChunks, ownedBudgetAbandonedMs, responsesUsage, toolCallArgumentsCouldBeJson, toolCallArgumentsUsable, uuid, webSearchAction, webSearchCellItemId } from "./internal";
|
|
54
54
|
import type { OutputItem, StringChunks } from "./internal";
|
|
55
55
|
import { terminalRefusalFailure } from "./terminal-refusal";
|
|
56
56
|
|
|
@@ -770,9 +770,14 @@ export function bridgeToResponsesSSE(
|
|
|
770
770
|
returnIterator();
|
|
771
771
|
};
|
|
772
772
|
let handlingTranslatorOverflow = false;
|
|
773
|
-
terminateForTranslatorOverflow =
|
|
773
|
+
terminateForTranslatorOverflow = error => {
|
|
774
774
|
if (handlingTranslatorOverflow || terminated || clientCancelled || closed) return;
|
|
775
775
|
handlingTranslatorOverflow = true;
|
|
776
|
+
// An adapter-raised overflow event carries the usage the turn already consumed; the
|
|
777
|
+
// provider billed it, so it must not be logged as zero.
|
|
778
|
+
const usage = (error as AdapterEvent | null | undefined)?.type === "error"
|
|
779
|
+
? (error as Extract<AdapterEvent, { type: "error" }>).usage : undefined;
|
|
780
|
+
if (usage) options?.onUsage?.(usage);
|
|
776
781
|
abortCurrentToolCallForTranslatorOverflow();
|
|
777
782
|
currentWebSearch = null;
|
|
778
783
|
releasePendingWebSources();
|
|
@@ -788,6 +793,7 @@ export function bridgeToResponsesSSE(
|
|
|
788
793
|
sequence_number: seq++,
|
|
789
794
|
response: {
|
|
790
795
|
...responseSnapshot("failed", finishedItems),
|
|
796
|
+
...(usage ? { usage: responsesUsage(usage) } : {}),
|
|
791
797
|
error: failure,
|
|
792
798
|
last_error: failure,
|
|
793
799
|
},
|
|
@@ -1211,7 +1217,7 @@ export function bridgeToResponsesSSE(
|
|
|
1211
1217
|
flushHiddenRawReasoning();
|
|
1212
1218
|
if (currentToolCall) closeCurrentToolCall();
|
|
1213
1219
|
if (currentWebSearch) closeCurrentWebSearch("completed", []);
|
|
1214
|
-
const wsItemId =
|
|
1220
|
+
const wsItemId = webSearchCellItemId(event.itemId);
|
|
1215
1221
|
emit("response.output_item.added", {
|
|
1216
1222
|
output_index: outputIndex,
|
|
1217
1223
|
item: { type: "web_search_call", id: wsItemId, status: "in_progress" },
|
|
@@ -1224,7 +1230,7 @@ export function bridgeToResponsesSSE(
|
|
|
1224
1230
|
// (defensive), synthesize the added frame first so the done has a matching item.
|
|
1225
1231
|
if (!currentWebSearch || currentWebSearch.eventId !== event.id) {
|
|
1226
1232
|
if (currentWebSearch) closeCurrentWebSearch("completed", []);
|
|
1227
|
-
const wsItemId2 =
|
|
1233
|
+
const wsItemId2 = webSearchCellItemId(event.itemId);
|
|
1228
1234
|
emit("response.output_item.added", {
|
|
1229
1235
|
output_index: outputIndex,
|
|
1230
1236
|
item: { type: "web_search_call", id: wsItemId2, status: "in_progress" },
|
package/src/claude/inbound.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { stabilizeClaudeInstructionsForPromptCache } from "./inbound-cache-stabi
|
|
|
21
21
|
import { decodeReasoningEnvelope, encodeReasoningEnvelope, OCX_REASONING_PREFIX } from "../responses/reasoning-envelope";
|
|
22
22
|
import { inlineDocumentMarker } from "../responses/inline-document";
|
|
23
23
|
import { createTranslatorBudget, type TranslatorBudget } from "../lib/translator-budget";
|
|
24
|
+
import { collectAnthropicCacheTtls } from "../adapters/anthropic/cache-retention";
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
|
|
@@ -390,34 +391,19 @@ export type ClaudeCacheKeySource = "metadata" | "system" | null;
|
|
|
390
391
|
/**
|
|
391
392
|
* The translated lane places its own prompt-cache breakpoints, so the caller's are dropped;
|
|
392
393
|
* their TTL is the one thing worth keeping. When every caller breakpoint asks for `ttl: "1h"`
|
|
393
|
-
* (Claude Code on a subscription marks them so) this maps to `cacheRetention: "long"`. A
|
|
394
|
-
*
|
|
395
|
-
* breakpoint 1h
|
|
396
|
-
*
|
|
394
|
+
* (Claude Code on a subscription marks them so) this maps to `cacheRetention: "long"`. A caller
|
|
395
|
+
* that pins any breakpoint to an explicit non-1h TTL ("5m") maps to "short": "long" would also
|
|
396
|
+
* make the moving per-turn breakpoint 1h and bill writes the caller asked to keep at 5m at the
|
|
397
|
+
* 1h rate. Breakpoints without a TTL express no preference, so they fall through to the
|
|
398
|
+
* destination default (1h on first-party Anthropic). Read out of body: the Responses body has
|
|
399
|
+
* no field for it.
|
|
397
400
|
*/
|
|
398
|
-
export function callerCacheRetention(raw: unknown): "long" | undefined {
|
|
399
|
-
if (!isRec(raw)) return undefined;
|
|
401
|
+
export function callerCacheRetention(raw: unknown): "long" | "short" | undefined {
|
|
400
402
|
// Top-level automatic caching is a breakpoint too, and content blocks nest (tool_result,
|
|
401
|
-
// search_result, document sources), so every block container is walked.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (depth > 16) return;
|
|
406
|
-
if (Array.isArray(node)) { for (const item of node) visit(item, depth + 1); return; }
|
|
407
|
-
if (!isRec(node)) return;
|
|
408
|
-
if (isRec(node.cache_control)) ttls.push(node.cache_control.ttl);
|
|
409
|
-
for (const [key, value] of Object.entries(node)) {
|
|
410
|
-
if (key !== "cache_control" && key !== "input" && key !== "input_schema"
|
|
411
|
-
&& (Array.isArray(value) || isRec(value))) visit(value, depth + 1);
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
if (isRec(raw.cache_control)) ttls.push(raw.cache_control.ttl);
|
|
415
|
-
visit(raw.system, 0);
|
|
416
|
-
visit(raw.tools, 0);
|
|
417
|
-
visit(raw.messages, 0);
|
|
418
|
-
return ttls.length > 0 && ttls.every(ttl => ttl === "1h")
|
|
419
|
-
? "long"
|
|
420
|
-
: undefined;
|
|
403
|
+
// search_result, document sources), so every block container is walked.
|
|
404
|
+
const ttls = collectAnthropicCacheTtls(raw);
|
|
405
|
+
if (ttls.some(ttl => ttl !== undefined && ttl !== "1h")) return "short";
|
|
406
|
+
return ttls.length > 0 && ttls.every(ttl => ttl === "1h") ? "long" : undefined;
|
|
421
407
|
}
|
|
422
408
|
|
|
423
409
|
export interface ClaudeInboundTranslation {
|
package/src/claude/outbound.ts
CHANGED
|
@@ -156,6 +156,8 @@ export function anthropicUsage(usage: unknown, webSearchRequests = 0): Rec {
|
|
|
156
156
|
const details = isRec(u.input_tokens_details) ? u.input_tokens_details : {};
|
|
157
157
|
const cached = typeof details.cached_tokens === "number" ? details.cached_tokens : 0;
|
|
158
158
|
const cacheWrite = typeof details.cache_write_tokens === "number" ? details.cache_write_tokens : 0;
|
|
159
|
+
const cacheWrite1h = typeof details.cache_write_1h_tokens === "number"
|
|
160
|
+
? Math.max(0, Math.min(details.cache_write_1h_tokens, cacheWrite)) : undefined;
|
|
159
161
|
const input = typeof u.input_tokens === "number" ? u.input_tokens : 0;
|
|
160
162
|
const output = typeof u.output_tokens === "number" ? u.output_tokens : 0;
|
|
161
163
|
return {
|
|
@@ -163,6 +165,9 @@ export function anthropicUsage(usage: unknown, webSearchRequests = 0): Rec {
|
|
|
163
165
|
output_tokens: output,
|
|
164
166
|
cache_read_input_tokens: cached,
|
|
165
167
|
cache_creation_input_tokens: cacheWrite,
|
|
168
|
+
...(cacheWrite1h !== undefined
|
|
169
|
+
? { cache_creation: { ephemeral_5m_input_tokens: cacheWrite - cacheWrite1h, ephemeral_1h_input_tokens: cacheWrite1h } }
|
|
170
|
+
: {}),
|
|
166
171
|
// Only successful searches are billed/counted (Anthropic contract; Claude Code cost accounting).
|
|
167
172
|
...(webSearchRequests > 0 ? { server_tool_use: { web_search_requests: webSearchRequests } } : {}),
|
|
168
173
|
};
|
package/src/cli/account-api.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { projectApiKeyQuotaRows } from "./account-key-quota";
|
|
|
16
16
|
import { projectAccountHealth } from "./account-next-actions";
|
|
17
17
|
import type { OAuthHealthLabel } from "../oauth/health";
|
|
18
18
|
|
|
19
|
+
import { accountPauseDetailsSchema } from "../config/schema/account-auto-pause";
|
|
19
20
|
export type AccountType = "codex" | "oauth" | "api-key";
|
|
20
21
|
|
|
21
22
|
export interface AccountRow {
|
|
@@ -61,6 +62,7 @@ export interface AccountRow {
|
|
|
61
62
|
* human listing (#2703).
|
|
62
63
|
*/
|
|
63
64
|
paused?: boolean;
|
|
65
|
+
pauseDetails?: import("../types/account-auto-pause").AccountPauseDetails;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
export type ClassifyResult = { type: AccountType } | { error: string };
|
|
@@ -342,6 +344,7 @@ interface CodexAccountDto {
|
|
|
342
344
|
quota?: CodexQuotaDto | null;
|
|
343
345
|
quotaRefresh?: unknown;
|
|
344
346
|
paused?: boolean;
|
|
347
|
+
pauseDetails?: import("../types/account-auto-pause").AccountPauseDetails;
|
|
345
348
|
}
|
|
346
349
|
|
|
347
350
|
function projectQuota(quota: CodexQuotaDto | null | undefined): CodexQuotaDto | null {
|
|
@@ -415,6 +418,8 @@ export async function fetchCodexRows(
|
|
|
415
418
|
? a.autoSwitchThresholdOverride
|
|
416
419
|
: null,
|
|
417
420
|
paused: a.paused === true,
|
|
421
|
+
// Unknown display details are dropped defensively; the pause state above is retained.
|
|
422
|
+
pauseDetails: accountPauseDetailsSchema.safeParse(a.pauseDetails).data,
|
|
418
423
|
...(includeQuota ? {
|
|
419
424
|
quota: projectQuota(a.quota),
|
|
420
425
|
quotaRefresh: projectCodexQuotaRefreshOutcome(a.quotaRefresh),
|
|
@@ -432,6 +437,7 @@ interface OAuthAccountDto {
|
|
|
432
437
|
healthLabel?: unknown;
|
|
433
438
|
/** Present only for providers that support operator pause (generic OAuth pools). */
|
|
434
439
|
paused?: boolean;
|
|
440
|
+
pauseDetails?: import("../types/account-auto-pause").AccountPauseDetails;
|
|
435
441
|
autoSwitchThresholdOverride?: number | null;
|
|
436
442
|
needsReauthReason?: "verify_account";
|
|
437
443
|
autoSelectable?: boolean;
|
|
@@ -475,7 +481,8 @@ async function fetchOAuthRows(
|
|
|
475
481
|
active: a.active ?? a.id === activeId,
|
|
476
482
|
needsReauth: a.needsReauth,
|
|
477
483
|
...projectAccountHealth(a, name, a.id),
|
|
478
|
-
|
|
484
|
+
// Unknown display details are dropped defensively; paused remains authoritative.
|
|
485
|
+
...(a.paused === true ? { paused: true, pauseDetails: accountPauseDetailsSchema.safeParse(a.pauseDetails).data } : {}),
|
|
479
486
|
...(isAnthropicOAuthInstance(name) && Object.hasOwn(a, "autoSwitchThresholdOverride") ? { autoSwitchThresholdOverride: a.autoSwitchThresholdOverride } : {}),
|
|
480
487
|
...(a.needsReauthReason === "verify_account" ? { needsReauthReason: a.needsReauthReason } : {}),
|
|
481
488
|
...(name === "kiro" && typeof a.autoSelectable === "boolean"
|
|
@@ -495,6 +502,8 @@ async function fetchOAuthRows(
|
|
|
495
502
|
}
|
|
496
503
|
|
|
497
504
|
interface ApiKeyDto {
|
|
505
|
+
disabled?: boolean;
|
|
506
|
+
pauseDetails?: import("../types/account-auto-pause").AccountPauseDetails;
|
|
498
507
|
id: string;
|
|
499
508
|
label?: string;
|
|
500
509
|
masked?: string;
|
|
@@ -523,6 +532,9 @@ async function fetchKeyRows(deps: AccountDeps, baseUrl: string, name: string, qu
|
|
|
523
532
|
label: k.label ?? k.masked,
|
|
524
533
|
masked: k.masked,
|
|
525
534
|
active: k.active ?? k.id === activeId,
|
|
535
|
+
paused: k.disabled === true,
|
|
536
|
+
// Unknown display details are dropped defensively; disabled still keeps the key paused.
|
|
537
|
+
pauseDetails: accountPauseDetailsSchema.safeParse(k.pauseDetails).data,
|
|
526
538
|
}));
|
|
527
539
|
return { rows, activeId, status: 200 };
|
|
528
540
|
}
|