mixdog 0.9.1 → 0.9.3
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 +9 -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/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/build-tui.mjs +13 -1
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/hook-bus-test.mjs +191 -0
- 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/path-suffix-test.mjs +57 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/recall-bench.mjs +207 -0
- 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 +310 -67
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +20 -9
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +17 -9
- 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 +1040 -2036
- 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 +17 -7
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +8 -12
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +13 -2
- package/src/rules/shared/01-tool.md +23 -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 +182 -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 +100 -18
- 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-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
- 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/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
- 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/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
- 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 +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- 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-diagnostics.mjs +42 -2
- 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 +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
- 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 +70 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
- package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +437 -1439
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +88 -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 +280 -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/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +465 -345
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +121 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/recall-format.mjs +183 -0
- 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 +10 -7
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- 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/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +156 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/session-runtime/config-helpers.mjs +209 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/output-styles.mjs +124 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool.mjs +302 -117
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +10 -292
- package/src/standalone/explore-tool.mjs +12 -5
- package/src/standalone/hook-bus.mjs +165 -8
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/opencode-go-login.mjs +121 -0
- package/src/standalone/provider-admin.mjs +25 -3
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2883 -778
- 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 +355 -22
- 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 +183 -101
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +161 -23
- 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 +6731 -2333
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +569 -948
- package/src/tui/index.jsx +117 -7
- package/src/tui/input-editing.mjs +58 -8
- package/src/tui/input-editing.selection.test.mjs +75 -0
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +469 -0
- package/src/tui/markdown/format-token.mjs +128 -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 +36 -9
- package/src/tui/paste-fix.test.mjs +119 -0
- 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 +80 -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 +75 -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 +46 -12
- package/src/workflows/sequential/WORKFLOW.md +51 -0
- package/src/workflows/solo/WORKFLOW.md +12 -1
- 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,15 +4,15 @@
|
|
|
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.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* -
|
|
12
|
+
* Incremental-input reuse is decided by diffing against the cached request
|
|
13
|
+
* the socket last sent (see _sansInput below), and requests carry a
|
|
14
|
+
* turn-state echo header so the backend can correlate WS frames to the
|
|
15
|
+
* in-flight turn.
|
|
16
16
|
*
|
|
17
17
|
* Exposes:
|
|
18
18
|
* sendViaWebSocket({ auth, body, sendOpts, onStreamDelta, onToolCall,
|
|
@@ -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,8 @@ 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 — any frame,
|
|
87
|
+
// including metadata/keepalive, proves the socket is live.
|
|
81
88
|
const WS_INTER_CHUNK_MS = PROVIDER_WS_INTER_CHUNK_TIMEOUT_MS;
|
|
82
89
|
// Mid-stream retry budgets + backoff now live in the shared MIDSTREAM_RETRY_POLICY
|
|
83
90
|
// table (retry-classifier.mjs). These aliases keep the local call sites readable
|
|
@@ -101,8 +108,8 @@ const WS_MIDSTREAM_POLICY = {
|
|
|
101
108
|
// decision and just double the user-visible latency.
|
|
102
109
|
// Aligned to the cross-provider default (retry-classifier DEFAULT_MAX_ATTEMPTS=5,
|
|
103
110
|
// anthropic-oauth MAX_ATTEMPTS=5, withRetry-using providers all default to 5).
|
|
104
|
-
//
|
|
105
|
-
//
|
|
111
|
+
// Bumped from 3 so this provider exhausts the same number of transient-5xx
|
|
112
|
+
// attempts as the others before surfacing failure to the caller.
|
|
106
113
|
const HANDSHAKE_MAX_ATTEMPTS = PROVIDER_RETRY_MAX_ATTEMPTS;
|
|
107
114
|
const HANDSHAKE_BACKOFF_BASE_MS = 500;
|
|
108
115
|
const HANDSHAKE_BACKOFF_CAP_MS = 5000;
|
|
@@ -934,9 +941,9 @@ function releaseWebSocket({ entry, poolKey, keep }) {
|
|
|
934
941
|
_scheduleIdleClose(poolKey, entry);
|
|
935
942
|
}
|
|
936
943
|
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
944
|
+
// If the cached request (sans input) matches the current one and the current
|
|
945
|
+
// input starts with the cached input, return only the tail. Otherwise return
|
|
946
|
+
// the full input (fresh turn).
|
|
940
947
|
function _sansInput(body) {
|
|
941
948
|
const { input: _ignored, previous_response_id: _prevIgnored, ...rest } = body;
|
|
942
949
|
return rest;
|
|
@@ -1206,19 +1213,23 @@ function _wsErrLabel(p) {
|
|
|
1206
1213
|
return 'OpenAI OAuth WS';
|
|
1207
1214
|
}
|
|
1208
1215
|
// tool_search_call.arguments parse. Module-scope (exported) for direct test
|
|
1209
|
-
// coverage.
|
|
1210
|
-
//
|
|
1216
|
+
// coverage. Same policy as the function_call_arguments.done path and
|
|
1217
|
+
// openai-oauth _parseJsonObject —
|
|
1211
1218
|
// object passes through; null/non-string/empty/whitespace → {} (no args); a
|
|
1212
1219
|
// non-empty string that fails JSON.parse is deterministic bad JSON, surfaced
|
|
1213
1220
|
// as an invalid-args MARKER (not silently swallowed to {}) so the dispatch
|
|
1214
1221
|
// loop returns an is_error tool_result and the model self-corrects in the same
|
|
1215
1222
|
// turn.
|
|
1216
1223
|
export function parseToolSearchArgs(value) {
|
|
1217
|
-
if (value && typeof value === 'object')
|
|
1224
|
+
if (value && typeof value === 'object') {
|
|
1225
|
+
// Reject arrays — the tool_search schema is an object
|
|
1226
|
+
// ({query,select,limit}); an array must never pass through as args.
|
|
1227
|
+
return Array.isArray(value) ? {} : value;
|
|
1228
|
+
}
|
|
1218
1229
|
if (typeof value !== 'string' || !value.trim()) return {};
|
|
1219
1230
|
try {
|
|
1220
1231
|
const parsed = JSON.parse(value);
|
|
1221
|
-
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
1232
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
1222
1233
|
} catch (err) {
|
|
1223
1234
|
return makeInvalidToolArgsMarker(value, err instanceof Error ? err.message : String(err));
|
|
1224
1235
|
}
|
|
@@ -1233,11 +1244,11 @@ export async function _streamResponse({
|
|
|
1233
1244
|
logSuppressedReasoningDeltas = true,
|
|
1234
1245
|
traceProvider = 'openai-oauth',
|
|
1235
1246
|
_timeouts = null,
|
|
1247
|
+
knownToolNames = null,
|
|
1236
1248
|
}) {
|
|
1237
1249
|
const errLabel = _wsErrLabel(traceProvider);
|
|
1238
1250
|
const socket = entry.socket;
|
|
1239
1251
|
const preResponseCreatedMs = _positiveInt(_timeouts?.preResponseCreatedMs, WS_PRE_RESPONSE_CREATED_MS);
|
|
1240
|
-
const firstMeaningfulMs = _positiveInt(_timeouts?.firstMeaningfulMs, WS_FIRST_MEANINGFUL_MS);
|
|
1241
1252
|
const interChunkMs = _positiveInt(_timeouts?.interChunkMs, WS_INTER_CHUNK_MS);
|
|
1242
1253
|
const _streamingStart = Date.now();
|
|
1243
1254
|
let _firstDeltaEmitted = false;
|
|
@@ -1253,6 +1264,19 @@ export async function _streamResponse({
|
|
|
1253
1264
|
const citations = [];
|
|
1254
1265
|
const citationKeys = new Set();
|
|
1255
1266
|
const pendingCalls = new Map();
|
|
1267
|
+
// Tool-work-in-flight flag: set the moment a function/custom tool call's
|
|
1268
|
+
// input starts streaming (before it lands in pendingCalls/toolCalls).
|
|
1269
|
+
// Gates partial-final SUCCESS so a stall mid tool-input never looks text-only.
|
|
1270
|
+
let _toolInFlight = false;
|
|
1271
|
+
// Fix 2: cross-path name+args dedupe. A text-leaked synthetic and an
|
|
1272
|
+
// identical native function_call must fire onToolCall exactly once. Every
|
|
1273
|
+
// dispatch site routes through emitToolCallDedupe.
|
|
1274
|
+
const _toolDedupe = createToolCallDedupe();
|
|
1275
|
+
const emitToolCallDedupe = (call) => {
|
|
1276
|
+
if (!_toolDedupe.shouldDispatch(call?.name, call?.arguments)) return;
|
|
1277
|
+
midState.emittedToolCall = true;
|
|
1278
|
+
try { onToolCall?.(call); } catch {}
|
|
1279
|
+
};
|
|
1256
1280
|
// Reasoning items collected from response.output_item.done (or salvaged
|
|
1257
1281
|
// from response.completed.response.output). The request still includes
|
|
1258
1282
|
// `reasoning.encrypted_content` so the server keeps emitting the blobs,
|
|
@@ -1340,8 +1364,7 @@ export async function _streamResponse({
|
|
|
1340
1364
|
const call = customToolCallFromResponseItem(item);
|
|
1341
1365
|
if (!call || toolCalls.some((existing) => existing.id === call.id)) return;
|
|
1342
1366
|
toolCalls.push(call);
|
|
1343
|
-
|
|
1344
|
-
try { onToolCall?.(call); } catch {}
|
|
1367
|
+
emitToolCallDedupe(call);
|
|
1345
1368
|
};
|
|
1346
1369
|
const pushToolSearchCall = (item) => {
|
|
1347
1370
|
if (!item || item.type !== 'tool_search_call') return;
|
|
@@ -1354,9 +1377,60 @@ export async function _streamResponse({
|
|
|
1354
1377
|
nativeType: 'tool_search_call',
|
|
1355
1378
|
};
|
|
1356
1379
|
toolCalls.push(call);
|
|
1380
|
+
emitToolCallDedupe(call);
|
|
1381
|
+
};
|
|
1382
|
+
// Leaked tool-call guard. The model sometimes emits a tool call as plain
|
|
1383
|
+
// text (XML `<invoke>`/`<function_calls>` or gpt-oss harmony
|
|
1384
|
+
// `<|channel|>...to=functions.NAME...<|call|>`) inside
|
|
1385
|
+
// `response.output_text.delta` instead of a native function_call. Route
|
|
1386
|
+
// text through the guard so leaked calls are suppressed from the visible
|
|
1387
|
+
// stream, synthesized (native `call_...` id shape), and dispatched like
|
|
1388
|
+
// native ones. Additive: the native function_call path is untouched.
|
|
1389
|
+
const leakGuard = createLeakGuard({ knownToolNames, harmony: true });
|
|
1390
|
+
const dispatchLeakedCall = (recovered) => {
|
|
1391
|
+
let args = recovered?.arguments;
|
|
1392
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args)) args = {};
|
|
1393
|
+
const call = {
|
|
1394
|
+
id: `call_leaked_${randomBytes(8).toString('hex')}`,
|
|
1395
|
+
name: recovered.name,
|
|
1396
|
+
arguments: args,
|
|
1397
|
+
};
|
|
1398
|
+
if (!_toolDedupe.shouldDispatch(call.name, call.arguments)) return;
|
|
1399
|
+
toolCalls.push(call);
|
|
1357
1400
|
midState.emittedToolCall = true;
|
|
1358
1401
|
try { onToolCall?.(call); } catch {}
|
|
1359
1402
|
};
|
|
1403
|
+
const relayLeakText = (delta) => {
|
|
1404
|
+
if (!leakGuard.enabled) {
|
|
1405
|
+
content += delta || '';
|
|
1406
|
+
if (delta && onTextDelta) {
|
|
1407
|
+
if (state) state.emittedText = true;
|
|
1408
|
+
try { onTextDelta(delta); } catch {}
|
|
1409
|
+
}
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
const { text, calls } = leakGuard.push(delta);
|
|
1413
|
+
if (text) {
|
|
1414
|
+
content += text;
|
|
1415
|
+
if (onTextDelta) {
|
|
1416
|
+
if (state) state.emittedText = true;
|
|
1417
|
+
try { onTextDelta(text); } catch {}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
1421
|
+
};
|
|
1422
|
+
const flushLeak = () => {
|
|
1423
|
+
if (!leakGuard.enabled) return;
|
|
1424
|
+
const { text, calls } = leakGuard.flush();
|
|
1425
|
+
if (text) {
|
|
1426
|
+
content += text;
|
|
1427
|
+
if (onTextDelta) {
|
|
1428
|
+
if (state) state.emittedText = true;
|
|
1429
|
+
try { onTextDelta(text); } catch {}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
1433
|
+
};
|
|
1360
1434
|
const logReasoningDeltaSuppression = () => {
|
|
1361
1435
|
if (!logSuppressedReasoningDeltas) return;
|
|
1362
1436
|
const total = reasoningTextDeltaCount + reasoningSummaryTextDeltaCount + reasoningOtherDeltaCount;
|
|
@@ -1385,6 +1459,16 @@ export async function _streamResponse({
|
|
|
1385
1459
|
let messageHandler = null;
|
|
1386
1460
|
let closeHandler = null;
|
|
1387
1461
|
let errorHandler = null;
|
|
1462
|
+
// SEMANTIC idle timer: distinct from the inter-chunk
|
|
1463
|
+
// timer, which resets on EVERY frame (rate_limits/metadata/keepalive keep
|
|
1464
|
+
// the socket "alive"). This timer resets ONLY on meaningful output deltas
|
|
1465
|
+
// (text/reasoning/tool args — the same events that call onStreamDelta) so a
|
|
1466
|
+
// stream that emits some deltas then goes silent (server keepalive frames
|
|
1467
|
+
// only) trips a short, named terminal StreamStalledError instead of coasting
|
|
1468
|
+
// to the 300s inter-chunk cap / 30-min agent watchdog.
|
|
1469
|
+
let semanticIdleTimer = null;
|
|
1470
|
+
const semanticIdleMs = PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS;
|
|
1471
|
+
const semanticIdleEnabled = PROVIDER_SSE_IDLE_WATCHDOG_ENABLED && semanticIdleMs > 0;
|
|
1388
1472
|
|
|
1389
1473
|
return new Promise((resolve, reject) => {
|
|
1390
1474
|
// Pre-stream watchdog: the timer fires if the server never sends a
|
|
@@ -1392,10 +1476,10 @@ export async function _streamResponse({
|
|
|
1392
1476
|
// after our last frame. The socket is open and the response.create
|
|
1393
1477
|
// frame was sent, but no server event has come back — a wedged
|
|
1394
1478
|
// post-upgrade socket. Healthy servers ack within seconds, so this
|
|
1395
|
-
// window is intentionally short (~
|
|
1396
|
-
//
|
|
1397
|
-
// the
|
|
1398
|
-
//
|
|
1479
|
+
// window is intentionally short (WS_PRE_RESPONSE_CREATED_MS, ~10s).
|
|
1480
|
+
// Once ANY server event arrives, resetIdle() cancels this watchdog and
|
|
1481
|
+
// the single inter-chunk idle timer takes over — silent gaps
|
|
1482
|
+
// mid-reasoning (openai-oauth spending 50s+ producing reasoning
|
|
1399
1483
|
// tokens) are normal and should not abort the turn.
|
|
1400
1484
|
const armPreStreamWatchdog = () => {
|
|
1401
1485
|
if (idleTimer) clearTimeout(idleTimer);
|
|
@@ -1427,8 +1511,6 @@ export async function _streamResponse({
|
|
|
1427
1511
|
}, preResponseCreatedMs);
|
|
1428
1512
|
};
|
|
1429
1513
|
let interChunkTimer = null;
|
|
1430
|
-
let firstMeaningfulTimer = null;
|
|
1431
|
-
let firstMeaningfulSeen = false;
|
|
1432
1514
|
const traceWsTimeout = (event, timeoutMs) => {
|
|
1433
1515
|
try {
|
|
1434
1516
|
const iteration = Number(midState.iteration);
|
|
@@ -1443,7 +1525,6 @@ export async function _streamResponse({
|
|
|
1443
1525
|
attempt_index: Number.isFinite(attemptIndex) ? attemptIndex : null,
|
|
1444
1526
|
warmup: midState.warmup === true,
|
|
1445
1527
|
saw_response_created: midState.sawResponseCreated === true,
|
|
1446
|
-
first_meaningful_seen: firstMeaningfulSeen === true,
|
|
1447
1528
|
};
|
|
1448
1529
|
appendAgentTrace({
|
|
1449
1530
|
sessionId: midState.sessionId || null,
|
|
@@ -1460,29 +1541,6 @@ export async function _streamResponse({
|
|
|
1460
1541
|
idleTimer = null;
|
|
1461
1542
|
}
|
|
1462
1543
|
};
|
|
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
1544
|
const resetInterChunk = () => {
|
|
1487
1545
|
if (interChunkTimer) clearTimeout(interChunkTimer);
|
|
1488
1546
|
interChunkTimer = setTimeout(() => {
|
|
@@ -1497,30 +1555,49 @@ export async function _streamResponse({
|
|
|
1497
1555
|
finish();
|
|
1498
1556
|
}, interChunkMs);
|
|
1499
1557
|
};
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1558
|
+
// pi per-event idle: (re)armed only on meaningful output deltas via
|
|
1559
|
+
// bumpSemanticIdle(). Keepalive/metadata frames DON'T touch it, so a
|
|
1560
|
+
// deltas-then-silent wedge trips this short semantic window.
|
|
1561
|
+
const resetSemanticIdle = () => {
|
|
1562
|
+
if (!semanticIdleEnabled) return;
|
|
1563
|
+
if (semanticIdleTimer) clearTimeout(semanticIdleTimer);
|
|
1564
|
+
semanticIdleTimer = setTimeout(() => {
|
|
1565
|
+
traceWsTimeout('semantic_idle_timeout', semanticIdleMs);
|
|
1566
|
+
terminalError = streamStalledError('Responses WS', semanticIdleMs, { emittedToolCall: !!midState?.emittedToolCall });
|
|
1567
|
+
// Partial-final recovery: attach streamed partial state so
|
|
1568
|
+
// a wedged FINAL no-tool summary can be accepted as partial-final
|
|
1569
|
+
// success by the loop. pendingToolUse gates out mid-flight tools.
|
|
1570
|
+
try {
|
|
1571
|
+
terminalError.partialContent = content;
|
|
1572
|
+
terminalError.partialToolCalls = toolCalls.length ? toolCalls.slice() : undefined;
|
|
1573
|
+
terminalError.pendingToolUse = pendingCalls.size > 0
|
|
1574
|
+
|| !!midState?.emittedToolCall
|
|
1575
|
+
|| toolCalls.length > 0
|
|
1576
|
+
|| _toolInFlight === true;
|
|
1577
|
+
terminalError.partialModel = model || undefined;
|
|
1578
|
+
} catch { /* best-effort enrichment */ }
|
|
1579
|
+
try { terminalError.wsCloseCode = 4000; } catch {}
|
|
1580
|
+
try { socket.close(4000, 'semantic_idle_timeout'); } catch {}
|
|
1581
|
+
finish();
|
|
1582
|
+
}, semanticIdleMs);
|
|
1583
|
+
try { semanticIdleTimer.unref?.(); } catch {}
|
|
1504
1584
|
};
|
|
1505
|
-
//
|
|
1506
|
-
//
|
|
1507
|
-
//
|
|
1508
|
-
//
|
|
1509
|
-
//
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
firstMeaningfulSeen = true;
|
|
1513
|
-
clearPreStreamWatchdog();
|
|
1514
|
-
clearFirstMeaningfulWatchdog();
|
|
1515
|
-
}
|
|
1585
|
+
// Single idle reset — called on EVERY parsed server event (matches
|
|
1586
|
+
// codex, which resets one idle timer on every received WS frame). Any
|
|
1587
|
+
// frame proves the socket is live; there is no separate "meaningful
|
|
1588
|
+
// output" gate. Also clears the pre-stream watchdog defensively in case
|
|
1589
|
+
// the first event is not response.created.
|
|
1590
|
+
const resetIdle = () => {
|
|
1591
|
+
clearPreStreamWatchdog();
|
|
1516
1592
|
resetInterChunk();
|
|
1517
1593
|
};
|
|
1518
|
-
//
|
|
1519
|
-
|
|
1594
|
+
// Meaningful-output progress bump: called by the same delta cases that
|
|
1595
|
+
// call onStreamDelta (text/reasoning/tool args). Arms the semantic idle.
|
|
1596
|
+
const bumpSemanticIdle = () => { resetSemanticIdle(); };
|
|
1520
1597
|
const cleanup = () => {
|
|
1521
1598
|
if (idleTimer) clearTimeout(idleTimer);
|
|
1522
|
-
clearFirstMeaningfulWatchdog();
|
|
1523
1599
|
if (interChunkTimer) { clearTimeout(interChunkTimer); interChunkTimer = null; }
|
|
1600
|
+
if (semanticIdleTimer) { clearTimeout(semanticIdleTimer); semanticIdleTimer = null; }
|
|
1524
1601
|
if (keepaliveTimer) { clearInterval(keepaliveTimer); keepaliveTimer = null; }
|
|
1525
1602
|
if (messageHandler) socket.off('message', messageHandler);
|
|
1526
1603
|
if (closeHandler) socket.off('close', closeHandler);
|
|
@@ -1529,6 +1606,9 @@ export async function _streamResponse({
|
|
|
1529
1606
|
};
|
|
1530
1607
|
const finish = () => {
|
|
1531
1608
|
logReasoningDeltaSuppression();
|
|
1609
|
+
// Flush any partial-sentinel tail held back mid-stream so
|
|
1610
|
+
// legitimate trailing text is never lost (streamed-text path).
|
|
1611
|
+
flushLeak();
|
|
1532
1612
|
cleanup();
|
|
1533
1613
|
if (terminalError) { reject(terminalError); return; }
|
|
1534
1614
|
resolve({
|
|
@@ -1536,11 +1616,17 @@ export async function _streamResponse({
|
|
|
1536
1616
|
model,
|
|
1537
1617
|
reasoningItems: reasoningItems.length ? reasoningItems : undefined,
|
|
1538
1618
|
responseItems: responseItemsAdded.length ? responseItemsAdded : undefined,
|
|
1539
|
-
|
|
1619
|
+
// Dedupe by name+args (Fix 2, array side) so an identical
|
|
1620
|
+
// synthetic-leaked + native pair can't run the tool twice.
|
|
1621
|
+
toolCalls: toolCalls.length ? dedupeToolCallList(toolCalls) : undefined,
|
|
1540
1622
|
citations: citations.length ? citations : undefined,
|
|
1541
1623
|
webSearchCalls: webSearchCalls.length ? webSearchCalls : undefined,
|
|
1542
1624
|
usage,
|
|
1543
1625
|
stopReason: stopReason || undefined,
|
|
1626
|
+
// P1 audit fix: mirror the HTTP/SSE fallback's truncated flag
|
|
1627
|
+
// for the WS path (sendViaWebSocket spreads this result
|
|
1628
|
+
// through to the provider caller unchanged).
|
|
1629
|
+
...(stopReason === 'length' && content.length > 0 ? { truncated: true } : {}),
|
|
1544
1630
|
incompleteReason: incompleteReason || undefined,
|
|
1545
1631
|
responseId: responseId || undefined,
|
|
1546
1632
|
serviceTier: responseServiceTier || undefined,
|
|
@@ -1549,7 +1635,10 @@ export async function _streamResponse({
|
|
|
1549
1635
|
|
|
1550
1636
|
messageHandler = (data) => {
|
|
1551
1637
|
resetIdle();
|
|
1552
|
-
//
|
|
1638
|
+
// resetIdle() above resets the SINGLE inter-chunk idle timer on
|
|
1639
|
+
// EVERY received frame — response.created, metadata,
|
|
1640
|
+
// rate_limits, and all deltas keep the socket alive. Separately, do
|
|
1641
|
+
// NOT call onStreamDelta for every frame — metadata/keepalive frames
|
|
1553
1642
|
// must not reset the agent stall watchdog's lastStreamDeltaAt. Only
|
|
1554
1643
|
// meaningful output (text delta / tool call) updates that timestamp.
|
|
1555
1644
|
const text = typeof data === 'string' ? data : data.toString('utf-8');
|
|
@@ -1571,13 +1660,11 @@ export async function _streamResponse({
|
|
|
1571
1660
|
midState.sawResponseCreated = true;
|
|
1572
1661
|
if (event.response?.model) model = event.response.model;
|
|
1573
1662
|
if (event.response?.id) responseId = event.response.id;
|
|
1574
|
-
// Server ack
|
|
1575
|
-
//
|
|
1576
|
-
//
|
|
1577
|
-
onResponseCreated();
|
|
1663
|
+
// Server ack (first event). resetIdle() at the top of
|
|
1664
|
+
// messageHandler already cleared the pre-stream watchdog and
|
|
1665
|
+
// armed the single idle timer — no extra bookkeeping here.
|
|
1578
1666
|
break;
|
|
1579
1667
|
case 'response.output_text.delta':
|
|
1580
|
-
content += event.delta || '';
|
|
1581
1668
|
try {
|
|
1582
1669
|
if (!_firstDeltaEmitted) {
|
|
1583
1670
|
_firstDeltaEmitted = true;
|
|
@@ -1587,6 +1674,7 @@ export async function _streamResponse({
|
|
|
1587
1674
|
}
|
|
1588
1675
|
onStreamDelta?.();
|
|
1589
1676
|
} catch {}
|
|
1677
|
+
bumpSemanticIdle();
|
|
1590
1678
|
// Live text relay (gateway): forward the raw text chunk so
|
|
1591
1679
|
// the client renders first tokens before the final replay.
|
|
1592
1680
|
// Tool-call/argument deltas intentionally stay off this path.
|
|
@@ -1594,11 +1682,10 @@ export async function _streamResponse({
|
|
|
1594
1682
|
// cannot be withdrawn, so flag the attempt so a later
|
|
1595
1683
|
// mid-stream/truncated failure is NOT retried (retry would
|
|
1596
1684
|
// concatenate a second attempt onto rendered text).
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
onMeaningfulOutput();
|
|
1685
|
+
// Routed through the leaked-tool-call guard: appends to
|
|
1686
|
+
// `content`, forwards visible text via onTextDelta, and
|
|
1687
|
+
// recovers/dispatches any leaked known-tool call.
|
|
1688
|
+
relayLeakText(event.delta || '');
|
|
1602
1689
|
break;
|
|
1603
1690
|
case 'response.reasoning_text.delta':
|
|
1604
1691
|
case 'response.reasoning_summary_text.delta':
|
|
@@ -1606,16 +1693,12 @@ export async function _streamResponse({
|
|
|
1606
1693
|
else reasoningSummaryTextDeltaCount += 1;
|
|
1607
1694
|
// Reasoning text is live model progress — refresh
|
|
1608
1695
|
// 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.
|
|
1696
|
+
// long reasoning span as a stall. The local WS idle timer
|
|
1697
|
+
// was already reset by resetIdle() at the top of
|
|
1698
|
+
// messageHandler. Reasoning is still suppressed from user
|
|
1699
|
+
// content (no `content +=` here).
|
|
1617
1700
|
try { onStreamDelta?.(); } catch {}
|
|
1618
|
-
|
|
1701
|
+
bumpSemanticIdle();
|
|
1619
1702
|
break;
|
|
1620
1703
|
case 'response.output_item.added':
|
|
1621
1704
|
if (event.item?.type === 'function_call') {
|
|
@@ -1623,26 +1706,37 @@ export async function _streamResponse({
|
|
|
1623
1706
|
name: event.item.name || '',
|
|
1624
1707
|
callId: event.item.call_id || '',
|
|
1625
1708
|
});
|
|
1626
|
-
|
|
1709
|
+
_toolInFlight = true;
|
|
1710
|
+
} else if (event.item?.type === 'custom_tool_call') {
|
|
1711
|
+
_toolInFlight = true;
|
|
1712
|
+
} else if (event.item?.type === 'tool_search_call') {
|
|
1713
|
+
// Mark tool_search in-flight at item-added time, same
|
|
1714
|
+
// as function_call/custom_tool_call above, so the
|
|
1715
|
+
// semantic-idle stall gate's pendingToolUse never
|
|
1716
|
+
// drops a mid-flight tool_search before
|
|
1717
|
+
// response.output_item.done.
|
|
1718
|
+
_toolInFlight = true;
|
|
1627
1719
|
}
|
|
1628
1720
|
break;
|
|
1629
1721
|
case 'response.function_call_arguments.delta':
|
|
1722
|
+
_toolInFlight = true;
|
|
1630
1723
|
try { onStreamDelta?.(); } catch {}
|
|
1631
|
-
|
|
1724
|
+
bumpSemanticIdle();
|
|
1632
1725
|
break;
|
|
1633
1726
|
case 'response.custom_tool_call_input.delta':
|
|
1727
|
+
_toolInFlight = true;
|
|
1634
1728
|
try { onStreamDelta?.(); } catch {}
|
|
1635
|
-
|
|
1729
|
+
bumpSemanticIdle();
|
|
1636
1730
|
break;
|
|
1637
1731
|
case 'response.function_call_arguments.done': {
|
|
1638
1732
|
const itemId = event.item_id || '';
|
|
1639
1733
|
const pending = pendingCalls.get(itemId);
|
|
1640
1734
|
// function_call_arguments.done is a completion signal:
|
|
1641
1735
|
// empty/whitespace → no args ({}); a non-empty string that
|
|
1642
|
-
// fails JSON.parse is deterministic bad JSON.
|
|
1643
|
-
//
|
|
1644
|
-
//
|
|
1645
|
-
//
|
|
1736
|
+
// fails JSON.parse is deterministic bad JSON. Surface an
|
|
1737
|
+
// invalid-args MARKER (not silent {}) so the dispatch loop
|
|
1738
|
+
// returns an is_error tool_result and the model re-issues
|
|
1739
|
+
// valid JSON in the same turn.
|
|
1646
1740
|
let args = {};
|
|
1647
1741
|
{
|
|
1648
1742
|
const _argText = typeof event.arguments === 'string' ? event.arguments : '';
|
|
@@ -1663,8 +1757,7 @@ export async function _streamResponse({
|
|
|
1663
1757
|
if (pending?.callId && pending?.name) {
|
|
1664
1758
|
const call = { id: pending.callId, name: pending.name, arguments: args };
|
|
1665
1759
|
toolCalls.push(call);
|
|
1666
|
-
|
|
1667
|
-
try { onToolCall?.(call); } catch {}
|
|
1760
|
+
emitToolCallDedupe(call);
|
|
1668
1761
|
} else {
|
|
1669
1762
|
// Synthesizing a `tc_${Date.now()}` callId here would
|
|
1670
1763
|
// make the next turn fail to match the model's
|
|
@@ -1684,7 +1777,7 @@ export async function _streamResponse({
|
|
|
1684
1777
|
});
|
|
1685
1778
|
}
|
|
1686
1779
|
try { onStreamDelta?.(); } catch {}
|
|
1687
|
-
|
|
1780
|
+
bumpSemanticIdle();
|
|
1688
1781
|
break;
|
|
1689
1782
|
}
|
|
1690
1783
|
case 'response.output_item.done':
|
|
@@ -1698,11 +1791,9 @@ export async function _streamResponse({
|
|
|
1698
1791
|
if (event.item?.type === 'web_search_call') pushWebSearchCall(event.item);
|
|
1699
1792
|
if (event.item?.type === 'tool_search_call') {
|
|
1700
1793
|
pushToolSearchCall(event.item);
|
|
1701
|
-
onMeaningfulOutput();
|
|
1702
1794
|
}
|
|
1703
1795
|
if (event.item?.type === 'custom_tool_call') {
|
|
1704
1796
|
pushCustomToolCall(event.item);
|
|
1705
|
-
onMeaningfulOutput();
|
|
1706
1797
|
}
|
|
1707
1798
|
break;
|
|
1708
1799
|
case 'response.completed': {
|
|
@@ -1734,7 +1825,18 @@ export async function _streamResponse({
|
|
|
1734
1825
|
if (!content && item.type === 'message') {
|
|
1735
1826
|
for (const c of item.content || []) {
|
|
1736
1827
|
if (c.type === 'output_text') {
|
|
1737
|
-
|
|
1828
|
+
// Completed-output fallback (no streamed
|
|
1829
|
+
// text). Route through the leak guard so
|
|
1830
|
+
// a tool call leaked only in the final
|
|
1831
|
+
// bundle is recovered, not surfaced as
|
|
1832
|
+
// visible content. final=true → full flush.
|
|
1833
|
+
if (leakGuard.enabled) {
|
|
1834
|
+
const { text, calls } = leakGuard.push(c.text || '', true);
|
|
1835
|
+
content += text;
|
|
1836
|
+
for (const lc of calls) dispatchLeakedCall(lc);
|
|
1837
|
+
} else {
|
|
1838
|
+
content += c.text || '';
|
|
1839
|
+
}
|
|
1738
1840
|
pushOutputTextAnnotations(c);
|
|
1739
1841
|
}
|
|
1740
1842
|
}
|
|
@@ -1770,8 +1872,7 @@ export async function _streamResponse({
|
|
|
1770
1872
|
if (tc.id && tc.name) {
|
|
1771
1873
|
delete tc._deferred;
|
|
1772
1874
|
delete tc._pendingItemId;
|
|
1773
|
-
|
|
1774
|
-
try { onToolCall?.(tc); } catch {}
|
|
1875
|
+
emitToolCallDedupe(tc);
|
|
1775
1876
|
}
|
|
1776
1877
|
}
|
|
1777
1878
|
}
|
|
@@ -1908,6 +2009,13 @@ export async function _streamResponse({
|
|
|
1908
2009
|
&& event.type.startsWith('response.reasoning')
|
|
1909
2010
|
&& event.type.endsWith('.delta')) {
|
|
1910
2011
|
reasoningOtherDeltaCount += 1;
|
|
2012
|
+
// These ARE live model progress (reviewer Medium): a
|
|
2013
|
+
// provider that emits only these reasoning variants for a
|
|
2014
|
+
// long span would otherwise trip the SEMANTIC idle abort.
|
|
2015
|
+
// Refresh both the watchdog and the semantic idle timer,
|
|
2016
|
+
// matching the named reasoning_text.delta case above.
|
|
2017
|
+
try { onStreamDelta?.(); } catch {}
|
|
2018
|
+
bumpSemanticIdle();
|
|
1911
2019
|
}
|
|
1912
2020
|
// Trace-only events (response.in_progress, etc.)
|
|
1913
2021
|
break;
|
|
@@ -2043,9 +2151,6 @@ export function _classifyHandshakeError(err) {
|
|
|
2043
2151
|
* response.create frame sent, but the server never
|
|
2044
2152
|
* emitted response.created within the short
|
|
2045
2153
|
* 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
2154
|
* 'response_failed_network' — response.failed with network_error
|
|
2050
2155
|
* 'response_failed_disconnected' — response.failed with stream_disconnected
|
|
2051
2156
|
*
|
|
@@ -2152,7 +2257,7 @@ export async function _acquireWithRetry({
|
|
|
2152
2257
|
try { err.retryClassifier = classifier; } catch {}
|
|
2153
2258
|
}
|
|
2154
2259
|
try {
|
|
2155
|
-
process.stderr.write(
|
|
2260
|
+
if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(
|
|
2156
2261
|
`[openai-oauth-ws] handshake failed after ${attempt}/${HANDSHAKE_MAX_ATTEMPTS} attempts: ${err?.message || err}\n`,
|
|
2157
2262
|
);
|
|
2158
2263
|
} catch {}
|
|
@@ -2161,7 +2266,7 @@ export async function _acquireWithRetry({
|
|
|
2161
2266
|
// Schedule backoff and emit progress.
|
|
2162
2267
|
const backoff = _backoffFor(attempt);
|
|
2163
2268
|
try {
|
|
2164
|
-
process.stderr.write(
|
|
2269
|
+
if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(
|
|
2165
2270
|
`[openai-oauth-ws] worker retry ${attempt}/${HANDSHAKE_MAX_ATTEMPTS} (transient: ${classifier}, backoff ${backoff}ms)\n`,
|
|
2166
2271
|
);
|
|
2167
2272
|
} catch {}
|
|
@@ -2249,6 +2354,14 @@ export async function sendViaWebSocket({
|
|
|
2249
2354
|
const MAX_MIDSTREAM_RETRIES = MIDSTREAM_WS_TRANSIENT_RETRY_LIMIT;
|
|
2250
2355
|
let firstAttemptError = null;
|
|
2251
2356
|
let firstAttemptClassifier = null;
|
|
2357
|
+
// Known tool names for the leaked-tool-call guard in _streamResponse.
|
|
2358
|
+
// Derived from the exact request body so a recovered leaked call only
|
|
2359
|
+
// synthesizes when it names a tool actually offered to this request.
|
|
2360
|
+
const knownToolNames = new Set(
|
|
2361
|
+
(Array.isArray(body?.tools) ? body.tools : [])
|
|
2362
|
+
.map((t) => (typeof t?.name === 'string' ? t.name : null))
|
|
2363
|
+
.filter(Boolean),
|
|
2364
|
+
);
|
|
2252
2365
|
// Live-text invariant across attempts: once ANY attempt has relayed a
|
|
2253
2366
|
// non-empty text chunk to the client, no error thrown out of this function
|
|
2254
2367
|
// may omit the liveTextEmitted/unsafeToRetry markers — otherwise an
|
|
@@ -2509,6 +2622,7 @@ export async function sendViaWebSocket({
|
|
|
2509
2622
|
logSuppressedReasoningDeltas,
|
|
2510
2623
|
traceProvider,
|
|
2511
2624
|
_timeouts: streamTimeouts,
|
|
2625
|
+
knownToolNames,
|
|
2512
2626
|
});
|
|
2513
2627
|
} catch (err) {
|
|
2514
2628
|
// Snapshot the xAI conversation anchor BEFORE releasing the
|
|
@@ -2555,7 +2669,7 @@ export async function sendViaWebSocket({
|
|
|
2555
2669
|
const backoff = _midstreamBackoffFor(retryNumber);
|
|
2556
2670
|
try {
|
|
2557
2671
|
const line = `[openai-oauth-ws] mid-stream recovered: retry ${retryNumber}/${retryLimit} (cause: ${classifier}, backoff ${backoff}ms)\n`;
|
|
2558
|
-
process.stderr.write(line);
|
|
2672
|
+
if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(line);
|
|
2559
2673
|
emittedProgress.push(line);
|
|
2560
2674
|
} catch {}
|
|
2561
2675
|
await _sleepWithAbort(backoff, externalSignal, _sleepFn);
|
|
@@ -2664,8 +2778,8 @@ export async function sendViaWebSocket({
|
|
|
2664
2778
|
transport: 'websocket',
|
|
2665
2779
|
ws_mode: mode,
|
|
2666
2780
|
ws_pre_response_created_timeout_ms: WS_PRE_RESPONSE_CREATED_MS,
|
|
2667
|
-
ws_first_meaningful_timeout_ms: WS_FIRST_MEANINGFUL_MS,
|
|
2668
2781
|
ws_inter_chunk_timeout_ms: WS_INTER_CHUNK_MS,
|
|
2782
|
+
ws_idle_ms: WS_IDLE_MS,
|
|
2669
2783
|
iteration_delta_tokens: deltaTokens,
|
|
2670
2784
|
reused_connection: reused,
|
|
2671
2785
|
requested_service_tier: requestedServiceTier,
|