@bitkyc08/opencodex 2.88.0 → 2.90.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-DgxkE6Md.js +58 -0
- package/gui/dist/assets/App-dnkJ85te.css +1 -0
- package/gui/dist/assets/{Tray-B5bC7AWL.js → Tray-DbTNc_88.js} +1 -1
- package/gui/dist/assets/{index-B5bsvxor.js → index-TmaHa54u.js} +15 -15
- package/gui/dist/assets/quota-summary-B16x5DZZ.js +1 -0
- package/gui/dist/index.html +1 -1
- package/package.json +2 -2
- 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/base.ts +4 -0
- package/src/adapters/claude-cli/adapter.ts +13 -7
- package/src/adapters/claude-cli/usage-admission.ts +14 -2
- package/src/adapters/coding-agent/protocol.ts +6 -0
- package/src/adapters/coding-agent/turn.ts +14 -4
- package/src/adapters/cursor/protobuf-request.ts +3 -0
- package/src/adapters/google-http.ts +3 -2
- package/src/adapters/google-progress-history.ts +19 -1
- package/src/adapters/google-vertex-endpoint.ts +67 -0
- package/src/adapters/google.ts +62 -37
- package/src/adapters/image.ts +5 -3
- package/src/adapters/input-media-guard.ts +5 -2
- package/src/adapters/kiro-refusal.ts +41 -1
- package/src/adapters/ollama-native.ts +1 -0
- package/src/adapters/openai-chat/messages.ts +34 -4
- package/src/adapters/openai-chat/passthrough.ts +37 -4
- package/src/adapters/openai-chat/reasoning-replay.ts +98 -13
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +95 -134
- package/src/adapters/openai-chat/serialized-tool-call-markup.ts +108 -0
- package/src/adapters/openai-chat/standalone-bare-tool-calls.ts +136 -0
- package/src/adapters/openai-chat.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +27 -0
- package/src/adapters/openai-responses/opengateway-input.ts +139 -0
- package/src/adapters/openai-responses/passthrough.ts +22 -13
- package/src/adapters/openai-responses/reasoning.ts +68 -1
- package/src/adapters/openai-responses/transport.ts +13 -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/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +20 -10
- package/src/claude/desktop-picker-profile.ts +18 -9
- package/src/claude/desktop-remote-store-io.ts +2 -2
- 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/ensure-desktop-cold-start.ts +2 -2
- 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-lifecycle.ts +19 -6
- package/src/codex/account-pause.ts +6 -1
- package/src/codex/account-runtime-state.ts +5 -0
- package/src/codex/account-store.ts +55 -9
- package/src/codex/account-usability.ts +4 -1
- package/src/codex/auth-api/account-list.ts +13 -5
- package/src/codex/auth-api/account-pause-group.ts +2 -22
- package/src/codex/auth-api/login-collision.ts +59 -0
- package/src/codex/auth-api/login-flow.ts +12 -7
- package/src/codex/auth-api/main-account-probe.ts +3 -2
- package/src/codex/auth-api/pool-mode-gate.ts +81 -13
- package/src/codex/auth-collision.ts +31 -4
- package/src/codex/auth-context.ts +57 -10
- 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/cli-update-plan.ts +18 -3
- package/src/codex/collision-identity.ts +78 -0
- 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-hard-lock.ts +14 -0
- 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/codex/routing/cooldown-math.ts +5 -0
- package/src/codex/routing/failure-window.ts +61 -9
- package/src/codex/routing.ts +4 -3
- package/src/codex/shim-diagnostics.ts +2 -1
- package/src/codex/shim-fingerprint.ts +11 -2
- package/src/codex/shim-templates.ts +16 -5
- package/src/codex/shim-windows-refresh.ts +224 -0
- package/src/codex/shim.ts +45 -21
- package/src/combos/failover.ts +10 -2
- 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/provider-validation.ts +7 -2
- 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 +476 -216
- package/src/generated/model-metadata.ts +1 -1
- package/src/images/loop.ts +34 -11
- 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 +82 -0
- package/src/lib/decision-quota-sink.ts +76 -0
- package/src/lib/provider-admission-wait.ts +14 -0
- package/src/lib/service-secrets.ts +37 -2
- package/src/lib/state-store-registrations.ts +25 -0
- package/src/lib/translator-budget.ts +1 -1
- package/src/lib/upstream-retry.ts +15 -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-import/google-antigravity-adapter.ts +38 -2
- package/src/oauth/account-import/parser.ts +15 -2
- package/src/oauth/account-import/types.ts +7 -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/chatgpt-shared.ts +10 -1
- package/src/oauth/dsh.ts +45 -2
- package/src/oauth/index.ts +5 -35
- package/src/oauth/kiro.ts +44 -1
- package/src/oauth/login-flow-state.ts +11 -2
- 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 +25 -5
- package/src/providers/initial-model-selection.ts +2 -2
- package/src/providers/key-failover.ts +21 -13
- package/src/providers/key-store.ts +8 -3
- package/src/providers/mistral-transport.ts +207 -0
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/openai-sidecar.ts +5 -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 +20 -7
- 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 +45 -8
- package/src/providers/quota/failure-codes.ts +18 -0
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +15 -6
- 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 +4 -1
- package/src/providers/quota-types.ts +6 -1
- package/src/providers/quota.ts +8 -3
- package/src/providers/registry/entries-extended.ts +14 -53
- package/src/providers/registry/model-seeds.ts +1 -1
- package/src/providers/registry.ts +1 -2
- package/src/providers/routed-config.ts +1 -1
- package/src/providers/stale-vision-classification-migration.ts +1 -1
- package/src/providers/xai-transport.ts +2 -1
- package/src/responses/audio.ts +46 -0
- package/src/responses/input-media.ts +37 -15
- package/src/responses/parser-content.ts +22 -27
- package/src/responses/parser.ts +18 -10
- package/src/responses/reasoning-retention.ts +218 -11
- package/src/responses/schema.ts +3 -4
- package/src/responses/web-search-replay.ts +147 -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 +36 -20
- package/src/server/claude-messages.ts +2 -1
- package/src/server/index/optional-listeners.ts +6 -0
- package/src/server/index/serve-options.ts +8 -2
- package/src/server/index/websocket-handler.ts +10 -0
- package/src/server/lifecycle.ts +17 -1
- package/src/server/management/combo-routes.ts +17 -2
- package/src/server/management/oauth-account-routes.ts +12 -2
- 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 +29 -4
- package/src/server/messages-shared.ts +4 -1
- package/src/server/request-log.ts +23 -9
- package/src/server/responses/account-auto-pause-recovery.ts +26 -0
- package/src/server/responses/adapter-continuation.ts +2 -2
- package/src/server/responses/adapter-dispatch.ts +79 -30
- package/src/server/responses/codex-ws-exchange.ts +3 -0
- package/src/server/responses/codex-ws-metadata.ts +44 -0
- package/src/server/responses/codex-ws-session.ts +21 -1
- package/src/server/responses/combo-session-recall.ts +25 -0
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/combo-tool-loop-pin.ts +70 -0
- package/src/server/responses/compact.ts +10 -5
- package/src/server/responses/context-overflow.ts +141 -2
- package/src/server/responses/core-auth.ts +20 -0
- package/src/server/responses/core-codex-account.ts +28 -15
- package/src/server/responses/core-combo.ts +34 -5
- package/src/server/responses/core-opaque-recovery.ts +20 -11
- package/src/server/responses/core.ts +12 -9
- package/src/server/responses/deepseek-image-files.ts +121 -0
- package/src/server/responses/empty-completion-guard.ts +10 -0
- package/src/server/responses/fetch-helpers.ts +65 -14
- package/src/server/responses/input-admission.ts +6 -2
- package/src/server/responses/jev-model-invoke.ts +13 -14
- package/src/server/responses/main-hard-lock-routing.ts +33 -8
- package/src/server/responses/native-steering-log.ts +2 -2
- package/src/server/responses/opengateway-completion-guard.ts +232 -0
- package/src/server/responses/passthrough-completion.ts +41 -0
- package/src/server/responses/passthrough-delivery.ts +5 -2
- package/src/server/responses/passthrough-dispatch.ts +42 -45
- package/src/server/responses/passthrough-key-rotation.ts +40 -0
- package/src/server/responses/passthrough-observations.ts +38 -0
- package/src/server/responses/reasoning-archive-error.ts +21 -0
- package/src/server/responses/request-compression.ts +98 -0
- package/src/server/responses/request-prepare.ts +11 -1
- package/src/server/responses/request-transport.ts +92 -5
- 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/service/orchestration.ts +17 -0
- package/src/types/account-auto-pause.ts +15 -0
- package/src/types/config.ts +23 -1
- package/src/types/provider.ts +11 -5
- package/src/types/request.ts +36 -3
- package/src/types.ts +2 -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 +39 -13
- 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/gui/dist/assets/quota-summary-D8y2wTsB.js +0 -1
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
DEEPSEEK_VISION_PREVIEW_MODEL,
|
|
55
55
|
COMMAND_CODE_MIMO_CONTEXT_WINDOWS,
|
|
56
56
|
COMMAND_CODE_MODEL_INPUT_MODALITIES,
|
|
57
|
-
|
|
57
|
+
OPENCODE_ZEN_FREE_DEEPSEEK_MODELS,
|
|
58
58
|
OPENCODE_ZEN_TEXT_ONLY_MODELS,
|
|
59
59
|
OPENCODE_ZEN_IMAGE_MODELS,
|
|
60
60
|
deepseekThinkingEffortsFor,
|
|
@@ -1020,7 +1020,12 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
1020
1020
|
},
|
|
1021
1021
|
},
|
|
1022
1022
|
// FREEZE 2026-07-10: codestral-latest is unconfirmed behind auth. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
1023
|
-
{
|
|
1023
|
+
{
|
|
1024
|
+
id: "mistral", label: "Mistral", baseUrl: "https://api.mistral.ai/v1", adapter: "openai-chat", authKind: "key",
|
|
1025
|
+
dashboardUrl: "https://console.mistral.ai/api-keys", defaultModel: "codestral-latest", promptCacheKey: true,
|
|
1026
|
+
// Native text-only input enrolls this model in the existing vision sidecar.
|
|
1027
|
+
modelInputModalities: { "zai-glm-5-3": ["text"] },
|
|
1028
|
+
},
|
|
1024
1029
|
{
|
|
1025
1030
|
id: "minimax", label: "MiniMax — Coding Plan", baseUrl: "https://api.minimax.io/v1", adapter: "openai-chat", authKind: "key",
|
|
1026
1031
|
dashboardUrl: "https://platform.minimax.io", defaultModel: "MiniMax-M3", models: MINIMAX_MODELS,
|
|
@@ -1089,20 +1094,20 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
1089
1094
|
},
|
|
1090
1095
|
{
|
|
1091
1096
|
id: "opencode-zen", label: "opencode zen", baseUrl: "https://opencode.ai/zen/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://opencode.ai/auth",
|
|
1092
|
-
//
|
|
1097
|
+
// Keyed opencode.ai/zen/v1 gateway: DeepSeek thinking mode
|
|
1093
1098
|
// requires the assistant's original reasoning_content to be replayed on tool-call
|
|
1094
1099
|
// continuations, or the gateway answers HTTP 400 (issues #950/#994). Mirror the DeepSeek
|
|
1095
1100
|
// reasoning + thinking metadata so `opencode-zen/deepseek-v4-flash-free` — and the other
|
|
1096
1101
|
// Zen DeepSeek thinking models — never serialize a bare tool-call turn.
|
|
1097
|
-
note: "Keyed OpenCode Zen gateway. Free models on this tier are often short-window rate-limited at roughly 15-20 requests/minute (community-measured; OpenCode does not publish RPM). Zen may return generic 429s without Retry-After / X-RateLimit headers; when Retry-After is omitted, opencodex adds a synthetic backoff hint (upstream Retry-After still wins).
|
|
1102
|
+
note: "Keyed OpenCode Zen gateway. Free models on this tier are often short-window rate-limited at roughly 15-20 requests/minute (community-measured; OpenCode does not publish RPM). Zen may return generic 429s without Retry-After / X-RateLimit headers; when Retry-After is omitted, opencodex adds a synthetic backoff hint (upstream Retry-After still wins). Docs: https://opencode.ai/docs/zen/. Free-model prompts may be retained for training — do not send confidential material.",
|
|
1098
1103
|
modelReasoningEfforts: Object.fromEntries(
|
|
1099
|
-
[...DEEPSEEK_GATEWAY_THINKING_MODELS, ...
|
|
1104
|
+
[...DEEPSEEK_GATEWAY_THINKING_MODELS, ...OPENCODE_ZEN_FREE_DEEPSEEK_MODELS].map(id => [id, deepseekThinkingEffortsFor(id)]),
|
|
1100
1105
|
),
|
|
1101
1106
|
modelReasoningEffortMap: Object.fromEntries(
|
|
1102
|
-
[...DEEPSEEK_GATEWAY_THINKING_MODELS, ...
|
|
1107
|
+
[...DEEPSEEK_GATEWAY_THINKING_MODELS, ...OPENCODE_ZEN_FREE_DEEPSEEK_MODELS].map(id => [id, deepseekReasoningMapFor(id)]),
|
|
1103
1108
|
),
|
|
1104
|
-
preserveReasoningContentModels: [...DEEPSEEK_GATEWAY_THINKING_MODELS, ...
|
|
1105
|
-
//
|
|
1109
|
+
preserveReasoningContentModels: [...DEEPSEEK_GATEWAY_THINKING_MODELS, ...OPENCODE_ZEN_FREE_DEEPSEEK_MODELS],
|
|
1110
|
+
// The DeepSeek vision preview id
|
|
1106
1111
|
// (merges into deepseek-v4-flash later).
|
|
1107
1112
|
modelContextWindows: {
|
|
1108
1113
|
[DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576,
|
|
@@ -1114,7 +1119,7 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
1114
1119
|
noVisionModels: [...OPENCODE_ZEN_TEXT_ONLY_MODELS, ...DEEPSEEK_GATEWAY_THINKING_MODELS],
|
|
1115
1120
|
// Same DeepSeek routes as the Go preset above, behind the same vendor, so they carry
|
|
1116
1121
|
// the same json_schema rejection (#1338 / #1415).
|
|
1117
|
-
noJsonSchemaModels: [...DEEPSEEK_GATEWAY_THINKING_MODELS, ...
|
|
1122
|
+
noJsonSchemaModels: [...DEEPSEEK_GATEWAY_THINKING_MODELS, ...OPENCODE_ZEN_FREE_DEEPSEEK_MODELS],
|
|
1118
1123
|
// Muse Spark on Zen can sit silent during prolonged reasoning and close without a protocol terminal.
|
|
1119
1124
|
modelResponsesTerminalRepair: {
|
|
1120
1125
|
"muse-spark-1.2-contributor-free": { graceMs: 5_000 },
|
|
@@ -1266,50 +1271,6 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
1266
1271
|
models: ["glm-5.3-flash", "claude-sonnet-5-5", "gpt-6.1-sol", "deepseek-v4-flash"],
|
|
1267
1272
|
note: "OpenAI-compatible relay for Claude, GPT, GLM and DeepSeek chat models. The seeded models were verified with chat, streaming and tool calls; deepseek-v4.1-flash is listed upstream but currently answers 503 and is left out. An ordinary key only reaches its own routing group's models; a Smart key reaches all seeded models. Image and video models are not served through this Chat preset. Upstream latency varies widely (a few seconds to about 30 s) with occasional transient 503s.",
|
|
1268
1273
|
},
|
|
1269
|
-
{
|
|
1270
|
-
id: "opencode-free",
|
|
1271
|
-
label: "OpenCode Free",
|
|
1272
|
-
adapter: "openai-chat",
|
|
1273
|
-
baseUrl: "https://opencode.ai/zen/v1",
|
|
1274
|
-
authKind: "key",
|
|
1275
|
-
keyOptional: true,
|
|
1276
|
-
featured: true,
|
|
1277
|
-
liveModels: true,
|
|
1278
|
-
note: "No key needed, but OpenCode now gates this tier to its own client: Zen refuses any request that arrives without an x-opencode-session header (error type MissingSessionID, \"OpenCode's free tier can only be used in OpenCode\"). opencodex does not mint that header or claim an OpenCode client identity, because no upstream contract authorizes a third-party agent to present itself as OpenCode. Until OpenCode publishes a third-party integration path for the keyless tier, use the keyed opencode-zen provider instead (https://opencode.ai/auth). Quota figures for when the tier admitted a request: OpenCode advertises about 200 Big Pickle/free-model requests per 5 hours, and the same Zen gateway can short-window rate-limit free models at roughly 15-20 requests/minute, and may return generic 429s without Retry-After (opencodex synthesizes backoff only when that header is omitted). Free models are discovered live from Zen. Data use: per OpenCode's Zen docs (https://opencode.ai/docs/zen/), prompts sent to free models may be retained and used for training/improvement — do not send confidential material through this provider.",
|
|
1279
|
-
dashboardUrl: "https://opencode.ai",
|
|
1280
|
-
staticHeaders: {
|
|
1281
|
-
// Zen answers a bare runtime User-Agent (Bun/x.y.z) more aggressively than a client
|
|
1282
|
-
// that identifies itself, which is what the 429 in #2067 traced to. The value is
|
|
1283
|
-
// deliberately unversioned: a pinned "opencode-cli/<version>" is a claim about an
|
|
1284
|
-
// install we do not have and goes stale on the vendor's schedule, not ours.
|
|
1285
|
-
// Corroboration, not authority: OmniRoute — an independent open-source broker against
|
|
1286
|
-
// the same Zen upstream — defaults to exactly this pair (userAgent "opencode", client
|
|
1287
|
-
// "desktop") in open-sse/executors/opencode.ts, and got there by RETREATING from its
|
|
1288
|
-
// own earlier "opencode-cli/1.0.0" pin. An operator can still override either value
|
|
1289
|
-
// through the provider headers API; user headers win case-insensitively at route time.
|
|
1290
|
-
"User-Agent": "opencode",
|
|
1291
|
-
"x-opencode-client": "desktop",
|
|
1292
|
-
},
|
|
1293
|
-
modelReasoningEfforts: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])),
|
|
1294
|
-
modelReasoningEffortMap: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
|
|
1295
|
-
preserveReasoningContentModels: OPENCODE_FREE_DEEPSEEK_MODELS,
|
|
1296
|
-
// The DeepSeek vision preview id is preemptive metadata for when Zen starts
|
|
1297
|
-
// serving it (merges into v4-flash later).
|
|
1298
|
-
modelContextWindows: {
|
|
1299
|
-
[DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576,
|
|
1300
|
-
},
|
|
1301
|
-
modelInputModalities: {
|
|
1302
|
-
[DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"],
|
|
1303
|
-
...Object.fromEntries(OPENCODE_ZEN_IMAGE_MODELS.map(id => [id, ["text", "image"] as string[]])),
|
|
1304
|
-
},
|
|
1305
|
-
// Same Zen roster behind the same base URL, so it carries the same measured
|
|
1306
|
-
// text-only list rather than only its DeepSeek member (#1043).
|
|
1307
|
-
noVisionModels: OPENCODE_ZEN_TEXT_ONLY_MODELS,
|
|
1308
|
-
// Same reasoning: the free tier is the same Zen roster, so its DeepSeek members get
|
|
1309
|
-
// the keyed tier's json_schema treatment and its reasoning contract rather than a
|
|
1310
|
-
// narrower table that silently falls behind whenever the keyed one is updated.
|
|
1311
|
-
noJsonSchemaModels: [...DEEPSEEK_GATEWAY_THINKING_MODELS, ...OPENCODE_FREE_DEEPSEEK_MODELS],
|
|
1312
|
-
},
|
|
1313
1274
|
// Xiaomi retires mimo-v2.5 and mimo-v2.5-pro on 2026-10-21 with no redirect
|
|
1314
1275
|
// (https://mimo.mi.com/docs/en-US/updates/deprecate), so the first-party presets default to V2.6.
|
|
1315
1276
|
// Saved defaults are not rewritten; V2.5 stays listed until it stops answering.
|
|
@@ -477,7 +477,7 @@ export const COMMAND_CODE_MODEL_INPUT_MODALITIES: Record<string, ["text"] | ["te
|
|
|
477
477
|
...Object.fromEntries(COMMAND_CODE_IMAGE_MODELS.map(id => [id, ["text", "image"] as ["text", "image"]])),
|
|
478
478
|
...Object.fromEntries(COMMAND_CODE_TEXT_ONLY_MODELS.map(id => [id, ["text"] as ["text"]])),
|
|
479
479
|
};
|
|
480
|
-
export const
|
|
480
|
+
export const OPENCODE_ZEN_FREE_DEEPSEEK_MODELS = ["deepseek-v4-flash-free"];
|
|
481
481
|
/*
|
|
482
482
|
* Zen free models that reject `image_url` upstream (#1043, and the reproducible
|
|
483
483
|
* half of #1024).
|
|
@@ -49,8 +49,7 @@ export function getProviderRegistryEntry(id: string): ProviderRegistryEntry | un
|
|
|
49
49
|
* was only ever true for a freshly seeded config: `providerConfigSeed` copies the block once
|
|
50
50
|
* (`derive.ts`), `enrichProviderFromCatalog` fills it only when the whole block is absent, and
|
|
51
51
|
* nothing merged it at request time. So an install that predates a header — or that saved any
|
|
52
|
-
* header of its own — never received the new one
|
|
53
|
-
* shipped for every existing opencode-free user.
|
|
52
|
+
* header of its own — never received the new one.
|
|
54
53
|
*
|
|
55
54
|
* The comparison is case-insensitive on purpose. HTTP header names are case-insensitive, but a
|
|
56
55
|
* plain object spread is not: merging a registry `User-Agent` over a user's `user-agent`
|
|
@@ -134,7 +134,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
134
134
|
const repairLegacyMimoFreeAuth = providerName === "mimo-free"
|
|
135
135
|
&& staticModelCatalog
|
|
136
136
|
&& (provider.authMode === undefined || provider.authMode === "local");
|
|
137
|
-
const explicitKeyOverride = providerUsesKeyAuthOverride(registryEntry, provider
|
|
137
|
+
const explicitKeyOverride = providerUsesKeyAuthOverride(registryEntry, provider);
|
|
138
138
|
const canonicalAuthMode = explicitKeyOverride
|
|
139
139
|
? "key"
|
|
140
140
|
: repairLegacyMimoFreeAuth
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* Scope is deliberately one entry. The Go gateway's `deepseek-v4.1-flash` was declared text-only
|
|
40
40
|
* from jawcode metadata and was measured natively multimodal on 2026-09-19 (see the note at that
|
|
41
41
|
* entry in `registry/entries-core.ts`). Its sibling `deepseek-v4-flash` still rejects images
|
|
42
|
-
* upstream, and the
|
|
42
|
+
* upstream, and the keyed Zen tier (`opencode-zen`) could not be probed at
|
|
43
43
|
* all — an unverified tier is not evidence, so neither is touched here.
|
|
44
44
|
*/
|
|
45
45
|
import { PROVIDER_REGISTRY, providerMatchesRegistryTransport } from "./registry";
|
|
@@ -3,6 +3,7 @@ import type { OcxProviderConfig } from "../types";
|
|
|
3
3
|
import { isEgressTransparentExecutor, markEgressTransparentExecutor } from "../lib/provider-egress";
|
|
4
4
|
import { configuredOutboundFetch } from "../lib/proxy-env";
|
|
5
5
|
import { resolveGithubCopilotTransport } from "./github-copilot-transport";
|
|
6
|
+
import { resolveMistralTransport } from "./mistral-transport";
|
|
6
7
|
|
|
7
8
|
export const XAI_GROK_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
8
9
|
|
|
@@ -121,7 +122,7 @@ export function resolveProviderTransport(
|
|
|
121
122
|
if (providerName === "github-copilot") {
|
|
122
123
|
return resolveGithubCopilotTransport(provider, apiBaseUrl);
|
|
123
124
|
}
|
|
124
|
-
if (providerName !== "xai") return provider;
|
|
125
|
+
if (providerName !== "xai") return resolveMistralTransport(provider);
|
|
125
126
|
|
|
126
127
|
const cacheKey = promptCacheKey?.trim();
|
|
127
128
|
const affinity = cacheKey ? deriveXaiConvId(cacheKey) : undefined;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { OcxAudioContent } from "../types";
|
|
2
|
+
|
|
3
|
+
export type ChatAudioFormat = OcxAudioContent["format"];
|
|
4
|
+
|
|
5
|
+
const MIME_FORMATS: Readonly<Record<string, ChatAudioFormat>> = {
|
|
6
|
+
"audio/mp3": "mp3",
|
|
7
|
+
"audio/mpeg": "mp3",
|
|
8
|
+
"audio/wav": "wav",
|
|
9
|
+
"audio/wave": "wav",
|
|
10
|
+
"audio/x-wav": "wav",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function normalizedFormat(value: unknown): ChatAudioFormat | undefined {
|
|
14
|
+
if (typeof value !== "string") return undefined;
|
|
15
|
+
const format = value.trim().toLowerCase();
|
|
16
|
+
return format === "mp3" || format === "wav" ? format : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function validBase64(value: string): boolean {
|
|
20
|
+
return value.length > 0
|
|
21
|
+
&& value.length % 4 === 0
|
|
22
|
+
&& /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse the inline audio shape Codex puts in a Responses content block. Chat Completions
|
|
27
|
+
* needs bare base64 plus `wav`/`mp3`, while Codex supplies an RFC 2397 data URL. A raw
|
|
28
|
+
* base64 value with an explicit supported format is accepted as well; remote URLs are not.
|
|
29
|
+
*/
|
|
30
|
+
export function audioContentFromRaw(audioUrl: unknown, declaredFormat?: unknown): OcxAudioContent | undefined {
|
|
31
|
+
if (typeof audioUrl !== "string" || audioUrl.length === 0) return undefined;
|
|
32
|
+
const dataUrl = /^data:([^;,]+);base64,([\s\S]*)$/.exec(audioUrl);
|
|
33
|
+
let data: string;
|
|
34
|
+
let format: ChatAudioFormat | undefined;
|
|
35
|
+
if (dataUrl) {
|
|
36
|
+
const declared = normalizedFormat(declaredFormat);
|
|
37
|
+
const mime = dataUrl[1].trim().toLowerCase();
|
|
38
|
+
format = MIME_FORMATS[mime] ?? (mime === "application/octet-stream" ? declared : undefined);
|
|
39
|
+
data = dataUrl[2].replace(/\s+/g, "");
|
|
40
|
+
} else {
|
|
41
|
+
format = normalizedFormat(declaredFormat);
|
|
42
|
+
data = audioUrl.replace(/\s+/g, "");
|
|
43
|
+
}
|
|
44
|
+
if (!format || !validBase64(data)) return undefined;
|
|
45
|
+
return { type: "audio", data, format, text: `[audio: ${format}]` };
|
|
46
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isInlineDocumentDataUrl } from "./inline-document";
|
|
2
|
+
import { audioContentFromRaw } from "./audio";
|
|
2
3
|
|
|
3
4
|
/** Input kinds for which the normalized request has no lossless content carrier. */
|
|
4
5
|
export type UntranslatedInputMedia = "audio" | "file";
|
|
@@ -10,23 +11,35 @@ function isRecord(value: unknown): value is RecordValue {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* Whether the position being scanned has a converter that builds a
|
|
14
|
+
* Whether the position being scanned has a converter that builds a lossless media part.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* User content carries documents and inline audio; developer content carries documents only.
|
|
17
|
+
* Tool output carries inline audio through the Chat adapter's follow-up user message. System and
|
|
18
|
+
* assistant content have no lossless translated media carrier, so exempting an attachment there
|
|
19
|
+
* would turn today's explicit refusal into the silent drop this scanner exists to prevent.
|
|
18
20
|
*/
|
|
19
|
-
type CarrierPosition = "user-content" | "flattened";
|
|
21
|
+
type CarrierPosition = "user-content" | "developer-content" | "tool-output" | "flattened";
|
|
20
22
|
|
|
21
|
-
function mediaKind(value: unknown, position: CarrierPosition): UntranslatedInputMedia | undefined {
|
|
23
|
+
function mediaKind(value: unknown, position: CarrierPosition, allowAudio: boolean): UntranslatedInputMedia | undefined {
|
|
22
24
|
if (!isRecord(value)) return undefined;
|
|
23
|
-
if (value.type === "input_audio" || value.type === "audio")
|
|
25
|
+
if (value.type === "input_audio" || value.type === "audio") {
|
|
26
|
+
// A Chat content part has a nested input_audio object, while Codex's Responses
|
|
27
|
+
// block uses audio_url. Only the latter is carried by the translated Chat adapter; the
|
|
28
|
+
// Chat-shaped `audio` spelling has no parser path and must never pass the guard.
|
|
29
|
+
return value.type === "input_audio"
|
|
30
|
+
&& allowAudio
|
|
31
|
+
&& (position === "user-content" || position === "tool-output")
|
|
32
|
+
&& audioContentFromRaw(value.audio_url, value.format)
|
|
33
|
+
? undefined
|
|
34
|
+
: "audio";
|
|
35
|
+
}
|
|
24
36
|
if (value.type === "input_file" || value.type === "file" || value.type === "document") {
|
|
25
37
|
// A document that carries its own bytes has a lossless carrier in user content, so refusing
|
|
26
38
|
// it there would reject the very request #5212 exists to preserve. A reference with no
|
|
27
39
|
// payload has none anywhere: translated adapters cannot dereference a file_id or a remote
|
|
28
40
|
// source.
|
|
29
|
-
return position === "user-content"
|
|
41
|
+
return (position === "user-content" || position === "developer-content")
|
|
42
|
+
&& carriesInlineDocumentBytes(value) ? undefined : "file";
|
|
30
43
|
}
|
|
31
44
|
// A file-id-only image is not pixels: translated adapters cannot dereference it.
|
|
32
45
|
if (value.type === "input_image" && typeof value.file_id === "string" && value.file_id.length > 0
|
|
@@ -46,10 +59,10 @@ function carriesInlineDocumentBytes(value: RecordValue): boolean {
|
|
|
46
59
|
&& value.source.data.length > 0;
|
|
47
60
|
}
|
|
48
61
|
|
|
49
|
-
function contentMedia(content: unknown, position: CarrierPosition): UntranslatedInputMedia | undefined {
|
|
62
|
+
function contentMedia(content: unknown, position: CarrierPosition, allowAudio: boolean): UntranslatedInputMedia | undefined {
|
|
50
63
|
if (!Array.isArray(content)) return undefined;
|
|
51
64
|
for (const part of content) {
|
|
52
|
-
const kind = mediaKind(part, position);
|
|
65
|
+
const kind = mediaKind(part, position, allowAudio);
|
|
53
66
|
if (kind) return kind;
|
|
54
67
|
}
|
|
55
68
|
return undefined;
|
|
@@ -60,20 +73,29 @@ function contentMedia(content: unknown, position: CarrierPosition): Untranslated
|
|
|
60
73
|
* arguments, schema properties, or arbitrary nested objects. No payload is copied,
|
|
61
74
|
* decoded, fetched or included in the returned value.
|
|
62
75
|
*/
|
|
63
|
-
export function untranslatedResponsesInputMedia(
|
|
76
|
+
export function untranslatedResponsesInputMedia(
|
|
77
|
+
body: unknown,
|
|
78
|
+
options: { allowAudio?: boolean } = {},
|
|
79
|
+
): UntranslatedInputMedia | undefined {
|
|
64
80
|
if (!isRecord(body) || !Array.isArray(body.input)) return undefined;
|
|
65
81
|
for (const item of body.input) {
|
|
66
82
|
if (!isRecord(item)) continue;
|
|
67
|
-
const direct = mediaKind(item, "flattened");
|
|
83
|
+
const direct = mediaKind(item, "flattened", options.allowAudio === true);
|
|
68
84
|
if (direct) return direct;
|
|
69
85
|
if (item.type === "function_call_output" || item.type === "custom_tool_call_output") {
|
|
70
|
-
const kind = contentMedia(item.output, "
|
|
86
|
+
const kind = contentMedia(item.output, "tool-output", options.allowAudio === true);
|
|
71
87
|
if (kind) return kind;
|
|
72
88
|
} else if (item.type === "message" || item.type === undefined) {
|
|
73
89
|
// `inputContentParts` runs for user and developer messages only; a system message is
|
|
74
90
|
// flattened to text by the parser.
|
|
75
91
|
const role = item.role;
|
|
76
|
-
const kind = contentMedia(
|
|
92
|
+
const kind = contentMedia(
|
|
93
|
+
item.content,
|
|
94
|
+
role === "user" ? "user-content"
|
|
95
|
+
: role === "developer" ? "developer-content"
|
|
96
|
+
: "flattened",
|
|
97
|
+
options.allowAudio === true,
|
|
98
|
+
);
|
|
77
99
|
if (kind) return kind;
|
|
78
100
|
}
|
|
79
101
|
}
|
|
@@ -87,7 +109,7 @@ export function untranslatedChatInputMedia(body: unknown): UntranslatedInputMedi
|
|
|
87
109
|
if (!isRecord(message)) continue;
|
|
88
110
|
// Only the `user` branch of the Chat projection builds content blocks. `system`,
|
|
89
111
|
// `developer`, `assistant` and `tool` all reduce their content to a string.
|
|
90
|
-
const kind = contentMedia(message.content, message.role === "user" ? "user-content" : "flattened");
|
|
112
|
+
const kind = contentMedia(message.content, message.role === "user" ? "user-content" : "flattened", false);
|
|
91
113
|
if (kind) return kind;
|
|
92
114
|
}
|
|
93
115
|
return undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OcxContentPart, OcxTextContent } from "../types";
|
|
2
|
+
import { audioContentFromRaw } from "./audio";
|
|
2
3
|
import { inlineDocumentFromDataUrl } from "./inline-document";
|
|
3
4
|
|
|
4
5
|
export function isObj(v: unknown): v is Record<string, unknown> {
|
|
@@ -65,23 +66,15 @@ export function inputContentParts(blocks: unknown): string | OcxContentPart[] {
|
|
|
65
66
|
const processing = nonEmptyString((block as { processing?: string }).processing);
|
|
66
67
|
if (videoUrl) parts.push({ type: "video", videoUrl, ...(processing ? { processing } : {}) });
|
|
67
68
|
} else if (block.type === "input_audio") {
|
|
68
|
-
// Upstream Codex sends input_audio with an audio_url (codex-rs
|
|
69
|
-
// protocol/src/models.rs). The IR has no audio carrier and no adapter consumes
|
|
70
|
-
// one, so this part used to vanish with no trace at all.
|
|
71
|
-
//
|
|
72
|
-
// This records PRESENCE only and is NOT audio support: never the payload, which
|
|
73
|
-
// is large base64 and would explode the token count, and never the URL, which
|
|
74
|
-
// can carry a signed token. This parser must stay non-throwing — the native
|
|
75
|
-
// Responses passthrough also runs through parseRequest before the adapter
|
|
76
|
-
// forwards _rawBody, so refusing here would regress legitimate raw passthrough.
|
|
77
|
-
//
|
|
78
|
-
// No adapter refuses audio at its wire today: by this point the part is already a
|
|
79
|
-
// text marker, so downstream adapters see text and continue. A typed unsupported-
|
|
80
|
-
// modality carrier that survives to final adapter dispatch is a separate, recorded
|
|
81
|
-
// residual — do not describe this branch as a refusal.
|
|
82
69
|
const b = block as { audio_url?: string; format?: string };
|
|
83
|
-
|
|
84
|
-
|
|
70
|
+
// Keep bytes only when they have a Chat-compatible inline form. Unsupported or
|
|
71
|
+
// remote references retain a marker for text-only wires; the final adapter guard
|
|
72
|
+
// refuses them rather than allowing a silent drop.
|
|
73
|
+
const audio = audioContentFromRaw(b.audio_url, b.format);
|
|
74
|
+
if (audio) {
|
|
75
|
+
parts.push(audio);
|
|
76
|
+
} else if (nonEmptyString(b.audio_url)) {
|
|
77
|
+
const format = safeAudioFormat(b.format);
|
|
85
78
|
parts.push({ type: "text", text: format ? `[audio: ${format}]` : "[audio]" });
|
|
86
79
|
}
|
|
87
80
|
} else if (block.type === "input_file") {
|
|
@@ -104,7 +97,7 @@ export function inputContentParts(blocks: unknown): string | OcxContentPart[] {
|
|
|
104
97
|
// fabricating a "[file: ...]" marker for an attachment that was never sent.
|
|
105
98
|
}
|
|
106
99
|
}
|
|
107
|
-
// Collapse to a plain string only for a single TEXT part;
|
|
100
|
+
// Collapse to a plain string only for a single TEXT part; media must stay structured.
|
|
108
101
|
if (parts.length === 1 && parts[0].type === "text") return parts[0].text;
|
|
109
102
|
return parts;
|
|
110
103
|
}
|
|
@@ -129,15 +122,15 @@ export function outputTextOf(blocks: unknown): OcxTextContent[] {
|
|
|
129
122
|
}
|
|
130
123
|
|
|
131
124
|
/**
|
|
132
|
-
* Tool-call output content. Preserves images (e.g. Codex `view_image` returns
|
|
133
|
-
*
|
|
134
|
-
* Never inlines an image_url as text (that would explode the token count).
|
|
125
|
+
* Tool-call output content. Preserves images (e.g. Codex `view_image` returns)
|
|
126
|
+
* and inline audio: returns content parts when either is present, else a plain joined string.
|
|
127
|
+
* Never inlines an image_url or audio payload as text (that would explode the token count).
|
|
135
128
|
*/
|
|
136
129
|
export function outputToToolResultContent(output: string | unknown[] | undefined): string | OcxContentPart[] {
|
|
137
130
|
if (typeof output === "string") return output;
|
|
138
131
|
if (!Array.isArray(output)) return "";
|
|
139
132
|
const parts: OcxContentPart[] = [];
|
|
140
|
-
let
|
|
133
|
+
let hasMedia = false;
|
|
141
134
|
for (const raw of output) {
|
|
142
135
|
if (!isObj(raw)) continue;
|
|
143
136
|
if (raw.type === "output_text" || raw.type === "text" || raw.type === "input_text") {
|
|
@@ -149,15 +142,17 @@ export function outputToToolResultContent(output: string | unknown[] | undefined
|
|
|
149
142
|
const fileId = nonEmptyString(raw.file_id);
|
|
150
143
|
if (imageUrl) {
|
|
151
144
|
parts.push({ type: "image", imageUrl, ...(typeof raw.detail === "string" ? { detail: normalizeImageDetail(raw.detail) } : {}) });
|
|
152
|
-
|
|
145
|
+
hasMedia = true;
|
|
153
146
|
} else if (fileId) {
|
|
154
147
|
parts.push({ type: "text", text: `[image: ${fileId}]` });
|
|
155
148
|
}
|
|
156
149
|
} else if (raw.type === "input_audio") {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
150
|
+
const audio = audioContentFromRaw(raw.audio_url, raw.format);
|
|
151
|
+
if (audio) {
|
|
152
|
+
parts.push(audio);
|
|
153
|
+
hasMedia = true;
|
|
154
|
+
} else if (nonEmptyString(raw.audio_url)) {
|
|
155
|
+
const format = safeAudioFormat(raw.format);
|
|
161
156
|
parts.push({ type: "text", text: format ? `[audio: ${format}]` : "[audio]" });
|
|
162
157
|
}
|
|
163
158
|
} else if (raw.type === "encrypted_content") {
|
|
@@ -165,7 +160,7 @@ export function outputToToolResultContent(output: string | unknown[] | undefined
|
|
|
165
160
|
parts.push({ type: "text", text: "[encrypted content omitted]" });
|
|
166
161
|
}
|
|
167
162
|
}
|
|
168
|
-
if (!
|
|
163
|
+
if (!hasMedia) return parts.map(p => (p.type === "text" ? p.text : "")).join("");
|
|
169
164
|
return parts;
|
|
170
165
|
}
|
|
171
166
|
|
package/src/responses/parser.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { previousResponseReplayPrefixLength } from "./state";
|
|
|
22
22
|
import { decodeReasoningEnvelope } from "./reasoning-envelope";
|
|
23
23
|
import { hasRoutedIdentity, nameRoutedIdentity } from "../adapters/identity";
|
|
24
24
|
import { extractHostedWebSearch, WEB_SEARCH_TOOL_NAME } from "../web-search/synthetic-tool";
|
|
25
|
+
import { rewriteReplayedWebSearchCells } from "./web-search-replay";
|
|
25
26
|
import { buildImageTool, extractHostedImageGeneration, IMAGE_GEN_TOOL_NAME } from "../images/synthetic-tool";
|
|
26
27
|
import { toolSearchDescription, toolSearchParameters } from "./tool-search-compat";
|
|
27
28
|
|
|
@@ -232,6 +233,13 @@ export function parseRequest(
|
|
|
232
233
|
// Built before the item loop: a custom_tool_call echoed back in `input` needs the
|
|
233
234
|
// namespace from the request's own tool catalog to survive the round trip.
|
|
234
235
|
const customToolNamespacesByName = customToolNamespaces(data.tools);
|
|
236
|
+
// Stash the hosted web_search config (if Codex enabled it) so the proxy can run searches via the
|
|
237
|
+
// sidecar for routed providers. buildTools still drops the hosted tool; the sidecar path
|
|
238
|
+
// re-injects a synthetic function tool only when it will actually handle the call. The same
|
|
239
|
+
// declaration decides how replayed search cells are rebuilt below.
|
|
240
|
+
const webSearch = extractHostedWebSearch(data.tools as unknown[] | undefined);
|
|
241
|
+
// With search declared, replayed hosted cells become web_search call/output pairs (#7269).
|
|
242
|
+
const webSearchReplay = webSearch && Array.isArray(data.input) ? rewriteReplayedWebSearchCells(data.input) : undefined;
|
|
235
243
|
const systemPrompt: string[] = [];
|
|
236
244
|
// Responses reasoning siblings belong to the following assistant, including across call items.
|
|
237
245
|
// Keep them off the message list until that assistant arrives; turn boundaries clear the array.
|
|
@@ -274,9 +282,12 @@ export function parseRequest(
|
|
|
274
282
|
if (data.previous_response_id) continuationConversationMessageIndex = messages.length;
|
|
275
283
|
messages.push({ role: "user", content: data.input, timestamp: now });
|
|
276
284
|
} else if (data.input) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
285
|
+
const inputItems = (webSearchReplay?.items ?? data.input) as typeof data.input;
|
|
286
|
+
for (let itemIndex = 0; itemIndex < inputItems.length; itemIndex++) {
|
|
287
|
+
const item = inputItems[itemIndex];
|
|
288
|
+
// Boundaries are measured on the caller's input, before any replayed-search rewrite.
|
|
289
|
+
const inputIndex = webSearchReplay?.sourceIndexes[itemIndex] ?? itemIndex;
|
|
290
|
+
if (itemIndex > 0 && isAnthropicProgressMirror(item, anthropicProgressEnvelope(inputItems[itemIndex - 1]))) continue;
|
|
280
291
|
const effectiveType = (item as { type?: string }).type ?? ("role" in item ? "message" : undefined);
|
|
281
292
|
const itemRole = (item as { role?: string }).role;
|
|
282
293
|
const externalTaskInput = effectiveType === "function_call_output" ? externalTaskInputContent(item) : undefined;
|
|
@@ -529,9 +540,9 @@ export function parseRequest(
|
|
|
529
540
|
}
|
|
530
541
|
|
|
531
542
|
if (effectiveType === "web_search_call") {
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
//
|
|
543
|
+
// Without a search declaration the cell stays out of routed history: a call to an undeclared
|
|
544
|
+
// tool, or assistant-visible marker text the model could echo as a fake answer, would be
|
|
545
|
+
// worse than the gap. With one, the cell was already rewritten into a web_search pair.
|
|
535
546
|
pendingReasoning.length = 0;
|
|
536
547
|
continue;
|
|
537
548
|
}
|
|
@@ -689,10 +700,6 @@ export function parseRequest(
|
|
|
689
700
|
if (data.service_tier !== undefined) options.serviceTier = data.service_tier;
|
|
690
701
|
if (data.prompt_cache_key !== undefined) options.promptCacheKey = data.prompt_cache_key;
|
|
691
702
|
|
|
692
|
-
// Stash the hosted web_search config (if Codex enabled it) so the proxy can run searches via the
|
|
693
|
-
// gpt-mini sidecar for routed providers. buildTools still drops the hosted tool; the sidecar path
|
|
694
|
-
// re-injects a synthetic function tool only when it will actually handle the call.
|
|
695
|
-
const webSearch = extractHostedWebSearch(data.tools as unknown[] | undefined);
|
|
696
703
|
const imageGen = extractHostedImageGeneration([
|
|
697
704
|
...(data.tools as unknown[] ?? []),
|
|
698
705
|
...loadedToolSpecs,
|
|
@@ -718,6 +725,7 @@ export function parseRequest(
|
|
|
718
725
|
? { _continuationConversationMessageIndex: continuationConversationMessageIndex }
|
|
719
726
|
: {}),
|
|
720
727
|
...(webSearch ? { _webSearch: webSearch } : {}),
|
|
728
|
+
...(webSearchReplay ? { _replayedWebSearchCells: webSearchReplay.cells } : {}),
|
|
721
729
|
...(imageGen ? { _imageGeneration: imageGen } : {}),
|
|
722
730
|
...(textFormat ? { _structuredOutput: true } : {}),
|
|
723
731
|
...(compactionRequest ? { _compactionRequest: true } : {}),
|