mixdog 0.9.3 → 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 +7 -3
- 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/freevar-smoke.mjs +95 -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/provider-toolcall-test.mjs +7 -3
- 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 +23 -63
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +6 -5
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/worker/AGENT.md +8 -4
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +632 -2042
- 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/rules/agent/00-common.md +2 -0
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +0 -11
- 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/providers/anthropic-effort.mjs +62 -20
- 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 +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- 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 +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- 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/openai-codex-model.mjs +108 -0
- 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 +40 -1143
- 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 +297 -2123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
- 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/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-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- 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/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -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/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- 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/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 +32 -41
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- 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 -92
- 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 +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- 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 +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- 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 +229 -663
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -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/output-forwarder.mjs +1 -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/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/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +201 -1948
- 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/embedding-warmup.mjs +58 -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 +24 -842
- 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-store.mjs +22 -2
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -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 +5 -13
- 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/atomic-file.mjs +26 -1
- package/src/runtime/shared/launcher-control.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 +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +84 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -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 +11 -9
- 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/settings-api.mjs +319 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -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 +110 -671
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +30 -9
- 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 +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +945 -8094
- 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 +23 -101
- 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 +52 -594
- 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/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +19652 -18630
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -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.mjs +122 -562
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- 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 +67 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/sequential/WORKFLOW.md +1 -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/external-tool-adapters.test.mjs +0 -162
- 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/task-notification-envelope.test.mjs +0 -107
- 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/input-editing.selection.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/paste-fix.test.mjs +0 -119
- 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,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gemini-stream.mjs — Gemini stream consumption (REST + SDK) and stream guards.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from gemini.mjs. Owns chunk aggregation, completion assertions,
|
|
5
|
+
* timeout/truncation error shapes and the text leak guard that recovers
|
|
6
|
+
* tool calls emitted as plain text. gemini.mjs imports the consumer entry
|
|
7
|
+
* points; parseToolCalls/emitGeminiToolCalls stay in gemini.mjs and are
|
|
8
|
+
* imported back (function-level, no init-time cycle).
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
12
|
+
PROVIDER_MAX_BEFORE_WARN_MS,
|
|
13
|
+
PROVIDER_SSE_IDLE_TIMEOUT_MS,
|
|
14
|
+
PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
|
|
15
|
+
providerTimeoutError,
|
|
16
|
+
resolveTimeoutMs,
|
|
17
|
+
} from '../stall-policy.mjs';
|
|
18
|
+
import { scanLeakedToolCalls } from './anthropic-leaked-toolcall.mjs';
|
|
19
|
+
import { traceHash, stableTraceStringify } from './trace-utils.mjs';
|
|
20
|
+
import { parseToolCalls, emitGeminiToolCalls, collectGeminiGroundingSources } from './gemini.mjs';
|
|
21
|
+
|
|
22
|
+
export const GEMINI_FIRST_BYTE_TIMEOUT_MS = resolveTimeoutMs(
|
|
23
|
+
'MIXDOG_GEMINI_FIRST_BYTE_TIMEOUT_MS',
|
|
24
|
+
PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
25
|
+
{ minMs: 30_000, maxMs: PROVIDER_MAX_BEFORE_WARN_MS },
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export function geminiTimeoutError(label, timeoutMs) {
|
|
29
|
+
const err = providerTimeoutError(label, timeoutMs);
|
|
30
|
+
err.name = 'GeminiTimeoutError';
|
|
31
|
+
err.code = 'EGEMINITIMEOUT';
|
|
32
|
+
return err;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function geminiTruncatedStreamError(message) {
|
|
36
|
+
return Object.assign(
|
|
37
|
+
new Error(message),
|
|
38
|
+
{ name: 'TruncatedStreamError', code: 'TRUNCATED_STREAM', truncatedStream: true },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Aggregate streamed GenerateContentResponse chunks into one response object
|
|
44
|
+
* (same shape as a non-streaming generateContent JSON body).
|
|
45
|
+
* Mirrors @google/generative-ai aggregateResponses().
|
|
46
|
+
*/
|
|
47
|
+
export function aggregateGeminiStreamChunks(responses) {
|
|
48
|
+
const lastResponse = responses[responses.length - 1];
|
|
49
|
+
const aggregatedResponse = {
|
|
50
|
+
promptFeedback: lastResponse?.promptFeedback,
|
|
51
|
+
};
|
|
52
|
+
for (const response of responses) {
|
|
53
|
+
if (response?.candidates) {
|
|
54
|
+
let candidateIndex = 0;
|
|
55
|
+
for (const candidate of response.candidates) {
|
|
56
|
+
if (!aggregatedResponse.candidates) aggregatedResponse.candidates = [];
|
|
57
|
+
if (!aggregatedResponse.candidates[candidateIndex]) {
|
|
58
|
+
aggregatedResponse.candidates[candidateIndex] = { index: candidateIndex };
|
|
59
|
+
}
|
|
60
|
+
const aggCand = aggregatedResponse.candidates[candidateIndex];
|
|
61
|
+
aggCand.citationMetadata = candidate.citationMetadata;
|
|
62
|
+
aggCand.groundingMetadata = candidate.groundingMetadata;
|
|
63
|
+
aggCand.finishReason = candidate.finishReason;
|
|
64
|
+
aggCand.finishMessage = candidate.finishMessage;
|
|
65
|
+
aggCand.safetyRatings = candidate.safetyRatings;
|
|
66
|
+
if (candidate.content?.parts) {
|
|
67
|
+
if (!aggCand.content) {
|
|
68
|
+
aggCand.content = {
|
|
69
|
+
role: candidate.content.role || 'user',
|
|
70
|
+
parts: [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
for (const part of candidate.content.parts) {
|
|
74
|
+
const newPart = {};
|
|
75
|
+
if (part.text) newPart.text = part.text;
|
|
76
|
+
if (part.functionCall) newPart.functionCall = part.functionCall;
|
|
77
|
+
if (part.thoughtSignature) newPart.thoughtSignature = part.thoughtSignature;
|
|
78
|
+
if (part.thought_signature) newPart.thought_signature = part.thought_signature;
|
|
79
|
+
if (part.executableCode) newPart.executableCode = part.executableCode;
|
|
80
|
+
if (part.codeExecutionResult) newPart.codeExecutionResult = part.codeExecutionResult;
|
|
81
|
+
if (Object.keys(newPart).length === 0) newPart.text = '';
|
|
82
|
+
aggCand.content.parts.push(newPart);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
candidateIndex++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (response?.usageMetadata) aggregatedResponse.usageMetadata = response.usageMetadata;
|
|
89
|
+
}
|
|
90
|
+
return aggregatedResponse;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function assertGeminiStreamCompleted({ sawStreamChunk, finishReason, label }) {
|
|
94
|
+
if (!sawStreamChunk) {
|
|
95
|
+
throw geminiTruncatedStreamError(`${label} truncated: empty stream`);
|
|
96
|
+
}
|
|
97
|
+
if (!finishReason) {
|
|
98
|
+
throw geminiTruncatedStreamError(`${label} truncated: no finishReason`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Concatenate the text parts of a single streamed Gemini chunk. Used to feed
|
|
103
|
+
// the gateway live-text relay (onTextDelta) with the incremental text payload
|
|
104
|
+
// as each SSE/SDK chunk arrives. Returns '' for tool-call / thought-only /
|
|
105
|
+
// malformed chunks so the caller can skip empty emits.
|
|
106
|
+
export function geminiChunkText(chunk) {
|
|
107
|
+
const parts = chunk?.candidates?.[0]?.content?.parts;
|
|
108
|
+
if (!Array.isArray(parts)) return '';
|
|
109
|
+
let text = '';
|
|
110
|
+
for (const p of parts) {
|
|
111
|
+
if (p && typeof p.text === 'string') text += p.text;
|
|
112
|
+
}
|
|
113
|
+
return text;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function relayGeminiStreamText(t, { onTextDelta, textLeakGuard }) {
|
|
117
|
+
if (!t) return;
|
|
118
|
+
if (textLeakGuard) textLeakGuard.feedText(t);
|
|
119
|
+
else if (onTextDelta) { try { onTextDelta(t); } catch {} }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Rolling scanner for tool calls leaked as plain XML/antml tags inside Gemini
|
|
124
|
+
* `part.text` streams. Mirrors the Anthropic OAuth guard: suppress tags from
|
|
125
|
+
* visible text, synthesize known-tool calls, dispatch via onToolCall.
|
|
126
|
+
*/
|
|
127
|
+
export function createGeminiTextLeakGuard({ knownToolNames, onTextDelta, onToolCall, onStreamDelta }) {
|
|
128
|
+
const _knownTools = knownToolNames instanceof Set
|
|
129
|
+
? knownToolNames
|
|
130
|
+
: new Set(Array.isArray(knownToolNames) ? knownToolNames : []);
|
|
131
|
+
const _enabled = _knownTools.size > 0;
|
|
132
|
+
const _isKnownTool = (name) => _knownTools.has(name);
|
|
133
|
+
let leakBuffer = '';
|
|
134
|
+
const leakedCalls = [];
|
|
135
|
+
const dispatchedFingerprints = new Set();
|
|
136
|
+
|
|
137
|
+
const toolCallFingerprint = (name, args) => {
|
|
138
|
+
let a = args;
|
|
139
|
+
if (a === null || typeof a !== 'object' || Array.isArray(a)) a = {};
|
|
140
|
+
return traceHash(stableTraceStringify({ name: name || '', args: a }));
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const dispatchLeakedCall = (recovered) => {
|
|
144
|
+
let args = recovered?.arguments;
|
|
145
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args)) args = {};
|
|
146
|
+
const fp = toolCallFingerprint(recovered.name, args);
|
|
147
|
+
if (dispatchedFingerprints.has(fp)) return;
|
|
148
|
+
dispatchedFingerprints.add(fp);
|
|
149
|
+
const idHash = traceHash(stableTraceStringify({
|
|
150
|
+
name: recovered.name,
|
|
151
|
+
args,
|
|
152
|
+
leak: true,
|
|
153
|
+
})).slice(0, 16);
|
|
154
|
+
const call = {
|
|
155
|
+
id: `gemini_leaked_${idHash}`,
|
|
156
|
+
name: recovered.name,
|
|
157
|
+
arguments: args,
|
|
158
|
+
};
|
|
159
|
+
leakedCalls.push(call);
|
|
160
|
+
try { onToolCall?.(call); } catch {}
|
|
161
|
+
try { onStreamDelta?.(); } catch {}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const pumpLeakBuffer = (final) => {
|
|
165
|
+
if (!_enabled) return;
|
|
166
|
+
if (!leakBuffer && !final) return;
|
|
167
|
+
const { emit, calls, rest } = scanLeakedToolCalls(leakBuffer, { isKnownTool: _isKnownTool, final });
|
|
168
|
+
leakBuffer = rest;
|
|
169
|
+
if (emit && onTextDelta) {
|
|
170
|
+
try { onTextDelta(emit); } catch {}
|
|
171
|
+
}
|
|
172
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
get enabled() { return _enabled; },
|
|
177
|
+
feedText(text) {
|
|
178
|
+
if (!text) return;
|
|
179
|
+
if (!_enabled) {
|
|
180
|
+
try { onTextDelta?.(text); } catch {}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
leakBuffer += text;
|
|
184
|
+
pumpLeakBuffer(false);
|
|
185
|
+
},
|
|
186
|
+
finalize() {
|
|
187
|
+
pumpLeakBuffer(true);
|
|
188
|
+
},
|
|
189
|
+
scrubAssistantText(raw) {
|
|
190
|
+
if (!raw) return '';
|
|
191
|
+
if (!_enabled) return raw;
|
|
192
|
+
const { emit, calls, rest } = scanLeakedToolCalls(raw, { isKnownTool: _isKnownTool, final: true });
|
|
193
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
194
|
+
return emit + rest;
|
|
195
|
+
},
|
|
196
|
+
filterNativeToolCalls(nativeCalls) {
|
|
197
|
+
if (!_enabled || !nativeCalls?.length) return nativeCalls;
|
|
198
|
+
const kept = [];
|
|
199
|
+
for (const call of nativeCalls) {
|
|
200
|
+
const fp = toolCallFingerprint(call?.name, call?.arguments);
|
|
201
|
+
if (dispatchedFingerprints.has(fp)) continue;
|
|
202
|
+
dispatchedFingerprints.add(fp);
|
|
203
|
+
kept.push(call);
|
|
204
|
+
}
|
|
205
|
+
return kept.length ? kept : undefined;
|
|
206
|
+
},
|
|
207
|
+
getLeakedToolCalls() {
|
|
208
|
+
return leakedCalls.length ? [...leakedCalls] : [];
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export async function consumeGeminiRestStreamResponse(response, { signal, onStreamDelta, onTextDelta, textLeakGuard, label }) {
|
|
214
|
+
if (!response?.body) throw new Error(`${label}: missing response body`);
|
|
215
|
+
const reader = response.body.getReader();
|
|
216
|
+
const decoder = new TextDecoder();
|
|
217
|
+
let buffer = '';
|
|
218
|
+
const allChunks = [];
|
|
219
|
+
let sawStreamChunk = false;
|
|
220
|
+
let idleTimedOut = false;
|
|
221
|
+
let idleTimer = null;
|
|
222
|
+
let idleReject = null;
|
|
223
|
+
|
|
224
|
+
let firstByteTimer = setTimeout(() => {
|
|
225
|
+
try { reader.cancel('first byte timeout'); } catch {}
|
|
226
|
+
if (idleReject) {
|
|
227
|
+
const e = geminiTimeoutError(`${label} first byte`, GEMINI_FIRST_BYTE_TIMEOUT_MS);
|
|
228
|
+
const r = idleReject; idleReject = null; r(e);
|
|
229
|
+
}
|
|
230
|
+
}, GEMINI_FIRST_BYTE_TIMEOUT_MS);
|
|
231
|
+
if (firstByteTimer.unref) firstByteTimer.unref();
|
|
232
|
+
|
|
233
|
+
const clearFirstByteTimer = () => {
|
|
234
|
+
if (firstByteTimer) {
|
|
235
|
+
clearTimeout(firstByteTimer);
|
|
236
|
+
firstByteTimer = null;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const resetIdleTimer = () => {
|
|
241
|
+
if (!PROVIDER_SSE_IDLE_WATCHDOG_ENABLED) return;
|
|
242
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
243
|
+
idleTimer = setTimeout(() => {
|
|
244
|
+
idleTimedOut = true;
|
|
245
|
+
try { reader.cancel('SSE idle timeout'); } catch {}
|
|
246
|
+
if (idleReject) {
|
|
247
|
+
const e = geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
|
|
248
|
+
const r = idleReject; idleReject = null; r(e);
|
|
249
|
+
}
|
|
250
|
+
}, PROVIDER_SSE_IDLE_TIMEOUT_MS);
|
|
251
|
+
if (idleTimer.unref) idleTimer.unref();
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const onAbort = () => {
|
|
255
|
+
try {
|
|
256
|
+
const c = reader.cancel('aborted');
|
|
257
|
+
if (c && typeof c.catch === 'function') c.catch(() => {});
|
|
258
|
+
} catch {}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
if (signal) {
|
|
262
|
+
if (signal.aborted) {
|
|
263
|
+
const reason = signal.reason;
|
|
264
|
+
throw reason instanceof Error ? reason : new Error(`${label} aborted`);
|
|
265
|
+
}
|
|
266
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
resetIdleTimer();
|
|
271
|
+
while (true) {
|
|
272
|
+
let chunk;
|
|
273
|
+
try {
|
|
274
|
+
chunk = await new Promise((resolve, reject) => {
|
|
275
|
+
idleReject = reject;
|
|
276
|
+
reader.read().then(resolve, reject);
|
|
277
|
+
});
|
|
278
|
+
} catch (err) {
|
|
279
|
+
if (idleTimedOut) {
|
|
280
|
+
throw geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
|
|
281
|
+
}
|
|
282
|
+
if (signal?.aborted) {
|
|
283
|
+
const reason = signal.reason;
|
|
284
|
+
throw reason instanceof Error ? reason : new Error(`${label} aborted`);
|
|
285
|
+
}
|
|
286
|
+
throw err;
|
|
287
|
+
} finally {
|
|
288
|
+
idleReject = null;
|
|
289
|
+
}
|
|
290
|
+
const { done, value } = chunk;
|
|
291
|
+
if (done) break;
|
|
292
|
+
resetIdleTimer();
|
|
293
|
+
buffer += decoder.decode(value, { stream: true });
|
|
294
|
+
let lineEnd;
|
|
295
|
+
while ((lineEnd = buffer.indexOf('\n')) >= 0) {
|
|
296
|
+
let line = buffer.slice(0, lineEnd);
|
|
297
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
298
|
+
line = line.replace(/\r$/, '');
|
|
299
|
+
if (!line.startsWith('data: ')) continue;
|
|
300
|
+
const data = line.slice(6).trim();
|
|
301
|
+
if (!data || data === '[DONE]') continue;
|
|
302
|
+
let parsed;
|
|
303
|
+
try { parsed = JSON.parse(data); } catch { continue; }
|
|
304
|
+
if (!sawStreamChunk) {
|
|
305
|
+
sawStreamChunk = true;
|
|
306
|
+
clearFirstByteTimer();
|
|
307
|
+
}
|
|
308
|
+
allChunks.push(parsed);
|
|
309
|
+
try { onStreamDelta?.(); } catch {}
|
|
310
|
+
if (onTextDelta || textLeakGuard) {
|
|
311
|
+
const t = geminiChunkText(parsed);
|
|
312
|
+
relayGeminiStreamText(t, { onTextDelta, textLeakGuard });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (buffer.trim()) {
|
|
317
|
+
const line = buffer.trim().replace(/\r$/, '');
|
|
318
|
+
if (line.startsWith('data: ')) {
|
|
319
|
+
const data = line.slice(6).trim();
|
|
320
|
+
if (data && data !== '[DONE]') {
|
|
321
|
+
try {
|
|
322
|
+
const parsed = JSON.parse(data);
|
|
323
|
+
if (!sawStreamChunk) {
|
|
324
|
+
sawStreamChunk = true;
|
|
325
|
+
clearFirstByteTimer();
|
|
326
|
+
}
|
|
327
|
+
allChunks.push(parsed);
|
|
328
|
+
try { onStreamDelta?.(); } catch {}
|
|
329
|
+
if (onTextDelta || textLeakGuard) {
|
|
330
|
+
const t = geminiChunkText(parsed);
|
|
331
|
+
relayGeminiStreamText(t, { onTextDelta, textLeakGuard });
|
|
332
|
+
}
|
|
333
|
+
} catch { /* skip malformed tail */ }
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
} finally {
|
|
338
|
+
clearFirstByteTimer();
|
|
339
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
340
|
+
if (signal) signal.removeEventListener('abort', onAbort);
|
|
341
|
+
try { reader.releaseLock(); } catch {}
|
|
342
|
+
try { textLeakGuard?.finalize(); } catch {}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const aggregated = aggregateGeminiStreamChunks(allChunks);
|
|
346
|
+
const finishReason = aggregated.candidates?.[0]?.finishReason || null;
|
|
347
|
+
assertGeminiStreamCompleted({ sawStreamChunk, finishReason, label });
|
|
348
|
+
return aggregated;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export async function consumeGeminiSdkStream(streamResult, { signal, onStreamDelta, onTextDelta, textLeakGuard, label }) {
|
|
352
|
+
let sawStreamChunk = false;
|
|
353
|
+
let idleTimedOut = false;
|
|
354
|
+
let idleTimer = null;
|
|
355
|
+
let firstByteReject = null;
|
|
356
|
+
let firstByteTimer = null;
|
|
357
|
+
let inFlightReject = null;
|
|
358
|
+
|
|
359
|
+
const armFirstByteTimer = () => {
|
|
360
|
+
if (firstByteTimer) clearTimeout(firstByteTimer);
|
|
361
|
+
firstByteTimer = setTimeout(() => {
|
|
362
|
+
if (firstByteReject) {
|
|
363
|
+
const e = geminiTimeoutError(`${label} first byte`, GEMINI_FIRST_BYTE_TIMEOUT_MS);
|
|
364
|
+
const r = firstByteReject; firstByteReject = null; r(e);
|
|
365
|
+
}
|
|
366
|
+
}, GEMINI_FIRST_BYTE_TIMEOUT_MS);
|
|
367
|
+
if (firstByteTimer.unref) firstByteTimer.unref();
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const clearFirstByteTimer = () => {
|
|
371
|
+
if (firstByteTimer) {
|
|
372
|
+
clearTimeout(firstByteTimer);
|
|
373
|
+
firstByteTimer = null;
|
|
374
|
+
}
|
|
375
|
+
firstByteReject = null;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const resetIdleTimer = () => {
|
|
379
|
+
if (!PROVIDER_SSE_IDLE_WATCHDOG_ENABLED) return;
|
|
380
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
381
|
+
idleTimer = setTimeout(() => {
|
|
382
|
+
idleTimedOut = true;
|
|
383
|
+
if (inFlightReject) {
|
|
384
|
+
const e = geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
|
|
385
|
+
const r = inFlightReject; inFlightReject = null; r(e);
|
|
386
|
+
}
|
|
387
|
+
}, PROVIDER_SSE_IDLE_TIMEOUT_MS);
|
|
388
|
+
if (idleTimer.unref) idleTimer.unref();
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
if (signal?.aborted) {
|
|
392
|
+
const reason = signal.reason;
|
|
393
|
+
throw reason instanceof Error ? reason : new Error(`${label} aborted`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const iterator = streamResult.stream[Symbol.asyncIterator]();
|
|
397
|
+
|
|
398
|
+
// Wire the abort signal to actually CANCEL iteration (mirror of the REST
|
|
399
|
+
// consumer's onAbort -> reader.cancel). Without this, a parent / client /
|
|
400
|
+
// gateway abort that fires AFTER the first byte — with the SSE idle
|
|
401
|
+
// watchdog off by default — would leave iterator.next() hanging: the loop
|
|
402
|
+
// below only reads `signal` for error translation, never to interrupt the
|
|
403
|
+
// pending read. Rejecting the in-flight promise and returning the iterator
|
|
404
|
+
// releases it promptly even if the SDK is slow to propagate the underlying
|
|
405
|
+
// request abort.
|
|
406
|
+
let onSignalAbort = null;
|
|
407
|
+
if (signal) {
|
|
408
|
+
onSignalAbort = () => {
|
|
409
|
+
const reason = signal.reason;
|
|
410
|
+
const err = reason instanceof Error ? reason : new Error(`${label} aborted`);
|
|
411
|
+
if (inFlightReject) {
|
|
412
|
+
const r = inFlightReject;
|
|
413
|
+
inFlightReject = null;
|
|
414
|
+
firstByteReject = null;
|
|
415
|
+
r(err);
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
const ret = iterator.return?.();
|
|
419
|
+
if (ret && typeof ret.catch === 'function') ret.catch(() => {});
|
|
420
|
+
} catch {}
|
|
421
|
+
};
|
|
422
|
+
signal.addEventListener('abort', onSignalAbort, { once: true });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
armFirstByteTimer();
|
|
427
|
+
resetIdleTimer();
|
|
428
|
+
while (true) {
|
|
429
|
+
if (idleTimedOut) {
|
|
430
|
+
throw geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
|
|
431
|
+
}
|
|
432
|
+
let step;
|
|
433
|
+
try {
|
|
434
|
+
step = await new Promise((resolve, reject) => {
|
|
435
|
+
inFlightReject = reject;
|
|
436
|
+
if (!sawStreamChunk) firstByteReject = reject;
|
|
437
|
+
iterator.next().then(
|
|
438
|
+
(value) => {
|
|
439
|
+
inFlightReject = null;
|
|
440
|
+
firstByteReject = null;
|
|
441
|
+
resolve(value);
|
|
442
|
+
},
|
|
443
|
+
(err) => {
|
|
444
|
+
inFlightReject = null;
|
|
445
|
+
firstByteReject = null;
|
|
446
|
+
reject(err);
|
|
447
|
+
},
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
} catch (err) {
|
|
451
|
+
if (idleTimedOut) {
|
|
452
|
+
throw geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
|
|
453
|
+
}
|
|
454
|
+
if (signal?.aborted) {
|
|
455
|
+
const reason = signal.reason;
|
|
456
|
+
throw reason instanceof Error ? reason : new Error(`${label} aborted`);
|
|
457
|
+
}
|
|
458
|
+
throw err;
|
|
459
|
+
}
|
|
460
|
+
if (step.done) break;
|
|
461
|
+
if (!sawStreamChunk) {
|
|
462
|
+
sawStreamChunk = true;
|
|
463
|
+
clearFirstByteTimer();
|
|
464
|
+
}
|
|
465
|
+
resetIdleTimer();
|
|
466
|
+
try { onStreamDelta?.(); } catch {}
|
|
467
|
+
if (onTextDelta || textLeakGuard) {
|
|
468
|
+
const t = geminiChunkText(step.value);
|
|
469
|
+
relayGeminiStreamText(t, { onTextDelta, textLeakGuard });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (idleTimedOut) {
|
|
473
|
+
throw geminiTimeoutError(`${label} SSE idle`, PROVIDER_SSE_IDLE_TIMEOUT_MS);
|
|
474
|
+
}
|
|
475
|
+
} catch (err) {
|
|
476
|
+
clearFirstByteTimer();
|
|
477
|
+
if (signal?.aborted) {
|
|
478
|
+
const reason = signal.reason;
|
|
479
|
+
throw reason instanceof Error ? reason : new Error(`${label} aborted`);
|
|
480
|
+
}
|
|
481
|
+
throw err;
|
|
482
|
+
} finally {
|
|
483
|
+
clearFirstByteTimer();
|
|
484
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
485
|
+
if (signal && onSignalAbort) {
|
|
486
|
+
try { signal.removeEventListener('abort', onSignalAbort); } catch {}
|
|
487
|
+
}
|
|
488
|
+
try { textLeakGuard?.finalize(); } catch {}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
let response;
|
|
492
|
+
try {
|
|
493
|
+
response = await streamResult.response;
|
|
494
|
+
} catch (err) {
|
|
495
|
+
if (signal?.aborted) {
|
|
496
|
+
const reason = signal.reason;
|
|
497
|
+
throw reason instanceof Error ? reason : new Error(`${label} aborted`);
|
|
498
|
+
}
|
|
499
|
+
throw err;
|
|
500
|
+
}
|
|
501
|
+
const raw = response?.candidates ? response : (response?.response || response);
|
|
502
|
+
const finishReason = raw?.candidates?.[0]?.finishReason || null;
|
|
503
|
+
assertGeminiStreamCompleted({ sawStreamChunk, finishReason, label });
|
|
504
|
+
return raw;
|
|
505
|
+
}
|