@bitkyc08/opencodex 2.59.0 → 2.61.0-preview.20260922
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/AGENTS_INSTALL.md +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-E64Rzjap.js +50 -0
- package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
- package/gui/dist/assets/index-DpdfZWMK.js +86 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +8 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +20 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +32 -4
- package/src/adapters/command-code.ts +52 -4
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +20 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +14 -3
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +125 -36
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +34 -7
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google-http.ts +49 -10
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +66 -11
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +23 -58
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +17 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +13 -151
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +46 -7
- package/src/cli/doctor.ts +28 -9
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +203 -59
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +4 -7
- package/src/client/runtime.ts +26 -2
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/account-store.ts +65 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +43 -17
- package/src/codex/catalog/retained-sync.ts +24 -28
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +31 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +99 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +116 -54
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +12 -42
- package/src/codex/shim-templates.ts +29 -3
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/companion/settings.ts +132 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +52 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +31 -1
- package/src/config/schema/leaf-validators.ts +59 -0
- package/src/config.ts +3 -3
- package/src/generated/compatibility-version.json +604 -280
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +921 -0
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +134 -110
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +63 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +109 -16
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +27 -108
- package/src/oauth/login-cli.ts +80 -29
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +97 -19
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +179 -38
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +38 -23
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +47 -20
- package/src/providers/registry/entries-extended.ts +63 -4
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +56 -10
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +3 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +108 -117
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +60 -4
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +61 -20
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +142 -39
- package/src/server/index/spend-ledger-lifecycle.ts +92 -0
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +34 -38
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +20 -0
- package/src/server/management/shared.ts +28 -4
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +10 -9
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +138 -12
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +169 -2
- package/src/server/request-metrics.ts +298 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +43 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +91 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +45 -15
- package/src/server/responses/passthrough-dispatch.ts +215 -44
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +109 -18
- package/src/server/responses/request-send-budget.ts +17 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +26 -6
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +35 -2
- package/src/server/search.ts +25 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +156 -4
- package/src/types/config.ts +48 -3
- package/src/types/provider.ts +91 -0
- package/src/types/request.ts +30 -2
- package/src/types/tools.ts +33 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/alpha-search.ts +21 -1
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
package/src/adapters/google.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AdapterFetchContext, AdapterRequest, ProviderAdapter } from "./base";
|
|
2
|
-
import { debugDroppedFrame } from "../lib/debug";
|
|
2
|
+
import { debugDroppedFrame, debugProviderDiagnosticLazy } from "../lib/debug";
|
|
3
3
|
import { createToolCallIdAllocator } from "./tool-call-id";
|
|
4
4
|
import { createImageBudget, materializeInlineImage, MAX_ENCODED_BYTES_PER_IMAGE, artifactHttpUrl } from "../images/artifacts";
|
|
5
5
|
import type {
|
|
@@ -15,13 +15,16 @@ import type {
|
|
|
15
15
|
OcxUsage,
|
|
16
16
|
} from "../types";
|
|
17
17
|
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolChoiceToolPredicate } from "../types";
|
|
18
|
+
import type { OcxTool } from "../types";
|
|
18
19
|
import { contentPartsToText, parseDataUrl } from "./image";
|
|
19
20
|
import { getVertexAccessToken } from "../lib/gcp-adc";
|
|
20
21
|
import { fetchAntigravityWithRetry, fetchVertexWithRetry } from "./google-http";
|
|
21
22
|
import { safeAntigravityHttpErrorMessage, safeVertexHttpErrorMessage } from "./google-errors";
|
|
22
23
|
import { isVertexTruncatedTurn, vertexTruncationErrorMessage } from "./google-truncation";
|
|
23
|
-
import { ANTIGRAVITY_REQUEST_UA, antigravitySessionId, isLikelyRealThoughtSignature, sanitizeAntigravityClaudeSignatures } from "./google-antigravity-wire";
|
|
24
|
+
import { ANTIGRAVITY_REQUEST_UA, antigravitySessionAnchor, antigravitySessionId, isLikelyRealThoughtSignature, sanitizeAntigravityClaudeSignatures } from "./google-antigravity-wire";
|
|
25
|
+
import { summarizeGoogleWireShape } from "./google-wire-shape";
|
|
24
26
|
import { compileGoogleWireBody } from "./google-wire-compiler";
|
|
27
|
+
import type { GoogleToolSchemaLossReport, GoogleToolSchemaProfile } from "./google-tool-schema";
|
|
25
28
|
import { identifyRoutedModel } from "./identity";
|
|
26
29
|
import {
|
|
27
30
|
antigravityUsesReplayCache,
|
|
@@ -344,6 +347,13 @@ function messagesToGeminiFormat(
|
|
|
344
347
|
parts.push(data ? { inline_data: { mime_type: data.mediaType, data: data.base64 } } : { text: `[video: ${p.videoUrl}]` });
|
|
345
348
|
continue;
|
|
346
349
|
}
|
|
350
|
+
if (p.type === "document") {
|
|
351
|
+
// Gemini takes document bytes through the same inline_data part as images and
|
|
352
|
+
// video. The marker on the part is the fallback for wires without one, not this
|
|
353
|
+
// wire's best effort (#5212).
|
|
354
|
+
parts.push({ inline_data: { mime_type: p.mediaType, data: p.data } });
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
347
357
|
// Drop empty/malformed text instead of emitting `{ text: "" }` or a bare `{}` part.
|
|
348
358
|
const textPart = geminiTextPart(p.text);
|
|
349
359
|
if (textPart) parts.push(textPart);
|
|
@@ -463,9 +473,7 @@ function messagesToGeminiFormat(
|
|
|
463
473
|
|
|
464
474
|
function toolsToGeminiFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
465
475
|
if (!parsed.context.tools?.length) return undefined;
|
|
466
|
-
const tools =
|
|
467
|
-
? parsed.context.tools.filter(toolChoiceToolPredicate(parsed.options.toolChoice, parsed.context.tools))
|
|
468
|
-
: parsed.context.tools;
|
|
476
|
+
const tools = advertisedGeminiTools(parsed);
|
|
469
477
|
if (tools.length === 0) return undefined;
|
|
470
478
|
return [{
|
|
471
479
|
functionDeclarations: tools.map(t => ({
|
|
@@ -476,19 +484,37 @@ function toolsToGeminiFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
|
476
484
|
}];
|
|
477
485
|
}
|
|
478
486
|
|
|
487
|
+
/** The declarations this request actually advertises, after any allowed-tools filter. */
|
|
488
|
+
function advertisedGeminiTools(parsed: OcxParsedRequest): readonly OcxTool[] {
|
|
489
|
+
const declared = parsed.context.tools ?? [];
|
|
490
|
+
return isAllowedToolChoice(parsed.options.toolChoice)
|
|
491
|
+
? declared.filter(toolChoiceToolPredicate(parsed.options.toolChoice, declared))
|
|
492
|
+
: declared;
|
|
493
|
+
}
|
|
494
|
+
|
|
479
495
|
/**
|
|
480
496
|
* Client tool_choice enforcement on the wire. The catalog nudge states the same contract in
|
|
481
497
|
* prose, but without functionCallingConfig the model is free to ignore it. "auto" stays absent
|
|
482
498
|
* so the common case is byte-identical. The allowedTools variant already filters the
|
|
483
499
|
* declarations in toolsToGeminiFormat; only its "required" half needs a wire mode.
|
|
500
|
+
*
|
|
501
|
+
* A caller that declares strict tools is asking for its argument schemas to be enforced, and
|
|
502
|
+
* Gemini expresses that as VALIDATED. The mode existed and was plumbed end to end, but was only
|
|
503
|
+
* ever reachable by matching a model name, so a strict declaration arrived as an ordinary
|
|
504
|
+
* unvalidated AUTO turn and the response looked the same either way (#5210). VALIDATED replaces
|
|
505
|
+
* AUTO only: ANY and NONE are stronger constraints the caller asked for explicitly, and
|
|
506
|
+
* overwriting either of them would lose the choice this function exists to enforce.
|
|
484
507
|
*/
|
|
485
508
|
function toolChoiceToGeminiToolConfig(parsed: OcxParsedRequest): Record<string, unknown> | undefined {
|
|
486
509
|
const choice = parsed.options.toolChoice;
|
|
487
|
-
|
|
510
|
+
const validated = advertisedGeminiTools(parsed).some(t => t.strict === true)
|
|
511
|
+
? { functionCallingConfig: { mode: "VALIDATED" } }
|
|
512
|
+
: undefined;
|
|
513
|
+
if (!choice || choice === "auto") return validated;
|
|
488
514
|
if (choice === "none") return { functionCallingConfig: { mode: "NONE" } };
|
|
489
515
|
if (choice === "required") return { functionCallingConfig: { mode: "ANY" } };
|
|
490
516
|
if (isAllowedToolChoice(choice)) {
|
|
491
|
-
return choice.mode === "required" ? { functionCallingConfig: { mode: "ANY" } } :
|
|
517
|
+
return choice.mode === "required" ? { functionCallingConfig: { mode: "ANY" } } : validated;
|
|
492
518
|
}
|
|
493
519
|
return {
|
|
494
520
|
functionCallingConfig: {
|
|
@@ -737,6 +763,14 @@ function invalidGoogleShapeEvent(
|
|
|
737
763
|
}
|
|
738
764
|
|
|
739
765
|
export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
766
|
+
const toolSchemaPolicy = provider.googleToolSchemaPolicy ?? "compatible";
|
|
767
|
+
const toolSchemaProfile = {
|
|
768
|
+
endpointClass: provider.googleMode ?? "ai-studio",
|
|
769
|
+
} satisfies GoogleToolSchemaProfile;
|
|
770
|
+
const reportToolSchemaLoss = (report: GoogleToolSchemaLossReport): void => {
|
|
771
|
+
if (!report.lossy && report.uncertainComparisons === 0) return;
|
|
772
|
+
debugProviderDiagnosticLazy("google", "google-tool-schema-loss", () => ({ ...report }));
|
|
773
|
+
};
|
|
740
774
|
// Per-request closure: resolveAdapter builds a fresh adapter per request (server.ts), so buildRequest
|
|
741
775
|
// can stash the CCA model/session for parseStream's reasoning-replay observation.
|
|
742
776
|
let antigravityModel: string | undefined;
|
|
@@ -799,7 +833,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
799
833
|
...(provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
|
|
800
834
|
? {
|
|
801
835
|
fetchResponse: (request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> =>
|
|
802
|
-
(provider.googleMode === "cloud-code-assist" ? fetchAntigravityWithRetry : fetchVertexWithRetry)(
|
|
836
|
+
(provider.googleMode === "cloud-code-assist" ? fetchAntigravityWithRetry : fetchVertexWithRetry)(
|
|
837
|
+
request,
|
|
838
|
+
ctx,
|
|
839
|
+
{ toolSchemaProfile, toolSchemaPolicy },
|
|
840
|
+
),
|
|
803
841
|
formatErrorBody: (status: number, _headers: Headers, payloadText: string): string =>
|
|
804
842
|
(provider.googleMode === "cloud-code-assist" ? safeAntigravityHttpErrorMessage : safeVertexHttpErrorMessage)(status, payloadText),
|
|
805
843
|
}
|
|
@@ -970,7 +1008,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
970
1008
|
const fcc = (existing.functionCallingConfig ?? {}) as Record<string, unknown>;
|
|
971
1009
|
draftRequest.toolConfig = { ...existing, functionCallingConfig: { ...fcc, mode: "VALIDATED" } };
|
|
972
1010
|
}
|
|
973
|
-
const compiled = compileGoogleWireBody(draftRequest);
|
|
1011
|
+
const compiled = compileGoogleWireBody(draftRequest, toolSchemaProfile, toolSchemaPolicy);
|
|
1012
|
+
reportToolSchemaLoss(compiled.toolSchemaLossReport);
|
|
974
1013
|
const request = compiled.body;
|
|
975
1014
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
976
1015
|
// Compile names before replay: signatures are keyed by the exact provider-visible name.
|
|
@@ -989,6 +1028,20 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
989
1028
|
// Vertex and AI Studio share one decision. A second check here would append a
|
|
990
1029
|
// duplicate nudge whenever signature sanitization reshapes the tail afterwards.
|
|
991
1030
|
}
|
|
1031
|
+
// Opt-in structural description of the request that is about to leave (#5008).
|
|
1032
|
+
//
|
|
1033
|
+
// Passed as a BUILDER, not a value: the lazy form gates before invoking it, so a session
|
|
1034
|
+
// with provider debug off never pays the walk, and it evaluates the projection inside its
|
|
1035
|
+
// own try/catch, so a throw in here cannot turn a built request into a rejected one. The
|
|
1036
|
+
// projection only reads `request`, so the bytes below are the same either way.
|
|
1037
|
+
debugProviderDiagnosticLazy("google", "antigravity-wire-shape", () => summarizeGoogleWireShape(request, {
|
|
1038
|
+
sessionAnchor: antigravitySessionAnchor(parsed),
|
|
1039
|
+
// Signed at translation time, from client history or the durable store. The
|
|
1040
|
+
// Antigravity session cache signs afterwards, inside applyAntigravityReplay, and the
|
|
1041
|
+
// projection attributes that remainder to the cache rather than to this count.
|
|
1042
|
+
historySignedCalls: replayedCallIds.length,
|
|
1043
|
+
replayScopeBound: parsed._reasoningReplayScope !== undefined,
|
|
1044
|
+
}));
|
|
992
1045
|
const envelope = {
|
|
993
1046
|
model: wireModelId,
|
|
994
1047
|
// The envelope's `userAgent` field is a protocol constant ("antigravity"), distinct from
|
|
@@ -1006,7 +1059,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1006
1059
|
}
|
|
1007
1060
|
|
|
1008
1061
|
if (provider.googleMode === "vertex") {
|
|
1009
|
-
const compiled = compileGoogleWireBody(body);
|
|
1062
|
+
const compiled = compileGoogleWireBody(body, toolSchemaProfile, toolSchemaPolicy);
|
|
1063
|
+
reportToolSchemaLoss(compiled.toolSchemaLossReport);
|
|
1010
1064
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
1011
1065
|
const vertexProject = provider.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || "api-key";
|
|
1012
1066
|
const vertexLocation = provider.location || process.env.GOOGLE_CLOUD_LOCATION || "global";
|
|
@@ -1052,7 +1106,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1052
1106
|
if (!apiKey) throw new Error("google (AI Studio) requires a non-empty API key");
|
|
1053
1107
|
headers["x-goog-api-key"] = apiKey;
|
|
1054
1108
|
|
|
1055
|
-
const compiled = compileGoogleWireBody(body);
|
|
1109
|
+
const compiled = compileGoogleWireBody(body, toolSchemaProfile, toolSchemaPolicy);
|
|
1110
|
+
reportToolSchemaLoss(compiled.toolSchemaLossReport);
|
|
1056
1111
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
1057
1112
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
1058
1113
|
},
|
package/src/adapters/image.ts
CHANGED
|
@@ -18,6 +18,9 @@ export function parseDataUrl(url: string): { mediaType: string; base64: string }
|
|
|
18
18
|
*/
|
|
19
19
|
export function contentPartsToText(content: string | OcxContentPart[]): string {
|
|
20
20
|
if (typeof content === "string") return content;
|
|
21
|
-
|
|
21
|
+
// A document carries its own marker, so this wire states the attachment instead of
|
|
22
|
+
// mislabelling it as a video.
|
|
23
|
+
const text = content.map(p =>
|
|
24
|
+
p.type === "text" || p.type === "document" ? p.text : p.type === "image" ? "[image]" : "[video]").join("");
|
|
22
25
|
return text || "[image]";
|
|
23
26
|
}
|
|
@@ -1,31 +1,45 @@
|
|
|
1
1
|
import type { ProviderAdapter } from "./base";
|
|
2
2
|
import { untranslatedInputMediaMessage, untranslatedResponsesInputMedia } from "../responses/input-media";
|
|
3
|
+
import { unrepresentableDeclaration } from "./declaration-carrier";
|
|
4
|
+
import type { AdapterWire } from "./registry";
|
|
5
|
+
import type { OcxParsedRequest } from "../types";
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Refuse unrepresentable input at the final translated-adapter boundary. The registry
|
|
6
9
|
* applies this after wire resolution; Responses passthrough (including Azure) opts
|
|
7
10
|
* out because it uses the original body rather than the lossy normalized content.
|
|
11
|
+
*
|
|
12
|
+
* Two of the three checks are wire-scoped, and that is the point. A constraint the normalized
|
|
13
|
+
* request CAN carry — a caller restriction on a tool, an attached document's bytes — still has
|
|
14
|
+
* to reach a wire that can express it. Leaving that to each adapter means an adapter that never
|
|
15
|
+
* learned about the carrier rebuilds without it and answers normally, so the allowlist in
|
|
16
|
+
* `declaration-carrier.ts` is default-deny and this is the one place every registered adapter
|
|
17
|
+
* passes through.
|
|
8
18
|
*/
|
|
9
|
-
export function withInputMediaGuard<T extends ProviderAdapter>(adapter: T): T {
|
|
19
|
+
export function withInputMediaGuard<T extends ProviderAdapter>(adapter: T, wire: AdapterWire): T {
|
|
20
|
+
const refusal = (parsed: OcxParsedRequest): string | undefined => {
|
|
21
|
+
const kind = untranslatedResponsesInputMedia(parsed._rawBody);
|
|
22
|
+
return kind ? untranslatedInputMediaMessage(kind) : unrepresentableDeclaration(parsed, wire);
|
|
23
|
+
};
|
|
10
24
|
const build = adapter.buildRequest.bind(adapter);
|
|
11
25
|
adapter.buildRequest = (parsed, incoming) => {
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
26
|
+
const message = refusal(parsed);
|
|
27
|
+
if (message) throw new Error(message);
|
|
14
28
|
return build(parsed, incoming);
|
|
15
29
|
};
|
|
16
30
|
|
|
17
31
|
const runTurn = adapter.runTurn?.bind(adapter);
|
|
18
32
|
if (runTurn) {
|
|
19
33
|
adapter.runTurn = async (parsed, incoming, emit) => {
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
34
|
+
const message = refusal(parsed);
|
|
35
|
+
if (message) {
|
|
22
36
|
emit({
|
|
23
37
|
type: "error",
|
|
24
38
|
status: 400,
|
|
25
39
|
errorType: "invalid_request_error",
|
|
26
40
|
code: "unsupported_input_modality",
|
|
27
41
|
retryable: false,
|
|
28
|
-
message
|
|
42
|
+
message,
|
|
29
43
|
});
|
|
30
44
|
return;
|
|
31
45
|
}
|
|
@@ -37,9 +51,7 @@ export function withInputMediaGuard<T extends ProviderAdapter>(adapter: T): T {
|
|
|
37
51
|
if (localTerminal) {
|
|
38
52
|
// This hook is outside the builder's error catch. Decline its success shortcut;
|
|
39
53
|
// the ordinary buildRequest path then returns the established client-safe 400.
|
|
40
|
-
adapter.localTerminal = parsed =>
|
|
41
|
-
? undefined
|
|
42
|
-
: localTerminal(parsed);
|
|
54
|
+
adapter.localTerminal = parsed => refusal(parsed) ? undefined : localTerminal(parsed);
|
|
43
55
|
}
|
|
44
56
|
return adapter;
|
|
45
57
|
}
|
|
@@ -12,14 +12,15 @@ import type { KiroHistoryEntry } from "./wire";
|
|
|
12
12
|
|
|
13
13
|
export function userContentText(content: string | OcxContentPart[]): string {
|
|
14
14
|
if (typeof content === "string") return content;
|
|
15
|
-
|
|
15
|
+
// A document carries its own marker: dropping it built an empty user turn that Kiro rejects.
|
|
16
|
+
return content.map(p => (p.type === "text" || p.type === "document" ? p.text : "")).filter(Boolean).join("\n");
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function usageContentText(content: string | OcxContentPart[]): string {
|
|
19
20
|
if (typeof content === "string") return content;
|
|
20
21
|
return content
|
|
21
22
|
.map(p => {
|
|
22
|
-
if (p.type === "text") return p.text;
|
|
23
|
+
if (p.type === "text" || p.type === "document") return p.text;
|
|
23
24
|
if (p.type === "image") return `[image:${p.detail ?? "auto"}]`;
|
|
24
25
|
return "";
|
|
25
26
|
})
|
|
@@ -9,7 +9,7 @@ function contentText(content: string | OcxContentPart[]): string {
|
|
|
9
9
|
if (typeof content === "string") return content;
|
|
10
10
|
return content
|
|
11
11
|
.map(part => {
|
|
12
|
-
if (part.type === "text") return part.text;
|
|
12
|
+
if (part.type === "text" || part.type === "document") return part.text;
|
|
13
13
|
if (part.type === "image") return `[image:${part.detail ?? "auto"}]`;
|
|
14
14
|
return "";
|
|
15
15
|
})
|
|
@@ -262,6 +262,12 @@ function contentToNative(
|
|
|
262
262
|
text += part.text;
|
|
263
263
|
continue;
|
|
264
264
|
}
|
|
265
|
+
// No Ollama document carrier: keep the marker rather than falling through to the image
|
|
266
|
+
// branch below, which would read a nonexistent imageUrl.
|
|
267
|
+
if (part.type === "document") {
|
|
268
|
+
text += part.text;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
265
271
|
// Ollama's native /api/chat message shape carries `images: string[]` and has no video
|
|
266
272
|
// counterpart, so a video part is refused rather than silently dropped or mis-sent as an image.
|
|
267
273
|
if (part.type === "video") throw new Error(`ollama-native cannot send video content in ${label}`);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The role a `developer` message carries on the Chat wire, decided in one place for both the
|
|
5
|
+
* translated adapter and the native passthrough.
|
|
6
|
+
*
|
|
7
|
+
* `developer` belongs to the Chat Completions role set, but not every OpenAI-compatible gateway
|
|
8
|
+
* accepts it: one that does not answers `400 role 'developer' is not allowed` and the turn never
|
|
9
|
+
* starts. #5213 removed a hostname test that decided the role, which was right — a gateway
|
|
10
|
+
* proxying OpenAI accepts the role and the hostname cannot say so.
|
|
11
|
+
*
|
|
12
|
+
* `foldDeveloperRoleToSystem` is tri-state and only two of its states say anything about the
|
|
13
|
+
* destination: `true` records an upstream known to reject the role, `false` one known to accept
|
|
14
|
+
* it, and absent means nobody has recorded either. Returning `undefined` for the absent state
|
|
15
|
+
* instead of a role keeps the recorded fact separate from the default a route applies to
|
|
16
|
+
* silence, which is what lets the native route honour the first without inheriting the second.
|
|
17
|
+
*
|
|
18
|
+
* This decides the role and only the role. Which slot the message occupies is the caller's
|
|
19
|
+
* decision and must not change with this value.
|
|
20
|
+
*/
|
|
21
|
+
export function explicitChatDeveloperWireRole(
|
|
22
|
+
provider: OcxProviderConfig,
|
|
23
|
+
): "developer" | "system" | undefined {
|
|
24
|
+
if (provider.foldDeveloperRoleToSystem === undefined) return undefined;
|
|
25
|
+
return provider.foldDeveloperRoleToSystem ? "system" : "developer";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The translated route's role: the recorded one, or `system` when nothing is recorded.
|
|
30
|
+
*
|
|
31
|
+
* The unrecorded state folds because the two mistakes are not symmetrical. Sending `developer`
|
|
32
|
+
* to a destination that rejects it fails the request outside this repository, where no test here
|
|
33
|
+
* can reach it; sending `system` to one that would have accepted `developer` costs the role name
|
|
34
|
+
* and nothing else.
|
|
35
|
+
*/
|
|
36
|
+
export function translatedChatDeveloperWireRole(provider: OcxProviderConfig): "developer" | "system" {
|
|
37
|
+
return explicitChatDeveloperWireRole(provider) ?? "system";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Apply a recorded role to a caller-supplied `messages` array, leaving every other field of
|
|
42
|
+
* every message, and the order of all of them, exactly as they arrived.
|
|
43
|
+
*
|
|
44
|
+
* The native route forwards the caller's messages verbatim, so an operator who recorded that a
|
|
45
|
+
* destination rejects the role still sent `developer` there and the turn failed upstream with a
|
|
46
|
+
* 400. Only an explicit record changes anything here: with the key unset, or set to the role the
|
|
47
|
+
* message already carries, the same array reference is returned and the wire is byte-identical
|
|
48
|
+
* to the one the caller sent.
|
|
49
|
+
*/
|
|
50
|
+
export function applyExplicitChatDeveloperRole(messages: unknown, provider: OcxProviderConfig): unknown {
|
|
51
|
+
const role = explicitChatDeveloperWireRole(provider);
|
|
52
|
+
if (role === undefined || role === "developer" || !Array.isArray(messages)) return messages;
|
|
53
|
+
let rewritten = false;
|
|
54
|
+
const applied = messages.map(message => {
|
|
55
|
+
if (typeof message !== "object" || message === null || Array.isArray(message)) return message;
|
|
56
|
+
if ((message as { role?: unknown }).role !== "developer") return message;
|
|
57
|
+
rewritten = true;
|
|
58
|
+
return { ...(message as Record<string, unknown>), role };
|
|
59
|
+
});
|
|
60
|
+
return rewritten ? applied : messages;
|
|
61
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { isNativeOpenAIChatTarget, stripBracketedModelSuffix } from "./wire";
|
|
2
2
|
import { reasoningDetailSegmentForWire } from "./response-events";
|
|
3
|
+
import { translatedChatDeveloperWireRole } from "./developer-role";
|
|
3
4
|
import { isVolcengineArkPaygChatTarget } from "./tool-schema";
|
|
4
5
|
import { contentPartsToText } from "../image";
|
|
5
6
|
import { EMPTY_TOOL_OUTPUT_ANNOTATION, isWhitespaceOnlyTextPartArray } from "../empty-tool-output-annotation";
|
|
6
7
|
import { identifyRoutedModel } from "../identity";
|
|
7
8
|
import { buildNonOpenAIToolCatalogNudgeForTools, shouldInjectNonOpenAIToolCatalogNudge } from "../tool-catalog-nudge";
|
|
8
|
-
import { registryEntryForProviderDestination } from "../../providers/registry";
|
|
9
9
|
import { peekReasoningForCall } from "../../responses/reasoning-replay-cache";
|
|
10
|
-
import
|
|
10
|
+
import { inlineDocumentDataUrl } from "../../responses/inline-document";
|
|
11
|
+
import type { OcxAssistantMessage, OcxContentPart, OcxParsedRequest, OcxProviderConfig, OcxTextContent, OcxThinkingContent, OcxToolCall } from "../../types";
|
|
11
12
|
import { modelInList, namespacedToolName } from "../../types";
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -21,13 +22,6 @@ import { modelInList, namespacedToolName } from "../../types";
|
|
|
21
22
|
*/
|
|
22
23
|
const VIDEO_UNSUPPORTED_MARKER = "[video omitted: the translated Chat route has no video mapping]";
|
|
23
24
|
|
|
24
|
-
export function developerSystemText(message: OcxMessage): string | undefined {
|
|
25
|
-
if (message.role !== "developer") return undefined;
|
|
26
|
-
if (typeof message.content === "string") return message.content;
|
|
27
|
-
if (message.content.some(part => part.type === "image")) return undefined;
|
|
28
|
-
return message.content.map(part => (part as OcxTextContent).text).join("");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
25
|
/**
|
|
32
26
|
* Chat-completions image_url parts for images carried inside a tool result (issue #888). role:"tool"
|
|
33
27
|
* content is text-only on every chat provider, so these ride in a follow-up user message instead of
|
|
@@ -121,21 +115,26 @@ export function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProv
|
|
|
121
115
|
};
|
|
122
116
|
|
|
123
117
|
const nativeOpenAI = isNativeOpenAIChatTarget(provider);
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
118
|
+
// Which role a developer message carries, and why the unrecorded state folds, is stated once
|
|
119
|
+
// in ./developer-role.ts and read from there by the native passthrough as well. Either way the
|
|
120
|
+
// message keeps the slot it arrived in — only the role changes, never the position.
|
|
121
|
+
const developerWireRole = translatedChatDeveloperWireRole(provider);
|
|
122
|
+
// A developer message keeps the slot it arrived in. Hoisting its text into the leading
|
|
123
|
+
// system block moved a mid-conversation instruction ahead of every turn it was written to
|
|
124
|
+
// follow, and the caller saw an ordinary answer either way (#5213). The Claude inbound mints
|
|
125
|
+
// chronological developer items for exactly this reason (#4161), so the two halves of the
|
|
126
|
+
// route were working against each other on every host but api.openai.com. Placement is now
|
|
127
|
+
// uniform; which ROLE that slot carries is decided separately below.
|
|
128
|
+
//
|
|
129
|
+
// One destination already had the chronological behaviour, keyed to a model and a registry
|
|
130
|
+
// id, because hoisting a newly appended reminder rewrites the reusable prompt prefix. That is
|
|
131
|
+
// a property of prompt-prefix caching rather than of that destination, and it is now what
|
|
132
|
+
// every destination gets.
|
|
128
133
|
const toolCatalogNudge = shouldInjectNonOpenAIToolCatalogNudge(provider)
|
|
129
134
|
? buildNonOpenAIToolCatalogNudgeForTools(context.tools, options.toolChoice)
|
|
130
135
|
: undefined;
|
|
131
|
-
const developerSystemParts = nativeOpenAI || chronologicalSystem
|
|
132
|
-
? []
|
|
133
|
-
: context.messages
|
|
134
|
-
.map(developerSystemText)
|
|
135
|
-
.filter((part): part is string => part !== undefined && part.length > 0);
|
|
136
136
|
const systemParts = [
|
|
137
137
|
...(context.systemPrompt ?? []),
|
|
138
|
-
...developerSystemParts,
|
|
139
138
|
...(toolCatalogNudge ? [toolCatalogNudge] : []),
|
|
140
139
|
];
|
|
141
140
|
if (systemParts.length > 0) {
|
|
@@ -152,20 +151,23 @@ export function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProv
|
|
|
152
151
|
case "developer": {
|
|
153
152
|
const parts = typeof msg.content === "string" ? undefined : msg.content as OcxContentPart[];
|
|
154
153
|
const hasImages = parts?.some(p => p.type === "image") ?? false;
|
|
154
|
+
// A document has a structured counterpart on this wire, so it needs the parts array for
|
|
155
|
+
// the same reason an image does: flattening it to a string would drop the bytes.
|
|
156
|
+
const hasStructured = hasImages || (parts?.some(p => p.type === "document") ?? false);
|
|
155
157
|
let chatMsg: Record<string, unknown>;
|
|
156
|
-
if (msg.role === "developer" && !
|
|
157
|
-
if (!nativeOpenAI && !chronologicalSystem) break;
|
|
158
|
+
if (msg.role === "developer" && !hasStructured) {
|
|
158
159
|
const text = typeof msg.content === "string"
|
|
159
160
|
? msg.content
|
|
160
161
|
: parts!.map(p => (p as OcxTextContent).text).join("");
|
|
161
|
-
// A non-text timeline part (video, for example) serializes to nothing here.
|
|
162
|
-
//
|
|
163
|
-
//
|
|
162
|
+
// A non-text timeline part (video, for example) serializes to nothing here. The
|
|
163
|
+
// generic user path drops such a message, and emitting a content-free system
|
|
164
|
+
// message instead is rejected by some upstreams. Native OpenAI keeps its existing
|
|
165
|
+
// empty-developer wire, which is a separate question from placement.
|
|
164
166
|
if (!nativeOpenAI && text.length === 0) break;
|
|
165
|
-
chatMsg = { role:
|
|
167
|
+
chatMsg = { role: developerWireRole, content: text };
|
|
166
168
|
} else if (typeof msg.content === "string") {
|
|
167
169
|
chatMsg = { role: "user", content: msg.content };
|
|
168
|
-
} else if (!
|
|
170
|
+
} else if (!hasStructured) {
|
|
169
171
|
// A video part has no `text`, so joining it produced "" and the whole message
|
|
170
172
|
// was dropped: a video-only or text-plus-video turn vanished silently. OpenAI's
|
|
171
173
|
// Chat Completions wire has no video content part, so state the omission
|
|
@@ -183,13 +185,30 @@ export function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProv
|
|
|
183
185
|
if (p.type === "image") {
|
|
184
186
|
return { type: "image_url", image_url: { url: p.imageUrl, ...(p.detail ? { detail: p.detail } : {}) } };
|
|
185
187
|
}
|
|
188
|
+
// Chat Completions carries an attached document as a file part with inline bytes,
|
|
189
|
+
// the direct counterpart of the Anthropic document block the caller sent.
|
|
190
|
+
if (p.type === "document") {
|
|
191
|
+
return {
|
|
192
|
+
type: "file",
|
|
193
|
+
file: {
|
|
194
|
+
file_data: inlineDocumentDataUrl(p),
|
|
195
|
+
...(p.filename !== undefined ? { filename: p.filename } : {}),
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
186
199
|
// Previously this produced { type: "text", text: undefined } for a video
|
|
187
200
|
// part — a malformed part, worse than a drop because it can fail upstream
|
|
188
201
|
// schema validation.
|
|
189
202
|
if (p.type === "video") return { type: "text", text: VIDEO_UNSUPPORTED_MARKER };
|
|
190
203
|
return { type: "text", text: (p as OcxTextContent).text };
|
|
191
204
|
});
|
|
192
|
-
|
|
205
|
+
// A developer message with images keeps the user-compatible shape it has always had on
|
|
206
|
+
// this wire. One carrying only a document has no such precedent, and demoting it would
|
|
207
|
+
// undo the role this adapter just finished preserving.
|
|
208
|
+
chatMsg = {
|
|
209
|
+
role: msg.role === "developer" && !hasImages ? developerWireRole : "user",
|
|
210
|
+
content: chatParts,
|
|
211
|
+
};
|
|
193
212
|
}
|
|
194
213
|
if (pendingToolCalls.length > 0) deferredBarrierMessages.push(chatMsg);
|
|
195
214
|
else out.push(chatMsg);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `parallel_tool_calls` value for a translated Chat request, or `undefined` to omit the key.
|
|
5
|
+
*
|
|
6
|
+
* A provider has three states here, not two, and the third is the default for every provider that
|
|
7
|
+
* never configured the knob. While the call site only branched on the two configured states, a
|
|
8
|
+
* caller's own explicit `parallel_tool_calls: false` reached the parser, was carried on
|
|
9
|
+
* `options.parallelToolCalls`, and was then dropped on the way to the wire — the upstream stayed
|
|
10
|
+
* free to emit concurrent calls and the response looked entirely normal (#5211).
|
|
11
|
+
*
|
|
12
|
+
* Only an explicit request-side `false` is forwarded in the unset state. `true` is already the
|
|
13
|
+
* upstream default, so emitting it would introduce the knob to strict OpenAI-compatible hosts
|
|
14
|
+
* that have never had to accept it, which is the reason the configured opt-out below omits the
|
|
15
|
+
* key rather than sending `false`.
|
|
16
|
+
*/
|
|
17
|
+
export function chatParallelToolCallsWireValue(
|
|
18
|
+
provider: OcxProviderConfig,
|
|
19
|
+
requested: boolean | undefined,
|
|
20
|
+
): boolean | undefined {
|
|
21
|
+
if (provider.parallelToolCalls === false) {
|
|
22
|
+
// NIM documents the Boolean defaulting to false and kimi rejects true; pin the wire bit so
|
|
23
|
+
// Codex cannot opt in via request.options. Other opted-out providers omit the field, but a
|
|
24
|
+
// self-hosted gateway that DOES honor it and keeps emitting parallel calls without it can opt
|
|
25
|
+
// in via pinParallelToolCallsFalse.
|
|
26
|
+
const pinned = provider.baseUrl === "https://integrate.api.nvidia.com/v1"
|
|
27
|
+
|| provider.pinParallelToolCallsFalse === true;
|
|
28
|
+
return pinned ? false : undefined;
|
|
29
|
+
}
|
|
30
|
+
if (provider.parallelToolCalls === true) return requested !== false;
|
|
31
|
+
return requested === false ? false : undefined;
|
|
32
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { openAIChatTransport, stripBracketedModelSuffix } from "./wire";
|
|
2
2
|
import type { AdapterRequest } from "../base";
|
|
3
3
|
import { frameAgentRouterMessages } from "../agentrouter";
|
|
4
|
+
import { applyExplicitChatDeveloperRole } from "./developer-role";
|
|
4
5
|
import { openRouterProviderPayload, resolveOpenRouterRouting } from "../../providers/openrouter-routing";
|
|
5
6
|
import { resolveVercelGatewayRouting, vercelGatewayProviderPayload } from "../../providers/vercel-gateway-routing";
|
|
6
7
|
import { fastPolicyForModel } from "../../providers/service-tier";
|
|
@@ -9,6 +10,8 @@ import { debugProviderDiagnostic } from "../../lib/debug";
|
|
|
9
10
|
import { isDebugEnabled } from "../../lib/debug-settings";
|
|
10
11
|
import { modelRecordValue } from "../../reasoning-effort";
|
|
11
12
|
import { modelInList, type OcxProviderConfig } from "../../types";
|
|
13
|
+
import { chatParallelToolCallsWireValue } from "./parallel-tool-calls";
|
|
14
|
+
import { applyExplicitChatReasoningWirePolicy } from "./reasoning-wire";
|
|
12
15
|
|
|
13
16
|
const CHAT_PASSTHROUGH_FIELDS = [
|
|
14
17
|
"audio",
|
|
@@ -54,16 +57,37 @@ export function buildOpenAIChatPassthroughRequest(
|
|
|
54
57
|
|
|
55
58
|
const body: Record<string, unknown> = {
|
|
56
59
|
model: provider.modelSuffixBracketStrip ? stripBracketedModelSuffix(modelId) : modelId,
|
|
57
|
-
messages
|
|
60
|
+
// The caller's messages are forwarded as they arrived, with one exception: an operator who
|
|
61
|
+
// recorded that this destination rejects the `developer` role gets that role converted in
|
|
62
|
+
// place. Verbatim was not neutral there — it sent the role anyway and the turn failed
|
|
63
|
+
// upstream with a 400 before the model saw it. An unrecorded destination is still verbatim,
|
|
64
|
+
// and the conversion changes the role of those messages and nothing else, so the position
|
|
65
|
+
// of every message and every other field survive unchanged.
|
|
66
|
+
messages: applyExplicitChatDeveloperRole(
|
|
67
|
+
frameAgentRouterMessages(provider.baseUrl, rawBody.messages),
|
|
68
|
+
provider,
|
|
69
|
+
),
|
|
58
70
|
stream,
|
|
59
71
|
};
|
|
60
72
|
for (const field of CHAT_PASSTHROUGH_FIELDS) {
|
|
61
73
|
if (rawBody[field] !== undefined) body[field] = rawBody[field];
|
|
62
74
|
}
|
|
63
75
|
const rawEfforts = modelRecordValue(provider.modelReasoningEfforts, modelId) ?? provider.reasoningEfforts;
|
|
64
|
-
|
|
76
|
+
const reasoningDisabled = modelInList(provider.noReasoningModels, modelId) || rawEfforts?.length === 0;
|
|
77
|
+
if (reasoningDisabled) {
|
|
65
78
|
delete body.reasoning_effort;
|
|
66
79
|
}
|
|
80
|
+
const hasTools = Array.isArray(rawBody.tools) && rawBody.tools.length > 0;
|
|
81
|
+
const requestedEffort = typeof body.reasoning_effort === "string" ? body.reasoning_effort : undefined;
|
|
82
|
+
applyExplicitChatReasoningWirePolicy({
|
|
83
|
+
provider,
|
|
84
|
+
modelId,
|
|
85
|
+
hasTools,
|
|
86
|
+
requestedEffort,
|
|
87
|
+
wireEffort: requestedEffort,
|
|
88
|
+
reasoningDisabled,
|
|
89
|
+
body,
|
|
90
|
+
});
|
|
67
91
|
|
|
68
92
|
const openRouterRouting = resolveOpenRouterRouting(provider, modelId);
|
|
69
93
|
if (openRouterRouting) body.provider = openRouterProviderPayload(openRouterRouting);
|
|
@@ -107,13 +131,13 @@ export function buildOpenAIChatPassthroughRequest(
|
|
|
107
131
|
if (provider.promptCacheKey && rawBody.prompt_cache_key !== undefined) {
|
|
108
132
|
body.prompt_cache_key = rawBody.prompt_cache_key;
|
|
109
133
|
}
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
134
|
+
if (hasTools) {
|
|
135
|
+
// Same three provider states as the translated path, and the same defect in the unset one:
|
|
136
|
+
// a caller's explicit false was dropped here too (#5211). The native route reads the bit off
|
|
137
|
+
// the raw request rather than the parsed options, since nothing projects this body.
|
|
138
|
+
const requested = typeof rawBody.parallel_tool_calls === "boolean" ? rawBody.parallel_tool_calls : undefined;
|
|
139
|
+
const parallelToolCalls = chatParallelToolCallsWireValue(provider, requested);
|
|
140
|
+
if (parallelToolCalls !== undefined) body.parallel_tool_calls = parallelToolCalls;
|
|
117
141
|
}
|
|
118
142
|
if (stream) {
|
|
119
143
|
const callerOptions = rawBody.stream_options !== null
|