mixdog 0.9.1 → 0.9.2
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 +8 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +306 -66
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +4 -2
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +4 -2
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +710 -318
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +12 -2
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +7 -8
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +7 -0
- package/src/rules/shared/01-tool.md +17 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +332 -57
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +59 -32
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +78 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +202 -98
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +183 -20
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +15 -9
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
- package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
- package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +441 -1224
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/output-forwarder.mjs +80 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +283 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +184 -19
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory.mjs +101 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +6 -3
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +129 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/standalone/agent-tool.mjs +255 -109
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +8 -291
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/provider-admin.mjs +11 -0
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2096 -732
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +83 -7
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +170 -98
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +159 -21
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6965 -2391
- package/src/tui/engine.mjs +287 -126
- package/src/tui/index.jsx +117 -7
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +453 -0
- package/src/tui/markdown/format-token.mjs +129 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +0 -11
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +81 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +26 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +39 -12
- package/src/workflows/sequential/WORKFLOW.md +46 -0
- package/src/workflows/solo/WORKFLOW.md +7 -0
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Single dispatch path for the openai-oauth provider (SSE removed in
|
|
5
5
|
* v0.6.117). Uses the `responses_websockets=2026-02-06` beta WebSocket
|
|
6
6
|
* upgrade on chatgpt.com/backend-api/codex/responses. Per-session
|
|
7
|
-
* connections are pooled (
|
|
7
|
+
* connections are pooled (configurable idle TTL, up to 8 parallel sockets per
|
|
8
8
|
* key) so subsequent tool-loop iterations can send only the incremental
|
|
9
9
|
* `input` delta plus `previous_response_id`, skipping the full
|
|
10
10
|
* tools/system/history prefix each turn.
|
|
@@ -43,12 +43,16 @@ import {
|
|
|
43
43
|
sleepWithAbort,
|
|
44
44
|
} from './retry-classifier.mjs';
|
|
45
45
|
import { makeInvalidToolArgsMarker } from './openai-compat-stream.mjs';
|
|
46
|
+
import { createLeakGuard, createToolCallDedupe, dedupeToolCallList } from './anthropic-leaked-toolcall.mjs';
|
|
46
47
|
import {
|
|
47
48
|
PROVIDER_RETRY_MAX_ATTEMPTS,
|
|
48
49
|
PROVIDER_WS_ACQUIRE_TIMEOUT_MS,
|
|
49
|
-
PROVIDER_WS_FIRST_MEANINGFUL_TIMEOUT_MS,
|
|
50
50
|
PROVIDER_WS_HANDSHAKE_TIMEOUT_MS,
|
|
51
51
|
PROVIDER_WS_INTER_CHUNK_TIMEOUT_MS,
|
|
52
|
+
PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
|
|
53
|
+
PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
|
|
54
|
+
streamStalledError,
|
|
55
|
+
resolveTimeoutMs,
|
|
52
56
|
} from '../stall-policy.mjs';
|
|
53
57
|
import { customToolCallFromResponseItem } from './custom-tool-wire.mjs';
|
|
54
58
|
|
|
@@ -58,17 +62,19 @@ const CODEX_WS_URL = 'wss://chatgpt.com/backend-api/codex/responses';
|
|
|
58
62
|
const CODEX_OAUTH_ORIGINATOR = 'codex_cli_rs';
|
|
59
63
|
const OPENAI_WS_URL = 'wss://api.openai.com/v1/responses';
|
|
60
64
|
const XAI_WS_URL = 'wss://api.x.ai/v1/responses';
|
|
61
|
-
const WS_IDLE_MS =
|
|
65
|
+
const WS_IDLE_MS = resolveTimeoutMs(
|
|
66
|
+
'MIXDOG_PROVIDER_WS_IDLE_MS',
|
|
67
|
+
20 * 60_000,
|
|
68
|
+
{ minMs: 60_000, maxMs: 60 * 60_000 },
|
|
69
|
+
);
|
|
62
70
|
const WS_HANDSHAKE_TIMEOUT_MS = PROVIDER_WS_HANDSHAKE_TIMEOUT_MS;
|
|
63
71
|
const WS_ACQUIRE_TIMEOUT_MS = PROVIDER_WS_ACQUIRE_TIMEOUT_MS;
|
|
64
|
-
// Pre-stream watchdog uses the shared provider deadline so it fails before
|
|
65
|
-
// the 5-minute session slow warning.
|
|
66
|
-
const WS_FIRST_MEANINGFUL_MS = PROVIDER_WS_FIRST_MEANINGFUL_TIMEOUT_MS;
|
|
67
72
|
// Pre-`response.created` deadline. Once the socket is open and the
|
|
68
73
|
// response.create frame is sent, a healthy server emits response.created
|
|
69
74
|
// within seconds. If it stalls past this short bound the socket has wedged
|
|
70
75
|
// post-upgrade with zero server events — treat it as a fast, retryable
|
|
71
|
-
// first-byte timeout
|
|
76
|
+
// first-byte timeout. This is the ONLY pre-stream watchdog; once any server
|
|
77
|
+
// event arrives the single inter-chunk idle timer below takes over.
|
|
72
78
|
// Only this short window is shortened; the post-`response.created`
|
|
73
79
|
// inter-chunk / reasoning span keeps the longer deadlines below.
|
|
74
80
|
const WS_PRE_RESPONSE_CREATED_MS = (() => {
|
|
@@ -77,7 +83,9 @@ const WS_PRE_RESPONSE_CREATED_MS = (() => {
|
|
|
77
83
|
if (Number.isFinite(n) && n > 0) return Math.min(Math.max(n, 1_000), 120_000);
|
|
78
84
|
return 10_000;
|
|
79
85
|
})();
|
|
80
|
-
//
|
|
86
|
+
// Single inter-chunk idle timer. Resets on EVERY received frame (matches codex
|
|
87
|
+
// responses_websocket.rs timeout(idle_timeout, ws_stream.next()) and mixdog
|
|
88
|
+
// anthropic-oauth resetIdleTimer on every chunk).
|
|
81
89
|
const WS_INTER_CHUNK_MS = PROVIDER_WS_INTER_CHUNK_TIMEOUT_MS;
|
|
82
90
|
// Mid-stream retry budgets + backoff now live in the shared MIDSTREAM_RETRY_POLICY
|
|
83
91
|
// table (retry-classifier.mjs). These aliases keep the local call sites readable
|
|
@@ -1233,11 +1241,11 @@ export async function _streamResponse({
|
|
|
1233
1241
|
logSuppressedReasoningDeltas = true,
|
|
1234
1242
|
traceProvider = 'openai-oauth',
|
|
1235
1243
|
_timeouts = null,
|
|
1244
|
+
knownToolNames = null,
|
|
1236
1245
|
}) {
|
|
1237
1246
|
const errLabel = _wsErrLabel(traceProvider);
|
|
1238
1247
|
const socket = entry.socket;
|
|
1239
1248
|
const preResponseCreatedMs = _positiveInt(_timeouts?.preResponseCreatedMs, WS_PRE_RESPONSE_CREATED_MS);
|
|
1240
|
-
const firstMeaningfulMs = _positiveInt(_timeouts?.firstMeaningfulMs, WS_FIRST_MEANINGFUL_MS);
|
|
1241
1249
|
const interChunkMs = _positiveInt(_timeouts?.interChunkMs, WS_INTER_CHUNK_MS);
|
|
1242
1250
|
const _streamingStart = Date.now();
|
|
1243
1251
|
let _firstDeltaEmitted = false;
|
|
@@ -1253,6 +1261,19 @@ export async function _streamResponse({
|
|
|
1253
1261
|
const citations = [];
|
|
1254
1262
|
const citationKeys = new Set();
|
|
1255
1263
|
const pendingCalls = new Map();
|
|
1264
|
+
// Tool-work-in-flight flag: set the moment a function/custom tool call's
|
|
1265
|
+
// input starts streaming (before it lands in pendingCalls/toolCalls).
|
|
1266
|
+
// Gates partial-final SUCCESS so a stall mid tool-input never looks text-only.
|
|
1267
|
+
let _toolInFlight = false;
|
|
1268
|
+
// Fix 2: cross-path name+args dedupe. A text-leaked synthetic and an
|
|
1269
|
+
// identical native function_call must fire onToolCall exactly once. Every
|
|
1270
|
+
// dispatch site routes through emitToolCallDedupe.
|
|
1271
|
+
const _toolDedupe = createToolCallDedupe();
|
|
1272
|
+
const emitToolCallDedupe = (call) => {
|
|
1273
|
+
if (!_toolDedupe.shouldDispatch(call?.name, call?.arguments)) return;
|
|
1274
|
+
midState.emittedToolCall = true;
|
|
1275
|
+
try { onToolCall?.(call); } catch {}
|
|
1276
|
+
};
|
|
1256
1277
|
// Reasoning items collected from response.output_item.done (or salvaged
|
|
1257
1278
|
// from response.completed.response.output). The request still includes
|
|
1258
1279
|
// `reasoning.encrypted_content` so the server keeps emitting the blobs,
|
|
@@ -1340,8 +1361,7 @@ export async function _streamResponse({
|
|
|
1340
1361
|
const call = customToolCallFromResponseItem(item);
|
|
1341
1362
|
if (!call || toolCalls.some((existing) => existing.id === call.id)) return;
|
|
1342
1363
|
toolCalls.push(call);
|
|
1343
|
-
|
|
1344
|
-
try { onToolCall?.(call); } catch {}
|
|
1364
|
+
emitToolCallDedupe(call);
|
|
1345
1365
|
};
|
|
1346
1366
|
const pushToolSearchCall = (item) => {
|
|
1347
1367
|
if (!item || item.type !== 'tool_search_call') return;
|
|
@@ -1354,9 +1374,60 @@ export async function _streamResponse({
|
|
|
1354
1374
|
nativeType: 'tool_search_call',
|
|
1355
1375
|
};
|
|
1356
1376
|
toolCalls.push(call);
|
|
1377
|
+
emitToolCallDedupe(call);
|
|
1378
|
+
};
|
|
1379
|
+
// Leaked tool-call guard. The model sometimes emits a tool call as plain
|
|
1380
|
+
// text (XML `<invoke>`/`<function_calls>` or gpt-oss harmony
|
|
1381
|
+
// `<|channel|>...to=functions.NAME...<|call|>`) inside
|
|
1382
|
+
// `response.output_text.delta` instead of a native function_call. Route
|
|
1383
|
+
// text through the guard so leaked calls are suppressed from the visible
|
|
1384
|
+
// stream, synthesized (native `call_...` id shape), and dispatched like
|
|
1385
|
+
// native ones. Additive: the native function_call path is untouched.
|
|
1386
|
+
const leakGuard = createLeakGuard({ knownToolNames, harmony: true });
|
|
1387
|
+
const dispatchLeakedCall = (recovered) => {
|
|
1388
|
+
let args = recovered?.arguments;
|
|
1389
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args)) args = {};
|
|
1390
|
+
const call = {
|
|
1391
|
+
id: `call_leaked_${randomBytes(8).toString('hex')}`,
|
|
1392
|
+
name: recovered.name,
|
|
1393
|
+
arguments: args,
|
|
1394
|
+
};
|
|
1395
|
+
if (!_toolDedupe.shouldDispatch(call.name, call.arguments)) return;
|
|
1396
|
+
toolCalls.push(call);
|
|
1357
1397
|
midState.emittedToolCall = true;
|
|
1358
1398
|
try { onToolCall?.(call); } catch {}
|
|
1359
1399
|
};
|
|
1400
|
+
const relayLeakText = (delta) => {
|
|
1401
|
+
if (!leakGuard.enabled) {
|
|
1402
|
+
content += delta || '';
|
|
1403
|
+
if (delta && onTextDelta) {
|
|
1404
|
+
if (state) state.emittedText = true;
|
|
1405
|
+
try { onTextDelta(delta); } catch {}
|
|
1406
|
+
}
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const { text, calls } = leakGuard.push(delta);
|
|
1410
|
+
if (text) {
|
|
1411
|
+
content += text;
|
|
1412
|
+
if (onTextDelta) {
|
|
1413
|
+
if (state) state.emittedText = true;
|
|
1414
|
+
try { onTextDelta(text); } catch {}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
1418
|
+
};
|
|
1419
|
+
const flushLeak = () => {
|
|
1420
|
+
if (!leakGuard.enabled) return;
|
|
1421
|
+
const { text, calls } = leakGuard.flush();
|
|
1422
|
+
if (text) {
|
|
1423
|
+
content += text;
|
|
1424
|
+
if (onTextDelta) {
|
|
1425
|
+
if (state) state.emittedText = true;
|
|
1426
|
+
try { onTextDelta(text); } catch {}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
1430
|
+
};
|
|
1360
1431
|
const logReasoningDeltaSuppression = () => {
|
|
1361
1432
|
if (!logSuppressedReasoningDeltas) return;
|
|
1362
1433
|
const total = reasoningTextDeltaCount + reasoningSummaryTextDeltaCount + reasoningOtherDeltaCount;
|
|
@@ -1385,6 +1456,16 @@ export async function _streamResponse({
|
|
|
1385
1456
|
let messageHandler = null;
|
|
1386
1457
|
let closeHandler = null;
|
|
1387
1458
|
let errorHandler = null;
|
|
1459
|
+
// SEMANTIC idle timer (pi per-event parity): distinct from the inter-chunk
|
|
1460
|
+
// timer, which resets on EVERY frame (rate_limits/metadata/keepalive keep
|
|
1461
|
+
// the socket "alive"). This timer resets ONLY on meaningful output deltas
|
|
1462
|
+
// (text/reasoning/tool args — the same events that call onStreamDelta) so a
|
|
1463
|
+
// stream that emits some deltas then goes silent (server keepalive frames
|
|
1464
|
+
// only) trips a short, named terminal StreamStalledError instead of coasting
|
|
1465
|
+
// to the 300s inter-chunk cap / 30-min agent watchdog.
|
|
1466
|
+
let semanticIdleTimer = null;
|
|
1467
|
+
const semanticIdleMs = PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS;
|
|
1468
|
+
const semanticIdleEnabled = PROVIDER_SSE_IDLE_WATCHDOG_ENABLED && semanticIdleMs > 0;
|
|
1388
1469
|
|
|
1389
1470
|
return new Promise((resolve, reject) => {
|
|
1390
1471
|
// Pre-stream watchdog: the timer fires if the server never sends a
|
|
@@ -1392,10 +1473,10 @@ export async function _streamResponse({
|
|
|
1392
1473
|
// after our last frame. The socket is open and the response.create
|
|
1393
1474
|
// frame was sent, but no server event has come back — a wedged
|
|
1394
1475
|
// post-upgrade socket. Healthy servers ack within seconds, so this
|
|
1395
|
-
// window is intentionally short (~
|
|
1396
|
-
//
|
|
1397
|
-
// the
|
|
1398
|
-
//
|
|
1476
|
+
// window is intentionally short (WS_PRE_RESPONSE_CREATED_MS, ~10s).
|
|
1477
|
+
// Once ANY server event arrives, resetIdle() cancels this watchdog and
|
|
1478
|
+
// the single inter-chunk idle timer takes over — silent gaps
|
|
1479
|
+
// mid-reasoning (openai-oauth spending 50s+ producing reasoning
|
|
1399
1480
|
// tokens) are normal and should not abort the turn.
|
|
1400
1481
|
const armPreStreamWatchdog = () => {
|
|
1401
1482
|
if (idleTimer) clearTimeout(idleTimer);
|
|
@@ -1427,8 +1508,6 @@ export async function _streamResponse({
|
|
|
1427
1508
|
}, preResponseCreatedMs);
|
|
1428
1509
|
};
|
|
1429
1510
|
let interChunkTimer = null;
|
|
1430
|
-
let firstMeaningfulTimer = null;
|
|
1431
|
-
let firstMeaningfulSeen = false;
|
|
1432
1511
|
const traceWsTimeout = (event, timeoutMs) => {
|
|
1433
1512
|
try {
|
|
1434
1513
|
const iteration = Number(midState.iteration);
|
|
@@ -1443,7 +1522,6 @@ export async function _streamResponse({
|
|
|
1443
1522
|
attempt_index: Number.isFinite(attemptIndex) ? attemptIndex : null,
|
|
1444
1523
|
warmup: midState.warmup === true,
|
|
1445
1524
|
saw_response_created: midState.sawResponseCreated === true,
|
|
1446
|
-
first_meaningful_seen: firstMeaningfulSeen === true,
|
|
1447
1525
|
};
|
|
1448
1526
|
appendAgentTrace({
|
|
1449
1527
|
sessionId: midState.sessionId || null,
|
|
@@ -1460,29 +1538,6 @@ export async function _streamResponse({
|
|
|
1460
1538
|
idleTimer = null;
|
|
1461
1539
|
}
|
|
1462
1540
|
};
|
|
1463
|
-
const clearFirstMeaningfulWatchdog = () => {
|
|
1464
|
-
if (firstMeaningfulTimer) {
|
|
1465
|
-
clearTimeout(firstMeaningfulTimer);
|
|
1466
|
-
firstMeaningfulTimer = null;
|
|
1467
|
-
}
|
|
1468
|
-
};
|
|
1469
|
-
const armFirstMeaningfulWatchdog = () => {
|
|
1470
|
-
if (firstMeaningfulSeen || firstMeaningfulMs <= 0) return;
|
|
1471
|
-
clearFirstMeaningfulWatchdog();
|
|
1472
|
-
firstMeaningfulTimer = setTimeout(() => {
|
|
1473
|
-
if (process.env.MIXDOG_DEBUG_AGENT) {
|
|
1474
|
-
process.stderr.write(`[agent-trace] ws-timeout kind=first-meaningful afterMs=${firstMeaningfulMs}\n`);
|
|
1475
|
-
}
|
|
1476
|
-
traceWsTimeout('first_meaningful_timeout', firstMeaningfulMs);
|
|
1477
|
-
const err = new Error(`WS stream: no meaningful output within ${firstMeaningfulMs}ms after response.created`);
|
|
1478
|
-
err.wsCloseCode = 4000;
|
|
1479
|
-
err.firstMeaningfulTimeout = true;
|
|
1480
|
-
midState.firstMeaningfulTimeout = true;
|
|
1481
|
-
terminalError = err;
|
|
1482
|
-
try { socket.close(4000, 'first_meaningful_timeout'); } catch {}
|
|
1483
|
-
finish();
|
|
1484
|
-
}, firstMeaningfulMs);
|
|
1485
|
-
};
|
|
1486
1541
|
const resetInterChunk = () => {
|
|
1487
1542
|
if (interChunkTimer) clearTimeout(interChunkTimer);
|
|
1488
1543
|
interChunkTimer = setTimeout(() => {
|
|
@@ -1497,30 +1552,49 @@ export async function _streamResponse({
|
|
|
1497
1552
|
finish();
|
|
1498
1553
|
}, interChunkMs);
|
|
1499
1554
|
};
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1555
|
+
// pi per-event idle: (re)armed only on meaningful output deltas via
|
|
1556
|
+
// bumpSemanticIdle(). Keepalive/metadata frames DON'T touch it, so a
|
|
1557
|
+
// deltas-then-silent wedge trips this short semantic window.
|
|
1558
|
+
const resetSemanticIdle = () => {
|
|
1559
|
+
if (!semanticIdleEnabled) return;
|
|
1560
|
+
if (semanticIdleTimer) clearTimeout(semanticIdleTimer);
|
|
1561
|
+
semanticIdleTimer = setTimeout(() => {
|
|
1562
|
+
traceWsTimeout('semantic_idle_timeout', semanticIdleMs);
|
|
1563
|
+
terminalError = streamStalledError('Responses WS', semanticIdleMs, { emittedToolCall: !!midState?.emittedToolCall });
|
|
1564
|
+
// Partial-final recovery parity: attach streamed partial state so
|
|
1565
|
+
// a wedged FINAL no-tool summary can be accepted as partial-final
|
|
1566
|
+
// success by the loop. pendingToolUse gates out mid-flight tools.
|
|
1567
|
+
try {
|
|
1568
|
+
terminalError.partialContent = content;
|
|
1569
|
+
terminalError.partialToolCalls = toolCalls.length ? toolCalls.slice() : undefined;
|
|
1570
|
+
terminalError.pendingToolUse = pendingCalls.size > 0
|
|
1571
|
+
|| !!midState?.emittedToolCall
|
|
1572
|
+
|| toolCalls.length > 0
|
|
1573
|
+
|| _toolInFlight === true;
|
|
1574
|
+
terminalError.partialModel = model || undefined;
|
|
1575
|
+
} catch { /* best-effort enrichment */ }
|
|
1576
|
+
try { terminalError.wsCloseCode = 4000; } catch {}
|
|
1577
|
+
try { socket.close(4000, 'semantic_idle_timeout'); } catch {}
|
|
1578
|
+
finish();
|
|
1579
|
+
}, semanticIdleMs);
|
|
1580
|
+
try { semanticIdleTimer.unref?.(); } catch {}
|
|
1504
1581
|
};
|
|
1505
|
-
//
|
|
1506
|
-
//
|
|
1507
|
-
//
|
|
1508
|
-
//
|
|
1509
|
-
//
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
firstMeaningfulSeen = true;
|
|
1513
|
-
clearPreStreamWatchdog();
|
|
1514
|
-
clearFirstMeaningfulWatchdog();
|
|
1515
|
-
}
|
|
1582
|
+
// Single idle reset — called on EVERY parsed server event (matches
|
|
1583
|
+
// codex, which resets one idle timer on every received WS frame). Any
|
|
1584
|
+
// frame proves the socket is live; there is no separate "meaningful
|
|
1585
|
+
// output" gate. Also clears the pre-stream watchdog defensively in case
|
|
1586
|
+
// the first event is not response.created.
|
|
1587
|
+
const resetIdle = () => {
|
|
1588
|
+
clearPreStreamWatchdog();
|
|
1516
1589
|
resetInterChunk();
|
|
1517
1590
|
};
|
|
1518
|
-
//
|
|
1519
|
-
|
|
1591
|
+
// Meaningful-output progress bump: called by the same delta cases that
|
|
1592
|
+
// call onStreamDelta (text/reasoning/tool args). Arms the semantic idle.
|
|
1593
|
+
const bumpSemanticIdle = () => { resetSemanticIdle(); };
|
|
1520
1594
|
const cleanup = () => {
|
|
1521
1595
|
if (idleTimer) clearTimeout(idleTimer);
|
|
1522
|
-
clearFirstMeaningfulWatchdog();
|
|
1523
1596
|
if (interChunkTimer) { clearTimeout(interChunkTimer); interChunkTimer = null; }
|
|
1597
|
+
if (semanticIdleTimer) { clearTimeout(semanticIdleTimer); semanticIdleTimer = null; }
|
|
1524
1598
|
if (keepaliveTimer) { clearInterval(keepaliveTimer); keepaliveTimer = null; }
|
|
1525
1599
|
if (messageHandler) socket.off('message', messageHandler);
|
|
1526
1600
|
if (closeHandler) socket.off('close', closeHandler);
|
|
@@ -1529,6 +1603,9 @@ export async function _streamResponse({
|
|
|
1529
1603
|
};
|
|
1530
1604
|
const finish = () => {
|
|
1531
1605
|
logReasoningDeltaSuppression();
|
|
1606
|
+
// Flush any partial-sentinel tail held back mid-stream so
|
|
1607
|
+
// legitimate trailing text is never lost (streamed-text path).
|
|
1608
|
+
flushLeak();
|
|
1532
1609
|
cleanup();
|
|
1533
1610
|
if (terminalError) { reject(terminalError); return; }
|
|
1534
1611
|
resolve({
|
|
@@ -1536,11 +1613,17 @@ export async function _streamResponse({
|
|
|
1536
1613
|
model,
|
|
1537
1614
|
reasoningItems: reasoningItems.length ? reasoningItems : undefined,
|
|
1538
1615
|
responseItems: responseItemsAdded.length ? responseItemsAdded : undefined,
|
|
1539
|
-
|
|
1616
|
+
// Dedupe by name+args (Fix 2, array side) so an identical
|
|
1617
|
+
// synthetic-leaked + native pair can't run the tool twice.
|
|
1618
|
+
toolCalls: toolCalls.length ? dedupeToolCallList(toolCalls) : undefined,
|
|
1540
1619
|
citations: citations.length ? citations : undefined,
|
|
1541
1620
|
webSearchCalls: webSearchCalls.length ? webSearchCalls : undefined,
|
|
1542
1621
|
usage,
|
|
1543
1622
|
stopReason: stopReason || undefined,
|
|
1623
|
+
// P1 audit fix: mirror the HTTP/SSE fallback's truncated flag
|
|
1624
|
+
// for the WS path (sendViaWebSocket spreads this result
|
|
1625
|
+
// through to the provider caller unchanged).
|
|
1626
|
+
...(stopReason === 'length' && content.length > 0 ? { truncated: true } : {}),
|
|
1544
1627
|
incompleteReason: incompleteReason || undefined,
|
|
1545
1628
|
responseId: responseId || undefined,
|
|
1546
1629
|
serviceTier: responseServiceTier || undefined,
|
|
@@ -1549,7 +1632,10 @@ export async function _streamResponse({
|
|
|
1549
1632
|
|
|
1550
1633
|
messageHandler = (data) => {
|
|
1551
1634
|
resetIdle();
|
|
1552
|
-
//
|
|
1635
|
+
// resetIdle() above resets the SINGLE inter-chunk idle timer on
|
|
1636
|
+
// EVERY received frame (codex parity) — response.created, metadata,
|
|
1637
|
+
// rate_limits, and all deltas keep the socket alive. Separately, do
|
|
1638
|
+
// NOT call onStreamDelta for every frame — metadata/keepalive frames
|
|
1553
1639
|
// must not reset the agent stall watchdog's lastStreamDeltaAt. Only
|
|
1554
1640
|
// meaningful output (text delta / tool call) updates that timestamp.
|
|
1555
1641
|
const text = typeof data === 'string' ? data : data.toString('utf-8');
|
|
@@ -1571,13 +1657,11 @@ export async function _streamResponse({
|
|
|
1571
1657
|
midState.sawResponseCreated = true;
|
|
1572
1658
|
if (event.response?.model) model = event.response.model;
|
|
1573
1659
|
if (event.response?.id) responseId = event.response.id;
|
|
1574
|
-
// Server ack
|
|
1575
|
-
//
|
|
1576
|
-
//
|
|
1577
|
-
onResponseCreated();
|
|
1660
|
+
// Server ack (first event). resetIdle() at the top of
|
|
1661
|
+
// messageHandler already cleared the pre-stream watchdog and
|
|
1662
|
+
// armed the single idle timer — no extra bookkeeping here.
|
|
1578
1663
|
break;
|
|
1579
1664
|
case 'response.output_text.delta':
|
|
1580
|
-
content += event.delta || '';
|
|
1581
1665
|
try {
|
|
1582
1666
|
if (!_firstDeltaEmitted) {
|
|
1583
1667
|
_firstDeltaEmitted = true;
|
|
@@ -1587,6 +1671,7 @@ export async function _streamResponse({
|
|
|
1587
1671
|
}
|
|
1588
1672
|
onStreamDelta?.();
|
|
1589
1673
|
} catch {}
|
|
1674
|
+
bumpSemanticIdle();
|
|
1590
1675
|
// Live text relay (gateway): forward the raw text chunk so
|
|
1591
1676
|
// the client renders first tokens before the final replay.
|
|
1592
1677
|
// Tool-call/argument deltas intentionally stay off this path.
|
|
@@ -1594,11 +1679,10 @@ export async function _streamResponse({
|
|
|
1594
1679
|
// cannot be withdrawn, so flag the attempt so a later
|
|
1595
1680
|
// mid-stream/truncated failure is NOT retried (retry would
|
|
1596
1681
|
// concatenate a second attempt onto rendered text).
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
onMeaningfulOutput();
|
|
1682
|
+
// Routed through the leaked-tool-call guard: appends to
|
|
1683
|
+
// `content`, forwards visible text via onTextDelta, and
|
|
1684
|
+
// recovers/dispatches any leaked known-tool call.
|
|
1685
|
+
relayLeakText(event.delta || '');
|
|
1602
1686
|
break;
|
|
1603
1687
|
case 'response.reasoning_text.delta':
|
|
1604
1688
|
case 'response.reasoning_summary_text.delta':
|
|
@@ -1606,16 +1690,12 @@ export async function _streamResponse({
|
|
|
1606
1690
|
else reasoningSummaryTextDeltaCount += 1;
|
|
1607
1691
|
// Reasoning text is live model progress — refresh
|
|
1608
1692
|
// lastStreamDeltaAt so stream-watchdog does not flag a
|
|
1609
|
-
// long reasoning span as a stall.
|
|
1610
|
-
//
|
|
1611
|
-
//
|
|
1612
|
-
//
|
|
1613
|
-
// first-meaningful timer and abort an otherwise healthy
|
|
1614
|
-
// stream. Reasoning is still suppressed from user
|
|
1615
|
-
// content (no `content +=` here) — only the watchdog
|
|
1616
|
-
// timers are reset.
|
|
1693
|
+
// long reasoning span as a stall. The local WS idle timer
|
|
1694
|
+
// was already reset by resetIdle() at the top of
|
|
1695
|
+
// messageHandler. Reasoning is still suppressed from user
|
|
1696
|
+
// content (no `content +=` here).
|
|
1617
1697
|
try { onStreamDelta?.(); } catch {}
|
|
1618
|
-
|
|
1698
|
+
bumpSemanticIdle();
|
|
1619
1699
|
break;
|
|
1620
1700
|
case 'response.output_item.added':
|
|
1621
1701
|
if (event.item?.type === 'function_call') {
|
|
@@ -1623,16 +1703,20 @@ export async function _streamResponse({
|
|
|
1623
1703
|
name: event.item.name || '',
|
|
1624
1704
|
callId: event.item.call_id || '',
|
|
1625
1705
|
});
|
|
1626
|
-
|
|
1706
|
+
_toolInFlight = true;
|
|
1707
|
+
} else if (event.item?.type === 'custom_tool_call') {
|
|
1708
|
+
_toolInFlight = true;
|
|
1627
1709
|
}
|
|
1628
1710
|
break;
|
|
1629
1711
|
case 'response.function_call_arguments.delta':
|
|
1712
|
+
_toolInFlight = true;
|
|
1630
1713
|
try { onStreamDelta?.(); } catch {}
|
|
1631
|
-
|
|
1714
|
+
bumpSemanticIdle();
|
|
1632
1715
|
break;
|
|
1633
1716
|
case 'response.custom_tool_call_input.delta':
|
|
1717
|
+
_toolInFlight = true;
|
|
1634
1718
|
try { onStreamDelta?.(); } catch {}
|
|
1635
|
-
|
|
1719
|
+
bumpSemanticIdle();
|
|
1636
1720
|
break;
|
|
1637
1721
|
case 'response.function_call_arguments.done': {
|
|
1638
1722
|
const itemId = event.item_id || '';
|
|
@@ -1663,8 +1747,7 @@ export async function _streamResponse({
|
|
|
1663
1747
|
if (pending?.callId && pending?.name) {
|
|
1664
1748
|
const call = { id: pending.callId, name: pending.name, arguments: args };
|
|
1665
1749
|
toolCalls.push(call);
|
|
1666
|
-
|
|
1667
|
-
try { onToolCall?.(call); } catch {}
|
|
1750
|
+
emitToolCallDedupe(call);
|
|
1668
1751
|
} else {
|
|
1669
1752
|
// Synthesizing a `tc_${Date.now()}` callId here would
|
|
1670
1753
|
// make the next turn fail to match the model's
|
|
@@ -1684,7 +1767,7 @@ export async function _streamResponse({
|
|
|
1684
1767
|
});
|
|
1685
1768
|
}
|
|
1686
1769
|
try { onStreamDelta?.(); } catch {}
|
|
1687
|
-
|
|
1770
|
+
bumpSemanticIdle();
|
|
1688
1771
|
break;
|
|
1689
1772
|
}
|
|
1690
1773
|
case 'response.output_item.done':
|
|
@@ -1698,11 +1781,9 @@ export async function _streamResponse({
|
|
|
1698
1781
|
if (event.item?.type === 'web_search_call') pushWebSearchCall(event.item);
|
|
1699
1782
|
if (event.item?.type === 'tool_search_call') {
|
|
1700
1783
|
pushToolSearchCall(event.item);
|
|
1701
|
-
onMeaningfulOutput();
|
|
1702
1784
|
}
|
|
1703
1785
|
if (event.item?.type === 'custom_tool_call') {
|
|
1704
1786
|
pushCustomToolCall(event.item);
|
|
1705
|
-
onMeaningfulOutput();
|
|
1706
1787
|
}
|
|
1707
1788
|
break;
|
|
1708
1789
|
case 'response.completed': {
|
|
@@ -1734,7 +1815,18 @@ export async function _streamResponse({
|
|
|
1734
1815
|
if (!content && item.type === 'message') {
|
|
1735
1816
|
for (const c of item.content || []) {
|
|
1736
1817
|
if (c.type === 'output_text') {
|
|
1737
|
-
|
|
1818
|
+
// Completed-output fallback (no streamed
|
|
1819
|
+
// text). Route through the leak guard so
|
|
1820
|
+
// a tool call leaked only in the final
|
|
1821
|
+
// bundle is recovered, not surfaced as
|
|
1822
|
+
// visible content. final=true → full flush.
|
|
1823
|
+
if (leakGuard.enabled) {
|
|
1824
|
+
const { text, calls } = leakGuard.push(c.text || '', true);
|
|
1825
|
+
content += text;
|
|
1826
|
+
for (const lc of calls) dispatchLeakedCall(lc);
|
|
1827
|
+
} else {
|
|
1828
|
+
content += c.text || '';
|
|
1829
|
+
}
|
|
1738
1830
|
pushOutputTextAnnotations(c);
|
|
1739
1831
|
}
|
|
1740
1832
|
}
|
|
@@ -1770,8 +1862,7 @@ export async function _streamResponse({
|
|
|
1770
1862
|
if (tc.id && tc.name) {
|
|
1771
1863
|
delete tc._deferred;
|
|
1772
1864
|
delete tc._pendingItemId;
|
|
1773
|
-
|
|
1774
|
-
try { onToolCall?.(tc); } catch {}
|
|
1865
|
+
emitToolCallDedupe(tc);
|
|
1775
1866
|
}
|
|
1776
1867
|
}
|
|
1777
1868
|
}
|
|
@@ -1908,6 +1999,13 @@ export async function _streamResponse({
|
|
|
1908
1999
|
&& event.type.startsWith('response.reasoning')
|
|
1909
2000
|
&& event.type.endsWith('.delta')) {
|
|
1910
2001
|
reasoningOtherDeltaCount += 1;
|
|
2002
|
+
// These ARE live model progress (reviewer Medium): a
|
|
2003
|
+
// provider that emits only these reasoning variants for a
|
|
2004
|
+
// long span would otherwise trip the SEMANTIC idle abort.
|
|
2005
|
+
// Refresh both the watchdog and the semantic idle timer,
|
|
2006
|
+
// matching the named reasoning_text.delta case above.
|
|
2007
|
+
try { onStreamDelta?.(); } catch {}
|
|
2008
|
+
bumpSemanticIdle();
|
|
1911
2009
|
}
|
|
1912
2010
|
// Trace-only events (response.in_progress, etc.)
|
|
1913
2011
|
break;
|
|
@@ -2043,9 +2141,6 @@ export function _classifyHandshakeError(err) {
|
|
|
2043
2141
|
* response.create frame sent, but the server never
|
|
2044
2142
|
* emitted response.created within the short
|
|
2045
2143
|
* pre-stream deadline. Fast-fail retryable.
|
|
2046
|
-
* 'first_meaningful_timeout' — server ACKed response.created, then emitted
|
|
2047
|
-
* no real text/reasoning/tool progress before the
|
|
2048
|
-
* first-meaningful deadline.
|
|
2049
2144
|
* 'response_failed_network' — response.failed with network_error
|
|
2050
2145
|
* 'response_failed_disconnected' — response.failed with stream_disconnected
|
|
2051
2146
|
*
|
|
@@ -2152,7 +2247,7 @@ export async function _acquireWithRetry({
|
|
|
2152
2247
|
try { err.retryClassifier = classifier; } catch {}
|
|
2153
2248
|
}
|
|
2154
2249
|
try {
|
|
2155
|
-
process.stderr.write(
|
|
2250
|
+
if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(
|
|
2156
2251
|
`[openai-oauth-ws] handshake failed after ${attempt}/${HANDSHAKE_MAX_ATTEMPTS} attempts: ${err?.message || err}\n`,
|
|
2157
2252
|
);
|
|
2158
2253
|
} catch {}
|
|
@@ -2161,7 +2256,7 @@ export async function _acquireWithRetry({
|
|
|
2161
2256
|
// Schedule backoff and emit progress.
|
|
2162
2257
|
const backoff = _backoffFor(attempt);
|
|
2163
2258
|
try {
|
|
2164
|
-
process.stderr.write(
|
|
2259
|
+
if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(
|
|
2165
2260
|
`[openai-oauth-ws] worker retry ${attempt}/${HANDSHAKE_MAX_ATTEMPTS} (transient: ${classifier}, backoff ${backoff}ms)\n`,
|
|
2166
2261
|
);
|
|
2167
2262
|
} catch {}
|
|
@@ -2249,6 +2344,14 @@ export async function sendViaWebSocket({
|
|
|
2249
2344
|
const MAX_MIDSTREAM_RETRIES = MIDSTREAM_WS_TRANSIENT_RETRY_LIMIT;
|
|
2250
2345
|
let firstAttemptError = null;
|
|
2251
2346
|
let firstAttemptClassifier = null;
|
|
2347
|
+
// Known tool names for the leaked-tool-call guard in _streamResponse.
|
|
2348
|
+
// Derived from the exact request body so a recovered leaked call only
|
|
2349
|
+
// synthesizes when it names a tool actually offered to this request.
|
|
2350
|
+
const knownToolNames = new Set(
|
|
2351
|
+
(Array.isArray(body?.tools) ? body.tools : [])
|
|
2352
|
+
.map((t) => (typeof t?.name === 'string' ? t.name : null))
|
|
2353
|
+
.filter(Boolean),
|
|
2354
|
+
);
|
|
2252
2355
|
// Live-text invariant across attempts: once ANY attempt has relayed a
|
|
2253
2356
|
// non-empty text chunk to the client, no error thrown out of this function
|
|
2254
2357
|
// may omit the liveTextEmitted/unsafeToRetry markers — otherwise an
|
|
@@ -2509,6 +2612,7 @@ export async function sendViaWebSocket({
|
|
|
2509
2612
|
logSuppressedReasoningDeltas,
|
|
2510
2613
|
traceProvider,
|
|
2511
2614
|
_timeouts: streamTimeouts,
|
|
2615
|
+
knownToolNames,
|
|
2512
2616
|
});
|
|
2513
2617
|
} catch (err) {
|
|
2514
2618
|
// Snapshot the xAI conversation anchor BEFORE releasing the
|
|
@@ -2555,7 +2659,7 @@ export async function sendViaWebSocket({
|
|
|
2555
2659
|
const backoff = _midstreamBackoffFor(retryNumber);
|
|
2556
2660
|
try {
|
|
2557
2661
|
const line = `[openai-oauth-ws] mid-stream recovered: retry ${retryNumber}/${retryLimit} (cause: ${classifier}, backoff ${backoff}ms)\n`;
|
|
2558
|
-
process.stderr.write(line);
|
|
2662
|
+
if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(line);
|
|
2559
2663
|
emittedProgress.push(line);
|
|
2560
2664
|
} catch {}
|
|
2561
2665
|
await _sleepWithAbort(backoff, externalSignal, _sleepFn);
|
|
@@ -2664,8 +2768,8 @@ export async function sendViaWebSocket({
|
|
|
2664
2768
|
transport: 'websocket',
|
|
2665
2769
|
ws_mode: mode,
|
|
2666
2770
|
ws_pre_response_created_timeout_ms: WS_PRE_RESPONSE_CREATED_MS,
|
|
2667
|
-
ws_first_meaningful_timeout_ms: WS_FIRST_MEANINGFUL_MS,
|
|
2668
2771
|
ws_inter_chunk_timeout_ms: WS_INTER_CHUNK_MS,
|
|
2772
|
+
ws_idle_ms: WS_IDLE_MS,
|
|
2669
2773
|
iteration_delta_tokens: deltaTokens,
|
|
2670
2774
|
reused_connection: reused,
|
|
2671
2775
|
requested_service_tier: requestedServiceTier,
|