mixdog 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -3
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/build-tui.mjs +13 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +30 -67
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +5 -2
- package/src/agents/heavy-worker/AGENT.md +21 -11
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +3 -2
- package/src/agents/worker/AGENT.md +21 -11
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +933 -3731
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/repl.mjs +5 -5
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/agent/30-explorer.md +8 -11
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +2 -9
- package/src/rules/shared/01-tool.md +11 -5
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +241 -894
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/telegram-format.mjs +19 -22
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +258 -2050
- package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +20 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +238 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +6 -14
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +52 -2
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +291 -0
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +126 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +155 -677
- package/src/standalone/channel-worker.mjs +7 -9
- package/src/standalone/explore-tool.mjs +40 -12
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +110 -746
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +125 -0
- package/src/standalone/provider-admin.mjs +15 -19
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1163 -7571
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +259 -80
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +56 -588
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/components/tool-output-format.mjs +2 -2
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +18034 -17188
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +405 -1385
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +60 -10
- package/src/tui/keyboard-protocol.mjs +2 -2
- package/src/tui/lib/voice-recorder.mjs +35 -19
- package/src/tui/markdown/format-token.mjs +11 -9
- package/src/tui/paste-attachments.mjs +38 -0
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/tui/themes/base.mjs +2 -2
- package/src/tui/themes/kanagawa.mjs +4 -4
- package/src/tui/themes/teal.mjs +4 -5
- package/src/tui/themes/utils.mjs +1 -1
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +77 -462
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +16 -9
- package/src/workflows/sequential/WORKFLOW.md +16 -11
- package/src/workflows/solo/WORKFLOW.md +5 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -24,6 +24,7 @@ import { join } from 'path';
|
|
|
24
24
|
import { getPluginData } from '../config.mjs';
|
|
25
25
|
import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
|
|
26
26
|
import { enrichModels, getModelMetadataSync } from './model-catalog.mjs';
|
|
27
|
+
import { sanitizeModelList } from './model-list-sanitize.mjs';
|
|
27
28
|
import { makeModelCache } from './model-cache.mjs';
|
|
28
29
|
import { OpenAICompatProvider } from './openai-compat.mjs';
|
|
29
30
|
import { createTimeoutSignal } from '../stall-policy.mjs';
|
|
@@ -114,7 +115,7 @@ export function normalizeGrokModelId(id) {
|
|
|
114
115
|
}
|
|
115
116
|
const MODEL_CACHE_TTL_MS = 24 * 60 * 60_000;
|
|
116
117
|
// Bump when the on-disk cache shape changes so stale-shape entries are
|
|
117
|
-
// discarded instead of misread
|
|
118
|
+
// discarded instead of misread.
|
|
118
119
|
const GROK_MODEL_CACHE_SCHEMA_VERSION = 1;
|
|
119
120
|
const DISCOVERY_TIMEOUT_MS = 15_000;
|
|
120
121
|
const TOKEN_TIMEOUT_MS = 30_000;
|
|
@@ -473,6 +474,19 @@ function _sanitizeGrokModels(models) {
|
|
|
473
474
|
// Image/video generation ids — excluded from "latest chat model" resolution.
|
|
474
475
|
const NON_CHAT_MODEL_RE = /imagine|image|video/i;
|
|
475
476
|
|
|
477
|
+
// List-facing sanitizer: drop grok image/video generation ids (proxy-only
|
|
478
|
+
// chat models are kept), then apply the shared cross-provider sanitizer.
|
|
479
|
+
function _sanitizeGrokList(models) {
|
|
480
|
+
const base = _sanitizeGrokModels(models);
|
|
481
|
+
if (!Array.isArray(base)) return base;
|
|
482
|
+
const chatOnly = base.filter((m) => {
|
|
483
|
+
if (!m?.id) return false;
|
|
484
|
+
if (NON_CHAT_MODEL_RE.test(m.id) && !isProxyOnlyModel(m.id)) return false;
|
|
485
|
+
return true;
|
|
486
|
+
});
|
|
487
|
+
return sanitizeModelList(chatOnly, { provider: 'grok-oauth' });
|
|
488
|
+
}
|
|
489
|
+
|
|
476
490
|
function _markLatestGrok(models) {
|
|
477
491
|
let best = null;
|
|
478
492
|
for (const m of models) {
|
|
@@ -683,7 +697,7 @@ export class GrokOAuthProvider {
|
|
|
683
697
|
async listModels() {
|
|
684
698
|
const cached = _modelCache.loadSync();
|
|
685
699
|
if (cached) {
|
|
686
|
-
const sanitized =
|
|
700
|
+
const sanitized = _sanitizeGrokList(cached);
|
|
687
701
|
if (sanitized !== cached) _modelCache.save(sanitized);
|
|
688
702
|
return sanitized;
|
|
689
703
|
}
|
|
@@ -693,7 +707,7 @@ export class GrokOAuthProvider {
|
|
|
693
707
|
const items = await this._fetchAllModelItems();
|
|
694
708
|
const normalized = items.map(_normalizeGrokModel).filter(Boolean);
|
|
695
709
|
_markLatestGrok(normalized);
|
|
696
|
-
const enriched =
|
|
710
|
+
const enriched = _sanitizeGrokList(await enrichModels(normalized));
|
|
697
711
|
_modelCache.save(enriched);
|
|
698
712
|
return enriched;
|
|
699
713
|
}
|
|
@@ -705,7 +719,7 @@ export class GrokOAuthProvider {
|
|
|
705
719
|
const items = await this._fetchAllModelItems();
|
|
706
720
|
const normalized = items.map(_normalizeGrokModel).filter(Boolean);
|
|
707
721
|
_markLatestGrok(normalized);
|
|
708
|
-
const enriched =
|
|
722
|
+
const enriched = _sanitizeGrokList(await enrichModels(normalized));
|
|
709
723
|
_modelCache.save(enriched);
|
|
710
724
|
if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(`[grok-oauth] catalog refreshed (${enriched.length} models)\n`);
|
|
711
725
|
return enriched;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Shared numeric/string primitives for provider usage-accounting modules.
|
|
2
|
+
//
|
|
3
|
+
// These are byte-for-byte identical helpers that were duplicated across
|
|
4
|
+
// api-usage.mjs, oauth-usage.mjs and opencode-go-usage.mjs. Extracting them
|
|
5
|
+
// here is behavior-preserving: each exported function reproduces the exact
|
|
6
|
+
// logic of the copies it replaces.
|
|
7
|
+
//
|
|
8
|
+
// NOTE ON `num`: api-usage.mjs and oauth-usage.mjs use the guarded variant
|
|
9
|
+
// below (treats null/undefined/'' as the fallback before Number()).
|
|
10
|
+
// opencode-go-usage.mjs uses an *unguarded* variant (Number('') === 0), which
|
|
11
|
+
// is observably different, so it keeps its own local `num` and is NOT wired to
|
|
12
|
+
// this module.
|
|
13
|
+
|
|
14
|
+
export function num(value, fallback = null) {
|
|
15
|
+
if (value === null || value === undefined || value === '') return fallback;
|
|
16
|
+
const n = Number(value);
|
|
17
|
+
return Number.isFinite(n) ? n : fallback;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function round(value, digits = 4) {
|
|
21
|
+
const n = Number(value);
|
|
22
|
+
if (!Number.isFinite(n)) return null;
|
|
23
|
+
const scale = 10 ** digits;
|
|
24
|
+
return Math.round(n * scale) / scale;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// cleanString (oauth-usage.mjs) and clean (opencode-go-usage.mjs) were
|
|
28
|
+
// identical: trim strings, coerce empties/non-strings to null.
|
|
29
|
+
export function cleanString(value) {
|
|
30
|
+
const s = typeof value === 'string' ? value.trim() : '';
|
|
31
|
+
return s || null;
|
|
32
|
+
}
|
|
@@ -61,6 +61,33 @@ const _CATALOG_BEDROCK_PREFIXES = ['anthropic.', 'bedrock/anthropic.'];
|
|
|
61
61
|
// azure_ai- or openrouter-namespaced).
|
|
62
62
|
const _CATALOG_ENRICH_PREFIXES = ['anthropic/', 'openai/', 'gemini/', 'google/', 'xai/', 'deepseek/'];
|
|
63
63
|
|
|
64
|
+
// Provider hint → catalog prefixes to try (subset of _CATALOG_SIMPLE_PREFIXES /
|
|
65
|
+
// _CATALOG_ENRICH_PREFIXES). Keyed by the *mapped* models.dev provider id
|
|
66
|
+
// (see _modelsDevProviderId), so anthropic-oauth and anthropic share one
|
|
67
|
+
// entry, likewise grok-oauth/xai and gemini/google. A provider missing here
|
|
68
|
+
// (unknown/custom) gets bare-id lookup only — no prefix guessing across
|
|
69
|
+
// unrelated providers. No provider hint at all (mappedProvider null) keeps
|
|
70
|
+
// legacy behaviour: try every prefix.
|
|
71
|
+
const _PROVIDER_CATALOG_PREFIXES = {
|
|
72
|
+
openai: ['openai/'],
|
|
73
|
+
anthropic: ['anthropic/'],
|
|
74
|
+
google: ['gemini/', 'google/'],
|
|
75
|
+
xai: ['xai/'],
|
|
76
|
+
deepseek: ['deepseek/'],
|
|
77
|
+
azure: ['azure_ai/'],
|
|
78
|
+
};
|
|
79
|
+
function _prefixesForProvider(mappedProvider, allPrefixes) {
|
|
80
|
+
if (!mappedProvider) return allPrefixes;
|
|
81
|
+
const allowed = _PROVIDER_CATALOG_PREFIXES[mappedProvider];
|
|
82
|
+
if (!allowed) return [];
|
|
83
|
+
return allPrefixes.filter((p) => allowed.includes(p));
|
|
84
|
+
}
|
|
85
|
+
// Bedrock-style catalog keys (anthropic.<id>-v1:0) only ever describe
|
|
86
|
+
// Anthropic models; skip that lookup entirely for any other provider hint.
|
|
87
|
+
function _bedrockAllowed(mappedProvider) {
|
|
88
|
+
return !mappedProvider || mappedProvider === 'anthropic';
|
|
89
|
+
}
|
|
90
|
+
|
|
64
91
|
// Polyfill for models the LiteLLM catalog does not list yet. Values mirror
|
|
65
92
|
// the catalog row shape so _normalize works unchanged. Source: each provider's
|
|
66
93
|
// official pricing page; do not extrapolate. Promotional discounts are
|
|
@@ -299,6 +326,33 @@ function _modelsDevMetadataSync(id, provider) {
|
|
|
299
326
|
return _normalize(_modelsDevRowToOverride(row));
|
|
300
327
|
}
|
|
301
328
|
|
|
329
|
+
// Raw models.dev catalog row accessor for the model-list sanitizer's
|
|
330
|
+
// data-driven staleness filter. Unlike _modelsDevMetadataSync this does NOT
|
|
331
|
+
// require a `cost` field (staleness only needs family/release_date) and
|
|
332
|
+
// returns the raw row untouched. Warms from disk one-shot if memory is cold;
|
|
333
|
+
// returns null when the catalog is unavailable so callers can skip filtering.
|
|
334
|
+
// `_test` (tests only) injects a fake catalog map without touching disk.
|
|
335
|
+
export function getModelsDevRowSync(id, provider, _test) {
|
|
336
|
+
const cat = _test || _mdCache || (warmModelsDevFromDiskSync(), _mdCache);
|
|
337
|
+
if (!cat) { void loadModelsDevCatalog(); return null; }
|
|
338
|
+
const pid = _modelsDevProviderId(provider);
|
|
339
|
+
if (!pid) return null;
|
|
340
|
+
const row = cat?.[pid]?.models?.[id];
|
|
341
|
+
return row || null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// All models.dev rows for a provider (mapped id), keyed by model id. Used by
|
|
345
|
+
// the sanitizer's family-supersession pass to compare release dates across a
|
|
346
|
+
// provider's whole catalog. Returns null when the catalog is cold/unavailable.
|
|
347
|
+
export function getModelsDevProviderModelsSync(provider, _test) {
|
|
348
|
+
const cat = _test || _mdCache || (warmModelsDevFromDiskSync(), _mdCache);
|
|
349
|
+
if (!cat) { void loadModelsDevCatalog(); return null; }
|
|
350
|
+
const pid = _modelsDevProviderId(provider);
|
|
351
|
+
if (!pid) return null;
|
|
352
|
+
const models = cat?.[pid]?.models;
|
|
353
|
+
return (models && typeof models === 'object') ? models : null;
|
|
354
|
+
}
|
|
355
|
+
|
|
302
356
|
/**
|
|
303
357
|
* Sync lookup. Warm order:
|
|
304
358
|
* 1. in-memory cache (hot path),
|
|
@@ -323,16 +377,19 @@ export function getModelMetadataSync(id, provider) {
|
|
|
323
377
|
if (ov && (!mappedProvider || _modelsDevProviderId(ov.litellm_provider) === mappedProvider)) {
|
|
324
378
|
meta = _normalize(ov);
|
|
325
379
|
}
|
|
380
|
+
const metaFromPricingOverride = meta !== null;
|
|
326
381
|
// 2. LiteLLM community catalog (broad mainstream coverage).
|
|
327
382
|
if (!_memCache) warmFromDiskSync();
|
|
328
383
|
if (!meta && _memCache) {
|
|
329
384
|
const catalog = _memCache;
|
|
330
|
-
if (catalog[id]
|
|
331
|
-
|
|
385
|
+
if (catalog[id] && (!mappedProvider || _modelsDevProviderId(catalog[id].litellm_provider) === mappedProvider)) {
|
|
386
|
+
meta = _normalize(catalog[id]);
|
|
387
|
+
}
|
|
388
|
+
for (const prefix of _prefixesForProvider(mappedProvider, _CATALOG_SIMPLE_PREFIXES)) {
|
|
332
389
|
if (meta) break;
|
|
333
390
|
if (catalog[prefix + id]) meta = _normalize(catalog[prefix + id]);
|
|
334
391
|
}
|
|
335
|
-
for (const prefix of _CATALOG_BEDROCK_PREFIXES) {
|
|
392
|
+
for (const prefix of (_bedrockAllowed(mappedProvider) ? _CATALOG_BEDROCK_PREFIXES : [])) {
|
|
336
393
|
if (meta) break;
|
|
337
394
|
const v1 = catalog[prefix + id + '-v1:0'];
|
|
338
395
|
if (v1) meta = _normalize(v1);
|
|
@@ -343,7 +400,7 @@ export function getModelMetadataSync(id, provider) {
|
|
|
343
400
|
// id, and add fields LiteLLM lacks, such as opencode-go reasoning_options.
|
|
344
401
|
if (mappedProvider) {
|
|
345
402
|
const md = _modelsDevMetadataSync(id, provider);
|
|
346
|
-
if (md) meta = mergeModelMetadata(meta, md);
|
|
403
|
+
if (md) meta = mergeModelMetadata(meta, md, { preserveBaseCosts: metaFromPricingOverride });
|
|
347
404
|
}
|
|
348
405
|
return meta;
|
|
349
406
|
}
|
|
@@ -368,13 +425,23 @@ function _normalize(entry) {
|
|
|
368
425
|
};
|
|
369
426
|
}
|
|
370
427
|
|
|
371
|
-
function mergeModelMetadata(base, overlay) {
|
|
428
|
+
function mergeModelMetadata(base, overlay, opts = {}) {
|
|
372
429
|
if (!base) return overlay || null;
|
|
373
430
|
if (!overlay) return base;
|
|
374
431
|
return {
|
|
375
432
|
...base,
|
|
376
433
|
contextWindow: overlay.contextWindow || base.contextWindow || null,
|
|
377
434
|
outputTokens: overlay.outputTokens || base.outputTokens || null,
|
|
435
|
+
// Provider-scoped models.dev rates beat generic base (LiteLLM) rates
|
|
436
|
+
// when present — the overlay is the provider-scoped source, so a
|
|
437
|
+
// non-null overlay value always wins over the generic fallback —
|
|
438
|
+
// UNLESS base came from PRICING_OVERRIDES (preserveBaseCosts), which
|
|
439
|
+
// is a hand-verified, authoritative rate that must not be clobbered
|
|
440
|
+
// by a models.dev row for the same id.
|
|
441
|
+
inputCostPerM: (!opts.preserveBaseCosts && overlay.inputCostPerM != null) ? overlay.inputCostPerM : base.inputCostPerM,
|
|
442
|
+
outputCostPerM: (!opts.preserveBaseCosts && overlay.outputCostPerM != null) ? overlay.outputCostPerM : base.outputCostPerM,
|
|
443
|
+
cacheReadCostPerM: (!opts.preserveBaseCosts && overlay.cacheReadCostPerM != null) ? overlay.cacheReadCostPerM : base.cacheReadCostPerM,
|
|
444
|
+
cacheWriteCostPerM: (!opts.preserveBaseCosts && overlay.cacheWriteCostPerM != null) ? overlay.cacheWriteCostPerM : base.cacheWriteCostPerM,
|
|
378
445
|
supportsVision: base.supportsVision || overlay.supportsVision,
|
|
379
446
|
supportsFunctionCalling: base.supportsFunctionCalling || overlay.supportsFunctionCalling,
|
|
380
447
|
supportsWebSearch: base.supportsWebSearch || overlay.supportsWebSearch,
|
|
@@ -401,23 +468,31 @@ export async function enrichModels(models) {
|
|
|
401
468
|
const id = m.id || m.name;
|
|
402
469
|
if (!id) return m;
|
|
403
470
|
// Same lookup logic as getModelMetadata but inlined for speed.
|
|
404
|
-
|
|
471
|
+
const mappedProvider = m.provider ? _modelsDevProviderId(m.provider) : null;
|
|
472
|
+
// Provider-guarded, same as getModelMetadataSync: an override/catalog
|
|
473
|
+
// row is only honoured when it belongs to the hinted provider, so a
|
|
474
|
+
// model id shared across providers never leaks the wrong rate.
|
|
475
|
+
const ov = PRICING_OVERRIDES[id];
|
|
476
|
+
let entry = (ov && (!mappedProvider || _modelsDevProviderId(ov.litellm_provider) === mappedProvider)) ? ov : null;
|
|
477
|
+
if (!entry && catalog[id] && (!mappedProvider || _modelsDevProviderId(catalog[id].litellm_provider) === mappedProvider)) {
|
|
478
|
+
entry = catalog[id];
|
|
479
|
+
}
|
|
405
480
|
if (!entry) {
|
|
406
|
-
for (const prefix of _CATALOG_ENRICH_PREFIXES) {
|
|
481
|
+
for (const prefix of _prefixesForProvider(mappedProvider, _CATALOG_ENRICH_PREFIXES)) {
|
|
407
482
|
if (catalog[prefix + id]) { entry = catalog[prefix + id]; break; }
|
|
408
483
|
}
|
|
409
484
|
}
|
|
410
|
-
if (!entry) {
|
|
485
|
+
if (!entry && _bedrockAllowed(mappedProvider)) {
|
|
411
486
|
for (const prefix of _CATALOG_BEDROCK_PREFIXES) {
|
|
412
487
|
if (catalog[prefix + id + '-v1:0']) { entry = catalog[prefix + id + '-v1:0']; break; }
|
|
413
488
|
}
|
|
414
489
|
}
|
|
415
490
|
let meta = entry ? _normalize(entry) : null;
|
|
491
|
+
const metaFromPricingOverride = entry != null && entry === ov;
|
|
416
492
|
if (m.provider) {
|
|
417
|
-
const
|
|
418
|
-
const row = pid ? _mdCache?.[pid]?.models?.[id] : null;
|
|
493
|
+
const row = mappedProvider ? _mdCache?.[mappedProvider]?.models?.[id] : null;
|
|
419
494
|
const providerMeta = row ? _normalize(_modelsDevRowToOverride(row)) : null;
|
|
420
|
-
if (providerMeta) meta = mergeModelMetadata(meta, providerMeta);
|
|
495
|
+
if (providerMeta) meta = mergeModelMetadata(meta, providerMeta, { preserveBaseCosts: metaFromPricingOverride });
|
|
421
496
|
}
|
|
422
497
|
if (!meta) return m;
|
|
423
498
|
return {
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// Shared model-list sanitizer. Providers return raw /models catalogs that mix
|
|
2
|
+
// chat models with embeddings/tts/image/etc, keep long-deprecated legacy
|
|
3
|
+
// families, and expose the same model under many dated snapshot ids. This
|
|
4
|
+
// module narrows a provider's enriched model list down to current,
|
|
5
|
+
// chat-capable, coding-relevant models WITHOUT touching selection or
|
|
6
|
+
// default-model logic. Conservative by design: when a row's family/mode is
|
|
7
|
+
// unknown, KEEP it.
|
|
8
|
+
//
|
|
9
|
+
// Usage: `sanitizeModelList(await enrichModels(models), { provider })`.
|
|
10
|
+
// Input rows are the enriched shape ({ id, mode?, created?, ... }); output is
|
|
11
|
+
// a filtered + deduped subset of the SAME row objects (never mutated).
|
|
12
|
+
|
|
13
|
+
import { getModelsDevProviderModelsSync, getModelsDevRowSync } from './model-catalog.mjs';
|
|
14
|
+
|
|
15
|
+
// Id-level fallback for coding-unfit SKUs when the catalog row is missing.
|
|
16
|
+
// search-preview (grounded-search) and audio/realtime preview SKUs are
|
|
17
|
+
// unwanted for a coding agent even when chat-capable. This is applied
|
|
18
|
+
// regardless of an explicit mode:'chat' (unlike NON_CHAT_RE).
|
|
19
|
+
const CODING_UNFIT_ID_RE = /-search(-preview)?(\b|-|$)|-(audio|realtime)-preview/i;
|
|
20
|
+
|
|
21
|
+
// Known hosted providers get the full id-heuristic + legacy + dedupe
|
|
22
|
+
// treatment. Anything else (ollama/lmstudio/llamacpp/local, or any unknown
|
|
23
|
+
// custom provider name openai-compat forwards) is treated as CUSTOM: only the
|
|
24
|
+
// enriched-mode drop applies — no id regex, no legacy filter, no dedupe.
|
|
25
|
+
const HOSTED_PROVIDERS = new Set([
|
|
26
|
+
'openai', 'openai-oauth', 'openai-ws', 'anthropic', 'anthropic-oauth',
|
|
27
|
+
'gemini', 'google', 'xai', 'grok-oauth', 'deepseek', 'groq', 'mistral',
|
|
28
|
+
'opencode-go', 'azure',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
// (a) Non-chat modality ids. Matched as whole word-ish tokens on the id so we
|
|
32
|
+
// don't nuke unrelated substrings. NOTE: 'search'/'voice' intentionally
|
|
33
|
+
// excluded — chat SKUs like *-search-preview exist.
|
|
34
|
+
const NON_CHAT_RE = new RegExp(
|
|
35
|
+
'(^|[-_/\\s])(' +
|
|
36
|
+
[
|
|
37
|
+
'embed', 'embedding', 'embeddings',
|
|
38
|
+
'tts', 'stt', 'whisper', 'audio', 'speech', 'voice',
|
|
39
|
+
'image', 'images', 'video', 'videos',
|
|
40
|
+
'moderation', 'moderations', 'rerank', 'reranker', 'similarity',
|
|
41
|
+
'dall[-_]?e', 'sora', 'imagen', 'imagine', 'veo', 'kling', 'runway',
|
|
42
|
+
'realtime', 'transcribe', 'transcription', 'diarize',
|
|
43
|
+
'guard', 'safety', 'classifier',
|
|
44
|
+
].join('|') +
|
|
45
|
+
')([-_/\\s]|$)',
|
|
46
|
+
'i',
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// (a2) Whole-id non-LLM SKUs that don't carry a modality token in a matchable
|
|
50
|
+
// position. These are exact families that OpenAI/others expose via /v1/models
|
|
51
|
+
// but are never chat/completion LLMs (image gen, TTS, embeddings, moderation).
|
|
52
|
+
// Anchored to the full id so version/date siblings all match.
|
|
53
|
+
const NON_LLM_ID_PATTERNS = [
|
|
54
|
+
/^tts(\b|-)/i, // tts-1, tts-1-hd
|
|
55
|
+
/^whisper(\b|-)/i, // whisper-1
|
|
56
|
+
/^dall[-_]?e(\b|-)/i, // dall-e-2, dall-e-3
|
|
57
|
+
/^(text-)?embedding/i, // text-embedding-3-large, embedding-*
|
|
58
|
+
/^text-embedding-ada/i, // text-embedding-ada-002
|
|
59
|
+
/^text-moderation(\b|-)/i, // text-moderation-*
|
|
60
|
+
/^omni-moderation(\b|-)/i, // omni-moderation-latest
|
|
61
|
+
/^gpt-image(\b|-)/i, // gpt-image-1, gpt-image-2
|
|
62
|
+
/^chatgpt-image(\b|-)/i, // chatgpt-image-latest
|
|
63
|
+
/^gpt-(4o|4o-mini|realtime|audio)?-?(tts|transcribe)(\b|-)/i, // gpt-4o-*-tts / -transcribe
|
|
64
|
+
/^gpt-audio(\b|-)/i, // gpt-audio, gpt-audio-mini, gpt-audio-1.5
|
|
65
|
+
/^gpt-realtime(\b|-)/i, // gpt-realtime*, gpt-realtime-mini
|
|
66
|
+
/-tts(\b|-)/i, // any *-tts SKU
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
function _isNonLlmId(lid) {
|
|
70
|
+
return NON_LLM_ID_PATTERNS.some((re) => re.test(lid));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// (b) Legacy / deprecated families not useful for a coding agent. Current
|
|
74
|
+
// families (gpt-5.x, gpt-4.1, o1-pro, o3+, claude-4.x, gemini-2/3, grok-4,
|
|
75
|
+
// deepseek-v*) must NOT match. Anything not matched here is kept. Each entry
|
|
76
|
+
// is a full-id-anchored pattern with an explicit trailing boundary so version
|
|
77
|
+
// siblings (grok-4.20-beta, gpt-4.1) survive.
|
|
78
|
+
const LEGACY_PATTERNS = [
|
|
79
|
+
// OpenAI legacy — gpt-4 legacy = gpt-4o*, gpt-4-turbo/32k/0*, bare gpt-4.
|
|
80
|
+
// gpt-4.1* is CURRENT and deliberately not matched.
|
|
81
|
+
/^gpt-4(o|-turbo|-32k|-\d|$)/i,
|
|
82
|
+
/^chatgpt-4o(\b|-)/i,
|
|
83
|
+
/^gpt-3(\.5)?(\b|-)/i,
|
|
84
|
+
/^(text-)?(davinci|babbage|curie|ada)(\b|-)/i,
|
|
85
|
+
// o1 legacy = o1, o1-mini, o1-preview. o1-pro is CURRENT, not matched.
|
|
86
|
+
/^o1(-mini|-preview|-\d|$)/i,
|
|
87
|
+
/^o3-mini(\b|-)/i,
|
|
88
|
+
// Anthropic legacy
|
|
89
|
+
/^claude-(1|2|3|instant)(\b|-|\.)/i,
|
|
90
|
+
// Gemini legacy — gemini-1*, gemini-pro. gemini-2/3 not matched.
|
|
91
|
+
/^gemini-1(\b|-|\.)/i,
|
|
92
|
+
/^gemini-pro(\b|-)/i,
|
|
93
|
+
// Grok legacy — grok-1/2/3 and grok-beta. The digit boundary stops grok-3
|
|
94
|
+
// from catching grok-4.20-beta.
|
|
95
|
+
/^grok-(1|2|3)(\b|-|\.)/i,
|
|
96
|
+
/^grok-beta(\b|-)/i,
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
function _isLegacy(lid) {
|
|
100
|
+
return LEGACY_PATTERNS.some((re) => re.test(lid));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function _isNonChatMode(row) {
|
|
104
|
+
const mode = typeof row?.mode === 'string' ? row.mode.trim().toLowerCase() : '';
|
|
105
|
+
if (!mode) return false; // unknown -> keep
|
|
106
|
+
return !['chat', 'completion', 'responses', 'messages'].includes(mode);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// (c) Canonical key: strip ONLY unambiguous date suffixes: -YYYY-MM-DD and
|
|
110
|
+
// -20YYMMDD (8-digit starting 20). Generic -0xxx is NOT stripped here — it
|
|
111
|
+
// collides with version ids (grok-4.20-0309, custom foo-0123). MMDD-style
|
|
112
|
+
// collapse is handled separately and conditionally.
|
|
113
|
+
function _canonicalKey(id) {
|
|
114
|
+
let key = String(id || '').trim().toLowerCase();
|
|
115
|
+
key = key.replace(/-\d{4}-\d{2}-\d{2}$/, ''); // -YYYY-MM-DD
|
|
116
|
+
key = key.replace(/-20\d{6}$/, ''); // -20YYMMDD
|
|
117
|
+
return key;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function _isDated(id) {
|
|
121
|
+
return _canonicalKey(id) !== String(id || '').trim().toLowerCase();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// MMDD-style suffix (-0309): only collapse when the undated alias exists in
|
|
125
|
+
// the same list AND both rows share identical context/output limits.
|
|
126
|
+
function _mmddBase(id) {
|
|
127
|
+
const lid = String(id || '').trim().toLowerCase();
|
|
128
|
+
const m = lid.match(/^(.*)-0\d{3}$/);
|
|
129
|
+
return m ? m[1] : null;
|
|
130
|
+
}
|
|
131
|
+
function _sameLimits(a, b) {
|
|
132
|
+
const cw = (r) => Number(r?.contextWindow) || 0;
|
|
133
|
+
const ot = (r) => Number(r?.outputTokens) || 0;
|
|
134
|
+
return cw(a) === cw(b) && ot(a) === ot(b);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── Data-driven auto-staleness (models.dev release_date + family) ────────────
|
|
138
|
+
// Parse a models.dev release_date ("YYYY-MM-DD" or "YYYY-MM") to a UTC epoch.
|
|
139
|
+
function _releaseEpoch(row) {
|
|
140
|
+
const rd = row && typeof row.release_date === 'string' ? row.release_date.trim() : '';
|
|
141
|
+
if (!rd) return null;
|
|
142
|
+
const m = rd.match(/^(\d{4})-(\d{2})(?:-(\d{2}))?$/);
|
|
143
|
+
if (!m) return null;
|
|
144
|
+
const t = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3] || '01'));
|
|
145
|
+
return Number.isFinite(t) ? t : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function _staleMonths() {
|
|
149
|
+
const raw = process.env.MIXDOG_MODEL_STALE_MONTHS;
|
|
150
|
+
if (raw == null || raw === '') return 12;
|
|
151
|
+
const n = Number(raw);
|
|
152
|
+
return Number.isFinite(n) ? n : 12;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Apply catalog-driven staleness to the already-filtered `kept` rows.
|
|
156
|
+
// Rules (see slice brief): (1) family supersession by newer release_date,
|
|
157
|
+
// (2) absolute cut older than N months. Both consult the provider's
|
|
158
|
+
// models.dev catalog; rows not found in the catalog are KEPT (fallback to the
|
|
159
|
+
// static LEGACY_PATTERNS already applied earlier). Catalog cold → no-op.
|
|
160
|
+
// Guards: never empty a family via supersession; if the absolute cut would
|
|
161
|
+
// empty the whole provider list, skip the cut entirely.
|
|
162
|
+
function _applyAutoStaleness(kept, provider, testCatalog) {
|
|
163
|
+
const catModels = getModelsDevProviderModelsSync(provider, testCatalog);
|
|
164
|
+
if (!catModels || typeof catModels !== 'object') return kept; // cold/unavailable → skip
|
|
165
|
+
|
|
166
|
+
// Enrich each kept row with its catalog row (by id), if any.
|
|
167
|
+
const meta = kept.map((row) => {
|
|
168
|
+
const cr = catModels[row.id] || null;
|
|
169
|
+
return {
|
|
170
|
+
row,
|
|
171
|
+
cat: cr,
|
|
172
|
+
family: cr && typeof cr.family === 'string' ? cr.family.toLowerCase() : null,
|
|
173
|
+
epoch: cr ? _releaseEpoch(cr) : null,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// (1) Family supersession: within a family, drop a row when the catalog has
|
|
178
|
+
// ANY model (kept or not) in the same family with a strictly newer date.
|
|
179
|
+
// Compute the newest release_date per family from the FULL catalog so a
|
|
180
|
+
// superseding model that isn't in this list still counts.
|
|
181
|
+
const familyNewest = new Map();
|
|
182
|
+
for (const cr of Object.values(catModels)) {
|
|
183
|
+
const fam = cr && typeof cr.family === 'string' ? cr.family.toLowerCase() : null;
|
|
184
|
+
const ep = _releaseEpoch(cr);
|
|
185
|
+
if (!fam || ep == null) continue;
|
|
186
|
+
const prev = familyNewest.get(fam);
|
|
187
|
+
if (prev == null || ep > prev) familyNewest.set(fam, ep);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let dropped = new Set();
|
|
191
|
+
for (const m of meta) {
|
|
192
|
+
if (!m.cat || m.family == null || m.epoch == null) continue; // fallback: keep
|
|
193
|
+
const newest = familyNewest.get(m.family);
|
|
194
|
+
if (newest != null && newest > m.epoch) dropped.add(m.row);
|
|
195
|
+
}
|
|
196
|
+
// Never empty a family the user relies on: if supersession removed every
|
|
197
|
+
// in-list member of a family, restore the newest-in-list member.
|
|
198
|
+
const byFamilyInList = new Map();
|
|
199
|
+
for (const m of meta) {
|
|
200
|
+
if (m.family == null) continue;
|
|
201
|
+
if (!byFamilyInList.has(m.family)) byFamilyInList.set(m.family, []);
|
|
202
|
+
byFamilyInList.get(m.family).push(m);
|
|
203
|
+
}
|
|
204
|
+
for (const [, members] of byFamilyInList) {
|
|
205
|
+
if (!members.every((m) => dropped.has(m.row))) continue;
|
|
206
|
+
let best = null;
|
|
207
|
+
for (const m of members) {
|
|
208
|
+
if (!best || (m.epoch || 0) > (best.epoch || 0)) best = m;
|
|
209
|
+
}
|
|
210
|
+
if (best) dropped.delete(best.row);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// (2) Absolute cut: drop rows whose release_date is older than N months.
|
|
214
|
+
const months = _staleMonths();
|
|
215
|
+
if (months > 0) {
|
|
216
|
+
const cutoff = Date.now() - months * 30.4375 * 24 * 60 * 60 * 1000;
|
|
217
|
+
const cutDropped = new Set();
|
|
218
|
+
for (const m of meta) {
|
|
219
|
+
if (dropped.has(m.row)) continue;
|
|
220
|
+
if (!m.cat || m.epoch == null) continue; // fallback: keep
|
|
221
|
+
if (m.epoch < cutoff) cutDropped.add(m.row);
|
|
222
|
+
}
|
|
223
|
+
// If applying the absolute cut would empty the entire provider list, skip
|
|
224
|
+
// the cut (supersession drops still stand).
|
|
225
|
+
const survivorsAfterCut = kept.filter((r) => !dropped.has(r) && !cutDropped.has(r));
|
|
226
|
+
if (survivorsAfterCut.length > 0) {
|
|
227
|
+
for (const r of cutDropped) dropped.add(r);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return kept.filter((r) => !dropped.has(r));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── Coding-unfit filter (models.dev tool_call/modalities + id fallback) ──────
|
|
235
|
+
// HOSTED providers only, applied AFTER staleness, BEFORE dedupe. Data-driven:
|
|
236
|
+
// (1) catalog row exists and tool_call === false → drop (search-preview,
|
|
237
|
+
// roleplay, lightweight chat SKUs — regardless of mode:'chat');
|
|
238
|
+
// (2) catalog row exists and modalities.output is an array lacking 'text' →
|
|
239
|
+
// drop (non-text-emitting SKUs);
|
|
240
|
+
// (3) catalog row MISSING → id fallback CODING_UNFIT_ID_RE (search-preview /
|
|
241
|
+
// audio|realtime-preview), applied even for mode:'chat';
|
|
242
|
+
// (4) not in catalog + no id match → keep.
|
|
243
|
+
// Catalog cold/unavailable → id-fallback still applies (safe, id-only).
|
|
244
|
+
function _applyCodingUnfit(kept, provider, testCatalog) {
|
|
245
|
+
const out = [];
|
|
246
|
+
for (const row of kept) {
|
|
247
|
+
const id = row?.id;
|
|
248
|
+
if (!id) continue;
|
|
249
|
+
const cr = getModelsDevRowSync(id, provider, testCatalog);
|
|
250
|
+
if (cr) {
|
|
251
|
+
// (1) explicit non-tool-calling SKU
|
|
252
|
+
if (cr.tool_call === false) continue;
|
|
253
|
+
// (2) output modalities present but no 'text'
|
|
254
|
+
const outMods = cr?.modalities?.output;
|
|
255
|
+
if (Array.isArray(outMods) && !outMods.includes('text')) continue;
|
|
256
|
+
out.push(row);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
// (3) catalog row missing → id-level fallback
|
|
260
|
+
if (CODING_UNFIT_ID_RE.test(String(id).toLowerCase())) continue;
|
|
261
|
+
// (4) keep
|
|
262
|
+
out.push(row);
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Sanitize a provider model list.
|
|
269
|
+
* @param {Array<object>} models enriched rows ({ id, mode?, created?, ... })
|
|
270
|
+
* @param {{provider?: string}} [opts]
|
|
271
|
+
* @returns {Array<object>} filtered + deduped subset (same row objects)
|
|
272
|
+
*/
|
|
273
|
+
export function sanitizeModelList(models, opts = {}) {
|
|
274
|
+
if (!Array.isArray(models)) return models;
|
|
275
|
+
const provider = String(opts?.provider || '').trim().toLowerCase();
|
|
276
|
+
const hosted = HOSTED_PROVIDERS.has(provider);
|
|
277
|
+
|
|
278
|
+
// CUSTOM/local providers (ollama, lmstudio, unknown names): apply ONLY the
|
|
279
|
+
// enriched-mode drop. No id heuristics, no legacy filter, no dedupe.
|
|
280
|
+
if (!hosted) {
|
|
281
|
+
return models.filter((row) => row?.id && !_isNonChatMode(row));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const kept = [];
|
|
285
|
+
for (const row of models) {
|
|
286
|
+
const id = row?.id;
|
|
287
|
+
if (!id) continue;
|
|
288
|
+
const lid = String(id).toLowerCase();
|
|
289
|
+
const modeChat = typeof row?.mode === 'string' && row.mode.trim().toLowerCase() === 'chat';
|
|
290
|
+
// (a0) hard non-LLM SKUs (image/tts/embeddings/moderation/realtime/audio):
|
|
291
|
+
// dropped even if the row falsely claims mode:'chat'. These families are
|
|
292
|
+
// never text-completion LLMs.
|
|
293
|
+
if (_isNonLlmId(lid)) continue;
|
|
294
|
+
// (a) enriched mode present and not chat-like
|
|
295
|
+
if (_isNonChatMode(row)) continue;
|
|
296
|
+
// (a) non-chat modality id — but an explicit mode:'chat' overrides id regex
|
|
297
|
+
if (!modeChat && NON_CHAT_RE.test(lid)) continue;
|
|
298
|
+
// (b) legacy/deprecated family
|
|
299
|
+
if (_isLegacy(lid)) continue;
|
|
300
|
+
kept.push(row);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Data-driven auto-staleness (models.dev): applied AFTER the static drops
|
|
304
|
+
// above and BEFORE canonical dedupe. Catalog cold/unavailable → no-op.
|
|
305
|
+
// `opts._testCatalog` (tests only) injects a fake catalog map.
|
|
306
|
+
const staleFiltered = _applyAutoStaleness(kept, provider, opts?._testCatalog);
|
|
307
|
+
|
|
308
|
+
// Coding-unfit filter (models.dev tool_call/modalities + id fallback):
|
|
309
|
+
// AFTER staleness, BEFORE dedupe.
|
|
310
|
+
const codingFit = _applyCodingUnfit(staleFiltered, provider, opts?._testCatalog);
|
|
311
|
+
|
|
312
|
+
// (c) canonical dedupe on unambiguous date suffixes: prefer undated alias.
|
|
313
|
+
const byKey = new Map();
|
|
314
|
+
for (const row of codingFit) {
|
|
315
|
+
const key = _canonicalKey(row.id);
|
|
316
|
+
const prev = byKey.get(key);
|
|
317
|
+
if (!prev) { byKey.set(key, row); continue; }
|
|
318
|
+
byKey.set(key, _preferRow(prev, row));
|
|
319
|
+
}
|
|
320
|
+
let winners = new Set(byKey.values());
|
|
321
|
+
|
|
322
|
+
// Conditional MMDD collapse: drop a `-0xxx` row only when its undated alias
|
|
323
|
+
// survives in the winner set AND both share identical context/output limits.
|
|
324
|
+
const aliasByKey = new Map();
|
|
325
|
+
for (const row of winners) aliasByKey.set(_canonicalKey(row.id), row);
|
|
326
|
+
for (const row of [...winners]) {
|
|
327
|
+
const base = _mmddBase(row.id);
|
|
328
|
+
if (!base) continue;
|
|
329
|
+
const alias = aliasByKey.get(base);
|
|
330
|
+
if (alias && alias !== row && _sameLimits(alias, row)) winners.delete(row);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Preserve original ordering of the winners.
|
|
334
|
+
return codingFit.filter((row) => winners.has(row));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function _preferRow(a, b) {
|
|
338
|
+
const aDated = _isDated(a.id);
|
|
339
|
+
const bDated = _isDated(b.id);
|
|
340
|
+
// Prefer the undated canonical alias.
|
|
341
|
+
if (aDated !== bDated) return aDated ? b : a;
|
|
342
|
+
if (!aDated && !bDated) return a; // both undated (shouldn't happen) keep first
|
|
343
|
+
// Both dated: keep the newest by `created`, else the lexically-latest id.
|
|
344
|
+
const ac = Number(a.created) || 0;
|
|
345
|
+
const bc = Number(b.created) || 0;
|
|
346
|
+
if (ac !== bc) return ac > bc ? a : b;
|
|
347
|
+
return String(b.id) > String(a.id) ? b : a;
|
|
348
|
+
}
|