@bitkyc08/opencodex 2.76.0 → 2.78.0-preview.20261005
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/bin/ocx.mjs +11 -10
- package/gui/dist/assets/App-CppMz-6w.js +54 -0
- package/gui/dist/assets/App-lCIslZ1L.css +1 -0
- package/gui/dist/assets/{Tray-DjNwwrWS.js → Tray-CJWQqtVv.js} +1 -1
- package/gui/dist/assets/index-DQt9srNi.js +94 -0
- package/gui/dist/assets/index-DmQB8D_R.css +1 -0
- package/gui/dist/assets/quota-summary-BMsyoZH2.js +1 -0
- package/gui/dist/index.html +4 -3
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +19 -2
- package/src/adapters/anthropic/client-identity.ts +13 -4
- package/src/adapters/anthropic/native-client-preamble.ts +31 -0
- package/src/adapters/anthropic/passthrough.ts +103 -25
- package/src/adapters/antigravity-refusal.ts +28 -0
- package/src/adapters/cursor/protobuf-request.ts +6 -0
- package/src/adapters/cursor/request-builder.ts +3 -0
- package/src/adapters/cursor/structured-output.ts +13 -0
- package/src/adapters/cursor/types.ts +2 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -2
- package/src/adapters/devin/trajectory.ts +49 -0
- package/src/adapters/devin.ts +13 -0
- package/src/adapters/ollama-native.ts +62 -19
- package/src/adapters/openai-responses/passthrough.ts +133 -103
- package/src/adapters/openai-responses/request-strips.ts +10 -4
- package/src/adapters/zed.ts +26 -4
- package/src/bridge/response-json.ts +14 -14
- package/src/bridge/sse.ts +15 -5
- package/src/chatgpt/app-server-shim/bundle-trust.ts +40 -10
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +3 -0
- package/src/claude/agents-inject.ts +4 -43
- package/src/claude/gateway-cache.ts +16 -13
- package/src/claude/inbound-model-options.ts +9 -2
- package/src/claude/intercept/cli-catalog.ts +27 -16
- package/src/claude/intercept/client-hello.ts +103 -0
- package/src/claude/intercept/model-bindings.ts +3 -2
- package/src/claude/intercept/picker-bootstrap.ts +37 -16
- package/src/claude/intercept/picker-budget.ts +41 -0
- package/src/claude/intercept/picker-listener.ts +279 -66
- package/src/claude/outbound.ts +2 -0
- package/src/claude/subagent-force-status.ts +53 -0
- package/src/claude/subagent-model.ts +93 -0
- package/src/cli/access-audio-input.ts +84 -0
- package/src/cli/access-audio-live.ts +99 -0
- package/src/cli/access-audio.ts +123 -0
- package/src/cli/access-data-client.ts +127 -0
- package/src/cli/access-data-plane.ts +104 -0
- package/src/cli/access-data-response.ts +76 -0
- package/src/cli/access.ts +65 -2
- package/src/cli/account-api.ts +33 -6
- package/src/cli/account-auth.ts +111 -42
- package/src/cli/account-key-quota.ts +118 -0
- package/src/cli/account-main.ts +11 -4
- package/src/cli/account-next-actions.ts +63 -0
- package/src/cli/account-policy-dto.ts +68 -0
- package/src/cli/account-policy.ts +167 -0
- package/src/cli/account-target.ts +8 -0
- package/src/cli/account.ts +37 -9
- package/src/cli/agent-runtime-settings.ts +130 -0
- package/src/cli/agent-settings.ts +101 -0
- package/src/cli/agent.ts +29 -2
- package/src/cli/alias.ts +7 -2
- package/src/cli/aside-profiles.ts +2 -2
- package/src/cli/capabilities-access-remote.ts +147 -0
- package/src/cli/capabilities-accounts.ts +195 -0
- package/src/cli/capabilities-agents-routing.ts +414 -0
- package/src/cli/capabilities-base.ts +783 -0
- package/src/cli/capabilities-command.ts +1 -0
- package/src/cli/capabilities-integrations.ts +264 -0
- package/src/cli/capabilities-lab.ts +214 -0
- package/src/cli/capabilities-observe-system.ts +662 -0
- package/src/cli/capabilities-provider-models.ts +302 -0
- package/src/cli/capabilities.ts +30 -1190
- package/src/cli/capability-types.ts +54 -0
- package/src/cli/catalog-command-result.ts +57 -0
- package/src/cli/chatgpt-command.ts +35 -9
- package/src/cli/claude-agent-startup-sync.ts +17 -0
- package/src/cli/claude-desktop-profile.ts +100 -0
- package/src/cli/claude-desktop.ts +39 -6
- package/src/cli/claude.ts +22 -6
- package/src/cli/codex-shim-autorestore.ts +2 -1
- package/src/cli/codex-shim-readiness.ts +109 -0
- package/src/cli/combo-input.ts +88 -0
- package/src/cli/combo-result.ts +52 -0
- package/src/cli/combo-stats.ts +123 -0
- package/src/cli/combo.ts +37 -14
- package/src/cli/companion-timeline.ts +142 -0
- package/src/cli/companion-usage.ts +146 -0
- package/src/cli/companion.ts +12 -3
- package/src/cli/config-command.ts +24 -8
- package/src/cli/connect.ts +19 -0
- package/src/cli/dispatch.ts +55 -82
- package/src/cli/doctor.ts +22 -4
- package/src/cli/export-command.ts +20 -9
- package/src/cli/gui.ts +8 -3
- package/src/cli/help-catalog.ts +101 -0
- package/src/cli/help-models-context.ts +17 -0
- package/src/cli/help-navigation.ts +50 -0
- package/src/cli/help-recovery.ts +66 -0
- package/src/cli/help.ts +69 -11
- package/src/cli/index.ts +38 -27
- package/src/cli/injection-follow.ts +35 -0
- package/src/cli/inspect.ts +5 -0
- package/src/cli/integration-aside-sync.ts +69 -0
- package/src/cli/integration-cursor.ts +106 -0
- package/src/cli/integration-input.ts +60 -0
- package/src/cli/integration-journal.ts +49 -0
- package/src/cli/integration-plan-dto.ts +156 -0
- package/src/cli/integration-preview.ts +215 -0
- package/src/cli/integrations.ts +24 -18
- package/src/cli/json-input.ts +134 -0
- package/src/cli/link.ts +32 -4
- package/src/cli/local-sync-result.ts +41 -0
- package/src/cli/log-follow.ts +84 -0
- package/src/cli/log-view-filter.ts +146 -0
- package/src/cli/logout-command.ts +44 -0
- package/src/cli/model-picker-ordering.ts +137 -0
- package/src/cli/models-custom-input.ts +107 -0
- package/src/cli/models-custom-runtime.ts +112 -0
- package/src/cli/models-order.ts +194 -0
- package/src/cli/models-runtime-subcommands.ts +2 -0
- package/src/cli/models-runtime.ts +9 -1
- package/src/cli/models.ts +66 -81
- package/src/cli/observe-stream.ts +142 -0
- package/src/cli/observe.ts +75 -34
- package/src/cli/opencode.ts +54 -10
- package/src/cli/provider-batch.ts +77 -0
- package/src/cli/provider-lifecycle-runtime.ts +170 -0
- package/src/cli/provider-result.ts +124 -0
- package/src/cli/provider-runtime.ts +15 -12
- package/src/cli/provider-settings.ts +143 -0
- package/src/cli/provider.ts +116 -64
- package/src/cli/registry.ts +90 -24
- package/src/cli/remote-workspace-hub.ts +83 -0
- package/src/cli/remote-workspace.ts +6 -1
- package/src/cli/restart-failure.ts +27 -0
- package/src/cli/root.ts +42 -9
- package/src/cli/route-policy-write.ts +137 -0
- package/src/cli/route-policy.ts +8 -0
- package/src/cli/runtime-api.ts +57 -80
- package/src/cli/secret-args.ts +149 -0
- package/src/cli/settings-result.ts +30 -0
- package/src/cli/storage.ts +77 -7
- package/src/cli/system-command.ts +60 -2
- package/src/cli/system-restart-client.ts +20 -3
- package/src/cli/system-settings-parity.ts +83 -0
- package/src/cli/tray-proxy.ts +3 -2
- package/src/cli/update-restart-candidate.ts +21 -0
- package/src/cli/update-restart-child.ts +81 -0
- package/src/cli/update-restart-home.ts +38 -0
- package/src/cli/update-restart-transport.ts +193 -0
- package/src/cli/update-restart.ts +260 -0
- package/src/cli/usage-model-search.ts +58 -0
- package/src/cli/usage-report.ts +13 -5
- package/src/cli/v2-input.ts +66 -0
- package/src/cli/v2-local-output.ts +37 -0
- package/src/cli/v2-runtime.ts +90 -0
- package/src/cli/v2.ts +102 -144
- package/src/clients/config-export/contracts.ts +100 -0
- package/src/clients/config-export/droid.ts +54 -2
- package/src/clients/config-export/dsh.ts +21 -0
- package/src/clients/config-export/kilo.ts +12 -2
- package/src/clients/config-export/model-metadata.ts +10 -4
- package/src/clients/config-export/reasoning-metadata.ts +47 -0
- package/src/clients/config-export.ts +53 -67
- package/src/clients/omo-role-models.ts +17 -3
- package/src/codex/account-credit-use.ts +4 -2
- package/src/codex/account-runtime-state.ts +21 -0
- package/src/codex/account-store.ts +11 -47
- package/src/codex/account-usability.ts +11 -8
- package/src/codex/auth-api/account-list.ts +73 -63
- package/src/codex/auth-api/main-account-probe.ts +25 -12
- package/src/codex/auth-api/pool-mode-gate.ts +6 -9
- package/src/codex/auth-api/pool-quota-probe.ts +2 -4
- package/src/codex/auth-api/reset-credit-service.ts +3 -2
- package/src/codex/auth-context.ts +30 -11
- package/src/codex/catalog/aggregation.ts +3 -0
- package/src/codex/catalog/build-entries.ts +1 -1
- package/src/codex/catalog/bundled.ts +9 -2
- package/src/codex/catalog/heal-observation.ts +66 -0
- package/src/codex/catalog/model-hints.ts +2 -1
- package/src/codex/catalog/model-visibility.ts +6 -3
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +13 -4
- package/src/codex/catalog/publication-observer.ts +19 -0
- package/src/codex/catalog/remote.ts +13 -15
- package/src/codex/catalog/restore.ts +19 -6
- package/src/codex/catalog/retained-sync.ts +60 -11
- package/src/codex/catalog/routed-removal.ts +116 -0
- package/src/codex/catalog/write-audit-contract.ts +21 -0
- package/src/codex/catalog/write-audit.ts +171 -0
- package/src/codex/catalog-self-heal.ts +284 -0
- package/src/codex/catalog-write-serialization.ts +71 -7
- package/src/codex/chatgpt-refresh-failure.ts +56 -0
- package/src/codex/codex-home-owner.ts +124 -0
- package/src/codex/convergence.ts +57 -16
- package/src/codex/features.ts +8 -1
- package/src/codex/inject/restore.ts +46 -4
- package/src/codex/inject.ts +11 -1
- package/src/codex/internal/catalog-writer.ts +120 -13
- package/src/codex/journal.ts +49 -13
- package/src/codex/main-account.ts +144 -11
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/quota-refresh-outcome.ts +17 -2
- package/src/codex/quota-types.ts +36 -1
- package/src/codex/quota.ts +67 -16
- package/src/codex/refresh.ts +20 -9
- package/src/codex/routing/cooldown-math.ts +10 -4
- package/src/codex/routing/selection.ts +11 -4
- package/src/codex/routing.ts +9 -9
- package/src/codex/runtime.ts +7 -1
- package/src/codex/shim-diagnostics.ts +80 -0
- package/src/codex/shim-fingerprint.ts +7 -2
- package/src/codex/shim-migration.ts +153 -0
- package/src/codex/shim-overlay.ts +509 -0
- package/src/codex/shim-path-resolution.ts +145 -0
- package/src/codex/shim-probe.ts +4 -3
- package/src/codex/shim-state-file.ts +93 -7
- package/src/codex/shim.ts +54 -107
- package/src/codex/subagent-model-fallback.ts +2 -1
- package/src/codex/sync.ts +24 -4
- package/src/combos/failover.ts +101 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/jev.ts +13 -3
- package/src/combos/resolve.ts +12 -0
- package/src/config/atomic-write.ts +13 -8
- package/src/config/diagnostics.ts +23 -1
- package/src/config/load-degrade.ts +7 -0
- package/src/config/provider-validation.ts +3 -0
- package/src/config/schema/chatgpt-desktop.ts +22 -0
- package/src/config/schema/compaction-triggers.ts +8 -0
- package/src/config/schema/config-schema.ts +43 -14
- package/src/config/schema/leaf-validators.ts +8 -3
- package/src/config/subagent-models.ts +12 -0
- package/src/generated/compatibility-version.json +645 -237
- package/src/grok/session-identity.ts +33 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/merge.ts +13 -5
- package/src/integrations/mutation-plan.ts +50 -5
- package/src/integrations/omp-yaml-source.ts +181 -1
- package/src/integrations/ownership-policy.ts +66 -0
- package/src/integrations/registry.ts +50 -1
- package/src/integrations/state.ts +106 -19
- package/src/integrations/target.ts +25 -2
- package/src/integrations/writer.ts +107 -35
- package/src/lib/gui-pair-capability.ts +17 -0
- package/src/lib/package-tree-integrity.ts +42 -1
- package/src/lib/provider-client-headers.ts +62 -0
- package/src/lib/proxy-env.ts +3 -0
- package/src/lib/test-home-guard.ts +2 -1
- package/src/oauth/anthropic-account-refusal.ts +19 -8
- package/src/oauth/anthropic-continuity.ts +65 -0
- package/src/oauth/anthropic-identity.ts +75 -0
- package/src/oauth/anthropic-routing.ts +20 -15
- package/src/oauth/anthropic.ts +34 -3
- package/src/oauth/generic-account-failover.ts +21 -0
- package/src/oauth/health.ts +32 -5
- package/src/oauth/index.ts +42 -35
- package/src/oauth/local-token-detect.ts +7 -0
- package/src/oauth/pool-settings-capability.ts +9 -2
- package/src/oauth/refresh-policy.ts +2 -0
- package/src/oauth/store.ts +55 -6
- package/src/oauth/types.ts +9 -0
- package/src/protocols/encoders/messages.ts +4 -3
- package/src/protocols/plan-snapshot.ts +1 -1
- package/src/protocols/settings.ts +28 -4
- package/src/providers/antigravity-effort-families.ts +75 -0
- package/src/providers/antigravity-models.ts +54 -16
- package/src/providers/antigravity-wire-snapshot.ts +66 -0
- package/src/providers/derive.ts +2 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/new-model-policy.ts +48 -10
- package/src/providers/registry/entries-extended.ts +3 -0
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/model-seeds.ts +5 -0
- package/src/providers/registry/types.ts +4 -0
- package/src/responses/compaction.ts +19 -0
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +9 -0
- package/src/server/caller-session-identity.ts +32 -0
- package/src/server/chat-completions.ts +10 -0
- package/src/server/claude-messages.ts +29 -15
- package/src/server/droid-reasoning-default.ts +50 -0
- package/src/server/github-copilot-responses-repair.ts +3 -6
- package/src/server/grok-responses-control-frame.ts +2 -2
- package/src/server/grok-responses-snapshot-repair.ts +4 -5
- package/src/server/gui-session.ts +33 -11
- package/src/server/index/claude-intercept-lifecycle.ts +4 -0
- package/src/server/index/serve-options.ts +13 -7
- package/src/server/index.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +12 -134
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/context.ts +8 -1
- package/src/server/management/cursor-integration-routes.ts +3 -3
- package/src/server/management/integration-routes.ts +69 -11
- package/src/server/management/link-routes.ts +20 -13
- package/src/server/management/model-routes.ts +3 -3
- package/src/server/management/model-row-export-metadata.ts +150 -0
- package/src/server/management/model-rows.ts +148 -9
- package/src/server/management/native-integration-routes.ts +2 -2
- package/src/server/management/oauth-account-routes.ts +96 -30
- package/src/server/management/route-registry.ts +29 -27
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/subagent-model-routes.ts +214 -0
- package/src/server/messages-native-eligibility.ts +3 -6
- package/src/server/messages-native-oauth.ts +82 -35
- package/src/server/messages-native.ts +78 -16
- package/src/server/relay.ts +14 -44
- package/src/server/request-log-generation-window.ts +38 -0
- package/src/server/request-log.ts +13 -0
- package/src/server/responses/adapter-dispatch.ts +152 -28
- package/src/server/responses/codex-auth-error.ts +10 -2
- package/src/server/responses/combo-stream-preflight.ts +29 -3
- package/src/server/responses/compaction-routing.ts +10 -1
- package/src/server/responses/core-auth.ts +67 -30
- package/src/server/responses/core-codex-account.ts +6 -3
- package/src/server/responses/core-combo-failure.ts +5 -1
- package/src/server/responses/core-combo-native.ts +7 -1
- package/src/server/responses/core-combo.ts +64 -7
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/fetch-helpers.ts +9 -0
- package/src/server/responses/passthrough-delivery.ts +1 -1
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/request-prepare.ts +5 -0
- package/src/server/responses/request-spend.ts +30 -30
- package/src/server/responses-custom-tool-repair.ts +2 -2
- package/src/server/responses-hosted-image-display.ts +70 -9
- package/src/server/responses-terminal-repair.ts +15 -2
- package/src/server/sse-frame-buffer.ts +38 -36
- package/src/server/sse-payload-rewrite.ts +78 -49
- package/src/server/ws-bridge.ts +1 -11
- package/src/service/cli.ts +6 -1
- package/src/service/ownership-mutation-lease.d.mts +26 -0
- package/src/service/ownership-mutation-lease.mjs +76 -1
- package/src/service/windows-taskxml.ts +1 -2
- package/src/service-manager-probe.ts +36 -2
- package/src/types/config.ts +6 -0
- package/src/types/provider.ts +28 -10
- package/src/update/index.ts +2 -1
- package/src/update/npm-invocation.mjs +35 -3
- package/src/update/proxy-liveness-probe.mjs +38 -8
- package/src/update/update-failure-guidance.d.mts +6 -0
- package/src/update/update-failure-guidance.mjs +30 -5
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +7 -0
- package/src/usage/summary.ts +82 -1
- package/src/web-search/loop.ts +11 -8
- package/src/web-search/run-turn-loop.ts +10 -7
- package/gui/dist/assets/App-CddorHdA.js +0 -51
- package/gui/dist/assets/App-DMudtISW.css +0 -1
- package/gui/dist/assets/index-Btze1I3T.css +0 -1
- package/gui/dist/assets/index-CvnY7E6i.js +0 -86
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +0 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { devinCacheIdentity } from "./cloud-direct/chat";
|
|
2
|
+
|
|
3
|
+
const TRAJECTORY_MAX = 256;
|
|
4
|
+
const trajectories = new Map<string, { id: string; active: boolean }>();
|
|
5
|
+
|
|
6
|
+
export interface DevinTrajectoryClaim {
|
|
7
|
+
trajectoryId?: string;
|
|
8
|
+
release(): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** One process-local trajectory per named conversation, never shared by live turns. */
|
|
12
|
+
export function claimDevinTrajectory(
|
|
13
|
+
apiKey: string,
|
|
14
|
+
host: string,
|
|
15
|
+
conversation: string | null | undefined,
|
|
16
|
+
parentThreadId?: string,
|
|
17
|
+
): DevinTrajectoryClaim {
|
|
18
|
+
if (!conversation) return { release() {} };
|
|
19
|
+
// Tag both forms so a standalone JSON-like ID cannot alias a parent/own pair.
|
|
20
|
+
const identity = parentThreadId
|
|
21
|
+
? ["codex-child", parentThreadId, conversation]
|
|
22
|
+
: ["standalone", conversation];
|
|
23
|
+
// Only the fixed digest and UUID enter the retained map, never the token or conversation.
|
|
24
|
+
const key = new Bun.CryptoHasher("sha256")
|
|
25
|
+
.update(JSON.stringify([devinCacheIdentity(apiKey, host), identity])).digest("hex");
|
|
26
|
+
let entry = trajectories.get(key);
|
|
27
|
+
if (entry?.active) return { trajectoryId: crypto.randomUUID(), release() {} };
|
|
28
|
+
if (!entry) {
|
|
29
|
+
if (trajectories.size >= TRAJECTORY_MAX) {
|
|
30
|
+
const inactive = [...trajectories].find(([, candidate]) => !candidate.active);
|
|
31
|
+
if (!inactive) return { trajectoryId: crypto.randomUUID(), release() {} };
|
|
32
|
+
trajectories.delete(inactive[0]);
|
|
33
|
+
}
|
|
34
|
+
entry = { id: crypto.randomUUID(), active: true };
|
|
35
|
+
}
|
|
36
|
+
entry.active = true;
|
|
37
|
+
trajectories.delete(key);
|
|
38
|
+
trajectories.set(key, entry);
|
|
39
|
+
const claimed = entry;
|
|
40
|
+
let released = false;
|
|
41
|
+
return {
|
|
42
|
+
trajectoryId: claimed.id,
|
|
43
|
+
release() {
|
|
44
|
+
if (released) return;
|
|
45
|
+
released = true;
|
|
46
|
+
claimed.active = false;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
package/src/adapters/devin.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { DEVIN_DEFAULT_API_SERVER, resolveDevinApiServer } from "../oauth/devin"
|
|
|
18
18
|
import { devinAssistantReasoning, encodeDevinSignature, hasAnthropicSignature } from "./devin/reasoning-signature";
|
|
19
19
|
import { SendBudgetExhaustedError } from "../lib/upstream-retry";
|
|
20
20
|
import { devinContextOverflowEvent, isDevinHistoryOverflow } from "./devin/context-overflow";
|
|
21
|
+
import { claimDevinTrajectory, type DevinTrajectoryClaim } from "./devin/trajectory";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Combine two usage frames from one turn by keeping the larger count per field.
|
|
@@ -743,6 +744,7 @@ export function createDevinAdapter(
|
|
|
743
744
|
let contextWindow: number | undefined;
|
|
744
745
|
let messages: ChatHistoryItem[] = [];
|
|
745
746
|
let tools: ToolDef[] | undefined;
|
|
747
|
+
let trajectory: DevinTrajectoryClaim | undefined;
|
|
746
748
|
|
|
747
749
|
const closeOpenTool = () => {
|
|
748
750
|
if (!openToolId) return;
|
|
@@ -751,6 +753,14 @@ export function createDevinAdapter(
|
|
|
751
753
|
};
|
|
752
754
|
|
|
753
755
|
try {
|
|
756
|
+
const ownThreadId = incoming.headers.get("thread-id")?.trim() || parsed._codexOwnThreadId?.trim();
|
|
757
|
+
const conversation = ownThreadId
|
|
758
|
+
|| incoming.headers.get("session_id")?.trim() || incoming.headers.get("session-id")?.trim()
|
|
759
|
+
|| incoming.headers.get("x-session-affinity")?.trim()
|
|
760
|
+
|| (!incoming.headers.has("x-codex-parent-thread-id") ? parsed._clientThreadId : undefined);
|
|
761
|
+
trajectory = claimDevinTrajectory(apiKey, host, conversation,
|
|
762
|
+
ownThreadId ? incoming.headers.get("x-codex-parent-thread-id")?.trim() || undefined : undefined);
|
|
763
|
+
const trajectoryId = trajectory.trajectoryId;
|
|
754
764
|
// Read the selected UID's catalog row, not the picker's collapsed base.
|
|
755
765
|
contextWindow = resolveDevinContextWindow(provider, modelUid, catalog?.byUid.get(modelUid));
|
|
756
766
|
messages = mapOcxMessagesToDevin(parsed);
|
|
@@ -780,6 +790,7 @@ export function createDevinAdapter(
|
|
|
780
790
|
messages,
|
|
781
791
|
tools,
|
|
782
792
|
cascadeId,
|
|
793
|
+
trajectoryId,
|
|
783
794
|
completionOpts: {
|
|
784
795
|
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
785
796
|
...(typeof parsed.options.temperature === "number" ? { temperature: parsed.options.temperature } : {}),
|
|
@@ -961,6 +972,8 @@ export function createDevinAdapter(
|
|
|
961
972
|
...(error instanceof CloudChatError && error.code ? { code: error.code } : {}),
|
|
962
973
|
...(usage ? { usage } : {}),
|
|
963
974
|
});
|
|
975
|
+
} finally {
|
|
976
|
+
trajectory?.release();
|
|
964
977
|
}
|
|
965
978
|
},
|
|
966
979
|
};
|
|
@@ -71,12 +71,13 @@ interface PendingToolCall {
|
|
|
71
71
|
namespace?: string;
|
|
72
72
|
wireName: string;
|
|
73
73
|
order: number;
|
|
74
|
-
|
|
74
|
+
results: Array<OcxMessage & { role: "toolResult" }>;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
interface PendingToolBatch {
|
|
78
78
|
calls: PendingToolCall[];
|
|
79
79
|
byId: Map<string, PendingToolCall>;
|
|
80
|
+
unresolvedCount: number;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
interface NativeStreamToolCall {
|
|
@@ -302,6 +303,12 @@ function assistantTextThinkingAndCalls(message: OcxAssistantMessage): {
|
|
|
302
303
|
};
|
|
303
304
|
}
|
|
304
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Build native replay messages without mutating the parsed history. Keep tool results
|
|
308
|
+
* beside their originating batch, deferring intervening conversation until settlement.
|
|
309
|
+
* Reserve replayed call IDs for response translation and mark missing results explicitly.
|
|
310
|
+
* @throws When call IDs are invalid or results are orphaned or mismatched.
|
|
311
|
+
*/
|
|
305
312
|
function buildNativeMessages(
|
|
306
313
|
parsed: OcxParsedRequest,
|
|
307
314
|
reservedToolCallIds: Set<string>,
|
|
@@ -317,25 +324,27 @@ function buildNativeMessages(
|
|
|
317
324
|
// owned by this adapter/request lifecycle rather than process-global state.
|
|
318
325
|
reservedToolCallIds.clear();
|
|
319
326
|
let pending: PendingToolBatch | undefined;
|
|
327
|
+
const issuedCalls = new Map<string, PendingToolCall>();
|
|
320
328
|
// Codex records mid-turn injections (a PostToolUse hook verdict, a context notice) between an
|
|
321
329
|
// assistant tool call and that call's own tool result. Native Ollama needs the call and its
|
|
322
330
|
// results adjacent, so those conversational messages wait here instead of closing the batch
|
|
323
331
|
// early. The openai-chat adapter defers them the same way; refusing the replay killed the turn.
|
|
324
332
|
let deferred: OllamaNativeMessage[] = [];
|
|
325
333
|
|
|
334
|
+
/** Emit held conversation messages in their recorded arrival order after settlement. */
|
|
326
335
|
const releaseDeferred = (): void => {
|
|
327
336
|
if (deferred.length === 0) return;
|
|
328
337
|
messages.push(...deferred);
|
|
329
338
|
deferred = [];
|
|
330
339
|
};
|
|
331
340
|
|
|
341
|
+
/** Settle the open batch with genuine results or unknown markers, then release deferred messages. */
|
|
332
342
|
const flushPending = (): void => {
|
|
333
343
|
if (!pending) return;
|
|
334
344
|
for (const call of pending.calls) {
|
|
335
|
-
if (
|
|
336
|
-
// No result
|
|
337
|
-
//
|
|
338
|
-
// the conversation replayable.
|
|
345
|
+
if (call.results.length === 0) {
|
|
346
|
+
// No result was available when this batch settled. A later notification is carried
|
|
347
|
+
// separately, not used to invent an outcome at this earlier conversation boundary.
|
|
339
348
|
messages.push({
|
|
340
349
|
role: "tool",
|
|
341
350
|
tool_call_id: call.id,
|
|
@@ -347,13 +356,18 @@ function buildNativeMessages(
|
|
|
347
356
|
});
|
|
348
357
|
continue;
|
|
349
358
|
}
|
|
350
|
-
|
|
359
|
+
// A code-mode cell can yield, then notify repeatedly. Join once at settlement rather
|
|
360
|
+
// than repeatedly copying an ever-growing result, and never mutate the source history.
|
|
361
|
+
const fragments = call.results.map(result => ({
|
|
362
|
+
...contentToNative(result.content, "tool result"), isError: result.isError,
|
|
363
|
+
}));
|
|
364
|
+
const images = fragments.flatMap(fragment => fragment.images ?? []);
|
|
351
365
|
messages.push({
|
|
352
366
|
role: "tool",
|
|
353
367
|
tool_call_id: call.id,
|
|
354
368
|
tool_name: call.wireName,
|
|
355
|
-
content:
|
|
356
|
-
...(
|
|
369
|
+
content: fragments.map(fragment => `${fragment.isError ? "ERROR: " : ""}${fragment.content}`).join("\n"),
|
|
370
|
+
...(images.length ? { images } : {}),
|
|
357
371
|
});
|
|
358
372
|
}
|
|
359
373
|
pending = undefined;
|
|
@@ -362,27 +376,37 @@ function buildNativeMessages(
|
|
|
362
376
|
|
|
363
377
|
for (const message of parsed.context.messages) {
|
|
364
378
|
if (message.role === "toolResult") {
|
|
365
|
-
|
|
366
|
-
throw new Error(`ollama-native orphan tool result ${message.toolCallId || "<missing-id>"}`);
|
|
367
|
-
}
|
|
368
|
-
const call = pending.byId.get(message.toolCallId);
|
|
379
|
+
const call = issuedCalls.get(message.toolCallId);
|
|
369
380
|
if (!call) {
|
|
370
|
-
throw new Error(
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
throw new Error(`ollama-native duplicate tool result for ${message.toolCallId}`);
|
|
381
|
+
throw new Error(pending
|
|
382
|
+
? `ollama-native tool result ${message.toolCallId || "<missing-id>"} has no originating call`
|
|
383
|
+
: `ollama-native orphan tool result ${message.toolCallId || "<missing-id>"}`);
|
|
374
384
|
}
|
|
375
385
|
if (call.name !== message.toolName || call.namespace !== message.toolNamespace) {
|
|
376
386
|
throw new Error(`ollama-native tool result ${message.toolCallId} names the wrong originating tool`);
|
|
377
387
|
}
|
|
378
|
-
call.
|
|
388
|
+
if (pending?.byId.has(call.id)) {
|
|
389
|
+
if (call.results.length === 0) pending.unresolvedCount--;
|
|
390
|
+
call.results.push(message);
|
|
391
|
+
} else {
|
|
392
|
+
// Reopening a settled batch would split a later call/result pair. Keep known late
|
|
393
|
+
// output explicitly attributed as conversation text instead of dropping it or
|
|
394
|
+
// fabricating another executable call. Unknown/mismatched identities still fail above.
|
|
395
|
+
const translated = contentToNative(message.content, "late tool result");
|
|
396
|
+
const late: OllamaNativeMessage = { role: "user",
|
|
397
|
+
content: `[ocx] additional output for previously issued tool "${call.wireName}" (${call.id}):\n${message.isError ? "ERROR: " : ""}${translated.content}`,
|
|
398
|
+
...(translated.images ? { images: translated.images } : {}),
|
|
399
|
+
};
|
|
400
|
+
if (pending) deferred.push(late);
|
|
401
|
+
else messages.push(late);
|
|
402
|
+
}
|
|
379
403
|
continue;
|
|
380
404
|
}
|
|
381
405
|
|
|
382
406
|
// Native Ollama requires the whole assistant tool-call turn followed by its tool results. A
|
|
383
407
|
// conversational message that arrives while the batch is still open is held aside instead of
|
|
384
408
|
// closing it, so the call keeps its results adjacent; it is released right after the batch
|
|
385
|
-
// flushes.
|
|
409
|
+
// flushes. A new assistant tool-call batch settles the preceding batch first.
|
|
386
410
|
if (pending) {
|
|
387
411
|
if (message.role === "user" || message.role === "developer") {
|
|
388
412
|
const translated = message.role === "user"
|
|
@@ -393,6 +417,19 @@ function buildNativeMessages(
|
|
|
393
417
|
: { role: "system", content: translated.content });
|
|
394
418
|
continue;
|
|
395
419
|
}
|
|
420
|
+
if (message.role === "assistant" && pending.unresolvedCount > 0) {
|
|
421
|
+
const extracted = assistantTextThinkingAndCalls(message);
|
|
422
|
+
if (extracted.calls.length === 0) {
|
|
423
|
+
// Commentary can follow the call items but precede their recorded results.
|
|
424
|
+
// Keep the batch open and release its text/thinking after the actual results.
|
|
425
|
+
deferred.push({
|
|
426
|
+
role: "assistant",
|
|
427
|
+
content: extracted.content,
|
|
428
|
+
...(extracted.thinking ? { thinking: extracted.thinking } : {}),
|
|
429
|
+
});
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
396
433
|
flushPending();
|
|
397
434
|
}
|
|
398
435
|
|
|
@@ -427,8 +464,10 @@ function buildNativeMessages(
|
|
|
427
464
|
namespace: call.namespace,
|
|
428
465
|
wireName,
|
|
429
466
|
order: index,
|
|
467
|
+
results: [],
|
|
430
468
|
};
|
|
431
469
|
wireCalls.push(pendingCall);
|
|
470
|
+
issuedCalls.set(call.id, pendingCall);
|
|
432
471
|
return {
|
|
433
472
|
type: "function" as const,
|
|
434
473
|
id: call.id,
|
|
@@ -443,7 +482,11 @@ function buildNativeMessages(
|
|
|
443
482
|
};
|
|
444
483
|
messages.push(native);
|
|
445
484
|
if (wireCalls.length > 0) {
|
|
446
|
-
pending = {
|
|
485
|
+
pending = {
|
|
486
|
+
calls: wireCalls,
|
|
487
|
+
byId: new Map(wireCalls.map(call => [call.id, call])),
|
|
488
|
+
unresolvedCount: wireCalls.length,
|
|
489
|
+
};
|
|
447
490
|
}
|
|
448
491
|
break;
|
|
449
492
|
}
|
|
@@ -8,7 +8,7 @@ import { Buffer } from "node:buffer";
|
|
|
8
8
|
import type { IncomingMeta, ProviderAdapter } from "../base";
|
|
9
9
|
import { namespacedToolName, type AdapterEvent, type OcxParsedRequest, type OcxProviderConfig, type OcxUsage, type TierDecision } from "../../types";
|
|
10
10
|
import { applyCodexRoutingHint, CODEX_RESPONSES_LITE_HEADER, CODEX_ROUTING_HINT_HEADER } from "../../codex/forward-transport-headers";
|
|
11
|
-
import { COMPACT_PROMPT, compactionItemToText, decodeCompactionSummary, isCompactionItemType } from "../../responses/compaction";
|
|
11
|
+
import { COMPACT_PROMPT, compactionItemToText, decodeCompactionSummary, isCompactionItemType, transferCompactionCiphertextLease } from "../../responses/compaction";
|
|
12
12
|
import { decodeServerSentEvents } from "../../lib/sse-decoder";
|
|
13
13
|
import {
|
|
14
14
|
CODEX_FORWARD_BASE_URL,
|
|
@@ -46,6 +46,7 @@ import { applyTierDecisionToResponsesBody, normalizeCanonicalForwardContinuation
|
|
|
46
46
|
import { normalizeImageGenClientTools, preferConfiguredHostedTools } from "./image-gen";
|
|
47
47
|
import { stripMuseSparkUnsupportedWebSearchFields, stripOpenAiOnlyWebSearchFields } from "./web-search";
|
|
48
48
|
import { observeOutbound } from "../../usage/cache-diagnostic";
|
|
49
|
+
import { normalizeForwardedClientHeaderName } from "../../lib/provider-client-headers";
|
|
49
50
|
import { normalizeMuseToolChoice } from "./muse-tool-choice";
|
|
50
51
|
|
|
51
52
|
/**
|
|
@@ -94,6 +95,22 @@ function applyCallerUserAgentFallback(
|
|
|
94
95
|
if (userAgent) headers["User-Agent"] = userAgent;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
/** Copy only explicitly opted-in caller metadata; provider-owned headers remain authoritative. */
|
|
99
|
+
function applyConfiguredClientHeaderForwarding(
|
|
100
|
+
headers: Record<string, string>,
|
|
101
|
+
incoming: IncomingMeta,
|
|
102
|
+
provider: OcxProviderConfig,
|
|
103
|
+
): void {
|
|
104
|
+
if (!Array.isArray(provider.forwardClientHeaders)) return;
|
|
105
|
+
for (const rawName of provider.forwardClientHeaders.slice(0, 64)) {
|
|
106
|
+
const name = normalizeForwardedClientHeaderName(rawName);
|
|
107
|
+
if (name === null) continue;
|
|
108
|
+
if (Object.keys(headers).some(existing => existing.toLowerCase() === name)) continue;
|
|
109
|
+
const value = incoming.headers.get(name);
|
|
110
|
+
if (value) headers[name] = value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
97
114
|
/** Replace every `input_image` part under a routed-compaction body with a short marker. */
|
|
98
115
|
function stripInputImagesDeep(value: unknown): unknown {
|
|
99
116
|
if (Array.isArray(value)) return value.map(stripInputImagesDeep);
|
|
@@ -252,8 +269,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
252
269
|
if (provider.headers) Object.assign(headers, provider.headers);
|
|
253
270
|
}
|
|
254
271
|
// Some Responses-compatible gateways select their Codex compatibility path from the real
|
|
255
|
-
// client fingerprint.
|
|
256
|
-
//
|
|
272
|
+
// client fingerprint. Additional caller metadata is opt-in; static provider headers remain
|
|
273
|
+
// authoritative in either auth mode.
|
|
274
|
+
applyConfiguredClientHeaderForwarding(headers, incoming, provider);
|
|
257
275
|
applyCallerUserAgentFallback(headers, incoming);
|
|
258
276
|
|
|
259
277
|
const forward = provider.authMode === "forward";
|
|
@@ -374,7 +392,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
374
392
|
// OpenAI model inheriting history a routed provider had damaged.
|
|
375
393
|
outBody = repairLegacyDottedToolCallNames(outBody);
|
|
376
394
|
if (!isCanonicalOpenAiForwardProvider(provider)) {
|
|
377
|
-
|
|
395
|
+
if (provider.preserveResponsesMessageMetadata !== true) {
|
|
396
|
+
outBody = stripInternalChatMessageMetadataPassthrough(outBody);
|
|
397
|
+
}
|
|
378
398
|
// The same class of private field, one level up, but keyed on the DESTINATION rather than
|
|
379
399
|
// on the canonical surface alone. `src/server/responses/compact.ts` spreads the caller's
|
|
380
400
|
// raw body into the native `/responses/compact` request without passing through this
|
|
@@ -486,6 +506,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
486
506
|
),
|
|
487
507
|
),
|
|
488
508
|
isXaiResponsesDestination(provider),
|
|
509
|
+
provider.preserveResponsesInputItemIds === true,
|
|
489
510
|
),
|
|
490
511
|
isXaiSchemaTarget(provider),
|
|
491
512
|
);
|
|
@@ -590,112 +611,121 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
590
611
|
let compactionEncryptedContent: string | undefined;
|
|
591
612
|
let compactionEncryptedContentBytes = 0;
|
|
592
613
|
let completedSeen = false;
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
deltasBytes = nextBytes;
|
|
607
|
-
if (payload.delta.length > 0) deltasLastCodeUnit = payload.delta.charCodeAt(payload.delta.length - 1);
|
|
608
|
-
}
|
|
609
|
-
break;
|
|
610
|
-
case "response.output_text.done":
|
|
611
|
-
if (typeof payload.text === "string") {
|
|
612
|
-
const next = doneText + payload.text;
|
|
613
|
-
const nextBytes = appendedUtf8Bytes(doneTextBytes, doneTextLastCodeUnit, payload.text);
|
|
614
|
-
const reservation = budget.reserveTransient(nextBytes, { kind: "retained_collectors" });
|
|
615
|
-
doneText = next;
|
|
616
|
-
reservation.commitRetained();
|
|
617
|
-
budget.releaseRetained(doneTextBytes, { kind: "retained_collectors" });
|
|
618
|
-
doneTextBytes = nextBytes;
|
|
619
|
-
if (payload.text.length > 0) doneTextLastCodeUnit = payload.text.charCodeAt(payload.text.length - 1);
|
|
620
|
-
}
|
|
621
|
-
break;
|
|
622
|
-
case "response.failed":
|
|
623
|
-
case "error":
|
|
624
|
-
yield { type: "error", message: responsesErrorMessage(payload.response ?? payload) };
|
|
625
|
-
return;
|
|
626
|
-
case "response.incomplete":
|
|
627
|
-
yield { type: "incomplete", reason: responsesErrorMessage(payload.response ?? payload) };
|
|
628
|
-
return;
|
|
629
|
-
case "response.completed":
|
|
630
|
-
{
|
|
631
|
-
completedSeen = true;
|
|
632
|
-
const responsePayload = isPlainObject(payload.response) ? payload.response : undefined;
|
|
633
|
-
const output = Array.isArray(responsePayload?.output) ? responsePayload.output : [];
|
|
634
|
-
const compaction = output.find(item => isPlainObject(item) && item.type === "compaction");
|
|
635
|
-
if (isPlainObject(compaction) && typeof compaction.encrypted_content === "string") {
|
|
636
|
-
const nextEncryptedContent = compaction.encrypted_content;
|
|
637
|
-
const nextEncryptedContentBytes = Buffer.byteLength(nextEncryptedContent, "utf8");
|
|
638
|
-
const reservation = budget.reserveTransient(nextEncryptedContentBytes, { kind: "retained_collectors" });
|
|
639
|
-
compactionEncryptedContent = nextEncryptedContent;
|
|
614
|
+
let ciphertextTransferred = false;
|
|
615
|
+
try {
|
|
616
|
+
for await (const event of decodeServerSentEvents(response.body, { translatorBudget: budget })) {
|
|
617
|
+
let payload: unknown;
|
|
618
|
+
try { payload = JSON.parse(event.data); } catch { continue; }
|
|
619
|
+
if (!isPlainObject(payload)) continue;
|
|
620
|
+
switch (payload.type) {
|
|
621
|
+
case "response.output_text.delta":
|
|
622
|
+
if (typeof payload.delta === "string") {
|
|
623
|
+
const next = deltas + payload.delta;
|
|
624
|
+
const nextBytes = appendedUtf8Bytes(deltasBytes, deltasLastCodeUnit, payload.delta);
|
|
625
|
+
const reservation = budget.reserveTransient(nextBytes, { kind: "retained_collectors" });
|
|
626
|
+
deltas = next;
|
|
640
627
|
reservation.commitRetained();
|
|
641
|
-
budget.releaseRetained(
|
|
642
|
-
|
|
628
|
+
budget.releaseRetained(deltasBytes, { kind: "retained_collectors" });
|
|
629
|
+
deltasBytes = nextBytes;
|
|
630
|
+
if (payload.delta.length > 0) deltasLastCodeUnit = payload.delta.charCodeAt(payload.delta.length - 1);
|
|
643
631
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
{
|
|
653
|
-
const nextUsage = usageFromResponsesPayload(payload.response);
|
|
654
|
-
// The attached raw usage object can be event-sized (unknown keys carry arbitrary
|
|
655
|
-
// values); it stays reachable until the terminal yields, so charge it like the
|
|
656
|
-
// adjacent retained collectors or it would defeat the per-request memory cap.
|
|
657
|
-
const nextRawBytes = nextUsage?.rawUsage === undefined ? 0
|
|
658
|
-
: Buffer.byteLength(JSON.stringify(nextUsage.rawUsage), "utf8");
|
|
659
|
-
if (nextRawBytes > 0) {
|
|
660
|
-
const reservation = budget.reserveTransient(nextRawBytes, { kind: "retained_collectors" });
|
|
661
|
-
usage = nextUsage;
|
|
632
|
+
break;
|
|
633
|
+
case "response.output_text.done":
|
|
634
|
+
if (typeof payload.text === "string") {
|
|
635
|
+
const next = doneText + payload.text;
|
|
636
|
+
const nextBytes = appendedUtf8Bytes(doneTextBytes, doneTextLastCodeUnit, payload.text);
|
|
637
|
+
const reservation = budget.reserveTransient(nextBytes, { kind: "retained_collectors" });
|
|
638
|
+
doneText = next;
|
|
662
639
|
reservation.commitRetained();
|
|
663
|
-
|
|
664
|
-
|
|
640
|
+
budget.releaseRetained(doneTextBytes, { kind: "retained_collectors" });
|
|
641
|
+
doneTextBytes = nextBytes;
|
|
642
|
+
if (payload.text.length > 0) doneTextLastCodeUnit = payload.text.charCodeAt(payload.text.length - 1);
|
|
665
643
|
}
|
|
666
|
-
|
|
667
|
-
|
|
644
|
+
break;
|
|
645
|
+
case "response.failed":
|
|
646
|
+
case "error":
|
|
647
|
+
yield { type: "error", message: responsesErrorMessage(payload.response ?? payload) };
|
|
648
|
+
return;
|
|
649
|
+
case "response.incomplete":
|
|
650
|
+
yield { type: "incomplete", reason: responsesErrorMessage(payload.response ?? payload) };
|
|
651
|
+
return;
|
|
652
|
+
case "response.completed":
|
|
653
|
+
{
|
|
654
|
+
completedSeen = true;
|
|
655
|
+
const responsePayload = isPlainObject(payload.response) ? payload.response : undefined;
|
|
656
|
+
const output = Array.isArray(responsePayload?.output) ? responsePayload.output : [];
|
|
657
|
+
const compaction = output.find(item => isPlainObject(item) && item.type === "compaction");
|
|
658
|
+
if (isPlainObject(compaction) && typeof compaction.encrypted_content === "string") {
|
|
659
|
+
const nextEncryptedContent = compaction.encrypted_content;
|
|
660
|
+
const nextEncryptedContentBytes = Buffer.byteLength(nextEncryptedContent, "utf8");
|
|
661
|
+
const reservation = budget.reserveTransient(nextEncryptedContentBytes, { kind: "retained_collectors" });
|
|
662
|
+
compactionEncryptedContent = nextEncryptedContent;
|
|
663
|
+
reservation.commitRetained();
|
|
664
|
+
budget.releaseRetained(compactionEncryptedContentBytes, { kind: "retained_collectors" });
|
|
665
|
+
compactionEncryptedContentBytes = nextEncryptedContentBytes;
|
|
666
|
+
}
|
|
667
|
+
const next = responsesPayloadText(payload.response);
|
|
668
|
+
const nextBytes = Buffer.byteLength(next, "utf8");
|
|
669
|
+
const reservation = budget.reserveTransient(nextBytes, { kind: "retained_collectors" });
|
|
670
|
+
snapshot = next;
|
|
671
|
+
reservation.commitRetained();
|
|
672
|
+
budget.releaseRetained(snapshotBytes, { kind: "retained_collectors" });
|
|
673
|
+
snapshotBytes = nextBytes;
|
|
668
674
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
675
|
+
{
|
|
676
|
+
const nextUsage = usageFromResponsesPayload(payload.response);
|
|
677
|
+
// The attached raw usage object can be event-sized (unknown keys carry arbitrary
|
|
678
|
+
// values); it stays reachable until the terminal yields, so charge it like the
|
|
679
|
+
// adjacent retained collectors or it would defeat the per-request memory cap.
|
|
680
|
+
const nextRawBytes = nextUsage?.rawUsage === undefined ? 0
|
|
681
|
+
: Buffer.byteLength(JSON.stringify(nextUsage.rawUsage), "utf8");
|
|
682
|
+
if (nextRawBytes > 0) {
|
|
683
|
+
const reservation = budget.reserveTransient(nextRawBytes, { kind: "retained_collectors" });
|
|
684
|
+
usage = nextUsage;
|
|
685
|
+
reservation.commitRetained();
|
|
686
|
+
} else {
|
|
687
|
+
usage = nextUsage;
|
|
688
|
+
}
|
|
689
|
+
if (usageRawBytes > 0) {
|
|
690
|
+
budget.releaseRetained(usageRawBytes, { kind: "retained_collectors" });
|
|
691
|
+
}
|
|
692
|
+
usageRawBytes = nextRawBytes;
|
|
693
|
+
}
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
// Buffered text is still upstream progress, but gateway keepalives are not.
|
|
697
|
+
// Yield after accounting, directly to the consumer: no progress queue or content leak.
|
|
698
|
+
if (
|
|
699
|
+
!completedSeen
|
|
700
|
+
&& (payload.type === "response.output_text.delta"
|
|
701
|
+
|| payload.type === "response.reasoning_summary_text.delta"
|
|
702
|
+
|| payload.type === "response.reasoning_text.delta")
|
|
703
|
+
&& typeof payload.delta === "string"
|
|
704
|
+
&& payload.delta.length > 0
|
|
705
|
+
) {
|
|
706
|
+
yield { type: "heartbeat" };
|
|
707
|
+
}
|
|
684
708
|
}
|
|
709
|
+
// Gateways differ in which of these they emit; prefer the authoritative
|
|
710
|
+
// completed snapshot so text is never double-counted.
|
|
711
|
+
const text = snapshot || doneText || deltas;
|
|
712
|
+
if (text) yield { type: "text_delta", text };
|
|
713
|
+
const done: AdapterEvent = {
|
|
714
|
+
type: "done",
|
|
715
|
+
...(usage ? { usage } : {}),
|
|
716
|
+
...(compactionEncryptedContent ? { compactionEncryptedContent } : {}),
|
|
717
|
+
};
|
|
718
|
+
// Exact event ownership survives buffered collection without charging a second copy.
|
|
719
|
+
transferCompactionCiphertextLease(done, budget, compactionEncryptedContentBytes);
|
|
720
|
+
ciphertextTransferred = true;
|
|
721
|
+
yield done;
|
|
722
|
+
} finally {
|
|
723
|
+
budget.releaseRetained(
|
|
724
|
+
deltasBytes + doneTextBytes + snapshotBytes + usageRawBytes
|
|
725
|
+
+ (ciphertextTransferred ? 0 : compactionEncryptedContentBytes),
|
|
726
|
+
{ kind: "retained_collectors" },
|
|
727
|
+
);
|
|
685
728
|
}
|
|
686
|
-
// Gateways differ in which of these they emit; prefer the authoritative
|
|
687
|
-
// completed snapshot so text is never double-counted.
|
|
688
|
-
const text = snapshot || doneText || deltas;
|
|
689
|
-
if (text) yield { type: "text_delta", text };
|
|
690
|
-
budget.releaseRetained(
|
|
691
|
-
deltasBytes + doneTextBytes + snapshotBytes + usageRawBytes,
|
|
692
|
-
{ kind: "retained_collectors" },
|
|
693
|
-
);
|
|
694
|
-
yield {
|
|
695
|
-
type: "done",
|
|
696
|
-
...(usage ? { usage } : {}),
|
|
697
|
-
...(compactionEncryptedContent ? { compactionEncryptedContent } : {}),
|
|
698
|
-
};
|
|
699
729
|
},
|
|
700
730
|
|
|
701
731
|
async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
|
|
@@ -191,12 +191,18 @@ export function stripRejectedSamplingParams(
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/**
|
|
194
|
-
* When `store
|
|
194
|
+
* When `store: false`, the upstream cannot retrieve stored turns by ID. An ID
|
|
195
195
|
* forwarded in `input` is then interpreted as a reference to a stored item that does not
|
|
196
|
-
* exist, producing a 404. Strip all item IDs in this case
|
|
196
|
+
* exist, producing a 404. Strip all item IDs in this case unless `preserveIds` is true
|
|
197
|
+
* — `call_id` pairing is unaffected. Also repairs missing or broken xAI custom_tool_call IDs
|
|
198
|
+
* when `requireCustomCallIds` is true, regardless of `preserveIds`.
|
|
197
199
|
* Matches codex-rs behavior (core/src/client.rs:918-925).
|
|
198
200
|
*/
|
|
199
|
-
export function stripItemIdsWhenUnstored(
|
|
201
|
+
export function stripItemIdsWhenUnstored(
|
|
202
|
+
body: unknown,
|
|
203
|
+
requireCustomCallIds = false,
|
|
204
|
+
preserveIds = false,
|
|
205
|
+
): unknown {
|
|
200
206
|
const repairCustomCallIds = requireCustomCallIds === true;
|
|
201
207
|
if (!isPlainObject(body) || (body.store !== false && !repairCustomCallIds)) return body;
|
|
202
208
|
if (!Array.isArray(body.input)) return body;
|
|
@@ -226,7 +232,7 @@ export function stripItemIdsWhenUnstored(body: unknown, requireCustomCallIds = f
|
|
|
226
232
|
return item;
|
|
227
233
|
}
|
|
228
234
|
}
|
|
229
|
-
if (body.store !== false || !("id" in item)) return item;
|
|
235
|
+
if (preserveIds || body.store !== false || !("id" in item)) return item;
|
|
230
236
|
changed = true;
|
|
231
237
|
const next = { ...item };
|
|
232
238
|
delete next.id;
|
package/src/adapters/zed.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { createGoogleAdapter } from "./google";
|
|
|
5
5
|
import { createOpenAIChatAdapter } from "./openai-chat";
|
|
6
6
|
import { createResponsesPassthroughAdapter } from "./openai-responses";
|
|
7
7
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../types";
|
|
8
|
-
import { createTranslatorBudget, type TranslatorBudget } from "../lib/translator-budget";
|
|
8
|
+
import { createTranslatorBudget, releaseTranslatedEvent, retainTranslatedEvent, type TranslatorBudget } from "../lib/translator-budget";
|
|
9
|
+
import { releaseCompactionCiphertextLease } from "../responses/compaction";
|
|
9
10
|
import { redactSecretString } from "../lib/redact";
|
|
10
11
|
import {
|
|
11
12
|
normalizeZedProvider,
|
|
@@ -365,12 +366,33 @@ export function createZedAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
|
365
366
|
status: response.status,
|
|
366
367
|
headers: { "Content-Type": "text/event-stream" },
|
|
367
368
|
});
|
|
368
|
-
|
|
369
|
+
try {
|
|
370
|
+
yield* delegate.adapter.parseStream(translated, budget);
|
|
371
|
+
} finally {
|
|
372
|
+
// Some delegates only unlock their reader when a collector stops early.
|
|
373
|
+
try { await translated.body?.cancel(); } catch { /* already closed/errored */ }
|
|
374
|
+
}
|
|
369
375
|
},
|
|
370
376
|
async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
|
|
371
377
|
const events: AdapterEvent[] = [];
|
|
372
|
-
|
|
373
|
-
|
|
378
|
+
try {
|
|
379
|
+
for await (const event of adapter.parseStream(response, budget)) {
|
|
380
|
+
try {
|
|
381
|
+
retainTranslatedEvent(event, budget, events.at(-1));
|
|
382
|
+
} catch (error) {
|
|
383
|
+
releaseCompactionCiphertextLease(event, budget);
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
events.push(event);
|
|
387
|
+
}
|
|
388
|
+
return events;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
for (const event of events) {
|
|
391
|
+
releaseTranslatedEvent(event, budget);
|
|
392
|
+
releaseCompactionCiphertextLease(event, budget);
|
|
393
|
+
}
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
374
396
|
},
|
|
375
397
|
};
|
|
376
398
|
return adapter;
|