@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
package/src/bridge.ts
CHANGED
|
@@ -7,8 +7,18 @@ import type {
|
|
|
7
7
|
OcxUsage,
|
|
8
8
|
} from "./types";
|
|
9
9
|
import { coerceIntegerToolArguments } from "./lib/tool-argument-integers";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
adapterFailureFromMessage,
|
|
12
|
+
classifyError,
|
|
13
|
+
cyberPolicyErrorType,
|
|
14
|
+
CYBER_POLICY_ERROR_CODE,
|
|
15
|
+
isCyberPolicyCode,
|
|
16
|
+
type OcxErrorPayload,
|
|
17
|
+
} from "./lib/errors";
|
|
18
|
+
import { redactSecretString } from "./lib/redact";
|
|
19
|
+
import { repairFreeformToolInput } from "./responses/apply-patch-envelope";
|
|
11
20
|
import { encodeCompactionSummary } from "./responses/compaction";
|
|
21
|
+
import { compileCodeModeHelperInput } from "./responses/code-mode-helper-compat";
|
|
12
22
|
import { isTruncatedStopReason, truncationReasonFor } from "./responses/truncated-stop-reason";
|
|
13
23
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
|
|
14
24
|
import { rememberReasoningForCall } from "./responses/reasoning-replay-cache";
|
|
@@ -18,6 +28,7 @@ import {
|
|
|
18
28
|
awaitThoughtSignatureDurability,
|
|
19
29
|
} from "./responses/thought-signature-replay";
|
|
20
30
|
import { resolveStallTimeoutSec } from "./stall-timeout";
|
|
31
|
+
import { normalizeDeclaredToolName } from "./types";
|
|
21
32
|
import { usageDisplayTotalTokens } from "./usage/totals";
|
|
22
33
|
import { appendSafeWebSearchSource, safeWebSearchSources } from "./web-search/sources";
|
|
23
34
|
import {
|
|
@@ -112,18 +123,19 @@ function toolCallArgumentsUsable(args: string): boolean {
|
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>): { httpStatus: number; error: OcxErrorPayload } {
|
|
126
|
+
const message = redactSecretString(event.message);
|
|
115
127
|
if (event.status === undefined && event.errorType === undefined && event.code === undefined) {
|
|
116
|
-
return adapterFailureFromMessage(
|
|
128
|
+
return adapterFailureFromMessage(message);
|
|
117
129
|
}
|
|
118
|
-
const fallback = adapterFailureFromMessage(
|
|
130
|
+
const fallback = adapterFailureFromMessage(message);
|
|
119
131
|
let httpStatus = event.status ?? fallback.httpStatus;
|
|
120
|
-
const error = classifyError(httpStatus, event.errorType ?? fallback.error.type,
|
|
132
|
+
const error = classifyError(httpStatus, event.errorType ?? fallback.error.type, message);
|
|
121
133
|
if (event.errorType !== undefined) error.type = event.errorType;
|
|
122
134
|
if (event.code !== undefined) error.code = event.code;
|
|
123
135
|
// Codex maps cyber_policy on HTTP 400 (body) or mid-stream code; never leave it as 502.
|
|
124
136
|
if (isCyberPolicyCode(error.code) || isCyberPolicyCode(event.code)) {
|
|
125
137
|
error.code = CYBER_POLICY_ERROR_CODE;
|
|
126
|
-
error.type =
|
|
138
|
+
error.type = cyberPolicyErrorType(event.errorType);
|
|
127
139
|
httpStatus = 400;
|
|
128
140
|
}
|
|
129
141
|
return { httpStatus, error };
|
|
@@ -230,12 +242,17 @@ export function bridgeToResponsesSSE(
|
|
|
230
242
|
const replayCacheScope = options?.replayCacheScope;
|
|
231
243
|
const setBeatInterval = options?.timers?.setInterval ?? ((handler: () => void, ms: number) => setInterval(handler, ms));
|
|
232
244
|
const clearBeatInterval = options?.timers?.clearInterval ?? ((id: unknown) => clearInterval(id as ReturnType<typeof setInterval>));
|
|
233
|
-
// Freeform/custom tools (apply_patch) carry their body in `input`; the
|
|
234
|
-
// function with `{input:string}`, so unwrap it here when relaying back
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
245
|
+
// Freeform/custom tools (apply_patch, code-mode exec) carry their body in `input`; the
|
|
246
|
+
// model is given a function with `{input:string}`, so unwrap it here when relaying back
|
|
247
|
+
// as a custom_tool_call. Decorated apply_patch envelopes are repaired at this boundary.
|
|
248
|
+
const freeformInput = (
|
|
249
|
+
args: string,
|
|
250
|
+
toolName: string,
|
|
251
|
+
namespace?: string,
|
|
252
|
+
codeModeHelperName?: string,
|
|
253
|
+
): string => codeModeHelperName
|
|
254
|
+
? compileCodeModeHelperInput(args, codeModeHelperName)
|
|
255
|
+
: repairFreeformToolInput(args, toolName, namespace);
|
|
239
256
|
// Best-effort unwrap of a PARTIAL freeform arg buffer for live input streaming
|
|
240
257
|
// (`response.custom_tool_call_input.delta` — codex-rs uses it for UI preview only;
|
|
241
258
|
// the completed custom_tool_call item stays authoritative). Compact `{"input":"...`
|
|
@@ -519,7 +536,7 @@ export function bridgeToResponsesSSE(
|
|
|
519
536
|
// synthetic compaction item's payload on done.
|
|
520
537
|
let compactionText = "";
|
|
521
538
|
let compactionTextBytes = 0;
|
|
522
|
-
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
|
|
539
|
+
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string; codeModeHelperName?: string; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
|
|
523
540
|
// Open native web-search cell (between begin and end). Holds the output index allocated on
|
|
524
541
|
// begin so the matching done reuses it; closed as `failed` if the stream terminates early.
|
|
525
542
|
let currentWebSearch: { itemId: string; eventId: string; outputIndex: number } | null = null;
|
|
@@ -626,6 +643,7 @@ export function bridgeToResponsesSSE(
|
|
|
626
643
|
const argsStr = coerceIntegerToolArguments(
|
|
627
644
|
currentToolCall.args || "{}",
|
|
628
645
|
options?.toolParameterSchemas?.get(currentToolCall.name),
|
|
646
|
+
currentToolCall.namespace === undefined ? currentToolCall.name : undefined,
|
|
629
647
|
);
|
|
630
648
|
// Finalize streamed function-call arguments so Codex commits the call (incl. MCP / computer_use).
|
|
631
649
|
if (!currentToolCall.freeform && !currentToolCall.toolSearch) {
|
|
@@ -636,7 +654,8 @@ export function bridgeToResponsesSSE(
|
|
|
636
654
|
if (currentToolCall.freeform) {
|
|
637
655
|
emit("response.custom_tool_call_input.done", {
|
|
638
656
|
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
|
|
639
|
-
|
|
657
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
658
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName),
|
|
640
659
|
});
|
|
641
660
|
}
|
|
642
661
|
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
@@ -652,7 +671,8 @@ export function bridgeToResponsesSSE(
|
|
|
652
671
|
? {
|
|
653
672
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
654
673
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
655
|
-
|
|
674
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
675
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "completed",
|
|
656
676
|
}
|
|
657
677
|
: {
|
|
658
678
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -691,7 +711,8 @@ export function bridgeToResponsesSSE(
|
|
|
691
711
|
? {
|
|
692
712
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
693
713
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
694
|
-
|
|
714
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
715
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "incomplete",
|
|
695
716
|
}
|
|
696
717
|
: {
|
|
697
718
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -1036,13 +1057,17 @@ export function bridgeToResponsesSSE(
|
|
|
1036
1057
|
rememberReasoningForCall(event.id, rawReasoningForNextToolCall, replayCacheScope);
|
|
1037
1058
|
}
|
|
1038
1059
|
if (currentToolCall) closeCurrentToolCall();
|
|
1039
|
-
const
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1060
|
+
const effectiveName = normalizeDeclaredToolName(event.name, options?.declaredToolNames);
|
|
1061
|
+
const codeModeHelperName = effectiveName === "exec" && event.name !== effectiveName
|
|
1062
|
+
? event.name
|
|
1063
|
+
: undefined;
|
|
1064
|
+
const mapped = toolNsMap?.get(effectiveName);
|
|
1065
|
+
const realName = mapped?.name ?? effectiveName;
|
|
1066
|
+
if (options?.declaredToolNames && !options.declaredToolNames.has(effectiveName)) {
|
|
1042
1067
|
const failure = responseError(
|
|
1043
1068
|
502,
|
|
1044
1069
|
"upstream_error",
|
|
1045
|
-
`routed provider emitted undeclared client tool "${
|
|
1070
|
+
`routed provider emitted undeclared client tool "${effectiveName}"; only request-declared tools may be called`,
|
|
1046
1071
|
);
|
|
1047
1072
|
emit("response.failed", {
|
|
1048
1073
|
response: {
|
|
@@ -1064,10 +1089,10 @@ export function bridgeToResponsesSSE(
|
|
|
1064
1089
|
const item = toolSearch
|
|
1065
1090
|
? { type: "tool_search_call", id: itemId, call_id: event.id, execution: "client", arguments: {}, status: "in_progress" }
|
|
1066
1091
|
: freeform
|
|
1067
|
-
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, input: "", status: "in_progress" }
|
|
1092
|
+
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, ...(ns ? { namespace: ns } : {}), input: "", status: "in_progress" }
|
|
1068
1093
|
: { type: "function_call", id: itemId, call_id: event.id, name: realName, arguments: "", status: "in_progress", ...(ns ? { namespace: ns } : {}) };
|
|
1069
1094
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
1070
|
-
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, providerMetadata: event.providerMetadata };
|
|
1095
|
+
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, codeModeHelperName, providerMetadata: event.providerMetadata };
|
|
1071
1096
|
budget?.openCall(event.id);
|
|
1072
1097
|
break;
|
|
1073
1098
|
}
|
|
@@ -1086,7 +1111,7 @@ export function bridgeToResponsesSSE(
|
|
|
1086
1111
|
delta: event.arguments,
|
|
1087
1112
|
});
|
|
1088
1113
|
}
|
|
1089
|
-
if (currentToolCall.freeform) {
|
|
1114
|
+
if (currentToolCall.freeform && !currentToolCall.codeModeHelperName) {
|
|
1090
1115
|
// Hold while the buffer is still an ambiguous prefix of the JSON wrapper,
|
|
1091
1116
|
// then stream only the unwrapped input suffix (never rewind on mode flips).
|
|
1092
1117
|
if (!FREEFORM_WRAP_PREFIX.startsWith(currentToolCall.args)) {
|
|
@@ -1289,7 +1314,9 @@ export function bridgeToResponsesSSE(
|
|
|
1289
1314
|
...(event.usage ? { usage: responsesUsage(event.usage) } : {}),
|
|
1290
1315
|
error: failure.error,
|
|
1291
1316
|
last_error: failure.error,
|
|
1292
|
-
...(
|
|
1317
|
+
...(isCyberPolicyCode(failure.error.code)
|
|
1318
|
+
? { retryable: false }
|
|
1319
|
+
: event.retryable !== undefined ? { retryable: event.retryable } : {}),
|
|
1293
1320
|
},
|
|
1294
1321
|
});
|
|
1295
1322
|
reportTerminal("failed");
|
|
@@ -1313,11 +1340,17 @@ export function bridgeToResponsesSSE(
|
|
|
1313
1340
|
if (currentToolCall) failCurrentToolCall();
|
|
1314
1341
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1315
1342
|
releasePendingWebSources();
|
|
1343
|
+
const failure = responseError(
|
|
1344
|
+
500,
|
|
1345
|
+
"proxy_error",
|
|
1346
|
+
redactSecretString(err instanceof Error ? err.message : String(err)),
|
|
1347
|
+
);
|
|
1316
1348
|
emit("response.failed", {
|
|
1317
1349
|
response: {
|
|
1318
1350
|
...responseSnapshot("failed", finishedItems),
|
|
1319
|
-
error:
|
|
1320
|
-
last_error:
|
|
1351
|
+
error: failure,
|
|
1352
|
+
last_error: failure,
|
|
1353
|
+
...(isCyberPolicyCode(failure.code) ? { retryable: false } : {}),
|
|
1321
1354
|
},
|
|
1322
1355
|
});
|
|
1323
1356
|
reportTerminal("failed");
|
|
@@ -1370,6 +1403,8 @@ export function bridgeToResponsesSSE(
|
|
|
1370
1403
|
|
|
1371
1404
|
const startStream = () => {
|
|
1372
1405
|
emit("response.created", { response: responseSnapshot("in_progress", []) });
|
|
1406
|
+
// Responses spec parity: clients expect an explicit in_progress frame after created.
|
|
1407
|
+
emit("response.in_progress", { response: responseSnapshot("in_progress", []) });
|
|
1373
1408
|
// The default ReadableStream strategy has HWM=1. Once one event's frames fill that
|
|
1374
1409
|
// queue, pull stepping pauses; no custom FIFO or queuing strategy is layered on top.
|
|
1375
1410
|
gated = true;
|
|
@@ -1561,16 +1596,21 @@ function buildResponseJSONWithBudget(
|
|
|
1561
1596
|
let batchKiroRedactedBytes = 0;
|
|
1562
1597
|
let currentToolCallId = "";
|
|
1563
1598
|
let currentToolCallName = "";
|
|
1599
|
+
let currentToolCallCodeModeHelperName: string | undefined;
|
|
1564
1600
|
let currentToolCallArgs = "";
|
|
1565
1601
|
let currentToolCallProviderMetadata: OcxProviderOpaqueToolCallMetadata | undefined;
|
|
1566
1602
|
let currentToolCallArgsBytes = 0;
|
|
1567
1603
|
// Web-search citations awaiting the next assistant message (attached as url_citation annotations).
|
|
1568
1604
|
let pendingWebSources: { url: string; title?: string }[] = [];
|
|
1569
1605
|
|
|
1570
|
-
const freeformInput = (
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1606
|
+
const freeformInput = (
|
|
1607
|
+
args: string,
|
|
1608
|
+
toolName: string,
|
|
1609
|
+
namespace?: string,
|
|
1610
|
+
codeModeHelperName?: string,
|
|
1611
|
+
): string => codeModeHelperName
|
|
1612
|
+
? compileCodeModeHelperInput(args, codeModeHelperName)
|
|
1613
|
+
: repairFreeformToolInput(args, toolName, namespace);
|
|
1574
1614
|
const parseArgsObj = (args: string): Record<string, unknown> => {
|
|
1575
1615
|
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
1576
1616
|
};
|
|
@@ -1656,6 +1696,7 @@ function buildResponseJSONWithBudget(
|
|
|
1656
1696
|
const coercedArgs = coerceIntegerToolArguments(
|
|
1657
1697
|
currentToolCallArgs,
|
|
1658
1698
|
options?.toolParameterSchemas?.get(currentToolCallName),
|
|
1699
|
+
ns === undefined ? realName : undefined,
|
|
1659
1700
|
);
|
|
1660
1701
|
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
1661
1702
|
// signature server-side regardless so the replayed call can be re-signed (#1735).
|
|
@@ -1670,7 +1711,8 @@ function buildResponseJSONWithBudget(
|
|
|
1670
1711
|
pushOutput({
|
|
1671
1712
|
type: "custom_tool_call", id: `ctc_${uuid()}`,
|
|
1672
1713
|
call_id: currentToolCallId, name: realName,
|
|
1673
|
-
|
|
1714
|
+
...(ns ? { namespace: ns } : {}),
|
|
1715
|
+
input: freeformInput(currentToolCallArgs, realName, ns, currentToolCallCodeModeHelperName), status,
|
|
1674
1716
|
});
|
|
1675
1717
|
} else {
|
|
1676
1718
|
pushOutput({
|
|
@@ -1684,6 +1726,7 @@ function buildResponseJSONWithBudget(
|
|
|
1684
1726
|
budget?.closeCall(currentToolCallId);
|
|
1685
1727
|
currentToolCallId = "";
|
|
1686
1728
|
currentToolCallName = "";
|
|
1729
|
+
currentToolCallCodeModeHelperName = undefined;
|
|
1687
1730
|
currentToolCallProviderMetadata = undefined;
|
|
1688
1731
|
currentToolCallArgs = "";
|
|
1689
1732
|
currentToolCallArgsBytes = 0;
|
|
@@ -1777,7 +1820,7 @@ function buildResponseJSONWithBudget(
|
|
|
1777
1820
|
));
|
|
1778
1821
|
}
|
|
1779
1822
|
break;
|
|
1780
|
-
case "tool_call_start":
|
|
1823
|
+
case "tool_call_start": {
|
|
1781
1824
|
if (currentText) flushText("commentary");
|
|
1782
1825
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1783
1826
|
if (currentRawReasoning) flushRawReasoning();
|
|
@@ -1785,10 +1828,11 @@ function buildResponseJSONWithBudget(
|
|
|
1785
1828
|
rememberReasoningForCall(e.id, rawReasoningForNextToolCall, replayCacheScope);
|
|
1786
1829
|
}
|
|
1787
1830
|
flushToolCall();
|
|
1788
|
-
|
|
1831
|
+
const effectiveName = normalizeDeclaredToolName(e.name, options?.declaredToolNames);
|
|
1832
|
+
if (options?.declaredToolNames && !options.declaredToolNames.has(effectiveName)) {
|
|
1789
1833
|
errorEvent = {
|
|
1790
1834
|
type: "error",
|
|
1791
|
-
message: `routed provider emitted undeclared client tool "${
|
|
1835
|
+
message: `routed provider emitted undeclared client tool "${effectiveName}"; only request-declared tools may be called`,
|
|
1792
1836
|
status: 502,
|
|
1793
1837
|
errorType: "upstream_error",
|
|
1794
1838
|
};
|
|
@@ -1796,11 +1840,15 @@ function buildResponseJSONWithBudget(
|
|
|
1796
1840
|
}
|
|
1797
1841
|
currentToolCallId = e.id;
|
|
1798
1842
|
budget?.openCall(e.id);
|
|
1799
|
-
currentToolCallName =
|
|
1843
|
+
currentToolCallName = effectiveName;
|
|
1844
|
+
currentToolCallCodeModeHelperName = effectiveName === "exec" && e.name !== effectiveName
|
|
1845
|
+
? e.name
|
|
1846
|
+
: undefined;
|
|
1800
1847
|
currentToolCallArgs = "";
|
|
1801
1848
|
currentToolCallArgsBytes = 0;
|
|
1802
1849
|
currentToolCallProviderMetadata = e.providerMetadata;
|
|
1803
1850
|
break;
|
|
1851
|
+
}
|
|
1804
1852
|
case "tool_call_delta":
|
|
1805
1853
|
{
|
|
1806
1854
|
({ value: currentToolCallArgs, bytes: currentToolCallArgsBytes } = appendBatchString(
|
|
@@ -1942,7 +1990,9 @@ function buildResponseJSONWithBudget(
|
|
|
1942
1990
|
model: modelId, output,
|
|
1943
1991
|
...(endTurn !== undefined ? { end_turn: endTurn } : {}),
|
|
1944
1992
|
...(failure ? { error: failure.error, last_error: failure.error } : {}),
|
|
1945
|
-
...(
|
|
1993
|
+
...(failure && isCyberPolicyCode(failure.error.code)
|
|
1994
|
+
? { retryable: false }
|
|
1995
|
+
: errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}),
|
|
1946
1996
|
...(incompleteEvent ? {
|
|
1947
1997
|
incomplete_details: {
|
|
1948
1998
|
reason: incompleteEvent.reason,
|
|
@@ -1971,12 +2021,15 @@ export function formatErrorResponse(
|
|
|
1971
2021
|
const error = classifyError(status, type, message);
|
|
1972
2022
|
if (isCyberPolicyCode(options?.code)) {
|
|
1973
2023
|
error.code = CYBER_POLICY_ERROR_CODE;
|
|
1974
|
-
error.type =
|
|
2024
|
+
error.type = cyberPolicyErrorType(type);
|
|
1975
2025
|
}
|
|
1976
2026
|
const finalStatus = error.code === CYBER_POLICY_ERROR_CODE ? 400 : status;
|
|
1977
2027
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1978
2028
|
const retryAfter = options?.retryAfter?.trim();
|
|
1979
|
-
if (
|
|
2029
|
+
if (error.code !== CYBER_POLICY_ERROR_CODE
|
|
2030
|
+
&& retryAfter
|
|
2031
|
+
&& retryAfter.length > 0
|
|
2032
|
+
&& retryAfter.length <= 128) {
|
|
1980
2033
|
headers.set("Retry-After", retryAfter);
|
|
1981
2034
|
}
|
|
1982
2035
|
return new Response(JSON.stringify({ error }), {
|
package/src/chat/inbound.ts
CHANGED
|
@@ -53,6 +53,14 @@ function imageUrlFromPart(part: Rec): string | null {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function videoUrlFromPart(part: Rec): string | null {
|
|
57
|
+
if (part.type !== "video_url") return null;
|
|
58
|
+
const videoUrl = part.video_url;
|
|
59
|
+
if (typeof videoUrl === "string" && videoUrl.length > 0) return videoUrl;
|
|
60
|
+
if (isRec(videoUrl) && typeof videoUrl.url === "string" && videoUrl.url.length > 0) return videoUrl.url;
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
56
64
|
function userContentToBlocks(content: unknown): Rec[] {
|
|
57
65
|
if (typeof content === "string") {
|
|
58
66
|
return content.length > 0 ? [{ type: "input_text", text: content }] : [];
|
|
@@ -70,7 +78,12 @@ function userContentToBlocks(content: unknown): Rec[] {
|
|
|
70
78
|
continue;
|
|
71
79
|
}
|
|
72
80
|
const imageUrl = imageUrlFromPart(raw);
|
|
73
|
-
if (imageUrl)
|
|
81
|
+
if (imageUrl) {
|
|
82
|
+
blocks.push({ type: "input_image", image_url: imageUrl });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const videoUrl = videoUrlFromPart(raw);
|
|
86
|
+
if (videoUrl) blocks.push({ type: "input_video", video_url: videoUrl });
|
|
74
87
|
}
|
|
75
88
|
return blocks;
|
|
76
89
|
}
|
package/src/chat/outbound.ts
CHANGED
|
@@ -9,7 +9,14 @@ type Rec = Record<string, unknown>;
|
|
|
9
9
|
|
|
10
10
|
import { decodeServerSentEvents, sseFieldValue } from "../lib/sse-decoder";
|
|
11
11
|
import { isTranslatorBudgetExceededError, type TranslatorBudget } from "../lib/translator-budget";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
classifyError,
|
|
14
|
+
cyberPolicyErrorType,
|
|
15
|
+
CYBER_POLICY_ERROR_CODE,
|
|
16
|
+
isCyberPolicyCode,
|
|
17
|
+
isCyberPolicyMessage,
|
|
18
|
+
} from "../lib/errors";
|
|
19
|
+
import { redactSecretString } from "../lib/redact";
|
|
13
20
|
|
|
14
21
|
function isRec(v: unknown): v is Rec {
|
|
15
22
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
@@ -48,14 +55,14 @@ export function chatCompletionsUsage(usage: unknown): Rec {
|
|
|
48
55
|
export function chatCompletionsErrorBody(
|
|
49
56
|
status: number,
|
|
50
57
|
message: string,
|
|
51
|
-
type
|
|
58
|
+
type?: string,
|
|
52
59
|
code?: string | null,
|
|
53
60
|
): Rec {
|
|
54
61
|
if (isCyberPolicyCode(code) || isCyberPolicyMessage(message)) {
|
|
55
62
|
return {
|
|
56
63
|
error: {
|
|
57
64
|
message,
|
|
58
|
-
type:
|
|
65
|
+
type: cyberPolicyErrorType(type),
|
|
59
66
|
param: null,
|
|
60
67
|
code: CYBER_POLICY_ERROR_CODE,
|
|
61
68
|
},
|
|
@@ -64,7 +71,7 @@ export function chatCompletionsErrorBody(
|
|
|
64
71
|
return {
|
|
65
72
|
error: {
|
|
66
73
|
message,
|
|
67
|
-
type,
|
|
74
|
+
type: type ?? "invalid_request_error",
|
|
68
75
|
param: null,
|
|
69
76
|
code: code !== undefined
|
|
70
77
|
? code
|
|
@@ -311,8 +318,9 @@ export function responsesSseToChatCompletionsSse(
|
|
|
311
318
|
// Deliver the error frame then close the stream abnormally (no [DONE]).
|
|
312
319
|
// Do not controller.error() — that can drop already-enqueued bytes from consumers
|
|
313
320
|
// like response.text().
|
|
314
|
-
const
|
|
315
|
-
const
|
|
321
|
+
const safeMessage = redactSecretString(message);
|
|
322
|
+
const statusHint = details?.status ?? streamErrorStatus(safeMessage);
|
|
323
|
+
const classified = classifyError(statusHint, details?.type ?? "upstream_error", safeMessage);
|
|
316
324
|
const translatorOverflow = details?.code === "translation_buffer_limit";
|
|
317
325
|
if (translatorOverflow) {
|
|
318
326
|
upstreamAbort.abort(new Error("upstream translation buffer exceeded the safe limit"));
|
|
@@ -324,7 +332,7 @@ export function responsesSseToChatCompletionsSse(
|
|
|
324
332
|
classified.type = "upstream_error";
|
|
325
333
|
} else if (isCyberPolicyCode(details?.code) || classified.code === CYBER_POLICY_ERROR_CODE) {
|
|
326
334
|
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
327
|
-
classified.type =
|
|
335
|
+
classified.type = cyberPolicyErrorType(details?.type);
|
|
328
336
|
} else if (details?.code !== undefined && details.code !== null && !classified.code) {
|
|
329
337
|
classified.code = details.code;
|
|
330
338
|
}
|
|
@@ -218,7 +218,14 @@ function isOwnedFile(path: string): boolean {
|
|
|
218
218
|
export function syncClaudeAgentDefs(defs: readonly ClaudeAgentDef[], configDir = claudeConfigDir()): string[] | null {
|
|
219
219
|
try {
|
|
220
220
|
const dir = join(configDir, "agents");
|
|
221
|
-
|
|
221
|
+
if (defs.length === 0) {
|
|
222
|
+
try { lstatSync(dir); } catch (error) {
|
|
223
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
mkdirSync(dir, { recursive: true });
|
|
228
|
+
}
|
|
222
229
|
const keep = new Set(defs.map(d => d.file));
|
|
223
230
|
for (const existing of readdirSync(dir)) {
|
|
224
231
|
if (!existing.startsWith(OWNED_PREFIX) || !existing.endsWith(".md")) continue;
|
|
@@ -118,17 +118,24 @@ export function buildClaudeContextWindows(
|
|
|
118
118
|
put(desktop3pAlias("native", slug), window);
|
|
119
119
|
put(aliasForNative(slug), window);
|
|
120
120
|
}
|
|
121
|
+
// Anthropic passthrough guard (audit 021 #3): canonical claude ids ride the
|
|
122
|
+
// subscription passthrough — marking a sub-1M one would strap [1m]/1M-beta onto
|
|
123
|
+
// a model that cannot host it. Register anthropic rows only at >=1M.
|
|
124
|
+
const registrable = routedModels.filter(
|
|
125
|
+
m =>
|
|
126
|
+
typeof m.contextWindow === "number" &&
|
|
127
|
+
m.contextWindow > 0 &&
|
|
128
|
+
!(m.provider === "anthropic" && m.contextWindow < ONE_MILLION),
|
|
129
|
+
);
|
|
121
130
|
// Bare routed ids are registered only when unambiguous across providers (audit
|
|
122
|
-
// 021 #5) — natives are registered first, so a native slug always wins the bare
|
|
131
|
+
// 021 #5) — natives are registered first, so a native slug always wins the bare
|
|
132
|
+
// key. Counted over the rows that can actually claim the key: a row this loop
|
|
133
|
+
// skips contributes no window, so letting it veto the bare key withholds an
|
|
134
|
+
// answer that was never in doubt.
|
|
123
135
|
const bareCounts = new Map<string, number>();
|
|
124
|
-
for (const m of
|
|
125
|
-
for (const m of
|
|
126
|
-
const window = m.contextWindow;
|
|
127
|
-
if (typeof window !== "number" || window <= 0) continue;
|
|
128
|
-
// Anthropic passthrough guard (audit 021 #3): canonical claude ids ride the
|
|
129
|
-
// subscription passthrough — marking a sub-1M one would strap [1m]/1M-beta onto
|
|
130
|
-
// a model that cannot host it. Register anthropic rows only at >=1M.
|
|
131
|
-
if (m.provider === "anthropic" && window < ONE_MILLION) continue;
|
|
136
|
+
for (const m of registrable) bareCounts.set(m.id, (bareCounts.get(m.id) ?? 0) + 1);
|
|
137
|
+
for (const m of registrable) {
|
|
138
|
+
const window = m.contextWindow as number;
|
|
132
139
|
put(`${m.provider}/${m.id}`, window);
|
|
133
140
|
put(desktop3pAlias(m.provider, m.id), window);
|
|
134
141
|
put(aliasForRoute(m.provider, m.id), window);
|
package/src/claude/outbound.ts
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
* Claude Code outbound: internal /v1/responses output -> Anthropic Messages API shapes.
|
|
3
3
|
*
|
|
4
4
|
* Wire contract pinned in devlog/260711_claude_inbound/003_evidence.md (all Tier 2):
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
5
|
+
* - Transport-only `ping` events may appear at any point, including before
|
|
6
|
+
* message_start. Semantic framing stays message_start ->
|
|
7
|
+
* (content_block_start -> deltas -> content_block_stop)* -> message_delta -> message_stop.
|
|
7
8
|
* - thinking blocks get thinking_delta(s) then ONE synthetic signature_delta just
|
|
8
9
|
* before content_block_stop (CCR precedent: Claude Code does not verify signatures).
|
|
9
10
|
* - message_delta.usage is cumulative; message_start embeds a full message snapshot.
|
|
10
11
|
* - errors: {type:"error", error:{type,message}}; may arrive mid-stream after HTTP 200.
|
|
11
12
|
*/
|
|
12
13
|
import { createHash } from "node:crypto";
|
|
14
|
+
import { httpStatusFromTerminalError } from "../lib/errors";
|
|
13
15
|
import { isTransientUpstreamStatus } from "../lib/upstream-retry";
|
|
14
16
|
import {
|
|
15
17
|
isTranslatorBudgetExceededError,
|
|
@@ -267,12 +269,12 @@ export function responsesSseToAnthropicSse(
|
|
|
267
269
|
emit("message_start", { type: "message_start", message: messageSnapshot(model) });
|
|
268
270
|
emit("ping", { type: "ping" });
|
|
269
271
|
};
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
272
|
+
// Keepalive pings protect remote deployments behind LB/NAT idle timeouts even
|
|
273
|
+
// before semantic output. They are transport-only and must not manufacture a
|
|
274
|
+
// message before a possible initial error.
|
|
273
275
|
if (pingIntervalMs > 0) {
|
|
274
276
|
pingTimer = setInterval(() => {
|
|
275
|
-
if (terminated ||
|
|
277
|
+
if (terminated || (controller.desiredSize ?? 0) <= 0) return;
|
|
276
278
|
try {
|
|
277
279
|
emit("ping", { type: "ping" });
|
|
278
280
|
} catch { /* controller torn down; the read loop is ending anyway */ }
|
|
@@ -352,7 +354,8 @@ export function responsesSseToAnthropicSse(
|
|
|
352
354
|
const type = upstreamDerived && isTransientUpstreamStatus(status) ? "overloaded_error" : undefined;
|
|
353
355
|
if (!started) {
|
|
354
356
|
// An initial upstream failure is an Anthropic error stream, not a partial message.
|
|
355
|
-
// Do not manufacture message_start
|
|
357
|
+
// Do not manufacture message_start before the terminal error. Earlier transport-only
|
|
358
|
+
// pings remain valid and do not turn the failure into a partial message.
|
|
356
359
|
emit("error", anthropicErrorBody(status, message, type, code));
|
|
357
360
|
return;
|
|
358
361
|
}
|
|
@@ -366,7 +369,7 @@ export function responsesSseToAnthropicSse(
|
|
|
366
369
|
// Transport prelude only. Start Anthropic framing on semantic output or completion.
|
|
367
370
|
break;
|
|
368
371
|
case "response.heartbeat":
|
|
369
|
-
if (
|
|
372
|
+
if ((controller.desiredSize ?? 0) > 0) emit("ping", { type: "ping" });
|
|
370
373
|
break;
|
|
371
374
|
case "response.output_text.delta": {
|
|
372
375
|
if (typeof data.delta !== "string" || data.delta.length === 0) break;
|
|
@@ -553,9 +556,19 @@ export function responsesSseToAnthropicSse(
|
|
|
553
556
|
}
|
|
554
557
|
const status = code === "translation_buffer_limit"
|
|
555
558
|
? 413
|
|
556
|
-
: typeof error.status === "number"
|
|
557
|
-
|
|
558
|
-
|
|
559
|
+
: typeof error.status === "number"
|
|
560
|
+
? error.status
|
|
561
|
+
// Internal response.failed envelopes carry the classified {type, code, message}
|
|
562
|
+
// but no numeric status. Derive it with the same mapping /api/logs uses so a
|
|
563
|
+
// classified 429/401/400 reaches Claude Code as its real Anthropic error type
|
|
564
|
+
// instead of being masked as retryable overload.
|
|
565
|
+
: httpStatusFromTerminalError({
|
|
566
|
+
type: typeof error.type === "string" ? error.type : undefined,
|
|
567
|
+
code: typeof error.code === "string" ? error.code : null,
|
|
568
|
+
message,
|
|
569
|
+
});
|
|
570
|
+
// Unclassified status-absent response.failed (relaySseWithFailedTail synthetic
|
|
571
|
+
// tail) still lands on a transient 5xx here — the mid-stream reset shape maps to
|
|
559
572
|
// overloaded_error by design.
|
|
560
573
|
fail(
|
|
561
574
|
status,
|
package/src/cli/access.ts
CHANGED
|
@@ -17,6 +17,51 @@ const USAGE = `Usage:
|
|
|
17
17
|
ocx access models [--json]
|
|
18
18
|
ocx access test <model> [--protocol <chat|responses|messages>] [--json]`;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Render the key table with the usage fields the API already returns (#2705).
|
|
22
|
+
*
|
|
23
|
+
* `usage` is a DISCRIMINATED UNION server-side (`api-key-usage.ts`): the `{ambiguous:true}`
|
|
24
|
+
* variant carries no numbers at all, because when two config entries share an id there IS no
|
|
25
|
+
* per-key total. The union exists specifically so a consumer cannot print a number beside an
|
|
26
|
+
* ambiguity marker, so this renders the word `ambiguous` across the numeric columns rather
|
|
27
|
+
* than a fabricated 0 -- reporting 0 requests for a key that may be in heavy use is the
|
|
28
|
+
* dangerous answer to hand someone deciding what to delete.
|
|
29
|
+
*
|
|
30
|
+
* `attributionSince` and `historyTruncated` describe the DATA SET, not a key, so they print
|
|
31
|
+
* once as a footer. Without `attributionSince`, an absent `lastUsedAt` is unreadable: it
|
|
32
|
+
* could mean "never used" or "nothing is attributable yet".
|
|
33
|
+
*/
|
|
34
|
+
function formatKeyRows(payload: Record<string, unknown>, keys: Array<Record<string, unknown>>): string[] {
|
|
35
|
+
const cells: string[][] = [["ID", "NAME", "PREFIX", "REQ 7D", "TOTAL", "LAST USED"]];
|
|
36
|
+
for (const entry of keys) {
|
|
37
|
+
const usage = (entry.usage ?? {}) as Record<string, unknown>;
|
|
38
|
+
const ambiguous = usage.ambiguous === true;
|
|
39
|
+
const num = (value: unknown): string => (typeof value === "number" ? value.toLocaleString("en-US") : "-");
|
|
40
|
+
cells.push([
|
|
41
|
+
String(entry.id ?? ""),
|
|
42
|
+
String(entry.name ?? ""),
|
|
43
|
+
String(entry.prefix ?? ""),
|
|
44
|
+
// One marker spanning both numeric columns: the union guarantees neither exists.
|
|
45
|
+
ambiguous ? "ambiguous" : num(usage.requests7d),
|
|
46
|
+
ambiguous ? "" : num(usage.totalRequests),
|
|
47
|
+
ambiguous ? "" : (typeof usage.lastUsedAt === "string" ? usage.lastUsedAt : "never"),
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
const widths = cells[0]!.map((_, column) => Math.max(...cells.map(row => (row[column] ?? "").length)));
|
|
51
|
+
const lines = cells.map(row => row.map((cell, i) => (cell ?? "").padEnd(widths[i]!)).join(" ").trimEnd());
|
|
52
|
+
const footer: string[] = [];
|
|
53
|
+
if (typeof payload.attributionSince === "string") {
|
|
54
|
+
footer.push(`attribution since ${payload.attributionSince}`);
|
|
55
|
+
}
|
|
56
|
+
if (payload.historyTruncated === true) {
|
|
57
|
+
footer.push("older history truncated");
|
|
58
|
+
}
|
|
59
|
+
if (keys.some(entry => (entry.usage as Record<string, unknown> | undefined)?.ambiguous === true)) {
|
|
60
|
+
footer.push("ambiguous: two configured keys share an id, so per-key totals do not exist");
|
|
61
|
+
}
|
|
62
|
+
return footer.length > 0 ? [...lines, "", ...footer] : lines;
|
|
63
|
+
}
|
|
64
|
+
|
|
20
65
|
async function key(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
21
66
|
const args = [...argv];
|
|
22
67
|
const action = (args.shift() ?? "list").toLowerCase();
|
|
@@ -25,9 +70,7 @@ async function key(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
25
70
|
rejectArgs(args, USAGE);
|
|
26
71
|
const result = await runtimeRequest<Record<string, unknown>>("/api/keys", {}, deps);
|
|
27
72
|
const keys = Array.isArray(result.keys) ? result.keys as Array<Record<string, unknown>> : [];
|
|
28
|
-
printData(result, wantsJson, keys.length
|
|
29
|
-
? keys.map(entry => `${String(entry.id)} ${String(entry.name)} ${String(entry.prefix ?? "")}`)
|
|
30
|
-
: ["No API access keys configured."]);
|
|
73
|
+
printData(result, wantsJson, keys.length ? formatKeyRows(result, keys) : ["No API access keys configured."]);
|
|
31
74
|
return;
|
|
32
75
|
}
|
|
33
76
|
if (action === "create") {
|