@bitkyc08/opencodex 2.58.0 → 2.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -10
- package/gui/dist/assets/index-BTuCbqQd.css +1 -0
- package/gui/dist/assets/index-DoBVdPHP.js +134 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/crusoe.svg +1 -0
- package/gui/dist/provider-icons/opper.svg +3 -0
- package/package.json +4 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +4 -1
- package/src/adapters/base.ts +34 -1
- package/src/adapters/coding-agent/turn.ts +22 -2
- package/src/adapters/command-code.ts +50 -3
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/effort-map.ts +16 -2
- package/src/adapters/cursor/envelope-echo.ts +55 -2
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/message-mapper.ts +3 -2
- package/src/adapters/cursor/protobuf-request.ts +8 -5
- package/src/adapters/cursor/request-builder.ts +21 -4
- package/src/adapters/cursor/thread-continuity.ts +105 -31
- package/src/adapters/cursor/tool-guidance.ts +5 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +45 -2
- package/src/adapters/devin/cloud-direct/chat.ts +14 -3
- package/src/adapters/devin/cloud-direct/index.ts +7 -0
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +140 -0
- package/src/adapters/devin.ts +125 -26
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +55 -4
- package/src/adapters/google-http.ts +57 -11
- 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 +60 -10
- package/src/adapters/openai-chat/response-events.ts +61 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +10 -11
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +25 -4
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +75 -0
- package/src/adapters/openai-responses/tool-schema.ts +19 -7
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/responses-tool-schema.ts +76 -46
- package/src/adapters/run-turn-queue.ts +17 -4
- package/src/bridge/response-json.ts +1 -1
- package/src/bridge/sse.ts +28 -35
- package/src/claude/context-windows.ts +22 -0
- package/src/claude/outbound.ts +35 -4
- package/src/cli/account-api.ts +4 -3
- package/src/cli/account-extended.ts +26 -6
- package/src/cli/account-orca-import.ts +63 -0
- package/src/cli/account.ts +32 -4
- package/src/cli/capabilities.ts +40 -0
- package/src/cli/claude.ts +29 -1
- package/src/cli/codex-cli-update.ts +97 -2
- package/src/cli/dispatch.ts +57 -3
- package/src/cli/doctor.ts +218 -4
- package/src/cli/help.ts +4 -1
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +95 -22
- package/src/cli/models-runtime.ts +33 -4
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +11 -1
- package/src/cli/runtime-api.ts +44 -0
- package/src/cli/start-args.ts +94 -0
- package/src/cli/system-command.ts +2 -0
- package/src/client/machine-api.ts +6 -5
- package/src/client/machine-listener.ts +16 -3
- package/src/client/runtime.ts +26 -2
- package/src/clients/config-export/constants.ts +2 -3
- package/src/clients/config-export.ts +5 -5
- package/src/codex/account-store.ts +146 -5
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/pool-quota-probe.ts +44 -10
- package/src/codex/auth-api/routes.ts +17 -2
- package/src/codex/auth-context.ts +16 -12
- package/src/codex/catalog/build-entries.ts +25 -4
- package/src/codex/catalog/derive-entry.ts +8 -1
- package/src/codex/catalog/effort.ts +10 -6
- package/src/codex/catalog/gather-capture.ts +22 -2
- package/src/codex/catalog/model-hints.ts +66 -33
- package/src/codex/catalog/parsing.ts +90 -5
- package/src/codex/catalog/provider-models.ts +19 -2
- package/src/codex/catalog/reserve-warn.ts +96 -0
- package/src/codex/catalog/retained-sync.ts +41 -26
- package/src/codex/catalog/routed-gather.ts +61 -3
- package/src/codex/cli-installation-identity.ts +210 -0
- package/src/codex/cli-installation-targets.ts +158 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +5 -0
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +24 -167
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/history-state-open.ts +105 -0
- package/src/codex/inject/config-toml.ts +44 -2
- package/src/codex/inject.ts +3 -2
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/lineage.ts +83 -32
- package/src/codex/loopback-target.ts +31 -0
- package/src/codex/main-account-hard-lock.ts +2 -1
- package/src/codex/main-account.ts +10 -3
- package/src/codex/main-device-reauth.ts +17 -9
- package/src/codex/model-cache.ts +47 -0
- package/src/codex/model-entitlements.ts +80 -2
- package/src/codex/observed-model-denials.ts +230 -0
- package/src/codex/orca-auth-source.ts +94 -0
- package/src/codex/orca-import.ts +219 -0
- package/src/codex/prompt-text-probe.ts +289 -16
- package/src/codex/quota-401-recovery.ts +12 -0
- package/src/codex/quota-types.ts +65 -0
- package/src/codex/quota.ts +24 -19
- package/src/codex/routing/cooldown-math.ts +8 -47
- package/src/codex/routing/pin-drain.ts +57 -0
- package/src/codex/routing.ts +20 -16
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +114 -2
- package/src/codex/windows-installation-files.ts +224 -0
- package/src/combos/failover.ts +125 -5
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/diagnostics.ts +43 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +33 -0
- package/src/config/pending-teardown.ts +8 -0
- package/src/config/process-state.ts +36 -3
- package/src/config/provider-relative-send-path.ts +16 -0
- package/src/config/proxy-env.ts +31 -7
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +25 -0
- package/src/config/schema/leaf-validators.ts +76 -17
- package/src/config.ts +2 -2
- package/src/generated/compatibility-version.json +410 -258
- package/src/generated/model-metadata.ts +1 -1
- 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/mutation-plan.ts +815 -0
- package/src/integrations/writer.ts +85 -99
- 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/bounded-body.ts +4 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +23 -0
- package/src/lib/destination-policy.ts +48 -6
- package/src/lib/errors.ts +82 -15
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/local-destinations.ts +32 -5
- 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-outbound.ts +53 -5
- package/src/lib/proxy-env.ts +70 -3
- package/src/lib/request-execution-budget.ts +11 -3
- package/src/lib/response-body-inactivity.ts +193 -0
- package/src/lib/retry-delay.ts +69 -0
- package/src/lib/socks5-fetch.ts +741 -0
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +332 -35
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/workflow-budget.ts +145 -8
- package/src/oauth/account-quota-rank.ts +72 -15
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +41 -27
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +3 -107
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/oauth/orcarouter.ts +15 -2
- package/src/oauth/store.ts +8 -0
- package/src/providers/codex-capacity.ts +9 -0
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/devin-provider-merge-migration.ts +33 -12
- package/src/providers/free-directory.ts +20 -2
- package/src/providers/key-failover.ts +327 -25
- package/src/providers/model-rename-migration.ts +56 -1
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-sidecar.ts +4 -0
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/opencode-go-transport.ts +14 -5
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/report-cache.ts +3 -0
- package/src/providers/quota/vendor-probes-key.ts +1 -1
- package/src/providers/registry/entries-core.ts +39 -17
- package/src/providers/registry/entries-extended.ts +131 -1
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +87 -21
- 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/responses/apply-patch-envelope.ts +32 -11
- package/src/responses/bridge-search-replay-cache.ts +152 -0
- package/src/responses/code-mode-helper-compat.ts +26 -16
- package/src/responses/custom-tool-compat.ts +1 -1
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/hosted-tool-policy.ts +85 -2
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +9 -2
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +91 -115
- 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/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +13 -5
- package/src/server/chat-native-sse.ts +26 -9
- package/src/server/chat-native.ts +10 -4
- package/src/server/claude-messages.ts +30 -5
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/gui-static.ts +36 -2
- package/src/server/inbound-body-admission.ts +187 -0
- package/src/server/index/serve-options.ts +86 -29
- package/src/server/index/spend-ledger-lifecycle.ts +66 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +24 -25
- package/src/server/management/api-access.ts +3 -4
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/config-routes.ts +48 -7
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/provider-capability-config.ts +35 -7
- package/src/server/management/provider-routes.ts +70 -18
- package/src/server/management/route-registry.ts +14 -0
- package/src/server/management/shared.ts +10 -3
- 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-api.ts +2 -0
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +97 -2
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +136 -34
- package/src/server/request-log.ts +80 -3
- package/src/server/request-metrics.ts +236 -0
- package/src/server/responses/adapter-continuation.ts +74 -30
- package/src/server/responses/adapter-delivery.ts +39 -8
- package/src/server/responses/adapter-dispatch.ts +63 -30
- package/src/server/responses/compact.ts +89 -18
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +90 -24
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +16 -15
- package/src/server/responses/core-opaque-recovery.ts +1 -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 +68 -2
- package/src/server/responses/passthrough-delivery.ts +41 -15
- package/src/server/responses/passthrough-dispatch.ts +145 -53
- package/src/server/responses/passthrough-execution.ts +11 -1
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +96 -17
- package/src/server/responses/request-send-budget.ts +89 -8
- package/src/server/responses/request-sidecar-auth.ts +17 -9
- package/src/server/responses/request-spend.ts +38 -9
- package/src/server/responses/request-transport.ts +15 -12
- package/src/server/responses/run-turn-execution.ts +45 -8
- package/src/server/responses/sidecar-execution.ts +19 -2
- package/src/server/responses/ws-upstream.ts +16 -28
- package/src/server/responses-custom-tool-repair.ts +29 -56
- package/src/server/responses-undeclared-tool-guard.ts +31 -1
- package/src/server/sse-frame-buffer.ts +12 -10
- package/src/server/sse-payload-rewrite.ts +37 -10
- package/src/server/system-env-shell.ts +5 -1
- package/src/server/system-env.ts +7 -1
- package/src/server/workflow-refusal.ts +56 -2
- package/src/service/cli.ts +16 -6
- package/src/service/guards.ts +10 -0
- package/src/service/health.ts +43 -0
- package/src/service/state.ts +7 -2
- package/src/tray/windows-tray.ps1 +155 -3
- package/src/types/accounts.ts +4 -0
- package/src/types/config.ts +111 -6
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +9 -1
- package/src/types/tools.ts +14 -0
- package/src/types/wire.ts +9 -1
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +28 -0
- package/src/usage/log.ts +87 -4
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/src/web-search/passthrough-bridge.ts +39 -5
- package/gui/dist/assets/index-BbrHOIY0.js +0 -128
- package/gui/dist/assets/index-C5-RdDmD.css +0 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { collectResponsesToolGroups } from "./tool-groups";
|
|
2
|
+
import { isSchemaValidResponsesToolName, wireToolInnerName } from "./tool-name-aliases";
|
|
3
|
+
import { CODE_MODE_HELPER_WIRE_NAMES, dottedToolName, namespacedToolName } from "../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The one invented namespace this proxy is known to have emitted. Routed providers flatten a
|
|
7
|
+
* namespaced call as `ns.name`, and a provider that invents the grouping picks `default`; #3402,
|
|
8
|
+
* #4176 and #4412 all repaired that spelling on the way IN, each on its own path.
|
|
9
|
+
*
|
|
10
|
+
* This is deliberately one literal prefix rather than a rule. "Strip everything before the first
|
|
11
|
+
* dot" would silently rewrite a legitimate tool name that happens to contain a dot in some other
|
|
12
|
+
* provider's vocabulary, and a replayed history item is the worst possible place to guess: it names
|
|
13
|
+
* a call that already happened.
|
|
14
|
+
*/
|
|
15
|
+
const LEGACY_INVENTED_NAMESPACE_PREFIX = "default.";
|
|
16
|
+
|
|
17
|
+
/** Item types whose `name` the upstream validates against the tool-name pattern. */
|
|
18
|
+
const NAMED_CALL_ITEM_TYPES = new Set(["function_call", "custom_tool_call"]);
|
|
19
|
+
|
|
20
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
21
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type DeclarationView = {
|
|
25
|
+
/** Wire spellings the caller's own catalog declares, including flattened namespace forms. */
|
|
26
|
+
readonly spellings: ReadonlySet<string>;
|
|
27
|
+
/** How many declared identities answer to a given inner name. */
|
|
28
|
+
readonly ownersByInnerName: ReadonlyMap<string, number>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read the request's own catalog. Namespace children contribute their inner name and both
|
|
33
|
+
* flattened spellings, which is what makes a declared `default` namespace distinguishable from an
|
|
34
|
+
* invented one.
|
|
35
|
+
*/
|
|
36
|
+
function readDeclarations(body: unknown): DeclarationView {
|
|
37
|
+
const spellings = new Set<string>();
|
|
38
|
+
const owners = new Map<string, number>();
|
|
39
|
+
const identities = new Map<string, Set<string>>();
|
|
40
|
+
const claim = (innerName: string, identity: string): void => {
|
|
41
|
+
const seen = identities.get(innerName);
|
|
42
|
+
if (seen === undefined) identities.set(innerName, new Set([identity]));
|
|
43
|
+
else seen.add(identity);
|
|
44
|
+
};
|
|
45
|
+
for (const group of collectResponsesToolGroups(body)) {
|
|
46
|
+
for (const spec of group) {
|
|
47
|
+
if (!isPlainObject(spec)) continue;
|
|
48
|
+
if (spec.type === "namespace" && Array.isArray(spec.tools)) {
|
|
49
|
+
const namespace = typeof spec.name === "string" ? spec.name : undefined;
|
|
50
|
+
if (namespace === undefined) continue;
|
|
51
|
+
for (const child of spec.tools) {
|
|
52
|
+
const inner = wireToolInnerName(child);
|
|
53
|
+
if (!inner) continue;
|
|
54
|
+
spellings.add(namespacedToolName(namespace, inner));
|
|
55
|
+
spellings.add(dottedToolName(namespace, inner));
|
|
56
|
+
claim(inner, JSON.stringify([namespace, inner]));
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const name = wireToolInnerName(spec);
|
|
61
|
+
if (!name) continue;
|
|
62
|
+
spellings.add(name);
|
|
63
|
+
claim(name, JSON.stringify([null, name]));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const [inner, owning] of identities) owners.set(inner, owning.size);
|
|
67
|
+
return { spellings, ownersByInnerName: owners };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Canonical name a legacy dotted alias should be replayed under, or undefined to leave it alone.
|
|
72
|
+
*
|
|
73
|
+
* Fail-closed in both directions that matter. A dotted spelling the caller's catalog actually
|
|
74
|
+
* declares is a real tool identity, so it is never stripped — rewriting it would change which tool
|
|
75
|
+
* the history says was called. A suffix claimed by two declared identities is ambiguous and is
|
|
76
|
+
* left as it is. Only two things resolve: a suffix that names exactly one declared tool, and the
|
|
77
|
+
* bounded set of code-mode helper spellings, which a code-mode catalog never declares because they
|
|
78
|
+
* exist only as nested `tools.*` helpers inside `exec`.
|
|
79
|
+
*/
|
|
80
|
+
function repairedCallName(name: string, declarations: DeclarationView): string | undefined {
|
|
81
|
+
if (isSchemaValidResponsesToolName(name)) return undefined;
|
|
82
|
+
if (!name.startsWith(LEGACY_INVENTED_NAMESPACE_PREFIX)) return undefined;
|
|
83
|
+
if (declarations.spellings.has(name)) return undefined;
|
|
84
|
+
const suffix = name.slice(LEGACY_INVENTED_NAMESPACE_PREFIX.length);
|
|
85
|
+
if (!isSchemaValidResponsesToolName(suffix)) return undefined;
|
|
86
|
+
const owners = declarations.ownersByInnerName.get(suffix);
|
|
87
|
+
if (owners === 1) return suffix;
|
|
88
|
+
if (owners !== undefined) return undefined;
|
|
89
|
+
return CODE_MODE_HELPER_WIRE_NAMES.has(suffix) ? suffix : undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function repairedItem(item: unknown, declarations: DeclarationView): unknown {
|
|
93
|
+
if (!isPlainObject(item)) return item;
|
|
94
|
+
if (typeof item.type !== "string" || !NAMED_CALL_ITEM_TYPES.has(item.type)) return item;
|
|
95
|
+
// An item carrying an explicit namespace is a namespaced identity whose `name` is the inner
|
|
96
|
+
// name; it is not the flattened shape this repairs, and its own field is the authority.
|
|
97
|
+
if (item.namespace !== undefined) return item;
|
|
98
|
+
if (typeof item.name !== "string") return item;
|
|
99
|
+
const repaired = repairedCallName(item.name, declarations);
|
|
100
|
+
return repaired === undefined ? item : { ...item, name: repaired };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Make an already-damaged conversation replayable.
|
|
105
|
+
*
|
|
106
|
+
* A `function_call` whose name violates `^[a-zA-Z0-9_-]+$` is not a one-turn failure: Codex stores
|
|
107
|
+
* the item, and the upstream then refuses EVERY later request that replays it
|
|
108
|
+
* (`Invalid 'input[N].name': ...`) — including the automatic compaction a long task or a side chat
|
|
109
|
+
* triggers. The emit-side fix stops new items from being created; without this the conversations
|
|
110
|
+
* that already contain one stay permanently unusable, which is the part of #5095 that actually
|
|
111
|
+
* hurts.
|
|
112
|
+
*
|
|
113
|
+
* Only replayed input items are touched. The caller's tool catalog is never rewritten, and a body
|
|
114
|
+
* with nothing to repair is returned by reference.
|
|
115
|
+
*/
|
|
116
|
+
export function repairLegacyDottedToolCallNames(body: unknown): unknown {
|
|
117
|
+
if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
|
|
118
|
+
if (!body.input.some(item =>
|
|
119
|
+
isPlainObject(item)
|
|
120
|
+
&& typeof item.type === "string"
|
|
121
|
+
&& NAMED_CALL_ITEM_TYPES.has(item.type)
|
|
122
|
+
&& typeof item.name === "string"
|
|
123
|
+
&& item.name.startsWith(LEGACY_INVENTED_NAMESPACE_PREFIX)
|
|
124
|
+
)) return body;
|
|
125
|
+
|
|
126
|
+
const declarations = readDeclarations(body);
|
|
127
|
+
let changed = false;
|
|
128
|
+
const input = body.input.map(item => {
|
|
129
|
+
const repaired = repairedItem(item, declarations);
|
|
130
|
+
if (repaired !== item) changed = true;
|
|
131
|
+
return repaired;
|
|
132
|
+
});
|
|
133
|
+
return changed ? { ...body, input } : body;
|
|
134
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { unwrapFreeformToolInput } from "./apply-patch-envelope";
|
|
2
|
+
import { JSON_ESCAPES, scanFreeformWrapper } from "./freeform-wrapper-scan";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Whether a body could still grow into one complete outer Markdown fence.
|
|
6
|
+
*
|
|
7
|
+
* `stripMarkdownCodeFence` removes such a fence at completion for exactly the two tools that
|
|
8
|
+
* own the grammar, so a fenced body's streamed bytes and its completed input disagree unless
|
|
9
|
+
* the stream holds. A buffer that does not open with a fence can never acquire one, so
|
|
10
|
+
* ordinary bodies are unaffected; a buffer that does keeps its preview suppressed for the
|
|
11
|
+
* whole call, because a closing fence can still be followed by more text that withdraws it.
|
|
12
|
+
*/
|
|
13
|
+
function mayBecomeFencedBody(text: string, toolName: string): boolean {
|
|
14
|
+
if (toolName !== "exec" && toolName !== "apply_patch") return false;
|
|
15
|
+
const head = text.trimStart();
|
|
16
|
+
if (head === "") return true;
|
|
17
|
+
return head.startsWith("```") || "```".startsWith(head);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const LOW_SURROGATE_ESCAPE = /^\\u[dD][c-fC-F][0-9a-fA-F]{2}$/;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The decoded prefix of a JSON string body, stopping at the first byte it cannot resolve.
|
|
24
|
+
*
|
|
25
|
+
* Every stop is a hold rather than a guess, because `JSON.parse` decides the completed value
|
|
26
|
+
* and anything invented here would be retracted. Three of them are not obvious:
|
|
27
|
+
*
|
|
28
|
+
* An escape JSON does not define makes the whole wrapper unparseable no matter what arrives
|
|
29
|
+
* next, so completion falls back to the raw text. Returning `null` for that case stops the
|
|
30
|
+
* preview rather than continuing to decode a value the completed item will never carry.
|
|
31
|
+
* `\\b` and `\\f` are defined, and were previously decoded to the letters b and f.
|
|
32
|
+
*
|
|
33
|
+
* A lone high surrogate is not a character. Emitting one alone puts an unpaired code unit in a
|
|
34
|
+
* delta the client has to decode by itself, so the pair is emitted together or not at all.
|
|
35
|
+
*/
|
|
36
|
+
function decodeJsonStringPrefix(body: string): string | null {
|
|
37
|
+
let out = "";
|
|
38
|
+
for (let i = 0; i < body.length; i++) {
|
|
39
|
+
const c = body[i];
|
|
40
|
+
if (c === '"') break; // unescaped closing quote: value complete
|
|
41
|
+
if (c !== "\\") {
|
|
42
|
+
// A literal control character is not legal inside a JSON string, so `JSON.parse` will
|
|
43
|
+
// reject this wrapper and completion will return the raw text. Emitting the decoded value
|
|
44
|
+
// first is the disagreement this decoder exists to prevent, so stop instead.
|
|
45
|
+
if (c!.charCodeAt(0) <= 0x1f) return null;
|
|
46
|
+
out += c;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const n = body[i + 1];
|
|
50
|
+
if (n === undefined) break; // escape split across chunks: wait for more
|
|
51
|
+
if (n === "u") {
|
|
52
|
+
const hex = body.slice(i + 2, i + 6);
|
|
53
|
+
if (hex.length < 4) break; // split across chunks: wait for more
|
|
54
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null; // never parses
|
|
55
|
+
const code = parseInt(hex, 16);
|
|
56
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
57
|
+
const low = body.slice(i + 6, i + 12);
|
|
58
|
+
if (!LOW_SURROGATE_ESCAPE.test(low)) break;
|
|
59
|
+
out += String.fromCharCode(code, parseInt(low.slice(2), 16));
|
|
60
|
+
i += 11;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
out += String.fromCharCode(code);
|
|
64
|
+
i += 5;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const escaped = JSON_ESCAPES.get(n);
|
|
68
|
+
if (escaped === undefined) return null; // never parses
|
|
69
|
+
out += escaped;
|
|
70
|
+
i += 1;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The value to stream so far, or `null` to HOLD because nothing can be decided yet.
|
|
77
|
+
*
|
|
78
|
+
* `input` is decidable from its prefix: `unwrapFreeformToolInput` returns it whenever the
|
|
79
|
+
* key is present, whatever else the object carries, so its value can be unescaped
|
|
80
|
+
* progressively and never retracted.
|
|
81
|
+
*
|
|
82
|
+
* One case escapes that claim and is accepted rather than fixed: a duplicate `input` key.
|
|
83
|
+
* `JSON.parse` keeps the last one, so `{"input":"a","input":"b"}` streams a and completes with
|
|
84
|
+
* b. Closing it means holding every canonical wrapper until its object parses, which is the
|
|
85
|
+
* progressive streaming this path exists to provide. The completed item stays authoritative,
|
|
86
|
+
* and no model emits a duplicate key in practice.
|
|
87
|
+
*
|
|
88
|
+
* A wrapper that turns invalid AFTER streaming has committed has the same shape and the same
|
|
89
|
+
* answer. `{"input":"a` followed by `\\qb"}` has already published a when the undefined escape
|
|
90
|
+
* arrives, and completion returns the raw text because nothing parses. The preview stops
|
|
91
|
+
* there: no rewind, and no decoded text the completed item does not contain. Bounding the
|
|
92
|
+
* damage is what is available without giving up progressive streaming, and the args are
|
|
93
|
+
* unusable in that case whichever representation wins.
|
|
94
|
+
*
|
|
95
|
+
* A fallback key is not decidable. It only unwraps when it is the SINGLE string field, and a
|
|
96
|
+
* second key can still arrive — so a value emitted early would have to be taken back. That is
|
|
97
|
+
* the rewind this holds instead: stream nothing until the object closes, then publish the one
|
|
98
|
+
* repaired body. The routed passthrough in `responses-custom-tool-repair.ts` already holds
|
|
99
|
+
* any object prefix for the same reason (#5047).
|
|
100
|
+
*
|
|
101
|
+
* An object that has not reached a canonical key YET is in exactly that position, and used to
|
|
102
|
+
* be treated as raw because it did not match the literal `{"input":"`. It holds now: `input`
|
|
103
|
+
* can still arrive after other properties, or wearing an escaped spelling, and completion would
|
|
104
|
+
* then unwrap a body whose wrapper syntax had already been published as deltas (#5151). The
|
|
105
|
+
* cost of holding is preview on an object body that turns out not to be a wrapper; the cost of
|
|
106
|
+
* not holding was publishing bytes the completed item removes.
|
|
107
|
+
*/
|
|
108
|
+
export function progressiveFreeformInput(args: string, toolName: string): string | null {
|
|
109
|
+
const scan = scanFreeformWrapper(args);
|
|
110
|
+
if (scan.kind === "input") {
|
|
111
|
+
const decoded = decodeJsonStringPrefix(args.slice(scan.valueStart));
|
|
112
|
+
if (decoded === null) return null;
|
|
113
|
+
return mayBecomeFencedBody(decoded, toolName) ? null : decoded;
|
|
114
|
+
}
|
|
115
|
+
if (scan.kind === "raw") return mayBecomeFencedBody(args, toolName) ? null : args;
|
|
116
|
+
|
|
117
|
+
// Undecided: some wrapper may still apply, and only the completed object says which one.
|
|
118
|
+
// The parse runs exactly where the scan SAW the object close, so it reads a buffer the scan
|
|
119
|
+
// already walked and its cost is bounded by the same clamp. A hold from either limit stays
|
|
120
|
+
// held: an incomplete object has nothing to parse, and re-reading a budget-exhausted buffer
|
|
121
|
+
// on every delta is quadratic work for a delta that would arrive in the same instant as the
|
|
122
|
+
// authoritative completion behind it.
|
|
123
|
+
if (!scan.parse) return null;
|
|
124
|
+
try {
|
|
125
|
+
JSON.parse(args);
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return unwrapFreeformToolInput(args, toolName);
|
|
130
|
+
}
|
|
@@ -63,6 +63,36 @@ export function encodeReasoningEnvelope(envelope: ReasoningEnvelope, budget?: Tr
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Whether a thinking part's `signature` is an attestation some provider issued, rather than
|
|
68
|
+
* the serialized reasoning item the parser parks in the same field.
|
|
69
|
+
*
|
|
70
|
+
* `src/responses/parser.ts` stores `JSON.stringify(reasoningItem)` on an UNSIGNED thinking
|
|
71
|
+
* part so the opaque item survives a same-provider round trip. Every adapter that forwards a
|
|
72
|
+
* signature upstream is forwarding an opaque attestation, and a provider asked to verify our
|
|
73
|
+
* own parser state cannot recognize it. The predicate lives beside the envelope code that owns
|
|
74
|
+
* this field's representation rather than in one adapter, because a private second copy is how
|
|
75
|
+
* the side that writes the field and a side that reads it come to disagree about what it holds.
|
|
76
|
+
*
|
|
77
|
+
* This is a deny-list for exactly that one shape, not a guess at what an attestation looks
|
|
78
|
+
* like. `src/adapters/anthropic.ts` applies a stricter allow-list on top of it, because an
|
|
79
|
+
* Anthropic signature has a known base64 spelling; that shape is a fact about Anthropic's wire
|
|
80
|
+
* and is not assumed of any other provider's token here.
|
|
81
|
+
*/
|
|
82
|
+
export function isProviderIssuedThinkingSignature(
|
|
83
|
+
signature: string | undefined,
|
|
84
|
+
): signature is string {
|
|
85
|
+
if (typeof signature !== "string" || signature.length === 0) return false;
|
|
86
|
+
if (!signature.startsWith("{")) return true;
|
|
87
|
+
try {
|
|
88
|
+
const parsed: unknown = JSON.parse(signature);
|
|
89
|
+
return !parsed || typeof parsed !== "object" || Array.isArray(parsed)
|
|
90
|
+
|| (parsed as { type?: unknown }).type !== "reasoning";
|
|
91
|
+
} catch {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
66
96
|
/** Decode an ocxr1 envelope; returns null for native (OpenAI-encrypted) blobs or garbage. */
|
|
67
97
|
export function decodeReasoningEnvelope(encryptedContent: string, budget?: TranslatorBudget): ReasoningEnvelope | null {
|
|
68
98
|
if (!encryptedContent.startsWith(OCX_REASONING_PREFIX)) return null;
|
package/src/responses/schema.ts
CHANGED
|
@@ -126,10 +126,17 @@ export const toolSchema = z.object({
|
|
|
126
126
|
|
|
127
127
|
const builtinToolSchema = z.object({ type: z.string() }).loose();
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Hosted tool types a client may declare on an inbound Responses request. Exported so the
|
|
131
|
+
* provider-side capability vocabulary in `src/responses/hosted-tool-policy.ts` can be
|
|
132
|
+
* asserted to cover all of them: a gateway must be able to deny anything it can be sent.
|
|
133
|
+
*/
|
|
134
|
+
export const HOSTED_TOOL_TYPES = [
|
|
130
135
|
"web_search", "web_search_preview", "file_search", "computer_use_preview",
|
|
131
136
|
"code_interpreter", "image_generation", "mcp",
|
|
132
|
-
]
|
|
137
|
+
] as const;
|
|
138
|
+
|
|
139
|
+
const hostedToolType = z.enum(HOSTED_TOOL_TYPES);
|
|
133
140
|
|
|
134
141
|
const allowedToolEntrySchema = z.object({ type: z.string(), name: z.string().optional() });
|
|
135
142
|
|
package/src/responses/state.ts
CHANGED
|
@@ -135,11 +135,10 @@ export type ResidentInput = Omit<ResidentResponseState, "kind" | "sizeBytes">;
|
|
|
135
135
|
|
|
136
136
|
export type PreviousResponseReplayFailure = {
|
|
137
137
|
code: "previous_response_not_found";
|
|
138
|
-
reason: "spill_missing" | "spill_corrupt" | "spill_failed" | "spill_too_large";
|
|
138
|
+
reason: "spill_missing" | "spill_corrupt" | "spill_failed" | "spill_too_large" | "scope_mismatch";
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
const states = new Map<string, StoredResponseState>();
|
|
142
|
-
const replayScopeMismatches = new WeakSet<object>();
|
|
143
142
|
let storedResponseBytes = 0;
|
|
144
143
|
let residentResponseBytes = 0;
|
|
145
144
|
let oldestResidentId: string | undefined;
|
|
@@ -1044,11 +1043,6 @@ function normalizedClientThreadId(value: unknown): string | undefined {
|
|
|
1044
1043
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
1045
1044
|
}
|
|
1046
1045
|
|
|
1047
|
-
function withoutPreviousResponseId(request: Record<string, unknown>): Record<string, unknown> {
|
|
1048
|
-
const { previous_response_id: _previousResponseId, ...freshRequest } = request;
|
|
1049
|
-
return freshRequest;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
1046
|
export function expandPreviousResponseInput(body: unknown, clientThreadId?: string): unknown {
|
|
1053
1047
|
if (!body || typeof body !== "object" || Array.isArray(body)) return body;
|
|
1054
1048
|
const request = body as Record<string, unknown>;
|
|
@@ -1068,10 +1062,9 @@ export function expandPreviousResponseInput(body: unknown, clientThreadId?: stri
|
|
|
1068
1062
|
// A Codex task must never inherit another task's continuation, nor a legacy unscoped entry.
|
|
1069
1063
|
// Unscoped callers retain backward-compatible replay only with other unscoped entries.
|
|
1070
1064
|
if (requestThreadId !== storedThreadId) {
|
|
1071
|
-
|
|
1072
|
-
replayScopeMismatches.add(freshRequest);
|
|
1065
|
+
replayFailures.set(request, { code: "previous_response_not_found", reason: "scope_mismatch" });
|
|
1073
1066
|
replayScopeMismatchDrops += 1;
|
|
1074
|
-
return
|
|
1067
|
+
return body;
|
|
1075
1068
|
}
|
|
1076
1069
|
// The client already replayed this history verbatim. Prepending the stored copy would
|
|
1077
1070
|
// double it, and the doubled turn is stored again, so the next turn triples (#1412 saw
|
|
@@ -1136,9 +1129,9 @@ export function copyPreviousResponseReplayProvenance(source: unknown, target: un
|
|
|
1136
1129
|
replayedInputPrefixLengths.set(target, prefixLength);
|
|
1137
1130
|
}
|
|
1138
1131
|
|
|
1139
|
-
/** True when
|
|
1132
|
+
/** True when this exact request could not replay because its task scope did not match. */
|
|
1140
1133
|
export function previousResponseScopeMismatch(body: unknown): boolean {
|
|
1141
|
-
return
|
|
1134
|
+
return previousResponseReplayFailure(body)?.reason === "scope_mismatch";
|
|
1142
1135
|
}
|
|
1143
1136
|
|
|
1144
1137
|
export function previousResponseConversationId(responseId: string | undefined): string | undefined {
|
|
@@ -19,6 +19,21 @@ export function dottedAliasIsUnambiguous(namespace: string, name: string): boole
|
|
|
19
19
|
return !namespace.includes("__") && !name.includes("__");
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Charset the Responses API enforces on a tool-call `name`.
|
|
24
|
+
*
|
|
25
|
+
* A name outside it is not merely rejected for the turn that carries it: Codex stores the item in
|
|
26
|
+
* the conversation, and every later request that replays that history is refused before it runs
|
|
27
|
+
* (`Invalid 'input[N].name': string does not match pattern '^[a-zA-Z0-9_-]+$'`). One relayed item
|
|
28
|
+
* therefore ends the conversation permanently, which is why the emit boundary treats an invalid
|
|
29
|
+
* name as a defect to repair rather than as a name to forward. See #5095.
|
|
30
|
+
*/
|
|
31
|
+
const RESPONSES_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
32
|
+
|
|
33
|
+
export function isSchemaValidResponsesToolName(name: string): boolean {
|
|
34
|
+
return RESPONSES_TOOL_NAME_PATTERN.test(name);
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
export function wireToolInnerName(tool: unknown): string | undefined {
|
|
23
38
|
if (!isPlainObject(tool)) return undefined;
|
|
24
39
|
const nestedFunction = tool.type === "function" && isPlainObject(tool.function)
|
|
@@ -76,4 +91,3 @@ export function collectAmbiguousDottedAliases(specGroups: readonly unknown[]): S
|
|
|
76
91
|
for (const [alias, owner] of owners) if (owner === null) ambiguous.add(alias);
|
|
77
92
|
return ambiguous;
|
|
78
93
|
}
|
|
79
|
-
|