mixdog 0.9.2 → 0.9.4
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 -3
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/build-tui.mjs +13 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +30 -67
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +5 -2
- package/src/agents/heavy-worker/AGENT.md +21 -11
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +3 -2
- package/src/agents/worker/AGENT.md +21 -11
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +933 -3731
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/repl.mjs +5 -5
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/agent/30-explorer.md +8 -11
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +2 -9
- package/src/rules/shared/01-tool.md +11 -5
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -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 +47 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -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/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -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 +409 -1304
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
- 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 +551 -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/keyword-match.mjs +82 -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 +1080 -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-columns.mjs +45 -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 +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +241 -894
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/telegram-format.mjs +19 -22
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +258 -2050
- package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +20 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +238 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +6 -14
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +52 -2
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +291 -0
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +126 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +155 -677
- package/src/standalone/channel-worker.mjs +7 -9
- package/src/standalone/explore-tool.mjs +40 -12
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +110 -746
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +125 -0
- package/src/standalone/provider-admin.mjs +15 -19
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1163 -7571
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +259 -80
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +56 -588
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/components/tool-output-format.mjs +2 -2
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +18034 -17188
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/agent-job-feed.mjs +133 -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/notification-plan.mjs +76 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +405 -1385
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +60 -10
- package/src/tui/keyboard-protocol.mjs +2 -2
- package/src/tui/lib/voice-recorder.mjs +35 -19
- package/src/tui/markdown/format-token.mjs +11 -9
- package/src/tui/paste-attachments.mjs +38 -0
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/tui/themes/base.mjs +2 -2
- package/src/tui/themes/kanagawa.mjs +4 -4
- package/src/tui/themes/teal.mjs +4 -5
- package/src/tui/themes/utils.mjs +1 -1
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +77 -462
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +16 -9
- package/src/workflows/sequential/WORKFLOW.md +16 -11
- package/src/workflows/solo/WORKFLOW.md +5 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openai-oauth-http-sse.mjs — HTTP/SSE fallback transport for openai-oauth.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from openai-oauth.mjs. Used when the WebSocket transport is
|
|
5
|
+
* unhealthy (see _shouldUseOpenAIHttpFallback / shouldFallbackTransport).
|
|
6
|
+
* Owns SSE frame parsing, the single-emit tool-call dedupe contract
|
|
7
|
+
* (scripts/openai-oauth-http-sse-toolcall-smoke.mjs) and fallback headers.
|
|
8
|
+
*/
|
|
9
|
+
import { randomBytes } from 'crypto';
|
|
10
|
+
import {
|
|
11
|
+
traceAgentFetch,
|
|
12
|
+
traceAgentSse,
|
|
13
|
+
traceAgentUsage,
|
|
14
|
+
} from '../agent-trace.mjs';
|
|
15
|
+
import {
|
|
16
|
+
PROVIDER_HTTP_RESPONSE_TIMEOUT_MS,
|
|
17
|
+
PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
|
|
18
|
+
PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
|
|
19
|
+
streamStalledError,
|
|
20
|
+
createTimeoutSignal,
|
|
21
|
+
createPassthroughSignal,
|
|
22
|
+
} from '../stall-policy.mjs';
|
|
23
|
+
import { populateHttpStatusFromMessage, shouldFallbackTransport } from './retry-classifier.mjs';
|
|
24
|
+
import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
25
|
+
import { makeInvalidToolArgsMarker } from './openai-compat-stream.mjs';
|
|
26
|
+
import { createLeakGuard, createToolCallDedupe, dedupeToolCallList } from './anthropic-leaked-toolcall.mjs';
|
|
27
|
+
import { customToolCallFromResponseItem } from './custom-tool-wire.mjs';
|
|
28
|
+
import { CODEX_OAUTH_ORIGINATOR, CODEX_RESPONSES_URL, _displayCodexModel } from './openai-oauth.mjs';
|
|
29
|
+
|
|
30
|
+
export function _envFlag(name, fallback = true) {
|
|
31
|
+
const raw = process.env[name];
|
|
32
|
+
if (raw == null || raw === '') return fallback;
|
|
33
|
+
return !['0', 'false', 'off', 'no'].includes(String(raw).toLowerCase());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function _envPositiveInt(name, fallback) {
|
|
37
|
+
const raw = process.env[name];
|
|
38
|
+
if (raw == null || raw === '') return fallback;
|
|
39
|
+
const n = Number(raw);
|
|
40
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Completed function_call.arguments parse for the OpenAI Responses stream.
|
|
44
|
+
// A function_call item arrives only on a completion/done signal, so a
|
|
45
|
+
// non-empty-but-malformed
|
|
46
|
+
// arguments string is deterministic bad JSON — NOT mid-stream truncation.
|
|
47
|
+
// Empty/whitespace input legitimately means "no arguments" → {}. A non-empty
|
|
48
|
+
// string that fails JSON.parse is surfaced as an invalid-args MARKER (instead
|
|
49
|
+
// of being silently swallowed to {}) so the dispatch loop turns it into an
|
|
50
|
+
// is_error tool_result and the model self-corrects in the same turn.
|
|
51
|
+
function _parseJsonObject(value) {
|
|
52
|
+
const text = typeof value === 'string' ? value : (value == null ? '' : String(value));
|
|
53
|
+
if (text.trim() === '') return {};
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(text);
|
|
56
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return makeInvalidToolArgsMarker(text, err instanceof Error ? err.message : String(err));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _extractCachedTokens(usage) {
|
|
63
|
+
const details = usage?.input_tokens_details || usage?.prompt_tokens_details || {};
|
|
64
|
+
return Number(details.cached_tokens ?? details.cached ?? usage?.cached_tokens ?? 0) || 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function _sseEventsFromBuffer(buffer) {
|
|
68
|
+
const frames = [];
|
|
69
|
+
let rest = buffer.replace(/\r\n/g, '\n');
|
|
70
|
+
let idx;
|
|
71
|
+
while ((idx = rest.indexOf('\n\n')) >= 0) {
|
|
72
|
+
frames.push(rest.slice(0, idx));
|
|
73
|
+
rest = rest.slice(idx + 2);
|
|
74
|
+
}
|
|
75
|
+
return { frames, rest };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function _parseSseFrame(frame) {
|
|
79
|
+
const lines = String(frame || '').split('\n');
|
|
80
|
+
const data = [];
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
if (!line || line.startsWith(':')) continue;
|
|
83
|
+
if (line.startsWith('data:')) data.push(line.slice(5).trimStart());
|
|
84
|
+
}
|
|
85
|
+
if (!data.length) return null;
|
|
86
|
+
const raw = data.join('\n').trim();
|
|
87
|
+
if (!raw || raw === '[DONE]') return null;
|
|
88
|
+
try { return JSON.parse(raw); } catch { return null; }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function _incompleteReasonFromEvent(event) {
|
|
92
|
+
const reasonObj = event?.response?.incomplete_details
|
|
93
|
+
|| event?.incomplete_details
|
|
94
|
+
|| event?.response?.status_details
|
|
95
|
+
|| null;
|
|
96
|
+
return String(reasonObj?.reason || event?.response?.status || 'incomplete');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function _isMaxOutputIncompleteReason(reason) {
|
|
100
|
+
return /^(?:max_output_tokens|max_tokens|length|output_token_limit)$/i.test(String(reason || '').trim());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function _pushOutputTextAnnotations(part, citations, citationKeys) {
|
|
104
|
+
const annotations = Array.isArray(part?.annotations) ? part.annotations : [];
|
|
105
|
+
for (const raw of annotations) {
|
|
106
|
+
const url = raw?.url || raw?.uri || raw?.href || '';
|
|
107
|
+
if (!url || citationKeys.has(url)) continue;
|
|
108
|
+
citationKeys.add(url);
|
|
109
|
+
citations.push({
|
|
110
|
+
title: raw?.title || '',
|
|
111
|
+
url,
|
|
112
|
+
snippet: raw?.snippet || raw?.text || raw?.description || '',
|
|
113
|
+
source: 'openai-oauth',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function _buildOpenAIHttpFallbackHeaders({ auth, cacheKey }) {
|
|
119
|
+
const headers = {
|
|
120
|
+
Authorization: `Bearer ${auth.access_token}`,
|
|
121
|
+
'Content-Type': 'application/json',
|
|
122
|
+
Accept: 'text/event-stream',
|
|
123
|
+
'OpenAI-Beta': 'responses=experimental',
|
|
124
|
+
originator: CODEX_OAUTH_ORIGINATOR,
|
|
125
|
+
'chatgpt-account-id': auth.account_id || '',
|
|
126
|
+
'x-client-request-id': randomBytes(16).toString('hex'),
|
|
127
|
+
};
|
|
128
|
+
if (cacheKey) headers.session_id = String(cacheKey);
|
|
129
|
+
return headers;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// WS→HTTP/SSE fallback predicate → shared shouldFallbackTransport
|
|
133
|
+
// (retry-classifier.mjs). The per-provider env flag is computed here and passed
|
|
134
|
+
// as `enabled`; the deny-order + allow-list are identical to the former copy.
|
|
135
|
+
export function _shouldUseOpenAIHttpFallback(err, externalSignal) {
|
|
136
|
+
return shouldFallbackTransport(err, {
|
|
137
|
+
signal: externalSignal,
|
|
138
|
+
enabled: _envFlag('MIXDOG_OPENAI_OAUTH_HTTP_FALLBACK', true),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Exported for the single-emit regression smoke (scripts/openai-oauth-
|
|
143
|
+
// http-sse-toolcall-smoke.mjs): the SSE stream can surface the same
|
|
144
|
+
// function_call across response.function_call_arguments.done +
|
|
145
|
+
// response.output_item.done + response.completed, and onToolCall must fire
|
|
146
|
+
// exactly once per call id. No production caller imports this name; the
|
|
147
|
+
// provider invokes it internally.
|
|
148
|
+
export async function sendViaHttpSse({
|
|
149
|
+
auth,
|
|
150
|
+
body,
|
|
151
|
+
opts,
|
|
152
|
+
onStreamDelta,
|
|
153
|
+
onToolCall,
|
|
154
|
+
onTextDelta,
|
|
155
|
+
onStageChange,
|
|
156
|
+
externalSignal,
|
|
157
|
+
poolKey,
|
|
158
|
+
cacheKey,
|
|
159
|
+
iteration,
|
|
160
|
+
useModel,
|
|
161
|
+
fetchFn = fetch,
|
|
162
|
+
} = {}) {
|
|
163
|
+
// P1 audit fix: no fixed wall-clock total cap on the HTTP/SSE fallback
|
|
164
|
+
// stream. The old createTimeoutSignal(..., PROVIDER_GENERATE_TOTAL_TIMEOUT_MS)
|
|
165
|
+
// killed a healthy, still-streaming turn purely on elapsed time, unlike
|
|
166
|
+
// every other streaming provider path (anthropic-oauth uses the same
|
|
167
|
+
// createPassthroughSignal pattern — see anthropic-oauth.mjs "Option A").
|
|
168
|
+
// The stream is bounded instead by:
|
|
169
|
+
// (a) headerTimeout below (PROVIDER_HTTP_RESPONSE_TIMEOUT_MS) for a
|
|
170
|
+
// socket that never sends the initial response,
|
|
171
|
+
// (b) the SEMANTIC idle watchdog (_armSemanticIdle /
|
|
172
|
+
// PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS), which resets on every
|
|
173
|
+
// meaningful() chunk — a live stream stays alive, a truly silent
|
|
174
|
+
// one still aborts, and
|
|
175
|
+
// (c) externalSignal (client disconnect / replaced-by-newer-request).
|
|
176
|
+
const totalTimeout = createPassthroughSignal(externalSignal);
|
|
177
|
+
const headerTimeout = createTimeoutSignal(
|
|
178
|
+
totalTimeout.signal,
|
|
179
|
+
PROVIDER_HTTP_RESPONSE_TIMEOUT_MS,
|
|
180
|
+
'OpenAI OAuth HTTP fallback initial response',
|
|
181
|
+
);
|
|
182
|
+
const headers = _buildOpenAIHttpFallbackHeaders({ auth, cacheKey });
|
|
183
|
+
const fetchStartedAt = Date.now();
|
|
184
|
+
let response;
|
|
185
|
+
try {
|
|
186
|
+
try { onStageChange?.('requesting'); } catch {}
|
|
187
|
+
response = await fetchFn(CODEX_RESPONSES_URL, {
|
|
188
|
+
method: 'POST',
|
|
189
|
+
headers,
|
|
190
|
+
body: JSON.stringify(body),
|
|
191
|
+
signal: headerTimeout.signal,
|
|
192
|
+
dispatcher: getLlmDispatcher(),
|
|
193
|
+
});
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (headerTimeout.signal?.aborted && headerTimeout.signal.reason instanceof Error) throw headerTimeout.signal.reason;
|
|
196
|
+
throw err;
|
|
197
|
+
} finally {
|
|
198
|
+
headerTimeout.cleanup();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
traceAgentFetch({
|
|
202
|
+
sessionId: poolKey,
|
|
203
|
+
headersMs: Date.now() - fetchStartedAt,
|
|
204
|
+
httpStatus: response.status,
|
|
205
|
+
provider: 'openai-oauth',
|
|
206
|
+
model: useModel,
|
|
207
|
+
transport: 'http',
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
const text = await response.text().catch(() => '');
|
|
212
|
+
const err = new Error(`OpenAI OAuth HTTP fallback ${response.status}: ${text.slice(0, 200)}`);
|
|
213
|
+
err.httpStatus = response.status;
|
|
214
|
+
err.headers = response.headers;
|
|
215
|
+
populateHttpStatusFromMessage(err, text);
|
|
216
|
+
totalTimeout.cleanup();
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
if (!response.body) {
|
|
220
|
+
totalTimeout.cleanup();
|
|
221
|
+
throw new Error('OpenAI OAuth HTTP fallback returned no response body');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try { onStageChange?.('streaming'); } catch {}
|
|
225
|
+
const sseStartedAt = Date.now();
|
|
226
|
+
const reader = response.body.getReader();
|
|
227
|
+
const decoder = new TextDecoder();
|
|
228
|
+
// After headerTimeout.cleanup() the in-flight fetch no longer carries a live
|
|
229
|
+
// signal, so a totalTimeout / external abort that fires during a pending
|
|
230
|
+
// reader.read() would otherwise leave the pooled request hanging. Keep the
|
|
231
|
+
// reader tied to totalTimeout for the whole stream: on abort, cancel the
|
|
232
|
+
// reader so the awaited read() unblocks and the socket is released back to
|
|
233
|
+
// the shared pool instead of leaking. reader.cancel() may resolve the
|
|
234
|
+
// pending read() as {done:true} rather than rejecting, which would let a
|
|
235
|
+
// partial response surface as success — so record the abort reason and
|
|
236
|
+
// re-throw it after the loop unblocks (see below).
|
|
237
|
+
let _streamAbortReason = null;
|
|
238
|
+
let _onTotalAbort = null;
|
|
239
|
+
if (totalTimeout.signal) {
|
|
240
|
+
_onTotalAbort = () => {
|
|
241
|
+
const reason = totalTimeout.signal.reason;
|
|
242
|
+
_streamAbortReason = reason instanceof Error
|
|
243
|
+
? reason
|
|
244
|
+
: new Error('OpenAI OAuth HTTP fallback aborted');
|
|
245
|
+
try { reader.cancel(_streamAbortReason).catch(() => {}); } catch {}
|
|
246
|
+
};
|
|
247
|
+
if (totalTimeout.signal.aborted) _onTotalAbort();
|
|
248
|
+
else totalTimeout.signal.addEventListener('abort', _onTotalAbort, { once: true });
|
|
249
|
+
}
|
|
250
|
+
// SEMANTIC idle watchdog: reset ONLY on meaningful() (text/reasoning/tool
|
|
251
|
+
// deltas), never on raw bytes/keepalive frames, so a stream that emits some
|
|
252
|
+
// deltas then goes silent trips a short, named terminal failure instead of
|
|
253
|
+
// hanging until the 30-min agent watchdog. Disablable via the shared env.
|
|
254
|
+
let _semanticIdleTimer = null;
|
|
255
|
+
const _clearSemanticIdle = () => {
|
|
256
|
+
if (_semanticIdleTimer) { clearTimeout(_semanticIdleTimer); _semanticIdleTimer = null; }
|
|
257
|
+
};
|
|
258
|
+
const _armSemanticIdle = () => {
|
|
259
|
+
if (!PROVIDER_SSE_IDLE_WATCHDOG_ENABLED || !(PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS > 0)) return;
|
|
260
|
+
_clearSemanticIdle();
|
|
261
|
+
_semanticIdleTimer = setTimeout(() => {
|
|
262
|
+
_streamAbortReason = streamStalledError('OpenAI OAuth HTTP fallback', PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS, { emittedToolCall: emittedToolCallIds.size > 0 });
|
|
263
|
+
// Partial-final recovery: attach the
|
|
264
|
+
// streamed partial state so the agent loop can accept a wedged FINAL
|
|
265
|
+
// no-tool summary as a successful partial-final instead of dropping
|
|
266
|
+
// the result. pendingToolUse gates out any mid-flight tool call.
|
|
267
|
+
try {
|
|
268
|
+
_streamAbortReason.partialContent = content;
|
|
269
|
+
_streamAbortReason.partialToolCalls = toolCalls.length ? toolCalls.slice() : undefined;
|
|
270
|
+
_streamAbortReason.pendingToolUse = pendingCalls.size > 0 || emittedToolCallIds.size > 0;
|
|
271
|
+
_streamAbortReason.partialModel = model || undefined;
|
|
272
|
+
} catch { /* best-effort enrichment */ }
|
|
273
|
+
try { reader.cancel(_streamAbortReason).catch(() => {}); } catch {}
|
|
274
|
+
}, PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS);
|
|
275
|
+
try { _semanticIdleTimer.unref?.(); } catch {}
|
|
276
|
+
};
|
|
277
|
+
let buffer = '';
|
|
278
|
+
let content = '';
|
|
279
|
+
let model = '';
|
|
280
|
+
let responseId = '';
|
|
281
|
+
let serviceTier = '';
|
|
282
|
+
let usage = null;
|
|
283
|
+
let ttftMs = null;
|
|
284
|
+
const toolCalls = [];
|
|
285
|
+
const pendingCalls = new Map();
|
|
286
|
+
const reasoningItems = [];
|
|
287
|
+
const citations = [];
|
|
288
|
+
const citationKeys = new Set();
|
|
289
|
+
const webSearchCalls = [];
|
|
290
|
+
const webSearchCallKeys = new Set();
|
|
291
|
+
let completed = false;
|
|
292
|
+
let stopReason = null;
|
|
293
|
+
// Gateway live-text relay invariant: set once a non-empty text chunk has
|
|
294
|
+
// been forwarded to the client. A failure afterwards is non-retryable —
|
|
295
|
+
// the rendered text cannot be withdrawn and a re-request would concatenate
|
|
296
|
+
// a second attempt.
|
|
297
|
+
let emittedText = false;
|
|
298
|
+
|
|
299
|
+
// Single-emit guard for tool calls (matches the WS path's
|
|
300
|
+
// emittedToolCall intent). The HTTP/SSE event stream can surface the
|
|
301
|
+
// same function_call across multiple frames — response.function_call_arguments.done,
|
|
302
|
+
// response.output_item.done, and the final response.completed.output
|
|
303
|
+
// bundle. Each frame independently completes the call (id + name) and
|
|
304
|
+
// would re-invoke onToolCall, double-executing a side-effecting tool.
|
|
305
|
+
// Route every emit through emitToolCall: it fires the callback exactly
|
|
306
|
+
// once per unique call id, the first time the call is complete. A call
|
|
307
|
+
// whose id/name only arrives in a later frame is NOT dropped — its
|
|
308
|
+
// first complete frame still emits; only redundant re-emits are
|
|
309
|
+
// suppressed.
|
|
310
|
+
const emittedToolCallIds = new Set();
|
|
311
|
+
// Fix 2: cross-path name+args dedupe. A text-leaked synthetic and an
|
|
312
|
+
// identical native function_call must fire onToolCall exactly once.
|
|
313
|
+
const _toolDedupe = createToolCallDedupe();
|
|
314
|
+
const emitToolCall = (call) => {
|
|
315
|
+
if (!call || !call.id) return;
|
|
316
|
+
if (emittedToolCallIds.has(call.id)) return;
|
|
317
|
+
emittedToolCallIds.add(call.id);
|
|
318
|
+
if (!_toolDedupe.shouldDispatch(call.name, call.arguments)) return;
|
|
319
|
+
try { onToolCall?.(call); } catch {}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Leaked tool-call guard. The model sometimes emits a tool call as plain
|
|
323
|
+
// text (XML `<invoke>`/`<function_calls>` or gpt-oss harmony
|
|
324
|
+
// `<|channel|>...to=functions.NAME...<|call|>`) inside
|
|
325
|
+
// `response.output_text.delta` instead of a native function_call. Route
|
|
326
|
+
// text through the guard so leaked calls are suppressed from the visible
|
|
327
|
+
// stream, synthesized (native `call_...` id shape), and dispatched like
|
|
328
|
+
// native ones. Known tool names come from the request body so recovery
|
|
329
|
+
// only fires for tools the model was actually offered. Additive: the
|
|
330
|
+
// native function_call path is untouched.
|
|
331
|
+
const _leakKnownTools = new Set(
|
|
332
|
+
(Array.isArray(body?.tools) ? body.tools : [])
|
|
333
|
+
.map((t) => (typeof t?.name === 'string' ? t.name : null))
|
|
334
|
+
.filter(Boolean),
|
|
335
|
+
);
|
|
336
|
+
const leakGuard = createLeakGuard({ knownToolNames: _leakKnownTools, harmony: true });
|
|
337
|
+
const dispatchLeakedCall = (recovered) => {
|
|
338
|
+
let args = recovered?.arguments;
|
|
339
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args)) args = {};
|
|
340
|
+
const call = {
|
|
341
|
+
id: `call_leaked_${randomBytes(8).toString('hex')}`,
|
|
342
|
+
name: recovered.name,
|
|
343
|
+
arguments: args,
|
|
344
|
+
};
|
|
345
|
+
toolCalls.push(call);
|
|
346
|
+
emitToolCall(call);
|
|
347
|
+
};
|
|
348
|
+
const relayLeakText = (delta) => {
|
|
349
|
+
if (!leakGuard.enabled) {
|
|
350
|
+
content += delta || '';
|
|
351
|
+
if (delta && onTextDelta) {
|
|
352
|
+
emittedText = true;
|
|
353
|
+
try { onTextDelta(delta); } catch {}
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const { text, calls } = leakGuard.push(delta);
|
|
358
|
+
if (text) {
|
|
359
|
+
content += text;
|
|
360
|
+
if (onTextDelta) {
|
|
361
|
+
emittedText = true;
|
|
362
|
+
try { onTextDelta(text); } catch {}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
366
|
+
};
|
|
367
|
+
const flushLeak = () => {
|
|
368
|
+
if (!leakGuard.enabled) return;
|
|
369
|
+
const { text, calls } = leakGuard.flush();
|
|
370
|
+
if (text) {
|
|
371
|
+
content += text;
|
|
372
|
+
if (onTextDelta) {
|
|
373
|
+
emittedText = true;
|
|
374
|
+
try { onTextDelta(text); } catch {}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const pushWebSearchCall = (item) => {
|
|
381
|
+
if (!item || item.type !== 'web_search_call') return;
|
|
382
|
+
const key = item.id || JSON.stringify(item.action || item);
|
|
383
|
+
if (webSearchCallKeys.has(key)) return;
|
|
384
|
+
webSearchCallKeys.add(key);
|
|
385
|
+
webSearchCalls.push({ id: item.id || '', status: item.status || '', action: item.action || null });
|
|
386
|
+
};
|
|
387
|
+
const pushReasoningItem = (item) => {
|
|
388
|
+
if (item?.type === 'reasoning' && item.encrypted_content && !reasoningItems.some(r => r.id === item.id)) {
|
|
389
|
+
reasoningItems.push({
|
|
390
|
+
id: item.id || '',
|
|
391
|
+
encrypted_content: item.encrypted_content,
|
|
392
|
+
summary: Array.isArray(item.summary) ? item.summary : [],
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
const pushToolSearchCall = (item) => {
|
|
397
|
+
if (!item || item.type !== 'tool_search_call') return;
|
|
398
|
+
const callId = item.call_id || item.id || '';
|
|
399
|
+
if (!callId || toolCalls.some(t => t.id === callId)) return;
|
|
400
|
+
let args = {};
|
|
401
|
+
if (item.arguments && typeof item.arguments === 'object') {
|
|
402
|
+
args = item.arguments;
|
|
403
|
+
} else if (typeof item.arguments === 'string' && item.arguments.trim()) {
|
|
404
|
+
// Non-empty but malformed tool_search arguments are deterministic
|
|
405
|
+
// bad JSON (the item is only emitted on completion). Surface an
|
|
406
|
+
// invalid-args marker instead of swallowing to {} so the model can
|
|
407
|
+
// self-correct in the same turn.
|
|
408
|
+
args = _parseJsonObject(item.arguments);
|
|
409
|
+
}
|
|
410
|
+
const call = {
|
|
411
|
+
id: callId,
|
|
412
|
+
name: 'tool_search',
|
|
413
|
+
arguments: args,
|
|
414
|
+
nativeType: 'tool_search_call',
|
|
415
|
+
};
|
|
416
|
+
toolCalls.push(call);
|
|
417
|
+
emitToolCall(call);
|
|
418
|
+
};
|
|
419
|
+
const pushCustomToolCall = (item) => {
|
|
420
|
+
const call = customToolCallFromResponseItem(item);
|
|
421
|
+
if (!call || toolCalls.some(t => t.id === call.id)) return;
|
|
422
|
+
toolCalls.push(call);
|
|
423
|
+
emitToolCall(call);
|
|
424
|
+
};
|
|
425
|
+
const meaningful = () => {
|
|
426
|
+
if (ttftMs == null) ttftMs = Date.now() - sseStartedAt;
|
|
427
|
+
_armSemanticIdle();
|
|
428
|
+
try { onStreamDelta?.(); } catch {}
|
|
429
|
+
};
|
|
430
|
+
const handleEvent = (event) => {
|
|
431
|
+
if (!event || typeof event.type !== 'string') return;
|
|
432
|
+
switch (event.type) {
|
|
433
|
+
case 'response.created':
|
|
434
|
+
if (event.response?.model) model = event.response.model;
|
|
435
|
+
if (event.response?.id) responseId = event.response.id;
|
|
436
|
+
break;
|
|
437
|
+
case 'response.output_text.delta':
|
|
438
|
+
meaningful();
|
|
439
|
+
relayLeakText(event.delta || '');
|
|
440
|
+
break;
|
|
441
|
+
case 'response.reasoning_text.delta':
|
|
442
|
+
case 'response.reasoning_summary_text.delta':
|
|
443
|
+
meaningful();
|
|
444
|
+
break;
|
|
445
|
+
case 'response.output_item.added':
|
|
446
|
+
if (event.item?.type === 'function_call') {
|
|
447
|
+
pendingCalls.set(event.item.id || '', {
|
|
448
|
+
name: event.item.name || '',
|
|
449
|
+
callId: event.item.call_id || '',
|
|
450
|
+
});
|
|
451
|
+
} else if (event.item?.type === 'tool_search_call') {
|
|
452
|
+
// Mark tool_search as in-flight the moment the item is
|
|
453
|
+
// added, mirroring function_call above, so the semantic
|
|
454
|
+
// idle watchdog's pendingToolUse gate (pendingCalls.size)
|
|
455
|
+
// sees a mid-flight tool_search and never lets stall
|
|
456
|
+
// recovery drop it before response.output_item.done.
|
|
457
|
+
// kind:'tool_search' tags the entry so the shared
|
|
458
|
+
// function_call_arguments.done handler (below) never
|
|
459
|
+
// mistakes it for a function call by id collision/empty id.
|
|
460
|
+
if (event.item.id) {
|
|
461
|
+
pendingCalls.set(event.item.id, {
|
|
462
|
+
name: 'tool_search',
|
|
463
|
+
callId: event.item.call_id || '',
|
|
464
|
+
kind: 'tool_search',
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
break;
|
|
469
|
+
case 'response.function_call_arguments.delta':
|
|
470
|
+
meaningful();
|
|
471
|
+
break;
|
|
472
|
+
case 'response.function_call_arguments.done': {
|
|
473
|
+
const itemId = event.item_id || '';
|
|
474
|
+
const pending = pendingCalls.get(itemId);
|
|
475
|
+
if (pending?.kind === 'tool_search') { meaningful(); break; }
|
|
476
|
+
const call = {
|
|
477
|
+
id: pending?.callId || event.call_id || '',
|
|
478
|
+
name: pending?.name || event.name || '',
|
|
479
|
+
arguments: _parseJsonObject(event.arguments),
|
|
480
|
+
_pendingItemId: itemId,
|
|
481
|
+
};
|
|
482
|
+
toolCalls.push(call);
|
|
483
|
+
if (call.id && call.name) {
|
|
484
|
+
delete call._pendingItemId;
|
|
485
|
+
emitToolCall(call);
|
|
486
|
+
}
|
|
487
|
+
meaningful();
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
case 'response.custom_tool_call_input.delta':
|
|
491
|
+
meaningful();
|
|
492
|
+
break;
|
|
493
|
+
case 'response.output_item.done': {
|
|
494
|
+
const item = event.item || {};
|
|
495
|
+
pushReasoningItem(item);
|
|
496
|
+
pushWebSearchCall(item);
|
|
497
|
+
if (item.type === 'function_call') {
|
|
498
|
+
const tc = toolCalls.find(t => t._pendingItemId === (item.id || ''));
|
|
499
|
+
if (tc) {
|
|
500
|
+
if (!tc.id && item.call_id) tc.id = item.call_id;
|
|
501
|
+
if (!tc.name && item.name) tc.name = item.name;
|
|
502
|
+
if (tc.id && tc.name) {
|
|
503
|
+
delete tc._pendingItemId;
|
|
504
|
+
emitToolCall(tc);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
} else if (item.type === 'tool_search_call') {
|
|
508
|
+
pendingCalls.delete(item.id || '');
|
|
509
|
+
pushToolSearchCall(item);
|
|
510
|
+
} else if (item.type === 'custom_tool_call') {
|
|
511
|
+
pushCustomToolCall(item);
|
|
512
|
+
meaningful();
|
|
513
|
+
}
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
case 'response.completed': {
|
|
517
|
+
const resp = event.response || {};
|
|
518
|
+
serviceTier = resp.service_tier || resp.serviceTier || serviceTier;
|
|
519
|
+
if (!model && resp.model) model = resp.model;
|
|
520
|
+
if (!responseId && resp.id) responseId = resp.id;
|
|
521
|
+
if (resp.usage) {
|
|
522
|
+
usage = {
|
|
523
|
+
inputTokens: resp.usage.input_tokens || 0,
|
|
524
|
+
outputTokens: resp.usage.output_tokens || 0,
|
|
525
|
+
cachedTokens: _extractCachedTokens(resp.usage),
|
|
526
|
+
promptTokens: resp.usage.input_tokens || 0,
|
|
527
|
+
raw: serviceTier ? { ...resp.usage, service_tier: serviceTier } : resp.usage,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
for (const item of resp.output || []) {
|
|
531
|
+
if (item.type === 'message') {
|
|
532
|
+
for (const part of item.content || []) {
|
|
533
|
+
if (!content && part.type === 'output_text') {
|
|
534
|
+
// Completed-output fallback (no streamed text).
|
|
535
|
+
// Route through the leak guard so a tool call
|
|
536
|
+
// leaked only in the final bundle is recovered
|
|
537
|
+
// rather than surfaced as visible content. push
|
|
538
|
+
// with final=true flushes fully (no held tail).
|
|
539
|
+
if (leakGuard.enabled) {
|
|
540
|
+
const { text, calls } = leakGuard.push(part.text || '', true);
|
|
541
|
+
content += text;
|
|
542
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
543
|
+
} else {
|
|
544
|
+
content += part.text || '';
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (part.type === 'output_text') _pushOutputTextAnnotations(part, citations, citationKeys);
|
|
548
|
+
}
|
|
549
|
+
} else if (item.type === 'reasoning') {
|
|
550
|
+
pushReasoningItem(item);
|
|
551
|
+
} else if (item.type === 'web_search_call') {
|
|
552
|
+
pushWebSearchCall(item);
|
|
553
|
+
} else if (item.type === 'tool_search_call') {
|
|
554
|
+
pushToolSearchCall(item);
|
|
555
|
+
} else if (item.type === 'custom_tool_call') {
|
|
556
|
+
pushCustomToolCall(item);
|
|
557
|
+
meaningful();
|
|
558
|
+
} else if (item.type === 'function_call') {
|
|
559
|
+
// Match the still-pending placeholder by item id, or
|
|
560
|
+
// an already-recorded call by its canonical call_id —
|
|
561
|
+
// so a call completed at args.done / output_item.done
|
|
562
|
+
// is reused here rather than re-pushed as a duplicate.
|
|
563
|
+
const tc = toolCalls.find(t =>
|
|
564
|
+
t._pendingItemId === (item.id || '')
|
|
565
|
+
|| (item.call_id && t.id === item.call_id));
|
|
566
|
+
if (tc) {
|
|
567
|
+
if (!tc.id && item.call_id) tc.id = item.call_id;
|
|
568
|
+
if (!tc.name && item.name) tc.name = item.name;
|
|
569
|
+
if (tc.id && tc.name) {
|
|
570
|
+
delete tc._pendingItemId;
|
|
571
|
+
emitToolCall(tc);
|
|
572
|
+
}
|
|
573
|
+
} else if (item.call_id && item.name) {
|
|
574
|
+
const call = {
|
|
575
|
+
id: item.call_id,
|
|
576
|
+
name: item.name,
|
|
577
|
+
arguments: _parseJsonObject(item.arguments),
|
|
578
|
+
};
|
|
579
|
+
toolCalls.push(call);
|
|
580
|
+
emitToolCall(call);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
completed = true;
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case 'response.done':
|
|
588
|
+
if (!event.response || event.response.status === 'completed') completed = true;
|
|
589
|
+
else if (event.response.status === 'failed') {
|
|
590
|
+
const msg = event.response?.error?.message || 'response.done failed';
|
|
591
|
+
const err = new Error(`OpenAI OAuth HTTP fallback response.done failed: ${msg}`);
|
|
592
|
+
populateHttpStatusFromMessage(err, msg);
|
|
593
|
+
throw err;
|
|
594
|
+
} else if (event.response.status === 'incomplete') {
|
|
595
|
+
const reason = _incompleteReasonFromEvent(event);
|
|
596
|
+
if (_isMaxOutputIncompleteReason(reason)) {
|
|
597
|
+
completed = true;
|
|
598
|
+
stopReason = 'length';
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
throw new Error(`OpenAI OAuth HTTP fallback response.done incomplete: ${reason}`);
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
604
|
+
case 'response.failed': {
|
|
605
|
+
const msg = event.response?.error?.message || event.error?.message || event.message || 'response.failed';
|
|
606
|
+
const err = new Error(`OpenAI OAuth HTTP fallback response.failed: ${msg}`);
|
|
607
|
+
populateHttpStatusFromMessage(err, msg);
|
|
608
|
+
throw err;
|
|
609
|
+
}
|
|
610
|
+
case 'response.incomplete': {
|
|
611
|
+
const reason = _incompleteReasonFromEvent(event);
|
|
612
|
+
if (_isMaxOutputIncompleteReason(reason)) {
|
|
613
|
+
completed = true;
|
|
614
|
+
stopReason = 'length';
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
throw new Error(`OpenAI OAuth HTTP fallback response.incomplete: ${reason}`);
|
|
618
|
+
}
|
|
619
|
+
case 'error': {
|
|
620
|
+
const msg = event.message || event.error?.message || 'unknown';
|
|
621
|
+
const err = new Error(`OpenAI OAuth HTTP fallback error: ${msg}`);
|
|
622
|
+
populateHttpStatusFromMessage(err, msg);
|
|
623
|
+
throw err;
|
|
624
|
+
}
|
|
625
|
+
default:
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
try {
|
|
631
|
+
while (true) {
|
|
632
|
+
if (totalTimeout.signal?.aborted) {
|
|
633
|
+
_clearSemanticIdle();
|
|
634
|
+
const reason = totalTimeout.signal.reason;
|
|
635
|
+
throw reason instanceof Error ? reason : new Error('OpenAI OAuth HTTP fallback aborted');
|
|
636
|
+
}
|
|
637
|
+
if (_streamAbortReason) throw _streamAbortReason;
|
|
638
|
+
const { value, done } = await reader.read();
|
|
639
|
+
if (done) break;
|
|
640
|
+
buffer += decoder.decode(value, { stream: true });
|
|
641
|
+
const parsed = _sseEventsFromBuffer(buffer);
|
|
642
|
+
buffer = parsed.rest;
|
|
643
|
+
for (const frame of parsed.frames) {
|
|
644
|
+
const event = _parseSseFrame(frame);
|
|
645
|
+
if (event) handleEvent(event);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
// The read() above can unblock via reader.cancel() as {done:true} on an
|
|
649
|
+
// external/total-timeout abort. Surface that as the abort/timeout error
|
|
650
|
+
// instead of treating the partial stream as a successful response.
|
|
651
|
+
if (_streamAbortReason) throw _streamAbortReason;
|
|
652
|
+
buffer += decoder.decode();
|
|
653
|
+
const parsed = _sseEventsFromBuffer(buffer + '\n\n');
|
|
654
|
+
for (const frame of parsed.frames) {
|
|
655
|
+
const event = _parseSseFrame(frame);
|
|
656
|
+
if (event) handleEvent(event);
|
|
657
|
+
}
|
|
658
|
+
// Flush any partial-sentinel tail held back mid-stream so legitimate
|
|
659
|
+
// trailing text is never lost (streamed-text path).
|
|
660
|
+
flushLeak();
|
|
661
|
+
} catch (err) {
|
|
662
|
+
// Live-text invariant: once a non-empty chunk has been relayed it
|
|
663
|
+
// cannot be withdrawn — flag the error so no upstream layer retries.
|
|
664
|
+
if (emittedText && err) { try { err.liveTextEmitted = true; err.unsafeToRetry = true; } catch {} }
|
|
665
|
+
throw err;
|
|
666
|
+
} finally {
|
|
667
|
+
_clearSemanticIdle();
|
|
668
|
+
try { reader.releaseLock?.(); } catch {}
|
|
669
|
+
if (_onTotalAbort && totalTimeout.signal) {
|
|
670
|
+
try { totalTimeout.signal.removeEventListener('abort', _onTotalAbort); } catch {}
|
|
671
|
+
}
|
|
672
|
+
totalTimeout.cleanup();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const unresolved = toolCalls.find(t => t._pendingItemId);
|
|
676
|
+
if (unresolved) {
|
|
677
|
+
throw new Error(`OpenAI OAuth HTTP fallback function_call salvage failed: missing call_id/name for item_id=${unresolved._pendingItemId || '?'}`);
|
|
678
|
+
}
|
|
679
|
+
if (!completed && !content && !toolCalls.length) {
|
|
680
|
+
throw new Error('OpenAI OAuth HTTP fallback ended before response.completed');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const liveModel = model || useModel;
|
|
684
|
+
traceAgentSse({
|
|
685
|
+
sessionId: poolKey,
|
|
686
|
+
sseParseMs: Date.now() - sseStartedAt,
|
|
687
|
+
ttftMs,
|
|
688
|
+
provider: 'openai-oauth',
|
|
689
|
+
model: liveModel,
|
|
690
|
+
transport: 'sse',
|
|
691
|
+
});
|
|
692
|
+
if (usage) {
|
|
693
|
+
traceAgentUsage({
|
|
694
|
+
sessionId: poolKey,
|
|
695
|
+
iteration,
|
|
696
|
+
inputTokens: usage.inputTokens || 0,
|
|
697
|
+
outputTokens: usage.outputTokens || 0,
|
|
698
|
+
cachedTokens: usage.cachedTokens || 0,
|
|
699
|
+
promptTokens: usage.promptTokens || 0,
|
|
700
|
+
model: liveModel,
|
|
701
|
+
modelDisplay: _displayCodexModel(liveModel),
|
|
702
|
+
responseId: responseId || null,
|
|
703
|
+
rawUsage: usage.raw || null,
|
|
704
|
+
provider: 'openai-oauth',
|
|
705
|
+
serviceTier,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
// Dedupe the returned array by name+args (Fix 2, array side): a synthetic
|
|
709
|
+
// leaked call and an identical native function_call must not both survive,
|
|
710
|
+
// else the agent loop executes the side-effecting tool twice.
|
|
711
|
+
const _returnedToolCalls = toolCalls.length
|
|
712
|
+
? dedupeToolCallList(toolCalls.map(({ _pendingItemId, ...t }) => t))
|
|
713
|
+
: undefined;
|
|
714
|
+
return {
|
|
715
|
+
content,
|
|
716
|
+
model: liveModel,
|
|
717
|
+
reasoningItems: reasoningItems.length ? reasoningItems : undefined,
|
|
718
|
+
toolCalls: _returnedToolCalls,
|
|
719
|
+
citations: citations.length ? citations : undefined,
|
|
720
|
+
webSearchCalls: webSearchCalls.length ? webSearchCalls : undefined,
|
|
721
|
+
usage: usage || undefined,
|
|
722
|
+
stopReason: stopReason || undefined,
|
|
723
|
+
// P1 audit fix: text-only max-output cutoff (openai-oauth HTTP/SSE
|
|
724
|
+
// fallback maps status:'incomplete'/reason=max_output_tokens to
|
|
725
|
+
// stopReason='length' above and treats it as success). Flag it so
|
|
726
|
+
// loop.mjs can surface a truncation warning instead of accepting
|
|
727
|
+
// silently-cut content as a clean final answer.
|
|
728
|
+
...(stopReason === 'length' && content.length > 0 ? { truncated: true } : {}),
|
|
729
|
+
responseId: responseId || undefined,
|
|
730
|
+
serviceTier: serviceTier || undefined,
|
|
731
|
+
};
|
|
732
|
+
}
|