mixdog 0.9.87 → 0.9.89
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/package.json +13 -7
- package/scripts/tool-failures.mjs +60 -24
- package/src/agents/heavy-worker/AGENT.md +3 -4
- package/src/agents/worker/AGENT.md +1 -2
- package/src/cli.mjs +8 -0
- package/src/defaults/cycle3-review-prompt.md +4 -4
- package/src/rules/agent/00-core.md +3 -0
- package/src/rules/agent/42-cycle3-agent.md +5 -6
- package/src/rules/lead/01-general.md +2 -0
- package/src/rules/shared/01-tool.md +29 -35
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +3 -51
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +59 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +67 -0
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +57 -4
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +2 -3
- package/src/runtime/agent/orchestrator/config.mjs +29 -11
- package/src/runtime/agent/orchestrator/context/collect.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +1 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +0 -24
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +25 -17
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +321 -22
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -22
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +28 -23
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +10 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +2 -5
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +27 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/lib/stream-outcome.mjs +329 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +23 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +107 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +4 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +124 -12
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +19 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +110 -75
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +96 -113
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +160 -11
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +18 -1
- package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +147 -117
- package/src/runtime/agent/orchestrator/session/loop/stop-hooks.mjs +88 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +50 -8
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +25 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +47 -6
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +485 -24
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +100 -2
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +80 -14
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +142 -2
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +128 -32
- package/src/runtime/agent/orchestrator/session/manager.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +77 -4
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +70 -39
- package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +91 -0
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +141 -64
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +253 -0
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +211 -28
- package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +313 -0
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +622 -53
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +43 -10
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +53 -8
- package/src/runtime/agent/orchestrator/session/store/summary-rebuild-worker.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +113 -9
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +476 -52
- package/src/runtime/agent/orchestrator/session/store.mjs +721 -196
- package/src/runtime/agent/orchestrator/session/token-native.mjs +2 -4
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +4 -6
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +146 -90
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +93 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +85 -5
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +52 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +54 -51
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +14 -15
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +132 -8
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +86 -3
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +210 -38
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +87 -19
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -8
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +12 -2
- package/src/runtime/media/lanes.mjs +2 -2
- package/src/runtime/media/renditions.mjs +101 -17
- package/src/runtime/media/renditions.test.mjs +54 -0
- package/src/runtime/media/store.mjs +111 -43
- package/src/runtime/media/store.test.mjs +53 -11
- package/src/runtime/memory/index.mjs +26 -50
- package/src/runtime/memory/lib/core-memory-candidates.mjs +2 -7
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -9
- package/src/runtime/memory/lib/cycle-scheduler.mjs +10 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +3 -3
- package/src/runtime/memory/lib/embedding-worker.mjs +60 -15
- package/src/runtime/memory/lib/http-router.mjs +4 -0
- package/src/runtime/memory/lib/ko-morph.mjs +49 -6
- package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -18
- package/src/runtime/memory/lib/memory-cycle2.mjs +3 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -3
- package/src/runtime/memory/lib/memory-recall-store.mjs +24 -0
- package/src/runtime/memory/lib/query-handlers.mjs +26 -28
- package/src/runtime/memory/tool-defs.mjs +3 -3
- package/src/runtime/shared/atomic-file.mjs +5 -1
- package/src/runtime/shared/child-guardian.mjs +73 -2
- package/src/runtime/shared/provider-api-key.mjs +22 -5
- package/src/runtime/shared/tool-execution-contract.mjs +53 -0
- package/src/session-runtime/config-helpers.mjs +6 -5
- package/src/session-runtime/lifecycle-api.mjs +16 -4
- package/src/session-runtime/media-api.mjs +31 -16
- package/src/session-runtime/prewarm.mjs +2 -24
- package/src/session-runtime/runtime-core.mjs +33 -15
- package/src/session-runtime/runtime-tunables.mjs +0 -3
- package/src/session-runtime/session-lifecycle.mjs +0 -6
- package/src/session-runtime/session-title.mjs +228 -0
- package/src/session-runtime/session-turn-api.mjs +11 -7
- package/src/session-runtime/workflow-agents-api.mjs +16 -0
- package/src/session-runtime/workflow.mjs +7 -5
- package/src/standalone/agent-tool/job-views.mjs +90 -36
- package/src/standalone/agent-tool/spawn-flow.mjs +25 -216
- package/src/standalone/agent-tool/worker-index.mjs +8 -0
- package/src/standalone/agent-tool/worker-rows.mjs +2 -1
- package/src/standalone/agent-tool.mjs +17 -17
- package/src/standalone/agent-watchdog-registry.mjs +19 -2
- package/src/standalone/channel-daemon.mjs +8 -0
- package/src/standalone/explore-tool.mjs +1 -1
- package/src/standalone/memory-runtime-proxy.mjs +4 -7
- package/src/standalone/projects.mjs +4 -1
- package/src/standalone/usage-dashboard.mjs +25 -3
- package/src/tui/App.jsx +5 -3
- package/src/tui/app/resume-picker.mjs +7 -2
- package/src/tui/app/route-pickers.mjs +11 -117
- package/src/tui/app/slash-dispatch.mjs +13 -3
- package/src/tui/app/transcript-row-estimate.mjs +1 -1
- package/src/tui/app/transcript-window.mjs +17 -93
- package/src/tui/app/use-transcript-scroll.mjs +32 -2
- package/src/tui/app/use-transcript-window.mjs +64 -114
- package/src/tui/components/Markdown.jsx +19 -2
- package/src/tui/components/Message.jsx +8 -3
- package/src/tui/components/ToolExecution.jsx +7 -3
- package/src/tui/dist/index.mjs +243 -223
- package/src/tui/engine/live-share.mjs +15 -5
- package/src/tui/engine/render-timing.mjs +2 -2
- package/src/tui/engine/session-api-ext.mjs +74 -4
- package/src/tui/engine/session-flow.mjs +3 -1
- package/src/tui/engine.mjs +2 -2
- package/src/tui/hooks/useSharedTick.mjs +0 -2
- package/src/tui/index.jsx +5 -5
- package/src/ui/statusline-agents.mjs +44 -11
- package/src/vendor/statusline/bin/statusline-route.mjs +26 -3
- package/src/workflows/default/WORKFLOW.md +3 -3
- package/src/workflows/solo/WORKFLOW.md +5 -3
- package/src/runtime/media/index.mjs +0 -18
- package/src/runtime/memory/lib/embedding-warmup.mjs +0 -68
- package/src/standalone/agent-shard/shard-child.mjs +0 -300
- package/src/standalone/agent-shard/shard-pool.mjs +0 -443
|
@@ -15,9 +15,15 @@ import {
|
|
|
15
15
|
classifyMidstreamError,
|
|
16
16
|
MIDSTREAM_RETRY_POLICY,
|
|
17
17
|
sleepWithAbort,
|
|
18
|
+
typedStatusFrom,
|
|
18
19
|
} from './retry-classifier.mjs';
|
|
19
20
|
import { makeInvalidToolArgsMarker } from './openai-compat-stream.mjs';
|
|
20
21
|
import { scanLeakedToolCalls, createToolCallDedupe } from './anthropic-leaked-toolcall.mjs';
|
|
22
|
+
import {
|
|
23
|
+
NATIVE_SERVER_TOOL_CALL_BLOCK_TYPES,
|
|
24
|
+
NATIVE_SERVER_TOOL_RESULT_BLOCK_TYPES,
|
|
25
|
+
} from './lib/anthropic-native-blocks.mjs';
|
|
26
|
+
import { stampStreamOutcome, STREAM_TRANSPORTS, STREAM_OUTCOME_VERSION } from './lib/stream-outcome.mjs';
|
|
21
27
|
|
|
22
28
|
/** Bounded mid-stream SSE retries (transient stream loss); shared with anthropic.mjs.
|
|
23
29
|
* Sourced from the single shared retry-budget table (MIDSTREAM_RETRY_POLICY.sse). */
|
|
@@ -51,15 +57,32 @@ export function _midstreamSleepWithAbort(ms, signal, sleepFn) {
|
|
|
51
57
|
return sleepWithAbort(ms, signal, sleepFn, 'Anthropic OAuth mid-stream retry backoff aborted');
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
// Anthropic's documented error-event `type` enumeration → HTTP equivalent.
|
|
61
|
+
// This is STRUCTURED evidence (an explicit wire field with a closed value set),
|
|
62
|
+
// never a message-text guess: an event whose only hint is prose keeps no status
|
|
63
|
+
// at all, so the retry/auth layers see it as unknown and terminate the turn.
|
|
64
|
+
const ANTHROPIC_SSE_ERROR_TYPE_STATUS = new Map([
|
|
65
|
+
['invalid_request_error', 400],
|
|
66
|
+
['authentication_error', 401],
|
|
67
|
+
['permission_error', 403],
|
|
68
|
+
['not_found_error', 404],
|
|
69
|
+
['request_too_large', 413],
|
|
70
|
+
['rate_limit_error', 429],
|
|
71
|
+
['api_error', 500],
|
|
72
|
+
['overloaded_error', 503],
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
function _statusForAnthropicSseError(event, payload) {
|
|
76
|
+
// 1) A real numeric status carried by the event/error payload.
|
|
77
|
+
const numeric = typedStatusFrom(payload, event);
|
|
78
|
+
if (numeric) return numeric;
|
|
79
|
+
// 2) The explicit structured error type/code (exact enumeration match).
|
|
80
|
+
for (const field of [payload?.type, payload?.code, event?.error?.type, event?.error?.code]) {
|
|
81
|
+
const key = typeof field === 'string' ? field.trim().toLowerCase() : '';
|
|
82
|
+
if (key && ANTHROPIC_SSE_ERROR_TYPE_STATUS.has(key)) {
|
|
83
|
+
return ANTHROPIC_SSE_ERROR_TYPE_STATUS.get(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
63
86
|
return 0;
|
|
64
87
|
}
|
|
65
88
|
|
|
@@ -72,7 +95,7 @@ function _anthropicSseError(event) {
|
|
|
72
95
|
err.code = 'EANTHROPIC_SSE_ERROR';
|
|
73
96
|
err.providerErrorType = type;
|
|
74
97
|
err.requestId = event?.request_id || event?.requestId || null;
|
|
75
|
-
const status = _statusForAnthropicSseError(
|
|
98
|
+
const status = _statusForAnthropicSseError(event, payload);
|
|
76
99
|
if (status) {
|
|
77
100
|
err.httpStatus = status;
|
|
78
101
|
err.status = status;
|
|
@@ -80,6 +103,21 @@ function _anthropicSseError(event) {
|
|
|
80
103
|
return err;
|
|
81
104
|
}
|
|
82
105
|
|
|
106
|
+
// Anthropic NATIVE (server-side) tool blocks. Anthropic runs these itself and
|
|
107
|
+
// returns both the CALL block (`server_tool_use`, streamed `input`) and its
|
|
108
|
+
// RESULT block (`web_search_tool_result`, …, delivered whole). They are never
|
|
109
|
+
// dispatched to the agent loop, so they only survive a continuation turn if
|
|
110
|
+
// they are captured here and replayed verbatim in original block order. The
|
|
111
|
+
// type sets are shared with the non-streaming normalizer so both transports
|
|
112
|
+
// classify identically (see lib/anthropic-native-blocks.mjs).
|
|
113
|
+
|
|
114
|
+
// Detach the provider payload from the parser's event object without altering
|
|
115
|
+
// any field (opaque server-tool result payloads must round-trip byte-exact).
|
|
116
|
+
function cloneNativeBlock(block) {
|
|
117
|
+
try { return structuredClone(block); }
|
|
118
|
+
catch { return { ...block }; }
|
|
119
|
+
}
|
|
120
|
+
|
|
83
121
|
export async function parseSSEStream(response, signal, abortStream, onStreamDelta, onToolCall, state, onTextDelta, knownToolNames) {
|
|
84
122
|
// onStreamDelta is a semantic-progress callback. HTTP headers and raw SSE
|
|
85
123
|
// bytes prove transport health but must not refresh semantic watchdogs.
|
|
@@ -107,6 +145,58 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
107
145
|
// it can be round-tripped verbatim on tool-continuation turns (required
|
|
108
146
|
// back on tool_use turns; empty thinking + signature is valid).
|
|
109
147
|
const thinkingBlocks = new Map();
|
|
148
|
+
// Ordered NATIVE (server-side) tool blocks, keyed by content_block index.
|
|
149
|
+
// Anthropic executes these itself — they are never dispatched to the agent
|
|
150
|
+
// loop as client tool calls — but they are part of the assistant turn and
|
|
151
|
+
// MUST be replayed verbatim, in original block order, on a continuation
|
|
152
|
+
// turn (pause_turn): a `web_search_tool_result` is only valid immediately
|
|
153
|
+
// after the `server_tool_use` block that produced it.
|
|
154
|
+
const nativeServerToolBlocks = new Map();
|
|
155
|
+
// Streamed `input` JSON for native server-tool CALL blocks (same
|
|
156
|
+
// input_json_delta transport as client tool_use, separate index space).
|
|
157
|
+
const pendingNativeToolInputs = new Map();
|
|
158
|
+
// In-flight tool INPUT state: a client `tool_use` (pendingToolInputs) or an
|
|
159
|
+
// Anthropic-native `server_tool_use` (pendingNativeToolInputs) whose
|
|
160
|
+
// streamed input_json has not reached content_block_stop. While either is
|
|
161
|
+
// non-empty the assistant turn cannot be terminal: the arguments are
|
|
162
|
+
// incomplete, so the call was never pushed and never dispatched.
|
|
163
|
+
const _toolInputInFlight = () => pendingToolInputs.size > 0 || pendingNativeToolInputs.size > 0;
|
|
164
|
+
// Set when a terminal frame (message_stop) arrived while input was still in
|
|
165
|
+
// flight — used only to word the truncated-stream failure precisely.
|
|
166
|
+
let sawTerminalFrameWithPendingInput = false;
|
|
167
|
+
// Ordered assistant content builders. Shared by the SUCCESS return and the
|
|
168
|
+
// truncation failure so a cut-off turn preserves exactly the same completed
|
|
169
|
+
// native/client blocks (verbatim, in original content_block order) that a
|
|
170
|
+
// successful turn would have replayed.
|
|
171
|
+
const _orderedThinkingBlocks = () => (thinkingBlocks.size
|
|
172
|
+
? [...thinkingBlocks.entries()].sort((a, b) => a[0] - b[0]).map(([, b]) => b)
|
|
173
|
+
: undefined);
|
|
174
|
+
// A native CALL block is seeded at content_block_start and only gains its
|
|
175
|
+
// parsed `input` at content_block_stop, so a block whose input JSON is
|
|
176
|
+
// still streaming is INCOMPLETE and must never enter the replay list: it
|
|
177
|
+
// would be replayed with empty/partial arguments. Completed blocks only.
|
|
178
|
+
const _completedNativeBlocks = () => [...nativeServerToolBlocks.entries()]
|
|
179
|
+
.filter(([index]) => !pendingNativeToolInputs.has(index));
|
|
180
|
+
const _orderedAssistantBlocks = () => {
|
|
181
|
+
const nativeBlocks = _completedNativeBlocks();
|
|
182
|
+
if (!nativeBlocks.length) return undefined;
|
|
183
|
+
return [
|
|
184
|
+
...[...thinkingBlocks.entries()],
|
|
185
|
+
...[...textBlocks.entries()]
|
|
186
|
+
.filter(([, text]) => typeof text === 'string' && text.length > 0)
|
|
187
|
+
.map(([index, text]) => [index, { type: 'text', text }]),
|
|
188
|
+
...nativeBlocks,
|
|
189
|
+
...[...clientToolUseBlocks.entries()],
|
|
190
|
+
]
|
|
191
|
+
.sort((a, b) => a[0] - b[0])
|
|
192
|
+
.map(([, block]) => block);
|
|
193
|
+
};
|
|
194
|
+
// Per-index raw text, kept only so the ordered native-block replay list
|
|
195
|
+
// below can interleave text exactly where the provider emitted it.
|
|
196
|
+
const textBlocks = new Map();
|
|
197
|
+
// Client tool_use blocks that were actually dispatched, kept for the same
|
|
198
|
+
// ordered replay list (deduped/skipped calls are intentionally absent).
|
|
199
|
+
const clientToolUseBlocks = new Map();
|
|
110
200
|
let model = '';
|
|
111
201
|
let toolCalls = [];
|
|
112
202
|
let usage = { inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, raw: null };
|
|
@@ -240,12 +330,48 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
240
330
|
try {
|
|
241
331
|
err.partialContent = content;
|
|
242
332
|
err.partialToolCalls = toolCalls.length ? toolCalls.slice() : undefined;
|
|
243
|
-
|
|
333
|
+
// `_toolInputInFlight()` is the single authority for "arguments
|
|
334
|
+
// never finished streaming": a client tool_use OR an Anthropic
|
|
335
|
+
// NATIVE server_tool_use whose input JSON is still open. A mixed
|
|
336
|
+
// turn (dispatched client call, then an incomplete server_tool_use)
|
|
337
|
+
// must stay a continuation failure — promoting it would report a
|
|
338
|
+
// native call that never ran as part of a finished turn.
|
|
339
|
+
err.pendingToolUse = _toolInputInFlight();
|
|
244
340
|
err.partialModel = model || undefined;
|
|
245
341
|
err.partialUsage = usage;
|
|
246
342
|
err.partialStopReason = stopReason || undefined;
|
|
247
343
|
err.partialHasThinking = hasThinkingContent;
|
|
344
|
+
// A stalled continuation must carry the SAME ordered block state a
|
|
345
|
+
// successful turn returns: Anthropic requires the verbatim thinking
|
|
346
|
+
// blocks (signatures intact) back before tool_use on the next turn,
|
|
347
|
+
// and a native server_tool_use call is only valid immediately
|
|
348
|
+
// followed by its result block. Without these the recovered
|
|
349
|
+
// tool-call turn replays with the native/thinking state dropped.
|
|
350
|
+
err.partialThinkingBlocks = _orderedThinkingBlocks();
|
|
351
|
+
err.partialAssistantBlocks = _orderedAssistantBlocks();
|
|
248
352
|
} catch { /* best-effort enrichment */ }
|
|
353
|
+
// Canonical stream-outcome contract. Anthropic historically attached
|
|
354
|
+
// ONLY the partial-state fields above, so retry/fallback/persistence
|
|
355
|
+
// consumers had to guess; the record makes terminal-vs-continuation,
|
|
356
|
+
// observed text/reasoning and tool exposure explicit and fail-closed.
|
|
357
|
+
try {
|
|
358
|
+
stampStreamOutcome(err, {
|
|
359
|
+
transport: STREAM_TRANSPORTS.SSE,
|
|
360
|
+
provider: 'anthropic',
|
|
361
|
+
terminalObserved: state?.sawCompleted === true,
|
|
362
|
+
continuation: state?.sawCompleted !== true,
|
|
363
|
+
textEmitted: state?.emittedText === true,
|
|
364
|
+
textObservedChars: content.length,
|
|
365
|
+
// Buffered/empty thinking blocks are not exposure; only
|
|
366
|
+
// relayed reasoning text is.
|
|
367
|
+
reasoningEmitted: state?.emittedThinking === true,
|
|
368
|
+
toolCallsStarted: state?.partialToolCall === true,
|
|
369
|
+
toolCallsComplete: toolCalls.length,
|
|
370
|
+
toolCallsDispatched: state?.emittedToolCall === true ? Math.max(1, toolCalls.length) : 0,
|
|
371
|
+
pendingToolInput: _toolInputInFlight(),
|
|
372
|
+
stallObserved: true,
|
|
373
|
+
});
|
|
374
|
+
} catch { /* stamping is best-effort */ }
|
|
249
375
|
return err;
|
|
250
376
|
};
|
|
251
377
|
|
|
@@ -407,9 +533,31 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
407
533
|
inputJson: '',
|
|
408
534
|
});
|
|
409
535
|
}
|
|
536
|
+
if (block?.type === 'text') {
|
|
537
|
+
// Seed the ordered text slot (a text block may open
|
|
538
|
+
// with a non-empty `text` before any delta).
|
|
539
|
+
textBlocks.set(
|
|
540
|
+
event.index,
|
|
541
|
+
(textBlocks.get(event.index) || '') + (typeof block.text === 'string' ? block.text : ''),
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
if (NATIVE_SERVER_TOOL_CALL_BLOCK_TYPES.has(block?.type)) {
|
|
545
|
+
// Verbatim seed; `input` is completed from the
|
|
546
|
+
// streamed input_json_delta at content_block_stop.
|
|
547
|
+
nativeServerToolBlocks.set(event.index, cloneNativeBlock(block));
|
|
548
|
+
pendingNativeToolInputs.set(event.index, '');
|
|
549
|
+
try { onStreamDelta?.('tool'); } catch {}
|
|
550
|
+
} else if (NATIVE_SERVER_TOOL_RESULT_BLOCK_TYPES.has(block?.type)) {
|
|
551
|
+
// Server-tool RESULT blocks arrive whole (no deltas);
|
|
552
|
+
// keep the payload byte-for-byte for replay.
|
|
553
|
+
nativeServerToolBlocks.set(event.index, cloneNativeBlock(block));
|
|
554
|
+
try { onStreamDelta?.('tool'); } catch {}
|
|
555
|
+
}
|
|
410
556
|
if (block?.type === 'thinking' || block?.type === 'redacted_thinking') {
|
|
411
|
-
if (state) state.emittedThinking = true;
|
|
412
557
|
if (block.type === 'redacted_thinking') {
|
|
558
|
+
// Opaque redacted payload: real reasoning
|
|
559
|
+
// content, exposed as a block.
|
|
560
|
+
if (state) state.emittedThinking = true;
|
|
413
561
|
// Redacted blocks round-trip EXACTLY as
|
|
414
562
|
// {type:'redacted_thinking',data} — no thinking/
|
|
415
563
|
// signature fields (the API rejects the extras).
|
|
@@ -442,6 +590,10 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
442
590
|
// ttftMs was always null and reported as 0ms.
|
|
443
591
|
if (state && !state.ttftAt) state.ttftAt = Date.now();
|
|
444
592
|
if (delta?.type === 'text_delta') {
|
|
593
|
+
// Ordered verbatim text for native-block replay —
|
|
594
|
+
// independent of the leak-guard's visible-stream
|
|
595
|
+
// bookkeeping below (which may hold text back).
|
|
596
|
+
textBlocks.set(event.index, (textBlocks.get(event.index) || '') + (delta.text || ''));
|
|
445
597
|
// Live text relay (gateway): forward the explicit
|
|
446
598
|
// text chunk. thinking/signature/input_json deltas
|
|
447
599
|
// intentionally stay off this path.
|
|
@@ -473,7 +625,14 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
473
625
|
}
|
|
474
626
|
}
|
|
475
627
|
if (delta?.type === 'thinking_delta' || delta?.type === 'signature_delta') {
|
|
476
|
-
|
|
628
|
+
// Only actual reasoning TEXT counts as exposed
|
|
629
|
+
// reasoning. An empty thinking block (a
|
|
630
|
+
// signature-only delta from a display-omitted
|
|
631
|
+
// model) shows the user nothing, so it must not
|
|
632
|
+
// deny a replay.
|
|
633
|
+
if (state && delta.type === 'thinking_delta' && delta.thinking) {
|
|
634
|
+
state.emittedThinking = true;
|
|
635
|
+
}
|
|
477
636
|
// Extended-thinking block: provider reasoning without
|
|
478
637
|
// user-visible text. Track presence so a final turn
|
|
479
638
|
// that emitted ONLY thinking (no text_delta, no
|
|
@@ -501,6 +660,13 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
501
660
|
}
|
|
502
661
|
}
|
|
503
662
|
if (delta?.type === 'input_json_delta') {
|
|
663
|
+
if (pendingNativeToolInputs.has(event.index)) {
|
|
664
|
+
pendingNativeToolInputs.set(
|
|
665
|
+
event.index,
|
|
666
|
+
pendingNativeToolInputs.get(event.index) + (delta.partial_json || ''),
|
|
667
|
+
);
|
|
668
|
+
try { onStreamDelta?.('tool'); } catch {}
|
|
669
|
+
}
|
|
504
670
|
if (state) state.partialToolCall = true;
|
|
505
671
|
const pending = pendingToolInputs.get(event.index);
|
|
506
672
|
if (pending) {
|
|
@@ -511,6 +677,25 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
511
677
|
}
|
|
512
678
|
|
|
513
679
|
if (event.type === 'content_block_stop') {
|
|
680
|
+
if (pendingNativeToolInputs.has(event.index)) {
|
|
681
|
+
const rawInput = pendingNativeToolInputs.get(event.index);
|
|
682
|
+
pendingNativeToolInputs.delete(event.index);
|
|
683
|
+
const nativeBlock = nativeServerToolBlocks.get(event.index);
|
|
684
|
+
if (nativeBlock) {
|
|
685
|
+
let input = nativeBlock.input && typeof nativeBlock.input === 'object'
|
|
686
|
+
? nativeBlock.input
|
|
687
|
+
: {};
|
|
688
|
+
if (rawInput) {
|
|
689
|
+
try { input = JSON.parse(rawInput); }
|
|
690
|
+
catch (parseErr) {
|
|
691
|
+
process.stderr.write(`[anthropic-oauth] native server-tool input JSON.parse failed (type=${nativeBlock.type}, id=${nativeBlock.id || ''}): ${parseErr?.message || parseErr}\n`);
|
|
692
|
+
input = {};
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) input = {};
|
|
696
|
+
nativeBlock.input = input;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
514
699
|
const pending = pendingToolInputs.get(event.index);
|
|
515
700
|
if (pending) {
|
|
516
701
|
// Bare JSON.parse threw straight up into the
|
|
@@ -558,6 +743,15 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
558
743
|
// dispatch (the marker path is unaffected).
|
|
559
744
|
if (_toolDedupe.shouldDispatch(call.name, call.arguments)) {
|
|
560
745
|
toolCalls.push(call);
|
|
746
|
+
// Ordered replay copy of the dispatched call —
|
|
747
|
+
// skipped/deduped calls stay out so a replayed
|
|
748
|
+
// turn never carries an orphan tool_use.
|
|
749
|
+
clientToolUseBlocks.set(event.index, {
|
|
750
|
+
type: 'tool_use',
|
|
751
|
+
id: call.id,
|
|
752
|
+
name: call.name,
|
|
753
|
+
input: call.arguments,
|
|
754
|
+
});
|
|
561
755
|
if (state) state.emittedToolCall = true;
|
|
562
756
|
// Eager dispatch: let the loop start this tool
|
|
563
757
|
// before message_stop arrives. The loop keys
|
|
@@ -588,13 +782,43 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
588
782
|
usage.outputTokens = event.usage.output_tokens || 0;
|
|
589
783
|
usage.raw = { ...(usage.raw || {}), ...event.usage };
|
|
590
784
|
}
|
|
591
|
-
|
|
785
|
+
// A terminal stop_reason while ANY tool input is still
|
|
786
|
+
// streaming ends the turn truncated, immediately: the
|
|
787
|
+
// model declared it stopped sampling, so the pending
|
|
788
|
+
// tool arguments can never complete. Do not wait for
|
|
789
|
+
// message_stop/EOF (which may never arrive, or would
|
|
790
|
+
// arrive as ping-only wedge) — enter the canonical
|
|
791
|
+
// truncation failure now, with zero dispatch for the
|
|
792
|
+
// incomplete call.
|
|
793
|
+
if (event.delta?.stop_reason && _toolInputInFlight()) {
|
|
794
|
+
sawTerminalFrameWithPendingInput = true;
|
|
795
|
+
break streamLoop;
|
|
796
|
+
}
|
|
797
|
+
// Early terminal on a tool_use stop_reason is only valid
|
|
798
|
+
// when NO tool input is still streaming — client
|
|
799
|
+
// (pendingToolInputs) or Anthropic-native server tool
|
|
800
|
+
// (pendingNativeToolInputs). An in-flight input means the
|
|
801
|
+
// arguments never completed, so the turn is truncated,
|
|
802
|
+
// not finished (the guard after the loop owns it).
|
|
803
|
+
if (stopReason === 'tool_use' && toolCalls.length > 0 && !_toolInputInFlight()) {
|
|
592
804
|
if (state) state.sawCompleted = true;
|
|
593
805
|
break streamLoop;
|
|
594
806
|
}
|
|
595
807
|
}
|
|
596
808
|
if (event.type === 'message_stop') {
|
|
597
|
-
|
|
809
|
+
// A terminal frame does NOT finish a turn whose tool
|
|
810
|
+
// input is still incomplete. `message_stop` while a
|
|
811
|
+
// tool_use/server_tool_use input_json is mid-flight means
|
|
812
|
+
// the model's arguments were cut off: nothing was
|
|
813
|
+
// dispatched for that call and no partial success may be
|
|
814
|
+
// promoted. Leave sawCompleted false and fall through to
|
|
815
|
+
// the truncated-stream guard below, which throws the
|
|
816
|
+
// canonical TruncatedStreamError (pendingToolUse:true).
|
|
817
|
+
if (_toolInputInFlight()) {
|
|
818
|
+
sawTerminalFrameWithPendingInput = true;
|
|
819
|
+
} else if (state) {
|
|
820
|
+
state.sawCompleted = true;
|
|
821
|
+
}
|
|
598
822
|
// Anthropic streams can keep emitting `:ping` keepalive
|
|
599
823
|
// frames after `message_stop`; if we wait for EOF the
|
|
600
824
|
// outer reader.read() loop hangs indefinitely. Break
|
|
@@ -630,10 +854,12 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
630
854
|
// Throw a typed truncated-stream error so the loop can decide whether
|
|
631
855
|
// to retry, surface, or escalate instead of accepting the partial.
|
|
632
856
|
if (state?.sawMessageStart && !state?.sawCompleted) {
|
|
633
|
-
const pendingToolUse =
|
|
857
|
+
const pendingToolUse = _toolInputInFlight();
|
|
634
858
|
const err = Object.assign(
|
|
635
859
|
new Error(
|
|
636
|
-
|
|
860
|
+
(sawTerminalFrameWithPendingInput
|
|
861
|
+
? `Anthropic OAuth SSE stream truncated: terminal frame with incomplete tool input`
|
|
862
|
+
: `Anthropic OAuth SSE stream truncated: message_start without message_stop`)
|
|
637
863
|
+ (pendingToolUse ? ` (pending tool_use input)` : ''),
|
|
638
864
|
),
|
|
639
865
|
{
|
|
@@ -644,6 +870,43 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
644
870
|
stopReason,
|
|
645
871
|
},
|
|
646
872
|
);
|
|
873
|
+
// Completed client tool calls / native server-tool blocks captured
|
|
874
|
+
// before the cut-off ride on the error so the interrupted-turn
|
|
875
|
+
// persistence path keeps them; the INCOMPLETE call is absent (it was
|
|
876
|
+
// never pushed and never dispatched). `partialAssistantBlocks` is
|
|
877
|
+
// the SAME ordered native/client block list the success path
|
|
878
|
+
// returns as `assistantBlocks` — a native server_tool_use call and
|
|
879
|
+
// its result block are only valid in original order, so truncation
|
|
880
|
+
// must not drop the completed ones.
|
|
881
|
+
try {
|
|
882
|
+
err.partialContent = content;
|
|
883
|
+
err.partialToolCalls = toolCalls.length ? toolCalls.slice() : undefined;
|
|
884
|
+
err.partialModel = model || undefined;
|
|
885
|
+
err.partialUsage = usage;
|
|
886
|
+
err.partialHasThinking = hasThinkingContent;
|
|
887
|
+
err.partialThinkingBlocks = _orderedThinkingBlocks();
|
|
888
|
+
err.partialAssistantBlocks = _orderedAssistantBlocks();
|
|
889
|
+
err.partialStopReason = stopReason || undefined;
|
|
890
|
+
} catch { /* best-effort enrichment */ }
|
|
891
|
+
// Truncation is a continuation, never a terminal turn. A pending
|
|
892
|
+
// tool_use input alone stays replay-safe (nothing was dispatched);
|
|
893
|
+
// exposed text/reasoning or a complete tool call is not.
|
|
894
|
+
try {
|
|
895
|
+
stampStreamOutcome(err, {
|
|
896
|
+
transport: STREAM_TRANSPORTS.SSE,
|
|
897
|
+
provider: 'anthropic',
|
|
898
|
+
terminalObserved: false,
|
|
899
|
+
continuation: true,
|
|
900
|
+
truncatedStream: true,
|
|
901
|
+
textEmitted: state?.emittedText === true,
|
|
902
|
+
textObservedChars: content.length,
|
|
903
|
+
reasoningEmitted: state?.emittedThinking === true,
|
|
904
|
+
toolCallsStarted: state?.partialToolCall === true && toolCalls.length > 0,
|
|
905
|
+
toolCallsComplete: toolCalls.length,
|
|
906
|
+
toolCallsDispatched: state?.emittedToolCall === true ? Math.max(1, toolCalls.length) : 0,
|
|
907
|
+
pendingToolInput: pendingToolUse,
|
|
908
|
+
});
|
|
909
|
+
} catch { /* stamping is best-effort */ }
|
|
647
910
|
throw err;
|
|
648
911
|
}
|
|
649
912
|
|
|
@@ -660,11 +923,14 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
660
923
|
// signature) for round-tripping on tool-continuation turns. Emitted
|
|
661
924
|
// in content_block index order. Empty thinking + signature is a
|
|
662
925
|
// valid block (display-omitted models) and is kept intact.
|
|
663
|
-
thinkingBlocks:
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
926
|
+
thinkingBlocks: _orderedThinkingBlocks(),
|
|
927
|
+
// Complete ordered assistant content, emitted ONLY when the turn
|
|
928
|
+
// used Anthropic native server tools. Those blocks cannot be
|
|
929
|
+
// reconstructed from content/toolCalls/thinkingBlocks, so the loop
|
|
930
|
+
// replays this list verbatim (see toAnthropicMessages'
|
|
931
|
+
// assistantBlocks branch). Absent for every ordinary turn, which
|
|
932
|
+
// keeps the existing text/thinking/tool_use lowering untouched.
|
|
933
|
+
assistantBlocks: _orderedAssistantBlocks(),
|
|
668
934
|
};
|
|
669
935
|
} finally {
|
|
670
936
|
if (idleTimer) clearTimeout(idleTimer);
|
|
@@ -697,3 +963,36 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
697
963
|
export function _classifyMidstreamError(err, state) {
|
|
698
964
|
return classifyMidstreamError(err, state, SSE_MIDSTREAM_POLICY);
|
|
699
965
|
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Wrapper-level canonical stamp for the Anthropic providers (API key + OAuth).
|
|
969
|
+
*
|
|
970
|
+
* The PARSER is the authority on what the stream produced: it knows the
|
|
971
|
+
* difference between "a tool_use block started" (coarse
|
|
972
|
+
* `midState.partialToolCall`, set on the first input_json_delta) and "a tool
|
|
973
|
+
* input never completed and was never dispatched" (`pendingToolInput`, which
|
|
974
|
+
* stays replay-safe). Merging the coarse mid-state flag over a stamped parser
|
|
975
|
+
* verdict would downgrade an idempotent truncation into a non-replayable one.
|
|
976
|
+
*
|
|
977
|
+
* So: when the error already carries an authoritative parser record, only
|
|
978
|
+
* genuinely NEW exposure evidence observed by the wrapper is merged (relayed
|
|
979
|
+
* text, exposed thinking, dispatched tool call). Otherwise (transport/SDK
|
|
980
|
+
* errors that never reached the parser) the full mid-state is stamped.
|
|
981
|
+
*/
|
|
982
|
+
export function stampAnthropicStreamOutcome(err, midState, { provider = 'anthropic' } = {}) {
|
|
983
|
+
const hasParserVerdict = err?.streamOutcome?.version === STREAM_OUTCOME_VERSION;
|
|
984
|
+
if (hasParserVerdict) {
|
|
985
|
+
return stampStreamOutcome(err, {
|
|
986
|
+
transport: STREAM_TRANSPORTS.SSE,
|
|
987
|
+
provider,
|
|
988
|
+
textEmitted: midState?.emittedText === true,
|
|
989
|
+
reasoningEmitted: midState?.emittedThinking === true,
|
|
990
|
+
toolCallsDispatched: midState?.emittedToolCall === true ? 1 : 0,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
return stampStreamOutcome(err, midState || {}, {
|
|
994
|
+
transport: STREAM_TRANSPORTS.SSE,
|
|
995
|
+
provider,
|
|
996
|
+
continuation: midState?.sawCompleted !== true,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
withRetry,
|
|
14
14
|
retryAfterMsFromError,
|
|
15
15
|
} from './retry-classifier.mjs';
|
|
16
|
+
import { readStreamOutcome } from './lib/stream-outcome.mjs';
|
|
16
17
|
import { traceAgentUsage } from '../agent-trace.mjs';
|
|
17
18
|
import {
|
|
18
19
|
PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
ANTHROPIC_MAX_MIDSTREAM_RETRIES,
|
|
25
26
|
parseSSEStream,
|
|
26
27
|
_classifyMidstreamError,
|
|
28
|
+
stampAnthropicStreamOutcome,
|
|
27
29
|
} from './anthropic-sse.mjs';
|
|
28
30
|
import { buildAnthropicBetaHeaders, supportsAnthropicFastMode } from './anthropic-betas.mjs';
|
|
29
31
|
import {
|
|
@@ -108,15 +110,11 @@ export class AnthropicProvider {
|
|
|
108
110
|
return await this._doSend(messages, model, tools, sendOpts);
|
|
109
111
|
} catch (err) {
|
|
110
112
|
const status = Number(err?.status || err?.httpStatus || err?.response?.status || 0);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|| err?.emittedThinking === true
|
|
117
|
-
|| err?.unsafeToRetry === true;
|
|
118
|
-
if (status === 401
|
|
119
|
-
&& !outputWasExposed) {
|
|
113
|
+
// Auth recovery re-issues the whole turn, so it is a REPLAY: a
|
|
114
|
+
// typed 401 is re-sent once after reloading the key, unless the
|
|
115
|
+
// failure already exposed output or dispatched a tool call.
|
|
116
|
+
const replayPermitted = readStreamOutcome(err).replaySafe === true;
|
|
117
|
+
if (status === 401 && replayPermitted) {
|
|
120
118
|
process.stderr.write(`[provider] Auth error, re-reading provider authentication...\n`);
|
|
121
119
|
this.reloadApiKey();
|
|
122
120
|
return await this._doSend(messages, model, tools, sendOpts);
|
|
@@ -297,6 +295,16 @@ export class AnthropicProvider {
|
|
|
297
295
|
thinkingBlocks: Array.isArray(parseResult.thinkingBlocks) && parseResult.thinkingBlocks.length
|
|
298
296
|
? parseResult.thinkingBlocks
|
|
299
297
|
: undefined,
|
|
298
|
+
// Ordered NATIVE server-tool blocks (server_tool_use +
|
|
299
|
+
// *_tool_result) exist ONLY in this verbatim list — they are
|
|
300
|
+
// never dispatched as client tool calls and cannot be rebuilt
|
|
301
|
+
// from content/toolCalls/thinkingBlocks. Dropping them here
|
|
302
|
+
// broke `pause_turn` continuations on the API-key path (the
|
|
303
|
+
// OAuth provider returns the parse result as-is). Absent for
|
|
304
|
+
// ordinary turns, so nothing else changes.
|
|
305
|
+
assistantBlocks: Array.isArray(parseResult.assistantBlocks) && parseResult.assistantBlocks.length
|
|
306
|
+
? parseResult.assistantBlocks
|
|
307
|
+
: undefined,
|
|
300
308
|
usage: {
|
|
301
309
|
inputTokens: input,
|
|
302
310
|
outputTokens: output,
|
|
@@ -511,6 +519,14 @@ export class AnthropicProvider {
|
|
|
511
519
|
_fallbackTriggered: true,
|
|
512
520
|
});
|
|
513
521
|
}
|
|
522
|
+
// Canonical stream-outcome contract (same record as the
|
|
523
|
+
// OAuth SSE / WS / HTTP-SSE transports). The parser verdict
|
|
524
|
+
// wins over coarse midState flags; new wrapper-observed
|
|
525
|
+
// exposure is still merged. Aliases preserved.
|
|
526
|
+
let _outcome = null;
|
|
527
|
+
try {
|
|
528
|
+
_outcome = stampAnthropicStreamOutcome(err, midState, { provider: 'anthropic' });
|
|
529
|
+
} catch { /* stamping is best-effort */ }
|
|
514
530
|
// Acknowledged reset semantics let the owner tombstone this
|
|
515
531
|
// attempt before the full request is restarted non-streaming.
|
|
516
532
|
// Without that acknowledgement, recoverNonStreaming stamps
|
|
@@ -518,13 +534,14 @@ export class AnthropicProvider {
|
|
|
518
534
|
if (midState.emittedText) {
|
|
519
535
|
return await recoverNonStreaming(midState, err, streamController);
|
|
520
536
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
537
|
+
// Real exposure (dispatched tool, exposed thinking, or a
|
|
538
|
+
// parser-recorded started/complete call) is a replay
|
|
539
|
+
// boundary. The canonical merge above already recorded it
|
|
540
|
+
// AND wrote the compatibility aliases; writing coarse
|
|
541
|
+
// midState flags here would DOWNGRADE the parser's
|
|
542
|
+
// authoritative verdict that an incomplete, never
|
|
543
|
+
// dispatched tool input is still replay-safe.
|
|
544
|
+
if (_outcome?.replayUnsafe === true) {
|
|
528
545
|
try { streamController.abort?.(err); } catch {}
|
|
529
546
|
throw err;
|
|
530
547
|
}
|
|
@@ -546,9 +563,7 @@ export class AnthropicProvider {
|
|
|
546
563
|
}
|
|
547
564
|
if (classifyError(err) === 'transient'
|
|
548
565
|
&& !midState.sawMessageStart
|
|
549
|
-
&&
|
|
550
|
-
&& !midState.partialToolCall
|
|
551
|
-
&& !midState.emittedThinking
|
|
566
|
+
&& _outcome?.replayUnsafe !== true
|
|
552
567
|
&& attemptIndex < MAX_MIDSTREAM_RETRIES) {
|
|
553
568
|
firstAttemptError = err;
|
|
554
569
|
firstAttemptClassifier = err?.providerErrorType || 'sse_transient';
|
|
@@ -563,9 +578,7 @@ export class AnthropicProvider {
|
|
|
563
578
|
}
|
|
564
579
|
if ((err?.truncatedStream === true || err?.code === 'TRUNCATED_STREAM')
|
|
565
580
|
&& classifyError(err) === 'transient'
|
|
566
|
-
&&
|
|
567
|
-
&& !midState.partialToolCall
|
|
568
|
-
&& !midState.emittedThinking
|
|
581
|
+
&& _outcome?.replayUnsafe !== true
|
|
569
582
|
&& attemptIndex < MAX_MIDSTREAM_RETRIES) {
|
|
570
583
|
firstAttemptError = err;
|
|
571
584
|
firstAttemptClassifier = 'truncated_stream';
|