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
|
@@ -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';
|
|
@@ -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;
|
|
@@ -18,6 +18,10 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
18
18
|
import { join } from 'path';
|
|
19
19
|
import { getPluginData } from '../config.mjs';
|
|
20
20
|
import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
|
|
21
|
+
import {
|
|
22
|
+
providerCachedModelMetadataSync,
|
|
23
|
+
providerUsesEndpointScopedLimits,
|
|
24
|
+
} from './provider-catalog-cache.mjs';
|
|
21
25
|
|
|
22
26
|
const CATALOG_URL = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json';
|
|
23
27
|
const CATALOG_CACHE_FILE = 'litellm-catalog.json';
|
|
@@ -61,6 +65,33 @@ const _CATALOG_BEDROCK_PREFIXES = ['anthropic.', 'bedrock/anthropic.'];
|
|
|
61
65
|
// azure_ai- or openrouter-namespaced).
|
|
62
66
|
const _CATALOG_ENRICH_PREFIXES = ['anthropic/', 'openai/', 'gemini/', 'google/', 'xai/', 'deepseek/'];
|
|
63
67
|
|
|
68
|
+
// Provider hint → catalog prefixes to try (subset of _CATALOG_SIMPLE_PREFIXES /
|
|
69
|
+
// _CATALOG_ENRICH_PREFIXES). Keyed by the *mapped* models.dev provider id
|
|
70
|
+
// (see _modelsDevProviderId), so anthropic-oauth and anthropic share one
|
|
71
|
+
// entry, likewise grok-oauth/xai and gemini/google. A provider missing here
|
|
72
|
+
// (unknown/custom) gets bare-id lookup only — no prefix guessing across
|
|
73
|
+
// unrelated providers. No provider hint at all (mappedProvider null) keeps
|
|
74
|
+
// legacy behaviour: try every prefix.
|
|
75
|
+
const _PROVIDER_CATALOG_PREFIXES = {
|
|
76
|
+
openai: ['openai/'],
|
|
77
|
+
anthropic: ['anthropic/'],
|
|
78
|
+
google: ['gemini/', 'google/'],
|
|
79
|
+
xai: ['xai/'],
|
|
80
|
+
deepseek: ['deepseek/'],
|
|
81
|
+
azure: ['azure_ai/'],
|
|
82
|
+
};
|
|
83
|
+
function _prefixesForProvider(mappedProvider, allPrefixes) {
|
|
84
|
+
if (!mappedProvider) return allPrefixes;
|
|
85
|
+
const allowed = _PROVIDER_CATALOG_PREFIXES[mappedProvider];
|
|
86
|
+
if (!allowed) return [];
|
|
87
|
+
return allPrefixes.filter((p) => allowed.includes(p));
|
|
88
|
+
}
|
|
89
|
+
// Bedrock-style catalog keys (anthropic.<id>-v1:0) only ever describe
|
|
90
|
+
// Anthropic models; skip that lookup entirely for any other provider hint.
|
|
91
|
+
function _bedrockAllowed(mappedProvider) {
|
|
92
|
+
return !mappedProvider || mappedProvider === 'anthropic';
|
|
93
|
+
}
|
|
94
|
+
|
|
64
95
|
// Polyfill for models the LiteLLM catalog does not list yet. Values mirror
|
|
65
96
|
// the catalog row shape so _normalize works unchanged. Source: each provider's
|
|
66
97
|
// official pricing page; do not extrapolate. Promotional discounts are
|
|
@@ -299,6 +330,33 @@ function _modelsDevMetadataSync(id, provider) {
|
|
|
299
330
|
return _normalize(_modelsDevRowToOverride(row));
|
|
300
331
|
}
|
|
301
332
|
|
|
333
|
+
// Raw models.dev catalog row accessor for the model-list sanitizer's
|
|
334
|
+
// data-driven staleness filter. Unlike _modelsDevMetadataSync this does NOT
|
|
335
|
+
// require a `cost` field (staleness only needs family/release_date) and
|
|
336
|
+
// returns the raw row untouched. Warms from disk one-shot if memory is cold;
|
|
337
|
+
// returns null when the catalog is unavailable so callers can skip filtering.
|
|
338
|
+
// `_test` (tests only) injects a fake catalog map without touching disk.
|
|
339
|
+
export function getModelsDevRowSync(id, provider, _test) {
|
|
340
|
+
const cat = _test || _mdCache || (warmModelsDevFromDiskSync(), _mdCache);
|
|
341
|
+
if (!cat) { void loadModelsDevCatalog(); return null; }
|
|
342
|
+
const pid = _modelsDevProviderId(provider);
|
|
343
|
+
if (!pid) return null;
|
|
344
|
+
const row = cat?.[pid]?.models?.[id];
|
|
345
|
+
return row || null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// All models.dev rows for a provider (mapped id), keyed by model id. Used by
|
|
349
|
+
// the sanitizer's family-supersession pass to compare release dates across a
|
|
350
|
+
// provider's whole catalog. Returns null when the catalog is cold/unavailable.
|
|
351
|
+
export function getModelsDevProviderModelsSync(provider, _test) {
|
|
352
|
+
const cat = _test || _mdCache || (warmModelsDevFromDiskSync(), _mdCache);
|
|
353
|
+
if (!cat) { void loadModelsDevCatalog(); return null; }
|
|
354
|
+
const pid = _modelsDevProviderId(provider);
|
|
355
|
+
if (!pid) return null;
|
|
356
|
+
const models = cat?.[pid]?.models;
|
|
357
|
+
return (models && typeof models === 'object') ? models : null;
|
|
358
|
+
}
|
|
359
|
+
|
|
302
360
|
/**
|
|
303
361
|
* Sync lookup. Warm order:
|
|
304
362
|
* 1. in-memory cache (hot path),
|
|
@@ -313,6 +371,7 @@ function _modelsDevMetadataSync(id, provider) {
|
|
|
313
371
|
export function getModelMetadataSync(id, provider) {
|
|
314
372
|
if (!id) return null;
|
|
315
373
|
const mappedProvider = provider ? _modelsDevProviderId(provider) : null;
|
|
374
|
+
const providerNative = provider ? providerCachedModelMetadataSync(provider, id) : null;
|
|
316
375
|
let meta = null;
|
|
317
376
|
// 1. Manual overrides — authoritative + offline. Provider-guarded: when a
|
|
318
377
|
// provider hint is given, an override is only honoured if it belongs to
|
|
@@ -323,16 +382,19 @@ export function getModelMetadataSync(id, provider) {
|
|
|
323
382
|
if (ov && (!mappedProvider || _modelsDevProviderId(ov.litellm_provider) === mappedProvider)) {
|
|
324
383
|
meta = _normalize(ov);
|
|
325
384
|
}
|
|
385
|
+
const metaFromPricingOverride = meta !== null;
|
|
326
386
|
// 2. LiteLLM community catalog (broad mainstream coverage).
|
|
327
387
|
if (!_memCache) warmFromDiskSync();
|
|
328
388
|
if (!meta && _memCache) {
|
|
329
389
|
const catalog = _memCache;
|
|
330
|
-
if (catalog[id]
|
|
331
|
-
|
|
390
|
+
if (catalog[id] && (!mappedProvider || _modelsDevProviderId(catalog[id].litellm_provider) === mappedProvider)) {
|
|
391
|
+
meta = _normalize(catalog[id]);
|
|
392
|
+
}
|
|
393
|
+
for (const prefix of _prefixesForProvider(mappedProvider, _CATALOG_SIMPLE_PREFIXES)) {
|
|
332
394
|
if (meta) break;
|
|
333
395
|
if (catalog[prefix + id]) meta = _normalize(catalog[prefix + id]);
|
|
334
396
|
}
|
|
335
|
-
for (const prefix of _CATALOG_BEDROCK_PREFIXES) {
|
|
397
|
+
for (const prefix of (_bedrockAllowed(mappedProvider) ? _CATALOG_BEDROCK_PREFIXES : [])) {
|
|
336
398
|
if (meta) break;
|
|
337
399
|
const v1 = catalog[prefix + id + '-v1:0'];
|
|
338
400
|
if (v1) meta = _normalize(v1);
|
|
@@ -343,7 +405,16 @@ export function getModelMetadataSync(id, provider) {
|
|
|
343
405
|
// id, and add fields LiteLLM lacks, such as opencode-go reasoning_options.
|
|
344
406
|
if (mappedProvider) {
|
|
345
407
|
const md = _modelsDevMetadataSync(id, provider);
|
|
346
|
-
if (md) meta = mergeModelMetadata(meta, md);
|
|
408
|
+
if (md) meta = mergeModelMetadata(meta, md, { preserveBaseCosts: metaFromPricingOverride });
|
|
409
|
+
}
|
|
410
|
+
if (providerUsesEndpointScopedLimits(provider) && !providerNative && meta) {
|
|
411
|
+
// OAuth/backend routes can expose smaller account/backend windows than
|
|
412
|
+
// the public API SKU. External catalogs remain useful for costs and
|
|
413
|
+
// capabilities, but their limits are not authoritative for these routes.
|
|
414
|
+
meta = { ...meta, contextWindow: null, outputTokens: null };
|
|
415
|
+
}
|
|
416
|
+
if (providerNative) {
|
|
417
|
+
meta = mergeModelMetadata(meta, providerNative, { preserveBaseCosts: true });
|
|
347
418
|
}
|
|
348
419
|
return meta;
|
|
349
420
|
}
|
|
@@ -368,13 +439,23 @@ function _normalize(entry) {
|
|
|
368
439
|
};
|
|
369
440
|
}
|
|
370
441
|
|
|
371
|
-
function mergeModelMetadata(base, overlay) {
|
|
442
|
+
function mergeModelMetadata(base, overlay, opts = {}) {
|
|
372
443
|
if (!base) return overlay || null;
|
|
373
444
|
if (!overlay) return base;
|
|
374
445
|
return {
|
|
375
446
|
...base,
|
|
376
447
|
contextWindow: overlay.contextWindow || base.contextWindow || null,
|
|
377
448
|
outputTokens: overlay.outputTokens || base.outputTokens || null,
|
|
449
|
+
// Provider-scoped models.dev rates beat generic base (LiteLLM) rates
|
|
450
|
+
// when present — the overlay is the provider-scoped source, so a
|
|
451
|
+
// non-null overlay value always wins over the generic fallback —
|
|
452
|
+
// UNLESS base came from PRICING_OVERRIDES (preserveBaseCosts), which
|
|
453
|
+
// is a hand-verified, authoritative rate that must not be clobbered
|
|
454
|
+
// by a models.dev row for the same id.
|
|
455
|
+
inputCostPerM: (!opts.preserveBaseCosts && overlay.inputCostPerM != null) ? overlay.inputCostPerM : base.inputCostPerM,
|
|
456
|
+
outputCostPerM: (!opts.preserveBaseCosts && overlay.outputCostPerM != null) ? overlay.outputCostPerM : base.outputCostPerM,
|
|
457
|
+
cacheReadCostPerM: (!opts.preserveBaseCosts && overlay.cacheReadCostPerM != null) ? overlay.cacheReadCostPerM : base.cacheReadCostPerM,
|
|
458
|
+
cacheWriteCostPerM: (!opts.preserveBaseCosts && overlay.cacheWriteCostPerM != null) ? overlay.cacheWriteCostPerM : base.cacheWriteCostPerM,
|
|
378
459
|
supportsVision: base.supportsVision || overlay.supportsVision,
|
|
379
460
|
supportsFunctionCalling: base.supportsFunctionCalling || overlay.supportsFunctionCalling,
|
|
380
461
|
supportsWebSearch: base.supportsWebSearch || overlay.supportsWebSearch,
|
|
@@ -401,23 +482,36 @@ export async function enrichModels(models) {
|
|
|
401
482
|
const id = m.id || m.name;
|
|
402
483
|
if (!id) return m;
|
|
403
484
|
// Same lookup logic as getModelMetadata but inlined for speed.
|
|
404
|
-
|
|
485
|
+
const mappedProvider = m.provider ? _modelsDevProviderId(m.provider) : null;
|
|
486
|
+
// Provider-guarded, same as getModelMetadataSync: an override/catalog
|
|
487
|
+
// row is only honoured when it belongs to the hinted provider, so a
|
|
488
|
+
// model id shared across providers never leaks the wrong rate.
|
|
489
|
+
const ov = PRICING_OVERRIDES[id];
|
|
490
|
+
let entry = (ov && (!mappedProvider || _modelsDevProviderId(ov.litellm_provider) === mappedProvider)) ? ov : null;
|
|
491
|
+
if (!entry && catalog[id] && (!mappedProvider || _modelsDevProviderId(catalog[id].litellm_provider) === mappedProvider)) {
|
|
492
|
+
entry = catalog[id];
|
|
493
|
+
}
|
|
405
494
|
if (!entry) {
|
|
406
|
-
for (const prefix of _CATALOG_ENRICH_PREFIXES) {
|
|
495
|
+
for (const prefix of _prefixesForProvider(mappedProvider, _CATALOG_ENRICH_PREFIXES)) {
|
|
407
496
|
if (catalog[prefix + id]) { entry = catalog[prefix + id]; break; }
|
|
408
497
|
}
|
|
409
498
|
}
|
|
410
|
-
if (!entry) {
|
|
499
|
+
if (!entry && _bedrockAllowed(mappedProvider)) {
|
|
411
500
|
for (const prefix of _CATALOG_BEDROCK_PREFIXES) {
|
|
412
501
|
if (catalog[prefix + id + '-v1:0']) { entry = catalog[prefix + id + '-v1:0']; break; }
|
|
413
502
|
}
|
|
414
503
|
}
|
|
415
504
|
let meta = entry ? _normalize(entry) : null;
|
|
505
|
+
const metaFromPricingOverride = entry != null && entry === ov;
|
|
416
506
|
if (m.provider) {
|
|
417
|
-
const
|
|
418
|
-
const row = pid ? _mdCache?.[pid]?.models?.[id] : null;
|
|
507
|
+
const row = mappedProvider ? _mdCache?.[mappedProvider]?.models?.[id] : null;
|
|
419
508
|
const providerMeta = row ? _normalize(_modelsDevRowToOverride(row)) : null;
|
|
420
|
-
if (providerMeta) meta = mergeModelMetadata(meta, providerMeta);
|
|
509
|
+
if (providerMeta) meta = mergeModelMetadata(meta, providerMeta, { preserveBaseCosts: metaFromPricingOverride });
|
|
510
|
+
const providerNative = providerCachedModelMetadataSync(m.provider, id);
|
|
511
|
+
if (providerUsesEndpointScopedLimits(m.provider) && !providerNative && meta) {
|
|
512
|
+
meta = { ...meta, contextWindow: null, outputTokens: null };
|
|
513
|
+
}
|
|
514
|
+
if (providerNative) meta = mergeModelMetadata(meta, providerNative, { preserveBaseCosts: true });
|
|
421
515
|
}
|
|
422
516
|
if (!meta) return m;
|
|
423
517
|
return {
|
|
@@ -0,0 +1,356 @@
|
|
|
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 _stalenessFamily(row, id) {
|
|
149
|
+
const modelId = String(id || row?.id || row?.name || '').trim().toLowerCase();
|
|
150
|
+
const claude = modelId.match(/(?:^|\/)claude-(opus|sonnet|haiku|fable)(?:-|$)/);
|
|
151
|
+
if (claude) return `claude-${claude[1]}`;
|
|
152
|
+
const family = row && typeof row.family === 'string' ? row.family.trim().toLowerCase() : '';
|
|
153
|
+
return family || null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function _staleMonths() {
|
|
157
|
+
const raw = process.env.MIXDOG_MODEL_STALE_MONTHS;
|
|
158
|
+
if (raw == null || raw === '') return 12;
|
|
159
|
+
const n = Number(raw);
|
|
160
|
+
return Number.isFinite(n) ? n : 12;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Apply catalog-driven staleness to the already-filtered `kept` rows.
|
|
164
|
+
// Rules (see slice brief): (1) family supersession by newer release_date,
|
|
165
|
+
// (2) absolute cut older than N months. Both consult the provider's
|
|
166
|
+
// models.dev catalog; rows not found in the catalog are KEPT (fallback to the
|
|
167
|
+
// static LEGACY_PATTERNS already applied earlier). Catalog cold → no-op.
|
|
168
|
+
// Guards: never empty a family via supersession; if the absolute cut would
|
|
169
|
+
// empty the whole provider list, skip the cut entirely.
|
|
170
|
+
function _applyAutoStaleness(kept, provider, testCatalog) {
|
|
171
|
+
const catModels = getModelsDevProviderModelsSync(provider, testCatalog);
|
|
172
|
+
if (!catModels || typeof catModels !== 'object') return kept; // cold/unavailable → skip
|
|
173
|
+
|
|
174
|
+
// Enrich each kept row with its catalog row (by id), if any.
|
|
175
|
+
const meta = kept.map((row) => {
|
|
176
|
+
const cr = catModels[row.id] || null;
|
|
177
|
+
return {
|
|
178
|
+
row,
|
|
179
|
+
cat: cr,
|
|
180
|
+
family: cr ? _stalenessFamily(cr, row.id) : null,
|
|
181
|
+
epoch: cr ? _releaseEpoch(cr) : null,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// (1) Family supersession: within a family, drop a row when the catalog has
|
|
186
|
+
// ANY model (kept or not) in the same family with a strictly newer date.
|
|
187
|
+
// Compute the newest release_date per family from the FULL catalog so a
|
|
188
|
+
// superseding model that isn't in this list still counts.
|
|
189
|
+
const familyNewest = new Map();
|
|
190
|
+
for (const [id, cr] of Object.entries(catModels)) {
|
|
191
|
+
const fam = _stalenessFamily(cr, id);
|
|
192
|
+
const ep = _releaseEpoch(cr);
|
|
193
|
+
if (!fam || ep == null) continue;
|
|
194
|
+
const prev = familyNewest.get(fam);
|
|
195
|
+
if (prev == null || ep > prev) familyNewest.set(fam, ep);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let dropped = new Set();
|
|
199
|
+
for (const m of meta) {
|
|
200
|
+
if (!m.cat || m.family == null || m.epoch == null) continue; // fallback: keep
|
|
201
|
+
const newest = familyNewest.get(m.family);
|
|
202
|
+
if (newest != null && newest > m.epoch) dropped.add(m.row);
|
|
203
|
+
}
|
|
204
|
+
// Never empty a family the user relies on: if supersession removed every
|
|
205
|
+
// in-list member of a family, restore the newest-in-list member.
|
|
206
|
+
const byFamilyInList = new Map();
|
|
207
|
+
for (const m of meta) {
|
|
208
|
+
if (m.family == null) continue;
|
|
209
|
+
if (!byFamilyInList.has(m.family)) byFamilyInList.set(m.family, []);
|
|
210
|
+
byFamilyInList.get(m.family).push(m);
|
|
211
|
+
}
|
|
212
|
+
for (const [, members] of byFamilyInList) {
|
|
213
|
+
if (!members.every((m) => dropped.has(m.row))) continue;
|
|
214
|
+
let best = null;
|
|
215
|
+
for (const m of members) {
|
|
216
|
+
if (!best || (m.epoch || 0) > (best.epoch || 0)) best = m;
|
|
217
|
+
}
|
|
218
|
+
if (best) dropped.delete(best.row);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// (2) Absolute cut: drop rows whose release_date is older than N months.
|
|
222
|
+
const months = _staleMonths();
|
|
223
|
+
if (months > 0) {
|
|
224
|
+
const cutoff = Date.now() - months * 30.4375 * 24 * 60 * 60 * 1000;
|
|
225
|
+
const cutDropped = new Set();
|
|
226
|
+
for (const m of meta) {
|
|
227
|
+
if (dropped.has(m.row)) continue;
|
|
228
|
+
if (!m.cat || m.epoch == null) continue; // fallback: keep
|
|
229
|
+
if (m.epoch < cutoff) cutDropped.add(m.row);
|
|
230
|
+
}
|
|
231
|
+
// If applying the absolute cut would empty the entire provider list, skip
|
|
232
|
+
// the cut (supersession drops still stand).
|
|
233
|
+
const survivorsAfterCut = kept.filter((r) => !dropped.has(r) && !cutDropped.has(r));
|
|
234
|
+
if (survivorsAfterCut.length > 0) {
|
|
235
|
+
for (const r of cutDropped) dropped.add(r);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return kept.filter((r) => !dropped.has(r));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── Coding-unfit filter (models.dev tool_call/modalities + id fallback) ──────
|
|
243
|
+
// HOSTED providers only, applied AFTER staleness, BEFORE dedupe. Data-driven:
|
|
244
|
+
// (1) catalog row exists and tool_call === false → drop (search-preview,
|
|
245
|
+
// roleplay, lightweight chat SKUs — regardless of mode:'chat');
|
|
246
|
+
// (2) catalog row exists and modalities.output is an array lacking 'text' →
|
|
247
|
+
// drop (non-text-emitting SKUs);
|
|
248
|
+
// (3) catalog row MISSING → id fallback CODING_UNFIT_ID_RE (search-preview /
|
|
249
|
+
// audio|realtime-preview), applied even for mode:'chat';
|
|
250
|
+
// (4) not in catalog + no id match → keep.
|
|
251
|
+
// Catalog cold/unavailable → id-fallback still applies (safe, id-only).
|
|
252
|
+
function _applyCodingUnfit(kept, provider, testCatalog) {
|
|
253
|
+
const out = [];
|
|
254
|
+
for (const row of kept) {
|
|
255
|
+
const id = row?.id;
|
|
256
|
+
if (!id) continue;
|
|
257
|
+
const cr = getModelsDevRowSync(id, provider, testCatalog);
|
|
258
|
+
if (cr) {
|
|
259
|
+
// (1) explicit non-tool-calling SKU
|
|
260
|
+
if (cr.tool_call === false) continue;
|
|
261
|
+
// (2) output modalities present but no 'text'
|
|
262
|
+
const outMods = cr?.modalities?.output;
|
|
263
|
+
if (Array.isArray(outMods) && !outMods.includes('text')) continue;
|
|
264
|
+
out.push(row);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
// (3) catalog row missing → id-level fallback
|
|
268
|
+
if (CODING_UNFIT_ID_RE.test(String(id).toLowerCase())) continue;
|
|
269
|
+
// (4) keep
|
|
270
|
+
out.push(row);
|
|
271
|
+
}
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Sanitize a provider model list.
|
|
277
|
+
* @param {Array<object>} models enriched rows ({ id, mode?, created?, ... })
|
|
278
|
+
* @param {{provider?: string}} [opts]
|
|
279
|
+
* @returns {Array<object>} filtered + deduped subset (same row objects)
|
|
280
|
+
*/
|
|
281
|
+
export function sanitizeModelList(models, opts = {}) {
|
|
282
|
+
if (!Array.isArray(models)) return models;
|
|
283
|
+
const provider = String(opts?.provider || '').trim().toLowerCase();
|
|
284
|
+
const hosted = HOSTED_PROVIDERS.has(provider);
|
|
285
|
+
|
|
286
|
+
// CUSTOM/local providers (ollama, lmstudio, unknown names): apply ONLY the
|
|
287
|
+
// enriched-mode drop. No id heuristics, no legacy filter, no dedupe.
|
|
288
|
+
if (!hosted) {
|
|
289
|
+
return models.filter((row) => row?.id && !_isNonChatMode(row));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const kept = [];
|
|
293
|
+
for (const row of models) {
|
|
294
|
+
const id = row?.id;
|
|
295
|
+
if (!id) continue;
|
|
296
|
+
const lid = String(id).toLowerCase();
|
|
297
|
+
const modeChat = typeof row?.mode === 'string' && row.mode.trim().toLowerCase() === 'chat';
|
|
298
|
+
// (a0) hard non-LLM SKUs (image/tts/embeddings/moderation/realtime/audio):
|
|
299
|
+
// dropped even if the row falsely claims mode:'chat'. These families are
|
|
300
|
+
// never text-completion LLMs.
|
|
301
|
+
if (_isNonLlmId(lid)) continue;
|
|
302
|
+
// (a) enriched mode present and not chat-like
|
|
303
|
+
if (_isNonChatMode(row)) continue;
|
|
304
|
+
// (a) non-chat modality id — but an explicit mode:'chat' overrides id regex
|
|
305
|
+
if (!modeChat && NON_CHAT_RE.test(lid)) continue;
|
|
306
|
+
// (b) legacy/deprecated family
|
|
307
|
+
if (_isLegacy(lid)) continue;
|
|
308
|
+
kept.push(row);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Data-driven auto-staleness (models.dev): applied AFTER the static drops
|
|
312
|
+
// above and BEFORE canonical dedupe. Catalog cold/unavailable → no-op.
|
|
313
|
+
// `opts._testCatalog` (tests only) injects a fake catalog map.
|
|
314
|
+
const staleFiltered = _applyAutoStaleness(kept, provider, opts?._testCatalog);
|
|
315
|
+
|
|
316
|
+
// Coding-unfit filter (models.dev tool_call/modalities + id fallback):
|
|
317
|
+
// AFTER staleness, BEFORE dedupe.
|
|
318
|
+
const codingFit = _applyCodingUnfit(staleFiltered, provider, opts?._testCatalog);
|
|
319
|
+
|
|
320
|
+
// (c) canonical dedupe on unambiguous date suffixes: prefer undated alias.
|
|
321
|
+
const byKey = new Map();
|
|
322
|
+
for (const row of codingFit) {
|
|
323
|
+
const key = _canonicalKey(row.id);
|
|
324
|
+
const prev = byKey.get(key);
|
|
325
|
+
if (!prev) { byKey.set(key, row); continue; }
|
|
326
|
+
byKey.set(key, _preferRow(prev, row));
|
|
327
|
+
}
|
|
328
|
+
let winners = new Set(byKey.values());
|
|
329
|
+
|
|
330
|
+
// Conditional MMDD collapse: drop a `-0xxx` row only when its undated alias
|
|
331
|
+
// survives in the winner set AND both share identical context/output limits.
|
|
332
|
+
const aliasByKey = new Map();
|
|
333
|
+
for (const row of winners) aliasByKey.set(_canonicalKey(row.id), row);
|
|
334
|
+
for (const row of [...winners]) {
|
|
335
|
+
const base = _mmddBase(row.id);
|
|
336
|
+
if (!base) continue;
|
|
337
|
+
const alias = aliasByKey.get(base);
|
|
338
|
+
if (alias && alias !== row && _sameLimits(alias, row)) winners.delete(row);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Preserve original ordering of the winners.
|
|
342
|
+
return codingFit.filter((row) => winners.has(row));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function _preferRow(a, b) {
|
|
346
|
+
const aDated = _isDated(a.id);
|
|
347
|
+
const bDated = _isDated(b.id);
|
|
348
|
+
// Prefer the undated canonical alias.
|
|
349
|
+
if (aDated !== bDated) return aDated ? b : a;
|
|
350
|
+
if (!aDated && !bDated) return a; // both undated (shouldn't happen) keep first
|
|
351
|
+
// Both dated: keep the newest by `created`, else the lexically-latest id.
|
|
352
|
+
const ac = Number(a.created) || 0;
|
|
353
|
+
const bc = Number(b.created) || 0;
|
|
354
|
+
if (ac !== bc) return ac > bc ? a : b;
|
|
355
|
+
return String(b.id) > String(a.id) ? b : a;
|
|
356
|
+
}
|