mixdog 0.9.3 → 0.9.5
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/README.md +112 -38
- package/package.json +10 -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/r5-orchestrated-task.json +7 -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-r13-clientmeta.json +464 -0
- package/scripts/bench/round-r14-betafeatures.json +466 -0
- package/scripts/bench/round-r15-fulldefault.json +462 -0
- package/scripts/bench/round-r16-sessionid.json +466 -0
- package/scripts/bench/round-r17-wirebytes.json +456 -0
- package/scripts/bench/round-r18-prewarm.json +468 -0
- package/scripts/bench/round-r19-clean.json +472 -0
- package/scripts/bench/round-r20-prewarm-clean.json +475 -0
- package/scripts/bench/round-r21-delta-retry.json +473 -0
- package/scripts/bench/round-r22-full-probe.json +693 -0
- package/scripts/bench/round-r23-itemprobe.json +701 -0
- package/scripts/bench/round-r24-shapefix.json +677 -0
- package/scripts/bench/round-r25-serial.json +464 -0
- package/scripts/bench/round-r26-parallel3.json +671 -0
- package/scripts/bench/round-r27-parallel10.json +894 -0
- package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
- package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
- package/scripts/bench/round-r30-instid.json +253 -0
- package/scripts/bench/round-r31-upgradeprobe.json +256 -0
- package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
- package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
- package/scripts/bench/round-r34-orchestrated.json +120 -0
- package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
- package/scripts/bench/round-r36-orchestrated-capped.json +128 -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 +251 -32
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +3 -4
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/model-catalog-audit.mjs +209 -0
- package/scripts/model-list-sanitize-test.mjs +37 -0
- 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-bench.mjs +76 -13
- package/scripts/recall-quality-cases.json +12 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +25 -65
- 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 +9 -8
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/scheduler-task/AGENT.md +2 -3
- package/src/agents/webhook-handler/AGENT.md +2 -3
- package/src/agents/worker/AGENT.md +10 -7
- package/src/app.mjs +12 -1
- package/src/headless-role.mjs +7 -1
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +647 -2056
- package/src/output-styles/default.md +30 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +8 -6
- package/src/output-styles/simple.md +21 -7
- package/src/rules/agent/00-common.md +6 -3
- package/src/rules/agent/30-explorer.md +16 -5
- package/src/rules/lead/01-general.md +5 -5
- package/src/rules/lead/lead-brief.md +15 -0
- package/src/rules/lead/lead-tool.md +6 -15
- package/src/rules/shared/01-tool.md +17 -21
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
- 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 +105 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -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 +740 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -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/provider-catalog-cache.mjs +80 -0
- 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 +275 -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 +278 -402
- 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/stall-policy.mjs +20 -1
- 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 +35 -44
- 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 +27 -318
- package/src/runtime/channels/backends/telegram.mjs +8 -12
- package/src/runtime/channels/index.mjs +247 -701
- package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
- package/src/runtime/channels/lib/config.mjs +37 -149
- package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
- package/src/runtime/channels/lib/event-queue.mjs +78 -13
- package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
- package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
- 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/scheduler.mjs +27 -113
- 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 +313 -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 +43 -616
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +210 -1948
- package/src/runtime/memory/lib/core-memory-store.mjs +5 -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/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/ko-morph.mjs +195 -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 +69 -12
- package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +802 -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/config.mjs +14 -4
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
- package/src/runtime/shared/schedules-store.mjs +13 -3
- 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 +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +119 -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 +205 -0
- package/src/session-runtime/quick-search-models.mjs +47 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +352 -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/session-runtime/workflow.mjs +1 -1
- 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 +138 -669
- package/src/standalone/channel-admin.mjs +102 -90
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +64 -14
- 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 +1059 -8110
- package/src/tui/app/app-format.mjs +213 -0
- package/src/tui/app/channel-pickers.mjs +508 -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 +356 -0
- package/src/tui/app/model-options.mjs +334 -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 +489 -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 +677 -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 +512 -0
- package/src/tui/app/use-transcript-window.mjs +607 -0
- package/src/tui/components/ConfirmBar.jsx +10 -7
- package/src/tui/components/Picker.jsx +64 -15
- package/src/tui/components/PromptInput.jsx +33 -102
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +69 -15
- 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 +13553 -12384
- 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 +170 -574
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +65 -1
- 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 +77 -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 +16 -18
- package/src/workflows/sequential/WORKFLOW.md +16 -18
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +112 -7
- 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/channels/lib/holidays.mjs +0 -138
- 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,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codex-client-meta.mjs — codex client identity headers for the OpenAI OAuth
|
|
3
|
+
* transports.
|
|
4
|
+
*
|
|
5
|
+
* codex-rs sends two client-identity headers on EVERY request including the
|
|
6
|
+
* WS handshake:
|
|
7
|
+
* - `User-Agent: codex_cli_rs/<version> (<os> <ver>; <arch>) <terminal>`
|
|
8
|
+
* (login/src/auth/default_client.rs get_codex_user_agent + default_headers)
|
|
9
|
+
* - `version: <CARGO_PKG_VERSION>` via the built-in provider http_headers
|
|
10
|
+
* (model-provider-info/src/lib.rs:339-343), merged into the WS handshake by
|
|
11
|
+
* merge_request_headers (codex-api/src/endpoint/responses_websocket.rs).
|
|
12
|
+
* The backend uses these for client gating (model catalog visibility measured
|
|
13
|
+
* 2026-07-03) and plausibly for x-codex-turn-state issuance / sticky
|
|
14
|
+
* cache-node routing, so mixdog mirrors both.
|
|
15
|
+
*/
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
|
|
18
|
+
// Offline fallback only; live value refreshes from npm (24h TTL, in-process).
|
|
19
|
+
export const CODEX_CLIENT_VERSION_FLOOR = '0.142.5';
|
|
20
|
+
const VERSION_TTL_MS = 24 * 60 * 60_000;
|
|
21
|
+
let _cache = { value: null, fetchedAt: 0 };
|
|
22
|
+
let _refreshInFlight = null;
|
|
23
|
+
|
|
24
|
+
async function _refresh() {
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch('https://registry.npmjs.org/@openai/codex/latest', {
|
|
27
|
+
signal: AbortSignal.timeout(5_000),
|
|
28
|
+
});
|
|
29
|
+
if (res.ok) {
|
|
30
|
+
const j = await res.json();
|
|
31
|
+
const v = String(j?.version || '').trim();
|
|
32
|
+
if (/^\d+\.\d+\.\d+/.test(v)) {
|
|
33
|
+
_cache = { value: v, fetchedAt: Date.now() };
|
|
34
|
+
return v;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch { /* offline — keep floor */ }
|
|
38
|
+
_cache = { value: CODEX_CLIENT_VERSION_FLOOR, fetchedAt: Date.now() };
|
|
39
|
+
return CODEX_CLIENT_VERSION_FLOOR;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Sync accessor for hot request paths: returns the cached npm version when
|
|
44
|
+
* fresh, otherwise kicks a background refresh and returns the floor. The
|
|
45
|
+
* handshake must never await a registry fetch.
|
|
46
|
+
*/
|
|
47
|
+
export function codexClientVersionSync() {
|
|
48
|
+
if (_cache.value && Date.now() - _cache.fetchedAt < VERSION_TTL_MS) return _cache.value;
|
|
49
|
+
if (!_refreshInFlight) {
|
|
50
|
+
_refreshInFlight = _refresh().finally(() => { _refreshInFlight = null; });
|
|
51
|
+
}
|
|
52
|
+
return _cache.value || CODEX_CLIENT_VERSION_FLOOR;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function _osType() {
|
|
56
|
+
// codex os_info reports "Windows"/"Mac OS"/"Linux"; node os.type() gives
|
|
57
|
+
// Windows_NT/Darwin/Linux. Map to codex's vocabulary.
|
|
58
|
+
const t = os.type();
|
|
59
|
+
if (t === 'Windows_NT') return 'Windows';
|
|
60
|
+
if (t === 'Darwin') return 'Mac OS';
|
|
61
|
+
return t;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function _arch() {
|
|
65
|
+
// codex reports rust-style arch tokens.
|
|
66
|
+
const a = os.arch();
|
|
67
|
+
if (a === 'x64') return 'x86_64';
|
|
68
|
+
if (a === 'arm64') return 'aarch64';
|
|
69
|
+
return a;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** codex_cli_rs/<version> (<os> <ver>; <arch>) <terminal> */
|
|
73
|
+
export function codexUserAgent() {
|
|
74
|
+
const terminal = String(process.env.TERM_PROGRAM || 'unknown').trim() || 'unknown';
|
|
75
|
+
return `codex_cli_rs/${codexClientVersionSync()} (${_osType()} ${os.release()}; ${_arch()}) ${terminal}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Bare version header value — codex built-in provider http_headers "version". */
|
|
79
|
+
export function codexVersionHeader() {
|
|
80
|
+
return codexClientVersionSync();
|
|
81
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gemini-cache.mjs — Gemini explicit cachedContents prefix accounting:
|
|
3
|
+
* token estimation, prefix hashing, the process-global cache registry
|
|
4
|
+
* (create de-dupe + prune), cache-state attach, usage resolution, and the
|
|
5
|
+
* cache trace writer.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from gemini.mjs (no behavior change). Owns the module-level
|
|
8
|
+
* geminiGlobalCaches / geminiGlobalCacheCreates maps. gemini.mjs imports
|
|
9
|
+
* these helpers; no re-export needed (all names are internal to the
|
|
10
|
+
* provider — none were part of the module's external surface).
|
|
11
|
+
*/
|
|
12
|
+
import { traceHash, stableTraceStringify, summarizeTraceTools, traceTextShape } from './trace-utils.mjs';
|
|
13
|
+
|
|
14
|
+
function summarizeTracePart(part) {
|
|
15
|
+
if (!part || typeof part !== 'object') return { type: typeof part };
|
|
16
|
+
if ('text' in part) {
|
|
17
|
+
return { type: 'text', ...traceTextShape(part.text) };
|
|
18
|
+
}
|
|
19
|
+
if (part.functionCall) {
|
|
20
|
+
return {
|
|
21
|
+
type: 'functionCall',
|
|
22
|
+
name: part.functionCall.name || null,
|
|
23
|
+
argsHash: traceHash(stableTraceStringify(part.functionCall.args || {})),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (part.functionResponse) {
|
|
27
|
+
const response = part.functionResponse.response || {};
|
|
28
|
+
const responseShape = stableTraceStringify(response);
|
|
29
|
+
return {
|
|
30
|
+
type: 'functionResponse',
|
|
31
|
+
name: part.functionResponse.name || null,
|
|
32
|
+
responseChars: responseShape.length,
|
|
33
|
+
responseHash: traceHash(responseShape),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { type: Object.keys(part).sort().join('|') || 'unknown' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function summarizeTraceContents(contents) {
|
|
40
|
+
const summaries = (contents || []).map((content, index) => ({
|
|
41
|
+
index,
|
|
42
|
+
role: content?.role || null,
|
|
43
|
+
parts: Array.isArray(content?.parts) ? content.parts.map(summarizeTracePart) : [],
|
|
44
|
+
}));
|
|
45
|
+
if (summaries.length <= 12) return summaries;
|
|
46
|
+
return [
|
|
47
|
+
...summaries.slice(0, 8),
|
|
48
|
+
{ omittedTurns: summaries.length - 12 },
|
|
49
|
+
...summaries.slice(-4),
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Gemini cachedContents API rejects prefixes below the model-specific minimum
|
|
54
|
+
// (Gemini 2.0/2.5 = 2048 tokens, Gemini 3.x = 4096 tokens) with HTTP 400
|
|
55
|
+
// "Cached content is too small". Estimating chars/4 ≈ tokens lets us skip the
|
|
56
|
+
// roundtrip when the prefix cannot satisfy the invariant. Estimate undercount
|
|
57
|
+
// is harmless (we attempt and get the same 400 we would have skipped); overcount
|
|
58
|
+
// is harmless (we skip, fall back to implicit cache).
|
|
59
|
+
export function _estimateGeminiCacheTokens(systemInstruction, geminiTools, contents) {
|
|
60
|
+
let chars = 0;
|
|
61
|
+
if (typeof systemInstruction === 'string') chars += systemInstruction.length;
|
|
62
|
+
if (Array.isArray(geminiTools) && geminiTools.length) {
|
|
63
|
+
try { chars += JSON.stringify(geminiTools).length; } catch {}
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(contents) && contents.length > 1) {
|
|
66
|
+
for (let i = 0; i < contents.length - 1; i++) {
|
|
67
|
+
try { chars += JSON.stringify(contents[i]?.parts ?? '').length; } catch {}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return Math.ceil(chars / 4);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function _geminiCacheMinTokens(model) {
|
|
74
|
+
return /^gemini-3/i.test(String(model || '')) ? 4096 : 2048;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function _geminiCachePrefixCount(contents) {
|
|
78
|
+
return Array.isArray(contents) && contents.length > 1 ? contents.length - 1 : 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function _geminiCachePrefixContents(contents, prefixCount) {
|
|
82
|
+
if (!Array.isArray(contents) || prefixCount <= 0) return [];
|
|
83
|
+
return contents.slice(0, prefixCount).map(c => {
|
|
84
|
+
const r = c?.role;
|
|
85
|
+
const safeRole = (r === 'model' || r === 'user') ? r : 'user';
|
|
86
|
+
return {
|
|
87
|
+
role: safeRole,
|
|
88
|
+
parts: Array.isArray(c?.parts) ? c.parts : [],
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function _geminiCachePrefixHash({ model, systemInstruction, geminiTools, contents, prefixCount }) {
|
|
94
|
+
return traceHash(stableTraceStringify({
|
|
95
|
+
model: model || null,
|
|
96
|
+
systemInstruction: systemInstruction || '',
|
|
97
|
+
tools: geminiTools || [],
|
|
98
|
+
contents: _geminiCachePrefixContents(contents, prefixCount),
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const GEMINI_GLOBAL_CACHE_MIN_LIVE_MS = 6 * 60 * 1000;
|
|
103
|
+
export const GEMINI_GLOBAL_CACHE_MAX_ENTRIES = 128;
|
|
104
|
+
// Grace window before deleting a superseded cachedContents name (see the
|
|
105
|
+
// cross-session race note at the delete call site in gemini.mjs). Long enough
|
|
106
|
+
// that a concurrent session still mid-flight on the old name has time to finish.
|
|
107
|
+
export const GEMINI_GLOBAL_CACHE_DELETE_GRACE_MS = 2 * 60 * 1000;
|
|
108
|
+
export const geminiGlobalCaches = new Map();
|
|
109
|
+
export const geminiGlobalCacheCreates = new Map();
|
|
110
|
+
|
|
111
|
+
export function _geminiGlobalCacheKey({ apiKey, model, cachePrefixHash, cachePrefixContentCount }) {
|
|
112
|
+
return traceHash(stableTraceStringify({
|
|
113
|
+
apiKeyHash: traceHash(apiKey || ''),
|
|
114
|
+
model: model || null,
|
|
115
|
+
cachePrefixHash,
|
|
116
|
+
cachePrefixContentCount,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function _pruneGeminiGlobalCaches(now = Date.now()) {
|
|
121
|
+
for (const [key, entry] of geminiGlobalCaches) {
|
|
122
|
+
if (!entry?.cacheName || Number(entry.cacheExpiresAt || 0) <= now) {
|
|
123
|
+
geminiGlobalCaches.delete(key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
while (geminiGlobalCaches.size > GEMINI_GLOBAL_CACHE_MAX_ENTRIES) {
|
|
127
|
+
const oldest = geminiGlobalCaches.keys().next().value;
|
|
128
|
+
if (oldest === undefined) break;
|
|
129
|
+
geminiGlobalCaches.delete(oldest);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function _getGeminiGlobalCache(key, now = Date.now()) {
|
|
134
|
+
_pruneGeminiGlobalCaches(now);
|
|
135
|
+
const entry = geminiGlobalCaches.get(key);
|
|
136
|
+
if (!entry?.cacheName) return null;
|
|
137
|
+
if ((entry.cacheExpiresAt || 0) - now <= GEMINI_GLOBAL_CACHE_MIN_LIVE_MS) {
|
|
138
|
+
geminiGlobalCaches.delete(key);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
entry.lastUsedAt = now;
|
|
142
|
+
return entry;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function _setGeminiGlobalCache(key, entry) {
|
|
146
|
+
if (!key || !entry?.cacheName) return;
|
|
147
|
+
geminiGlobalCaches.set(key, {
|
|
148
|
+
...entry,
|
|
149
|
+
lastUsedAt: Date.now(),
|
|
150
|
+
});
|
|
151
|
+
_pruneGeminiGlobalCaches();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function _geminiGlobalCacheNameIsLive(cacheName, now = Date.now()) {
|
|
155
|
+
if (!cacheName) return false;
|
|
156
|
+
for (const entry of geminiGlobalCaches.values()) {
|
|
157
|
+
if (entry?.cacheName === cacheName && Number(entry.cacheExpiresAt || 0) > now) return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function _attachGeminiCacheState(opts, entry, currentIter) {
|
|
163
|
+
if (!entry?.cacheName) return;
|
|
164
|
+
opts.providerState = {
|
|
165
|
+
...(opts.providerState || {}),
|
|
166
|
+
gemini: {
|
|
167
|
+
cacheName: entry.cacheName,
|
|
168
|
+
cacheCreatedAt: entry.cacheCreatedAt,
|
|
169
|
+
cacheCreatedAtIter: currentIter,
|
|
170
|
+
cacheExpiresAt: entry.cacheExpiresAt,
|
|
171
|
+
cacheModel: entry.cacheModel,
|
|
172
|
+
cacheTokenSize: entry.cacheTokenSize,
|
|
173
|
+
cachePrefixContentCount: entry.cachePrefixContentCount,
|
|
174
|
+
cachePrefixHash: entry.cachePrefixHash,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function _resolveGeminiCacheUsage({ usageMetadata, cachedContent, providerState }) {
|
|
180
|
+
const inputTokens = Number(usageMetadata?.promptTokenCount || usageMetadata?.totalTokenCount || 0) || 0;
|
|
181
|
+
const reportedCachedTokens = Number(usageMetadata?.cachedContentTokenCount || 0) || 0;
|
|
182
|
+
const cachedFallbackTokens = cachedContent
|
|
183
|
+
? Number(providerState?.gemini?.cacheTokenSize || 0) || 0
|
|
184
|
+
: 0;
|
|
185
|
+
const rawCachedTokens = reportedCachedTokens > 0 ? reportedCachedTokens : cachedFallbackTokens;
|
|
186
|
+
const cachedTokens = inputTokens > 0 ? Math.min(rawCachedTokens, inputTokens) : rawCachedTokens;
|
|
187
|
+
const cacheTokenSource = reportedCachedTokens > 0
|
|
188
|
+
? 'usage_metadata'
|
|
189
|
+
: (cachedFallbackTokens > 0 ? 'cache_create_fallback' : 'none');
|
|
190
|
+
return {
|
|
191
|
+
inputTokens,
|
|
192
|
+
reportedCachedTokens,
|
|
193
|
+
cachedFallbackTokens,
|
|
194
|
+
cachedTokens,
|
|
195
|
+
cacheTokenSource,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function writeGeminiCacheTrace({ opts, model, systemInstruction, tools, contents, usageMetadata, cachedContent }) {
|
|
200
|
+
if (process.env.MIXDOG_GEMINI_CACHE_TRACE !== '1') return;
|
|
201
|
+
try {
|
|
202
|
+
const session = opts?.session || {};
|
|
203
|
+
const {
|
|
204
|
+
inputTokens,
|
|
205
|
+
reportedCachedTokens,
|
|
206
|
+
cachedFallbackTokens,
|
|
207
|
+
cachedTokens,
|
|
208
|
+
cacheTokenSource,
|
|
209
|
+
} = _resolveGeminiCacheUsage({
|
|
210
|
+
usageMetadata,
|
|
211
|
+
cachedContent,
|
|
212
|
+
providerState: opts?.providerState,
|
|
213
|
+
});
|
|
214
|
+
const toolShape = summarizeTraceTools(tools);
|
|
215
|
+
const trace = {
|
|
216
|
+
event: 'generate',
|
|
217
|
+
provider: 'gemini',
|
|
218
|
+
model,
|
|
219
|
+
owner: session.owner || null,
|
|
220
|
+
agent: session.agent || null,
|
|
221
|
+
permission: session.permission || null,
|
|
222
|
+
toolPermission: session.toolPermission || null,
|
|
223
|
+
profileId: session.profileId || null,
|
|
224
|
+
sourceType: session.sourceType || null,
|
|
225
|
+
sourceName: session.sourceName || null,
|
|
226
|
+
sessionIdHash: opts?.sessionId ? traceHash(opts.sessionId) : null,
|
|
227
|
+
providerCacheKeyHash: opts?.providerCacheKey ? traceHash(opts.providerCacheKey) : null,
|
|
228
|
+
promptCacheKeyHash: opts?.promptCacheKey ? traceHash(opts.promptCacheKey) : null,
|
|
229
|
+
systemChars: systemInstruction ? systemInstruction.length : 0,
|
|
230
|
+
systemHash: systemInstruction ? traceHash(systemInstruction) : null,
|
|
231
|
+
toolCount: Array.isArray(tools) ? tools.length : 0,
|
|
232
|
+
toolSchemaHash: traceHash(stableTraceStringify(toolShape)),
|
|
233
|
+
contentTurnCount: Array.isArray(contents) ? contents.length : 0,
|
|
234
|
+
contents: summarizeTraceContents(contents),
|
|
235
|
+
inputTokens,
|
|
236
|
+
cachedTokens,
|
|
237
|
+
reportedCachedTokens,
|
|
238
|
+
cachedFallbackTokens,
|
|
239
|
+
cacheTokenSource,
|
|
240
|
+
cacheAttached: !!cachedContent,
|
|
241
|
+
cachePrefixContentCount: opts?.providerState?.gemini?.cachePrefixContentCount ?? null,
|
|
242
|
+
cacheHitRate: inputTokens > 0 ? Number((cachedTokens / inputTokens).toFixed(6)) : null,
|
|
243
|
+
};
|
|
244
|
+
process.stderr.write(`[gemini-cache-trace] ${JSON.stringify(trace)}\n`);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
process.stderr.write(`[gemini-cache-trace] failed: ${err?.message || err}\n`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gemini-schema.mjs — Gemini request-shape conversion: JSON Schema → Gemini
|
|
3
|
+
* FunctionDeclarationSchema, tool/tool-config mapping, message → content
|
|
4
|
+
* mapping, tool-call parsing/emission, and grounding-source collection.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from gemini.mjs (no behavior change). Pure functions only — no
|
|
7
|
+
* module state. gemini.mjs re-exports parseToolCalls / emitGeminiToolCalls /
|
|
8
|
+
* collectGeminiGroundingSources so existing importers (tests, gemini-stream)
|
|
9
|
+
* keep resolving through the facade.
|
|
10
|
+
*/
|
|
11
|
+
import { SchemaType } from '@google/generative-ai';
|
|
12
|
+
import { traceHash, stableTraceStringify } from './trace-utils.mjs';
|
|
13
|
+
import { normalizeContentForGeminiParts, splitToolContentForGemini } from './media-normalization.mjs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert JSON Schema type string to Gemini SchemaType.
|
|
17
|
+
* Gemini SDK uses its own enum instead of plain strings.
|
|
18
|
+
*/
|
|
19
|
+
function toSchemaType(t) {
|
|
20
|
+
const map = {
|
|
21
|
+
string: SchemaType.STRING,
|
|
22
|
+
number: SchemaType.NUMBER,
|
|
23
|
+
integer: SchemaType.INTEGER,
|
|
24
|
+
boolean: SchemaType.BOOLEAN,
|
|
25
|
+
array: SchemaType.ARRAY,
|
|
26
|
+
object: SchemaType.OBJECT,
|
|
27
|
+
};
|
|
28
|
+
return map[t] ?? SchemaType.STRING;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Recursively convert a JSON Schema object to Gemini's FunctionDeclarationSchema.
|
|
33
|
+
* Gemini requires `type` to be a SchemaType enum, not a plain string, and
|
|
34
|
+
* rejects several JSON Schema fields the API does not understand
|
|
35
|
+
* (additionalProperties, $schema, $ref, const, examples, definitions,
|
|
36
|
+
* patternProperties). We strip those at every level.
|
|
37
|
+
*/
|
|
38
|
+
const GEMINI_SCHEMA_STRIP = new Set([
|
|
39
|
+
'additionalProperties',
|
|
40
|
+
'$schema',
|
|
41
|
+
'$ref',
|
|
42
|
+
'const',
|
|
43
|
+
'examples',
|
|
44
|
+
'definitions',
|
|
45
|
+
'patternProperties',
|
|
46
|
+
]);
|
|
47
|
+
export function convertSchema(schema) {
|
|
48
|
+
if (!schema || typeof schema !== 'object') return schema;
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const [k, v] of Object.entries(schema)) {
|
|
51
|
+
if (GEMINI_SCHEMA_STRIP.has(k)) continue;
|
|
52
|
+
result[k] = v;
|
|
53
|
+
}
|
|
54
|
+
// Gemini's Schema validator requires every `enum` entry to be a string,
|
|
55
|
+
// even when the parent `type` is integer/number/boolean. Drop the enum in
|
|
56
|
+
// that case rather than emit an invalid typed enum — `type` plus the
|
|
57
|
+
// description still guides the model, and the tool handler revalidates.
|
|
58
|
+
const rawType = typeof result.type === 'string' ? result.type : undefined;
|
|
59
|
+
if (Array.isArray(result.enum) && (rawType === 'integer' || rawType === 'number' || rawType === 'boolean')) {
|
|
60
|
+
if (result.enum.some((item) => typeof item !== 'string')) {
|
|
61
|
+
delete result.enum;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Gemini rejects array schemas that omit `items`; fill a permissive
|
|
65
|
+
// default so the declaration validates.
|
|
66
|
+
if (rawType === 'array' && (!result.items || typeof result.items !== 'object')) {
|
|
67
|
+
result.items = { type: 'string' };
|
|
68
|
+
}
|
|
69
|
+
if (typeof result.type === 'string') {
|
|
70
|
+
result.type = toSchemaType(result.type);
|
|
71
|
+
}
|
|
72
|
+
if (result.properties && typeof result.properties === 'object') {
|
|
73
|
+
const props = {};
|
|
74
|
+
for (const [key, val] of Object.entries(result.properties)) {
|
|
75
|
+
props[key] = convertSchema(val);
|
|
76
|
+
}
|
|
77
|
+
result.properties = props;
|
|
78
|
+
}
|
|
79
|
+
if (result.items && typeof result.items === 'object') {
|
|
80
|
+
result.items = convertSchema(result.items);
|
|
81
|
+
}
|
|
82
|
+
// Recurse into JSON Schema combinator keys so disallowed fields
|
|
83
|
+
// (additionalProperties, $schema, etc.) get stripped at every nesting
|
|
84
|
+
// level. Without this, schemas using anyOf/oneOf/allOf/not pass the
|
|
85
|
+
// shallow strip but fail Gemini validation at depth.
|
|
86
|
+
//
|
|
87
|
+
// Two Gemini-specific normalizations are also applied per combinator
|
|
88
|
+
// subschema:
|
|
89
|
+
// 1. Inject `type: OBJECT` when a subschema uses object-only keys
|
|
90
|
+
// (`required` / `properties`) without an explicit type — Gemini
|
|
91
|
+
// rejects `required` outside of OBJECT type.
|
|
92
|
+
// 2. Materialize a local `properties` map from the parent's properties
|
|
93
|
+
// when the subschema only carries `required: [names]` — Gemini
|
|
94
|
+
// validates that every name in `required` exists in *this*
|
|
95
|
+
// subschema's `properties` (it does not inherit from the parent
|
|
96
|
+
// the way JSON Schema's compositional model does).
|
|
97
|
+
for (const combinator of ['anyOf', 'oneOf', 'allOf']) {
|
|
98
|
+
if (Array.isArray(result[combinator])) {
|
|
99
|
+
result[combinator] = result[combinator].map((s) => {
|
|
100
|
+
const sub = convertSchema(s);
|
|
101
|
+
if (sub && typeof sub === 'object') {
|
|
102
|
+
const usesObjectKeys = sub.required !== undefined || sub.properties !== undefined;
|
|
103
|
+
if (usesObjectKeys && sub.type === undefined) {
|
|
104
|
+
sub.type = toSchemaType('object');
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(sub.required) && !sub.properties && result.properties) {
|
|
107
|
+
const projected = {};
|
|
108
|
+
for (const k of sub.required) {
|
|
109
|
+
if (result.properties[k]) projected[k] = result.properties[k];
|
|
110
|
+
}
|
|
111
|
+
if (Object.keys(projected).length > 0) sub.properties = projected;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return sub;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (result.not && typeof result.not === 'object') {
|
|
119
|
+
result.not = convertSchema(result.not);
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function toGeminiTools(tools) {
|
|
125
|
+
return {
|
|
126
|
+
functionDeclarations: tools.map((t) => ({
|
|
127
|
+
name: t.name,
|
|
128
|
+
description: t.description,
|
|
129
|
+
parameters: convertSchema(t.inputSchema),
|
|
130
|
+
})),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function toGeminiNativeTools(nativeTools) {
|
|
134
|
+
if (!Array.isArray(nativeTools)) return [];
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const tool of nativeTools) {
|
|
137
|
+
const type = String(tool?.type || '').trim().toLowerCase();
|
|
138
|
+
if (type === 'google_search' || type === 'google_search_retrieval') {
|
|
139
|
+
out.push({ googleSearch: {} });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Exported for gemini-stream.mjs (stream consumers collect citations).
|
|
146
|
+
export function collectGeminiGroundingSources(candidate) {
|
|
147
|
+
const out = [];
|
|
148
|
+
const seen = new Set();
|
|
149
|
+
const add = (source) => {
|
|
150
|
+
if (!source || typeof source !== 'object') return;
|
|
151
|
+
const web = source.web && typeof source.web === 'object' ? source.web : source;
|
|
152
|
+
const url = String(web.uri || web.url || source.uri || source.url || '').trim();
|
|
153
|
+
if (!url || seen.has(url)) return;
|
|
154
|
+
seen.add(url);
|
|
155
|
+
out.push({
|
|
156
|
+
title: String(web.title || source.title || url).trim(),
|
|
157
|
+
url,
|
|
158
|
+
snippet: '',
|
|
159
|
+
source: 'gemini-grounding',
|
|
160
|
+
provider: 'gemini',
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
const grounding = candidate?.groundingMetadata || {};
|
|
164
|
+
for (const chunk of Array.isArray(grounding.groundingChunks) ? grounding.groundingChunks : []) add(chunk);
|
|
165
|
+
const citationMetadata = candidate?.citationMetadata || {};
|
|
166
|
+
for (const source of Array.isArray(citationMetadata.citationSources) ? citationMetadata.citationSources : []) add(source);
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Map the orchestrator-level toolChoice to Gemini's functionCallingConfig.
|
|
171
|
+
// auto -> AUTO
|
|
172
|
+
// required -> ANY
|
|
173
|
+
// none -> NONE
|
|
174
|
+
// { name } -> ANY + allowedFunctionNames:[name] (specific tool)
|
|
175
|
+
export function toGeminiToolConfig(toolChoice) {
|
|
176
|
+
if (toolChoice == null) return undefined;
|
|
177
|
+
if (typeof toolChoice === 'string') {
|
|
178
|
+
if (toolChoice === 'auto') return { functionCallingConfig: { mode: 'AUTO' } };
|
|
179
|
+
if (toolChoice === 'required') return { functionCallingConfig: { mode: 'ANY' } };
|
|
180
|
+
if (toolChoice === 'none') return { functionCallingConfig: { mode: 'NONE' } };
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
if (typeof toolChoice === 'object') {
|
|
184
|
+
const name = toolChoice.name || toolChoice.function?.name;
|
|
185
|
+
if (typeof name === 'string' && name) {
|
|
186
|
+
return { functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [name] } };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function toGeminiContent(message, toolNameByCallId) {
|
|
193
|
+
if (!message || message.role === 'system') return null;
|
|
194
|
+
if (message.role === 'assistant' && message.toolCalls?.length) {
|
|
195
|
+
const parts = [];
|
|
196
|
+
if (message.content) parts.push(...normalizeContentForGeminiParts(message.content));
|
|
197
|
+
for (const tc of message.toolCalls) {
|
|
198
|
+
// Gemini 3 thinking models require the original thoughtSignature
|
|
199
|
+
// echoed back on every prior functionCall so the cached thinking
|
|
200
|
+
// prefix stays valid. v1beta places the field at the Part level
|
|
201
|
+
// (sibling of functionCall) — putting it inside functionCall returns
|
|
202
|
+
// 400 "Unknown name". Older models / first turn have no signature.
|
|
203
|
+
const part = { functionCall: { name: tc.name, args: tc.arguments } };
|
|
204
|
+
if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature;
|
|
205
|
+
parts.push(part);
|
|
206
|
+
}
|
|
207
|
+
return { role: 'model', parts };
|
|
208
|
+
}
|
|
209
|
+
if (message.role === 'tool') {
|
|
210
|
+
// Tool result content stays byte-identical for cache prefix stability.
|
|
211
|
+
// Gemini accepts functionResponse parts under role 'user' (per docs).
|
|
212
|
+
// Using 'user' keeps tool_result entries byte-identical between
|
|
213
|
+
// cachedContents.create (which rejects role:'function') and
|
|
214
|
+
// generateContent, so the cached prefix actually matches at runtime.
|
|
215
|
+
// functionResponse.name must be the FUNCTION name, not the synthetic
|
|
216
|
+
// toolCallId. Resolve it from the toolCallId->functionName map built
|
|
217
|
+
// from prior assistant tool_calls; fall back to the raw id only when
|
|
218
|
+
// no mapping exists.
|
|
219
|
+
const functionName = (toolNameByCallId && toolNameByCallId.get(message.toolCallId))
|
|
220
|
+
|| message.toolCallId
|
|
221
|
+
|| '';
|
|
222
|
+
const { response, mediaParts } = splitToolContentForGemini(message.content);
|
|
223
|
+
const parts = [{ functionResponse: { name: functionName, response } }];
|
|
224
|
+
if (mediaParts.length) parts.push(...mediaParts);
|
|
225
|
+
return {
|
|
226
|
+
role: 'user',
|
|
227
|
+
parts,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
role: message.role === 'assistant' ? 'model' : 'user',
|
|
232
|
+
parts: normalizeContentForGeminiParts(message.content),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function toGeminiContents(messages) {
|
|
237
|
+
const contents = [];
|
|
238
|
+
// Map synthetic toolCallId -> function name from prior assistant
|
|
239
|
+
// tool_calls so each functionResponse part carries the real function name.
|
|
240
|
+
const toolNameByCallId = new Map();
|
|
241
|
+
for (const m of messages) {
|
|
242
|
+
if (m?.role === 'assistant' && Array.isArray(m.toolCalls)) {
|
|
243
|
+
for (const tc of m.toolCalls) {
|
|
244
|
+
if (tc?.id && tc?.name) toolNameByCallId.set(tc.id, tc.name);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const message of messages) {
|
|
249
|
+
const content = toGeminiContent(message, toolNameByCallId);
|
|
250
|
+
if (content) contents.push(content);
|
|
251
|
+
}
|
|
252
|
+
return contents;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function parseToolCalls(parts) {
|
|
256
|
+
const calls = parts.filter((p) => 'functionCall' in p && !!p.functionCall);
|
|
257
|
+
if (!calls.length)
|
|
258
|
+
return undefined;
|
|
259
|
+
// The @google/generative-ai 0.24.1 SDK predates Gemini 3 thinking — its
|
|
260
|
+
// FunctionCall type only declares { name, args }. The runtime object,
|
|
261
|
+
// however, retains whatever the wire response carried, which means the
|
|
262
|
+
// signature may sit under any of:
|
|
263
|
+
// • part.functionCall.thoughtSignature (camelCase, expected)
|
|
264
|
+
// • part.functionCall.thought_signature (snake_case, raw protobuf)
|
|
265
|
+
// • part.thoughtSignature / part.thought_signature (sibling on Part)
|
|
266
|
+
// Read all four and use the first non-empty hit. Set MIXDOG_DEBUG_GEMINI=1
|
|
267
|
+
// to dump the raw parts so we can confirm the actual key location on the
|
|
268
|
+
// next session and harden the parser.
|
|
269
|
+
if (process.env.MIXDOG_DEBUG_GEMINI === '1') {
|
|
270
|
+
try { process.stderr.write(`[gemini fc raw] ${JSON.stringify(parts)}\n`); } catch {}
|
|
271
|
+
}
|
|
272
|
+
return calls.map((p, i) => {
|
|
273
|
+
const fc = p.functionCall;
|
|
274
|
+
const sig = fc.thoughtSignature
|
|
275
|
+
|| fc.thought_signature
|
|
276
|
+
|| p.thoughtSignature
|
|
277
|
+
|| p.thought_signature
|
|
278
|
+
|| null;
|
|
279
|
+
const idHash = traceHash(stableTraceStringify({
|
|
280
|
+
index: i,
|
|
281
|
+
name: fc.name || '',
|
|
282
|
+
args: fc.args ?? {},
|
|
283
|
+
})).slice(0, 16);
|
|
284
|
+
const call = {
|
|
285
|
+
id: `gemini_${idHash}`,
|
|
286
|
+
name: fc.name,
|
|
287
|
+
arguments: (fc.args ?? {}),
|
|
288
|
+
};
|
|
289
|
+
if (sig) call.thoughtSignature = sig;
|
|
290
|
+
return call;
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Exported for gemini-stream.mjs (leak-guard + stream consumers).
|
|
295
|
+
export function emitGeminiToolCalls(toolCalls, onToolCall) {
|
|
296
|
+
if (typeof onToolCall !== 'function' || !Array.isArray(toolCalls)) return;
|
|
297
|
+
const emitted = new Set();
|
|
298
|
+
for (const call of toolCalls) {
|
|
299
|
+
if (!call?.id || !call?.name || emitted.has(call.id)) continue;
|
|
300
|
+
emitted.add(call.id);
|
|
301
|
+
try { onToolCall(call); } catch {}
|
|
302
|
+
}
|
|
303
|
+
}
|