@iislee/opencodex 2.31.0 → 2.35.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 +3 -3
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- package/gui/dist/assets/index-DMiI18Kv.css +0 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
2
|
+
import { normalizeApplyPatchDelimiters } from "../responses/apply-patch-envelope";
|
|
3
|
+
import { compileCodeModeHelperInput } from "../responses/code-mode-helper-compat";
|
|
2
4
|
import {
|
|
3
5
|
customToolItemId,
|
|
4
6
|
restoreRoutedCustomCalls,
|
|
7
|
+
routedCustomToolTargetName,
|
|
5
8
|
routedCustomToolWireName,
|
|
6
9
|
unwrapRoutedCustomToolArguments,
|
|
7
10
|
} from "../responses/custom-tool-compat";
|
|
@@ -85,8 +88,12 @@ type PendingArgumentBlock = {
|
|
|
85
88
|
export function createRoutedCustomToolRestoreBlockRewrite(
|
|
86
89
|
names: ReadonlySet<string>,
|
|
87
90
|
budget?: TranslatorBudget,
|
|
91
|
+
repairNames: ReadonlySet<string> = new Set(),
|
|
92
|
+
declaredNames?: ReadonlySet<string>,
|
|
88
93
|
): SseBlockRewrite {
|
|
89
|
-
const itemNames = new Map<string, string>();
|
|
94
|
+
const itemNames = new Map<string, { name: string; aliased: boolean; namespace?: string }>();
|
|
95
|
+
const customAliasItemNames = new Map<string, string>();
|
|
96
|
+
const repairItemNames = new Map<string, string>();
|
|
90
97
|
const ordinaryItemIds = new Set<string>();
|
|
91
98
|
const openCalls = new Map<string, OpenCustomCall>();
|
|
92
99
|
let pendingArguments: PendingArgumentBlock[] = [];
|
|
@@ -111,6 +118,8 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
111
118
|
}
|
|
112
119
|
pendingArguments = [];
|
|
113
120
|
itemNames.clear();
|
|
121
|
+
customAliasItemNames.clear();
|
|
122
|
+
repairItemNames.clear();
|
|
114
123
|
ordinaryItemIds.clear();
|
|
115
124
|
};
|
|
116
125
|
|
|
@@ -175,6 +184,32 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
175
184
|
&& parsed.output_index >= 0
|
|
176
185
|
? parsed.output_index
|
|
177
186
|
: undefined;
|
|
187
|
+
if (
|
|
188
|
+
(type === "response.output_item.added" || type === "response.output_item.done")
|
|
189
|
+
&& isPlainObject(parsed.item)
|
|
190
|
+
&& parsed.item.type === "custom_tool_call"
|
|
191
|
+
&& typeof parsed.item.name === "string"
|
|
192
|
+
) {
|
|
193
|
+
const upstreamItemId = typeof parsed.item.id === "string" ? parsed.item.id : undefined;
|
|
194
|
+
const wireName = routedCustomToolWireName(parsed.item);
|
|
195
|
+
const targetName = routedCustomToolTargetName(parsed.item, names, declaredNames);
|
|
196
|
+
const aliased = targetName !== undefined && targetName !== wireName;
|
|
197
|
+
if (upstreamItemId && aliased) {
|
|
198
|
+
customAliasItemNames.set(upstreamItemId, parsed.item.name);
|
|
199
|
+
if (type === "response.output_item.added") {
|
|
200
|
+
openCalls.set(upstreamItemId, { argumentsText: "", emittedInput: "", retainedBytes: 0 });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const repairable = wireName !== undefined && repairNames.has(wireName);
|
|
204
|
+
if (upstreamItemId && repairable) repairItemNames.set(upstreamItemId, parsed.item.name);
|
|
205
|
+
const restored = repairable || aliased
|
|
206
|
+
? restoreRoutedCustomCalls(parsed, names, repairNames, declaredNames)
|
|
207
|
+
: { value: parsed, changed: false };
|
|
208
|
+
if (type === "response.output_item.done" && upstreamItemId) releaseCall(upstreamItemId);
|
|
209
|
+
return restored.changed
|
|
210
|
+
? [replaceSseDataPayload(block, JSON.stringify(restored.value))]
|
|
211
|
+
: [block];
|
|
212
|
+
}
|
|
178
213
|
if (
|
|
179
214
|
(type === "response.output_item.added" || type === "response.output_item.done")
|
|
180
215
|
&& isPlainObject(parsed.item)
|
|
@@ -182,11 +217,16 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
182
217
|
&& typeof parsed.item.name === "string"
|
|
183
218
|
) {
|
|
184
219
|
const upstreamItemId = typeof parsed.item.id === "string" ? parsed.item.id : undefined;
|
|
220
|
+
const targetName = routedCustomToolTargetName(parsed.item, names, declaredNames);
|
|
221
|
+
const routed = targetName !== undefined;
|
|
185
222
|
const wireName = routedCustomToolWireName(parsed.item);
|
|
186
|
-
const routed = wireName !== undefined && names.has(wireName);
|
|
187
223
|
if (upstreamItemId) {
|
|
188
224
|
if (routed) {
|
|
189
|
-
itemNames.set(upstreamItemId,
|
|
225
|
+
itemNames.set(upstreamItemId, {
|
|
226
|
+
name: parsed.item.name,
|
|
227
|
+
aliased: targetName !== wireName,
|
|
228
|
+
...(typeof parsed.item.namespace === "string" ? { namespace: parsed.item.namespace } : {}),
|
|
229
|
+
});
|
|
190
230
|
ordinaryItemIds.delete(upstreamItemId);
|
|
191
231
|
} else {
|
|
192
232
|
ordinaryItemIds.add(upstreamItemId);
|
|
@@ -203,7 +243,7 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
203
243
|
if (upstreamItemId && pending.length > 0 && !openCalls.has(upstreamItemId)) {
|
|
204
244
|
openCalls.set(upstreamItemId, { argumentsText: "", emittedInput: "", retainedBytes: 0 });
|
|
205
245
|
}
|
|
206
|
-
const restored = restoreRoutedCustomCalls(parsed, names);
|
|
246
|
+
const restored = restoreRoutedCustomCalls(parsed, names, repairNames, declaredNames);
|
|
207
247
|
const restoredBlock = restored.changed
|
|
208
248
|
? replaceSseDataPayload(block, JSON.stringify(restored.value))
|
|
209
249
|
: block;
|
|
@@ -215,6 +255,44 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
215
255
|
}
|
|
216
256
|
|
|
217
257
|
const upstreamItemId = typeof parsed.item_id === "string" ? parsed.item_id : undefined;
|
|
258
|
+
if (
|
|
259
|
+
type === "response.custom_tool_call_input.delta"
|
|
260
|
+
&& upstreamItemId
|
|
261
|
+
&& customAliasItemNames.has(upstreamItemId)
|
|
262
|
+
) {
|
|
263
|
+
const open = openCalls.get(upstreamItemId) ?? { argumentsText: "", emittedInput: "", retainedBytes: 0 };
|
|
264
|
+
const delta = typeof parsed.delta === "string" ? parsed.delta : "";
|
|
265
|
+
const deltaBytes = Buffer.byteLength(delta, "utf8");
|
|
266
|
+
if (deltaBytes > 0) budget?.chargeRetained(deltaBytes, { kind: "retained_collectors" });
|
|
267
|
+
open.argumentsText += delta;
|
|
268
|
+
open.retainedBytes += deltaBytes;
|
|
269
|
+
openCalls.set(upstreamItemId, open);
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
if (
|
|
273
|
+
type === "response.custom_tool_call_input.done"
|
|
274
|
+
&& upstreamItemId
|
|
275
|
+
&& customAliasItemNames.has(upstreamItemId)
|
|
276
|
+
) {
|
|
277
|
+
const source = typeof parsed.input === "string"
|
|
278
|
+
? parsed.input
|
|
279
|
+
: openCalls.get(upstreamItemId)?.argumentsText ?? "";
|
|
280
|
+
return [replaceSseDataPayload(block, JSON.stringify({
|
|
281
|
+
...parsed,
|
|
282
|
+
input: compileCodeModeHelperInput(source, customAliasItemNames.get(upstreamItemId)!),
|
|
283
|
+
}))];
|
|
284
|
+
}
|
|
285
|
+
if (
|
|
286
|
+
type === "response.custom_tool_call_input.done"
|
|
287
|
+
&& upstreamItemId
|
|
288
|
+
&& repairItemNames.has(upstreamItemId)
|
|
289
|
+
&& typeof parsed.input === "string"
|
|
290
|
+
) {
|
|
291
|
+
const input = normalizeApplyPatchDelimiters(parsed.input);
|
|
292
|
+
if (input !== parsed.input) {
|
|
293
|
+
return [replaceSseDataPayload(block, JSON.stringify({ ...parsed, input }))];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
218
296
|
const argumentEvent = type === "response.function_call_arguments.delta"
|
|
219
297
|
|| type === "response.function_call_arguments.done";
|
|
220
298
|
if (argumentEvent && (!upstreamItemId || (!itemNames.has(upstreamItemId) && !ordinaryItemIds.has(upstreamItemId)))) {
|
|
@@ -261,16 +339,19 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
261
339
|
? parsed.arguments
|
|
262
340
|
: openCalls.get(upstreamItemId)?.argumentsText ?? "";
|
|
263
341
|
const { arguments: _arguments, ...rest } = parsed;
|
|
342
|
+
const itemName = itemNames.get(upstreamItemId);
|
|
264
343
|
const next = {
|
|
265
344
|
...rest,
|
|
266
345
|
type: nextType,
|
|
267
346
|
item_id: customToolItemId(upstreamItemId),
|
|
268
|
-
input:
|
|
347
|
+
input: itemName?.aliased
|
|
348
|
+
? compileCodeModeHelperInput(source, itemName.name)
|
|
349
|
+
: unwrapRoutedCustomToolArguments(source, itemName?.name ?? "", itemName?.namespace),
|
|
269
350
|
};
|
|
270
351
|
return [replaceSseDataPayload(replaceSseEventName(block, nextType), JSON.stringify(next))];
|
|
271
352
|
}
|
|
272
353
|
|
|
273
|
-
const restored = restoreRoutedCustomCalls(parsed, names);
|
|
354
|
+
const restored = restoreRoutedCustomCalls(parsed, names, repairNames, declaredNames);
|
|
274
355
|
const terminal = type === "response.completed" || type === "response.failed" || type === "response.incomplete";
|
|
275
356
|
if (terminal) releaseAll();
|
|
276
357
|
return restored.changed
|
|
@@ -22,6 +22,22 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
|
22
22
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function isUnframedTerminalLikeSuffix(block: string): boolean {
|
|
26
|
+
const payload = sseDataPayload(block);
|
|
27
|
+
if (payload === "[DONE]") return true;
|
|
28
|
+
if (!payload) return false;
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(payload);
|
|
31
|
+
if (!isPlainRecord(parsed)) return false;
|
|
32
|
+
return parsed.type === "response.completed"
|
|
33
|
+
|| parsed.type === "response.failed"
|
|
34
|
+
|| parsed.type === "response.incomplete"
|
|
35
|
+
|| parsed.type === "error";
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
function outputIndex(value: unknown): number | null {
|
|
26
42
|
return Number.isInteger(value) && (value as number) >= 0 ? value as number : null;
|
|
27
43
|
}
|
|
@@ -292,11 +308,16 @@ export function relayResponsesSseWithTerminalRepair(
|
|
|
292
308
|
if (done) {
|
|
293
309
|
appendBuffer(decoder.decode());
|
|
294
310
|
if (buffer.length > 0) {
|
|
295
|
-
// A delimiter-less suffix is not a complete SSE event. Preserve
|
|
296
|
-
//
|
|
297
|
-
//
|
|
311
|
+
// A delimiter-less suffix is not a complete SSE event. Preserve an
|
|
312
|
+
// ordinary suffix for passthrough compatibility, but never promote
|
|
313
|
+
// a terminal-like suffix by adding the delimiter it did not receive
|
|
314
|
+
// upstream. The latter must stay tainted and fail closed through the
|
|
315
|
+
// synthetic incomplete terminal below.
|
|
298
316
|
tainted = true;
|
|
299
|
-
|
|
317
|
+
if (!isUnframedTerminalLikeSuffix(buffer)) {
|
|
318
|
+
controller.enqueue(encoder.encode(buffer));
|
|
319
|
+
controller.enqueue(encoder.encode(buffer.includes("\r\n") ? "\r\n\r\n" : "\n\n"));
|
|
320
|
+
}
|
|
300
321
|
}
|
|
301
322
|
if (!realTerminalSeen) {
|
|
302
323
|
emitSynthetic(completeCandidate() ? "completed" : "incomplete", controller);
|
|
@@ -1,12 +1,63 @@
|
|
|
1
|
-
import { namespacedToolName } from "../types";
|
|
1
|
+
import { namespacedToolName, normalizeDeclaredToolName } from "../types";
|
|
2
2
|
import { sseDataPayload, type SseBlockRewrite } from "./sse-payload-rewrite";
|
|
3
3
|
|
|
4
|
+
/** Item types the client executes through a request-declared wire name. */
|
|
5
|
+
const CLIENT_EXECUTED_CALL_TYPES = new Set(["function_call", "custom_tool_call"]);
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
+
* Hosted declarations whose response items the PROVIDER executes, keyed by the request
|
|
9
|
+
* declaration type. These need no client answer, so their names are deliberately absent from
|
|
10
|
+
* the request catalog and must not be read as an undeclared client tool.
|
|
11
|
+
*
|
|
12
|
+
* xAI surfaces hosted `x_search` as `custom_tool_call`. Probed 2026-08-23 against the OAuth CLI
|
|
13
|
+
* destination: its hosted calls use an `xs_call-` call-id prefix. Observed call names were
|
|
14
|
+
* `x_keyword_search`, `x_semantic_search`, and `x_user_search` — three literals for one tool,
|
|
15
|
+
* which is why authorization keys on the declaration, item type, and call-id prefix, never on
|
|
16
|
+
* the name.
|
|
8
17
|
*/
|
|
9
|
-
|
|
18
|
+
export type ProviderExecutedCallType = Readonly<{
|
|
19
|
+
itemType: string;
|
|
20
|
+
callIdPrefix: string;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
type ProviderExecutedCallTypes = ReadonlySet<ProviderExecutedCallType>;
|
|
24
|
+
|
|
25
|
+
export const PROVIDER_EXECUTED_DECLARATION_CALL_TYPES = new Map<string, ProviderExecutedCallType>([
|
|
26
|
+
["x_search", { itemType: "custom_tool_call", callIdPrefix: "xs_call-" }],
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/** Nameless declaration kinds whose response items still require client execution. */
|
|
30
|
+
const NAMELESS_CLIENT_DECLARATION_CALL_TYPES = new Map([
|
|
31
|
+
["local_shell", "local_shell_call"],
|
|
32
|
+
["tool_search", "tool_search_call"],
|
|
33
|
+
["computer_use_preview", "computer_call"],
|
|
34
|
+
["computer_use", "computer_call"],
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const NAMELESS_CLIENT_CALL_DISPLAY_NAMES = new Map([
|
|
38
|
+
["local_shell_call", "local_shell"],
|
|
39
|
+
["tool_search_call", "tool_search"],
|
|
40
|
+
["computer_call", "computer_use"],
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES: ReadonlySet<string> = new Set();
|
|
44
|
+
const EMPTY_PROVIDER_EXECUTED_CALL_TYPES: ReadonlySet<ProviderExecutedCallType> = new Set();
|
|
45
|
+
|
|
46
|
+
/** Supported hosted/private declarations that carry no client-executable wire name. */
|
|
47
|
+
const NAMELESS_TOOL_SPEC_TYPES = new Set([
|
|
48
|
+
"web_search",
|
|
49
|
+
"web_search_preview",
|
|
50
|
+
"file_search",
|
|
51
|
+
"computer_use_preview",
|
|
52
|
+
"computer_use",
|
|
53
|
+
"code_interpreter",
|
|
54
|
+
"image_generation",
|
|
55
|
+
"image_gen",
|
|
56
|
+
"mcp",
|
|
57
|
+
"tool_search",
|
|
58
|
+
"local_shell",
|
|
59
|
+
"x_search",
|
|
60
|
+
]);
|
|
10
61
|
|
|
11
62
|
/** An upstream-supplied name reaches the error message; keep it bounded. */
|
|
12
63
|
const MAX_REPORTED_NAME_CHARS = 100;
|
|
@@ -18,12 +69,40 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
|
18
69
|
}
|
|
19
70
|
|
|
20
71
|
function addWireToolName(names: Set<string>, tool: unknown, namespace?: string): void {
|
|
21
|
-
if (!isPlainObject(tool)
|
|
22
|
-
|
|
72
|
+
if (!isPlainObject(tool)) return;
|
|
73
|
+
const nestedFunction = tool.type === "function" && isPlainObject(tool.function)
|
|
74
|
+
? tool.function
|
|
75
|
+
: undefined;
|
|
76
|
+
const name = typeof tool.name === "string" && tool.name.length > 0
|
|
77
|
+
? tool.name
|
|
78
|
+
: typeof nestedFunction?.name === "string" && nestedFunction.name.length > 0
|
|
79
|
+
? nestedFunction.name
|
|
80
|
+
: undefined;
|
|
81
|
+
if (!name) return;
|
|
82
|
+
names.add(name);
|
|
23
83
|
// Codex routes MCP calls by an explicit `namespace` field, so the same tool is reachable
|
|
24
84
|
// as a bare inner name or as the flattened form; accept both rather than guess which
|
|
25
85
|
// coordinate system this provider echoes back.
|
|
26
|
-
if (namespace) names.add(namespacedToolName(namespace,
|
|
86
|
+
if (namespace) names.add(namespacedToolName(namespace, name));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Catalog view owned by the current Responses turn.
|
|
91
|
+
*
|
|
92
|
+
* `previous_response_id` expansion prepends stored input items, including historical
|
|
93
|
+
* `additional_tools` declarations. Those items remain conversation history but cannot grant
|
|
94
|
+
* execution authority to this turn. Top-level `tools` always belongs to the current request;
|
|
95
|
+
* only input catalogs at or after the replay boundary are current.
|
|
96
|
+
*/
|
|
97
|
+
export function currentTurnWireToolCatalogBody(
|
|
98
|
+
body: unknown,
|
|
99
|
+
replayPrefixLength: number | undefined,
|
|
100
|
+
): unknown {
|
|
101
|
+
if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
|
|
102
|
+
if (typeof replayPrefixLength !== "number" || !Number.isFinite(replayPrefixLength)) return body;
|
|
103
|
+
const start = Math.min(body.input.length, Math.max(0, Math.trunc(replayPrefixLength)));
|
|
104
|
+
if (start === 0) return body;
|
|
105
|
+
return { ...body, input: body.input.slice(start) };
|
|
27
106
|
}
|
|
28
107
|
|
|
29
108
|
function addWireToolSpecs(names: Set<string>, specs: unknown): void {
|
|
@@ -53,21 +132,159 @@ export function collectDeclaredWireToolNames(body: unknown): Set<string> {
|
|
|
53
132
|
addWireToolSpecs(names, body.tools);
|
|
54
133
|
if (Array.isArray(body.input)) {
|
|
55
134
|
for (const item of body.input) {
|
|
56
|
-
if (
|
|
135
|
+
if (
|
|
136
|
+
isPlainObject(item)
|
|
137
|
+
&& (item.type === "additional_tools" || item.type === "tool_search_output")
|
|
138
|
+
) addWireToolSpecs(names, item.tools);
|
|
57
139
|
}
|
|
58
140
|
}
|
|
59
141
|
return names;
|
|
60
142
|
}
|
|
61
143
|
|
|
62
|
-
function
|
|
144
|
+
function addNamelessClientCallTypes(callTypes: Set<string>, specs: unknown): void {
|
|
145
|
+
if (!Array.isArray(specs)) return;
|
|
146
|
+
for (const spec of specs) {
|
|
147
|
+
if (!isPlainObject(spec) || typeof spec.type !== "string") continue;
|
|
148
|
+
const callType = NAMELESS_CLIENT_DECLARATION_CALL_TYPES.get(spec.type);
|
|
149
|
+
if (callType) callTypes.add(callType);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function addProviderExecutedCallTypes(
|
|
154
|
+
callTypes: Set<ProviderExecutedCallType>,
|
|
155
|
+
specs: unknown,
|
|
156
|
+
): void {
|
|
157
|
+
if (!Array.isArray(specs)) return;
|
|
158
|
+
for (const spec of specs) {
|
|
159
|
+
if (!isPlainObject(spec) || typeof spec.type !== "string") continue;
|
|
160
|
+
const callType = PROVIDER_EXECUTED_DECLARATION_CALL_TYPES.get(spec.type);
|
|
161
|
+
if (callType) callTypes.add(callType);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Item types this turn's hosted declarations authorize the PROVIDER to emit unnamed.
|
|
167
|
+
*
|
|
168
|
+
* Caller must gate this on the destination actually being that provider; a declaration alone
|
|
169
|
+
* is not authority, or any upstream could claim a hosted shape it never serves.
|
|
170
|
+
*/
|
|
171
|
+
export function collectProviderExecutedCallTypes(body: unknown): Set<ProviderExecutedCallType> {
|
|
172
|
+
const callTypes = new Set<ProviderExecutedCallType>();
|
|
173
|
+
if (!isPlainObject(body)) return callTypes;
|
|
174
|
+
addProviderExecutedCallTypes(callTypes, body.tools);
|
|
175
|
+
if (Array.isArray(body.input)) {
|
|
176
|
+
for (const item of body.input) {
|
|
177
|
+
if (
|
|
178
|
+
isPlainObject(item)
|
|
179
|
+
&& (item.type === "additional_tools" || item.type === "tool_search_output")
|
|
180
|
+
) addProviderExecutedCallTypes(callTypes, item.tools);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return callTypes;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isAuthorizedProviderExecutedCall(
|
|
187
|
+
item: Record<string, unknown>,
|
|
188
|
+
callTypes: ProviderExecutedCallTypes,
|
|
189
|
+
): boolean {
|
|
190
|
+
if (typeof item.call_id !== "string") return false;
|
|
191
|
+
for (const callType of callTypes) {
|
|
192
|
+
if (
|
|
193
|
+
item.type === callType.itemType
|
|
194
|
+
&& item.call_id.startsWith(callType.callIdPrefix)
|
|
195
|
+
) return true;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Nameless client-call item types authorized by supported request tool declarations. */
|
|
201
|
+
export function collectDeclaredNamelessClientCallTypes(body: unknown): Set<string> {
|
|
202
|
+
const callTypes = new Set<string>();
|
|
203
|
+
if (!isPlainObject(body)) return callTypes;
|
|
204
|
+
addNamelessClientCallTypes(callTypes, body.tools);
|
|
205
|
+
if (Array.isArray(body.input)) {
|
|
206
|
+
for (const item of body.input) {
|
|
207
|
+
if (
|
|
208
|
+
isPlainObject(item)
|
|
209
|
+
&& (item.type === "additional_tools" || item.type === "tool_search_output")
|
|
210
|
+
) {
|
|
211
|
+
addNamelessClientCallTypes(callTypes, item.tools);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return callTypes;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function isReadableWireToolSpec(spec: unknown): boolean {
|
|
219
|
+
if (!isPlainObject(spec) || typeof spec.type !== "string" || spec.type.length === 0) return false;
|
|
220
|
+
if (spec.type === "function") {
|
|
221
|
+
return (typeof spec.name === "string" && spec.name.length > 0)
|
|
222
|
+
|| (isPlainObject(spec.function)
|
|
223
|
+
&& typeof spec.function.name === "string"
|
|
224
|
+
&& spec.function.name.length > 0);
|
|
225
|
+
}
|
|
226
|
+
if (spec.type === "custom") return typeof spec.name === "string" && spec.name.length > 0;
|
|
227
|
+
if (spec.type === "namespace") {
|
|
228
|
+
return typeof spec.name === "string"
|
|
229
|
+
&& spec.name.length > 0
|
|
230
|
+
&& Array.isArray(spec.tools)
|
|
231
|
+
&& (spec.tools.length === 0 || spec.tools.some(inner =>
|
|
232
|
+
isPlainObject(inner)
|
|
233
|
+
&& (inner.type === "function" || inner.type === "custom")
|
|
234
|
+
&& typeof inner.name === "string"
|
|
235
|
+
&& inner.name.length > 0
|
|
236
|
+
));
|
|
237
|
+
}
|
|
238
|
+
if (NAMELESS_TOOL_SPEC_TYPES.has(spec.type)) return true;
|
|
239
|
+
return typeof spec.name === "string" && spec.name.length > 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function isReadableWireToolCatalog(value: unknown): boolean {
|
|
243
|
+
return Array.isArray(value)
|
|
244
|
+
&& (value.length === 0 || value.some(isReadableWireToolSpec));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Whether a request contains a supported catalog, including an explicit empty deny-all array. */
|
|
248
|
+
export function hasExplicitWireToolCatalog(body: unknown): boolean {
|
|
249
|
+
if (!isPlainObject(body)) return false;
|
|
250
|
+
if (isReadableWireToolCatalog(body.tools)) return true;
|
|
251
|
+
if (!Array.isArray(body.input)) return false;
|
|
252
|
+
return body.input.some(item =>
|
|
253
|
+
isPlainObject(item)
|
|
254
|
+
&& item.type === "additional_tools"
|
|
255
|
+
&& isReadableWireToolCatalog(item.tools)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function undeclaredNameInItem(
|
|
260
|
+
item: unknown,
|
|
261
|
+
declared: ReadonlySet<string>,
|
|
262
|
+
declaredNamelessClientCallTypes: ReadonlySet<string>,
|
|
263
|
+
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
264
|
+
): string | undefined {
|
|
63
265
|
if (!isPlainObject(item)) return undefined;
|
|
64
|
-
if (typeof item.type !== "string"
|
|
266
|
+
if (typeof item.type !== "string") return undefined;
|
|
267
|
+
// The provider executes this exact measured shape itself, so there is no client name to
|
|
268
|
+
// authorize. The caller supplies these signatures only for the matching destination and
|
|
269
|
+
// declarations; the item must additionally carry the hosted call-id prefix.
|
|
270
|
+
if (isAuthorizedProviderExecutedCall(item, providerExecutedCallTypes)) return undefined;
|
|
271
|
+
const namelessDisplayName = NAMELESS_CLIENT_CALL_DISPLAY_NAMES.get(item.type);
|
|
272
|
+
if (namelessDisplayName !== undefined) {
|
|
273
|
+
// Only Codex's explicit `execution: "client"` form delegates tool search to the client.
|
|
274
|
+
if (item.type === "tool_search_call" && item.execution !== "client") return undefined;
|
|
275
|
+
return declaredNamelessClientCallTypes.has(item.type) ? undefined : namelessDisplayName;
|
|
276
|
+
}
|
|
277
|
+
if (!CLIENT_EXECUTED_CALL_TYPES.has(item.type)) return undefined;
|
|
65
278
|
const name = item.name;
|
|
66
279
|
if (typeof name !== "string" || name.length === 0) return undefined;
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
280
|
+
if (typeof item.namespace === "string") {
|
|
281
|
+
// Namespaced calls are matched by their full wire name only — never legacy-normalize
|
|
282
|
+
// them, or an undeclared namespaced `exec_command` could slip through as bare `exec`.
|
|
283
|
+
if (declared.has(namespacedToolName(item.namespace, name))) return undefined;
|
|
284
|
+
return name;
|
|
70
285
|
}
|
|
286
|
+
const effectiveName = normalizeDeclaredToolName(name, declared);
|
|
287
|
+
if (declared.has(effectiveName)) return undefined;
|
|
71
288
|
return name;
|
|
72
289
|
}
|
|
73
290
|
|
|
@@ -75,14 +292,16 @@ function undeclaredNameInItem(item: unknown, declared: ReadonlySet<string>): str
|
|
|
75
292
|
export function undeclaredToolCallName(
|
|
76
293
|
payload: unknown,
|
|
77
294
|
declared: ReadonlySet<string>,
|
|
295
|
+
declaredNamelessClientCallTypes: ReadonlySet<string> = EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES,
|
|
296
|
+
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
78
297
|
): string | undefined {
|
|
79
298
|
if (!isPlainObject(payload)) return undefined;
|
|
80
299
|
if (payload.type === "response.output_item.added" || payload.type === "response.output_item.done") {
|
|
81
|
-
return undeclaredNameInItem(payload.item, declared);
|
|
300
|
+
return undeclaredNameInItem(payload.item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes);
|
|
82
301
|
}
|
|
83
302
|
// Sparse gateways skip incremental items and only ever ship the terminal snapshot.
|
|
84
303
|
if (payload.type === "response.completed" || payload.type === "response.incomplete") {
|
|
85
|
-
return undeclaredToolCallNameInResponse(payload.response, declared);
|
|
304
|
+
return undeclaredToolCallNameInResponse(payload.response, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes);
|
|
86
305
|
}
|
|
87
306
|
return undefined;
|
|
88
307
|
}
|
|
@@ -91,10 +310,12 @@ export function undeclaredToolCallName(
|
|
|
91
310
|
export function undeclaredToolCallNameInResponse(
|
|
92
311
|
response: unknown,
|
|
93
312
|
declared: ReadonlySet<string>,
|
|
313
|
+
declaredNamelessClientCallTypes: ReadonlySet<string> = EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES,
|
|
314
|
+
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
94
315
|
): string | undefined {
|
|
95
316
|
if (!isPlainObject(response) || !Array.isArray(response.output)) return undefined;
|
|
96
317
|
for (const item of response.output) {
|
|
97
|
-
const name = undeclaredNameInItem(item, declared);
|
|
318
|
+
const name = undeclaredNameInItem(item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes);
|
|
98
319
|
if (name !== undefined) return name;
|
|
99
320
|
}
|
|
100
321
|
return undefined;
|
|
@@ -133,6 +354,8 @@ function failedBlocks(name: string, newline: string): readonly string[] {
|
|
|
133
354
|
*/
|
|
134
355
|
export function createUndeclaredToolCallGuardBlockRewrite(
|
|
135
356
|
declared: ReadonlySet<string>,
|
|
357
|
+
declaredNamelessClientCallTypes: ReadonlySet<string> = EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES,
|
|
358
|
+
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
136
359
|
): SseBlockRewrite {
|
|
137
360
|
let tripped = false;
|
|
138
361
|
return (block: string) => {
|
|
@@ -145,7 +368,7 @@ export function createUndeclaredToolCallGuardBlockRewrite(
|
|
|
145
368
|
} catch {
|
|
146
369
|
return [block];
|
|
147
370
|
}
|
|
148
|
-
const name = undeclaredToolCallName(parsed, declared);
|
|
371
|
+
const name = undeclaredToolCallName(parsed, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes);
|
|
149
372
|
if (name === undefined) return [block];
|
|
150
373
|
tripped = true;
|
|
151
374
|
return failedBlocks(name, block.includes("\r\n") ? "\r\n" : "\n");
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isCyberPolicyCode, isCyberPolicyMessage } from "../lib/errors";
|
|
2
|
+
|
|
1
3
|
export const MAX_CLIENT_SSE_FRAME_BYTES = 4 * 1024 * 1024;
|
|
2
4
|
|
|
3
5
|
const LF_LF = Uint8Array.of(10, 10);
|
|
@@ -106,10 +108,35 @@ function isResponsesTerminalFrame(block: Uint8Array): boolean {
|
|
|
106
108
|
const payload = data.join("\n");
|
|
107
109
|
if (payload === "[DONE]") return false;
|
|
108
110
|
try {
|
|
109
|
-
const parsed = JSON.parse(payload) as {
|
|
110
|
-
|
|
111
|
+
const parsed = JSON.parse(payload) as {
|
|
112
|
+
type?: unknown;
|
|
113
|
+
code?: unknown;
|
|
114
|
+
message?: unknown;
|
|
115
|
+
error?: unknown;
|
|
116
|
+
last_error?: unknown;
|
|
117
|
+
response?: { error?: unknown; incomplete_details?: unknown };
|
|
118
|
+
};
|
|
119
|
+
if (parsed.type === "response.completed"
|
|
111
120
|
|| parsed.type === "response.failed"
|
|
112
|
-
|| parsed.type === "response.incomplete";
|
|
121
|
+
|| parsed.type === "response.incomplete") return true;
|
|
122
|
+
if (parsed.type !== "error") return false;
|
|
123
|
+
// A top-level error is terminal for this boundary only when it carries the
|
|
124
|
+
// same high-confidence cyber-policy evidence used by relay.ts. Ordinary
|
|
125
|
+
// upstream errors remain transport failures and still become a bounded 502.
|
|
126
|
+
const candidates = [
|
|
127
|
+
parsed,
|
|
128
|
+
parsed.error,
|
|
129
|
+
parsed.last_error,
|
|
130
|
+
parsed.response?.error,
|
|
131
|
+
parsed.response?.incomplete_details,
|
|
132
|
+
];
|
|
133
|
+
for (const candidate of candidates) {
|
|
134
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
|
|
135
|
+
const record = candidate as { code?: unknown; message?: unknown };
|
|
136
|
+
if (isCyberPolicyCode(typeof record.code === "string" ? record.code : undefined)) return true;
|
|
137
|
+
if (typeof record.message === "string" && isCyberPolicyMessage(record.message)) return true;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
113
140
|
} catch {
|
|
114
141
|
return false;
|
|
115
142
|
}
|
|
@@ -289,4 +316,4 @@ export function joinSseFrameBytes(parts: readonly Uint8Array[]): Uint8Array {
|
|
|
289
316
|
offset += part.byteLength;
|
|
290
317
|
}
|
|
291
318
|
return joined;
|
|
292
|
-
}
|
|
319
|
+
}
|
package/src/server/ws-bridge.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface WsData {
|
|
|
34
34
|
authContext?: CodexAuthContext; // last resolved account decision for observability/registry cleanup
|
|
35
35
|
cancel?: () => void; // cancels the in-flight stream reader/fetch
|
|
36
36
|
turnId?: number; // monotonically increasing per socket; prevents stale frames after replacement turns
|
|
37
|
+
/** Fixed-size logical session lane derived at the HTTP upgrade boundary. */
|
|
38
|
+
sessionLaneId?: string;
|
|
37
39
|
/** Discriminator: Responses reframing vs transparent live/realtime sideband relay. */
|
|
38
40
|
kind?: "responses" | "live-sideband";
|
|
39
41
|
liveUpstream?: WebSocket;
|
|
@@ -60,10 +62,20 @@ export interface WsData {
|
|
|
60
62
|
* A test that only asserts "the socket opened" would still pass if the admission
|
|
61
63
|
* were dropped from the payload, so the payload itself is what gets asserted.
|
|
62
64
|
*/
|
|
63
|
-
export function buildResponsesWsData(
|
|
65
|
+
export function buildResponsesWsData(
|
|
66
|
+
headers: Headers,
|
|
67
|
+
admission: DataPlaneAdmission,
|
|
68
|
+
admissionLease?: AdmissionReservation<ServerWebSocket<WsData>>,
|
|
69
|
+
sessionLaneId?: string,
|
|
70
|
+
): WsData {
|
|
64
71
|
// Auth is handshake-time only on this path: the per-frame contexts have no
|
|
65
72
|
// request headers left to re-resolve from, so the decision rides along here.
|
|
66
|
-
return {
|
|
73
|
+
return {
|
|
74
|
+
headers,
|
|
75
|
+
admission,
|
|
76
|
+
...(admissionLease ? { admissionLease } : {}),
|
|
77
|
+
...(sessionLaneId ? { sessionLaneId } : {}),
|
|
78
|
+
};
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
export class WsSendDroppedError extends Error {
|