mixdog 1.1.1 → 1.2.0
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 +1 -0
- package/package.json +2 -2
- package/scripts/lib/run-node-tests.mjs +9 -0
- package/scripts/prune-desktop-runtime.mjs +3 -0
- package/scripts/release-paths.mjs +39 -2
- package/scripts/test-direct.mjs +4 -2
- package/src/defaults/shipped-history.json +6 -0
- package/src/defaults/skills/setup/references/actions.md +5 -2
- package/src/defaults/skills/visualize/SKILL.md +157 -0
- package/src/defaults/skills/visualize/references/craft.md +88 -0
- package/src/headless-command.mjs +56 -3
- package/src/headless-exec.mjs +17 -1
- package/src/headless-role.mjs +2 -2
- package/src/help.mjs +2 -0
- package/src/lib/rules-builder.cjs +0 -23
- package/src/rules/lead/LEAD.md +2 -2
- package/src/rules/shared/70-delivery.md +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
- package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
- package/src/runtime/agent/orchestrator/config.mjs +9 -1
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
- package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
- package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +11 -8
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +12 -14
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +1 -5
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
- package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
- package/src/runtime/agent/orchestrator/session/read-dedup.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -10
- package/src/runtime/attachments/store.mjs +38 -0
- package/src/runtime/channels/index.mjs +1 -5
- package/src/runtime/channels/lib/runtime-paths.mjs +8 -23
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -13
- package/src/runtime/channels/lib/worker-ipc.mjs +2 -2
- package/src/runtime/channels/lib/worker-main.mjs +4 -34
- package/src/runtime/shared/atomic-file.mjs +8 -2
- package/src/runtime/shared/best-effort-stderr.mjs +8 -0
- package/src/runtime/shared/code-graph-state.mjs +12 -0
- package/src/runtime/shared/defaults-separation.mjs +2 -0
- package/src/runtime/shared/git-runner-worker.mjs +3 -2
- package/src/runtime/shared/git-runner.mjs +4 -3
- package/src/runtime/shared/llm/default-context-window.mjs +6 -0
- package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
- package/src/runtime/shared/llm/model-catalog.mjs +240 -118
- package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
- package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
- package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
- package/src/runtime/shared/model-proxy-mode.mjs +19 -0
- package/src/runtime/shared/provider-accounts.mjs +2 -2
- package/src/runtime/shared/provider-api-key.mjs +21 -1
- package/src/runtime/shared/schedule-model-ref.mjs +11 -2
- package/src/runtime/shared/semver.d.mts +1 -0
- package/src/runtime/shared/semver.mjs +57 -0
- package/src/runtime/shared/session-usage-totals.mjs +19 -0
- package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
- package/src/runtime/shared/singleton-owner.mjs +36 -6
- package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
- package/src/runtime/shared/tool-surface.mjs +9 -2
- package/src/runtime/shared/tool-work-units.mjs +14 -0
- package/src/runtime/shared/transcript-metadata.mjs +17 -1
- package/src/runtime/shared/turn-changes.mjs +791 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
- package/src/runtime/shared/turn-snapshot.mjs +690 -81
- package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
- package/src/runtime/shared/update-checker.mjs +2 -31
- package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
- package/src/runtime/shared/worktrees/contract.mjs +551 -0
- package/src/runtime/shared/worktrees/disk.mjs +57 -0
- package/src/runtime/shared/worktrees/git.mjs +247 -0
- package/src/runtime/shared/worktrees/include.mjs +480 -0
- package/src/runtime/shared/worktrees/lock.mjs +46 -0
- package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
- package/src/runtime/shared/worktrees/manager.mjs +1215 -0
- package/src/runtime/shared/worktrees/merge.mjs +182 -0
- package/src/runtime/shared/worktrees/owner.mjs +46 -0
- package/src/runtime/shared/worktrees/paths.mjs +62 -0
- package/src/runtime/shared/worktrees/registry.mjs +110 -0
- package/src/runtime/shared/worktrees/settings.mjs +84 -0
- package/src/runtime/shared/worktrees/slug.mjs +62 -0
- package/src/runtime/shared/worktrees/test-support.mjs +96 -0
- package/src/runtime/visualize-bridge/client.mjs +181 -0
- package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
- package/src/session-runtime/boot/apis.mjs +35 -0
- package/src/session-runtime/boot/begin.mjs +0 -2
- package/src/session-runtime/boot/config.mjs +2 -0
- package/src/session-runtime/boot/facade.mjs +20 -0
- package/src/session-runtime/boot/infrastructure.mjs +1 -0
- package/src/session-runtime/boot/session.mjs +1 -0
- package/src/session-runtime/boot/tools.mjs +9 -4
- package/src/session-runtime/config-lifecycle.mjs +10 -1
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
- package/src/session-runtime/global-extensions.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
- package/src/session-runtime/internal-tool-executor.mjs +9 -1
- package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
- package/src/session-runtime/mcp-glue/connect.mjs +11 -6
- package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
- package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
- package/src/session-runtime/model-route/route-persist.mjs +3 -0
- package/src/session-runtime/model-route/set-route.mjs +15 -2
- package/src/session-runtime/notification-bus.mjs +9 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
- package/src/session-runtime/resource-extension-sync.mjs +30 -7
- package/src/session-runtime/resource-mcp-api.mjs +27 -10
- package/src/session-runtime/route-resolution.mjs +10 -1
- package/src/session-runtime/route-state.mjs +16 -2
- package/src/session-runtime/runtime-bootstrap.mjs +0 -4
- package/src/session-runtime/runtime-core.mjs +5 -0
- package/src/session-runtime/runtime-facade.mjs +8 -0
- package/src/session-runtime/runtime-paths.mjs +0 -1
- package/src/session-runtime/runtime-review-api.mjs +9 -0
- package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
- package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
- package/src/session-runtime/services/agent-tool.mjs +1 -0
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/daemon-candidate.mjs +3 -2
- package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
- package/src/session-runtime/session-create/session-options.mjs +10 -1
- package/src/session-runtime/session-turn-api.mjs +1 -0
- package/src/session-runtime/settings-api.mjs +4 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
- package/src/session-runtime/settings-profile-api.mjs +27 -2
- package/src/session-runtime/settings-worktree-api.mjs +17 -0
- package/src/session-runtime/setup-tool/executor.mjs +11 -4
- package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
- package/src/session-runtime/starter-guide-state.mjs +62 -0
- package/src/session-runtime/turn/agent-ops.mjs +12 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
- package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
- package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
- package/src/session-runtime/turn/turn-run.mjs +6 -7
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -2
- package/src/session-runtime/workspace/admission-gate.mjs +197 -0
- package/src/session-runtime/workspace/project-branches.mjs +136 -0
- package/src/session-runtime/workspace/transition-steps.mjs +364 -0
- package/src/session-runtime/workspace/transitions.mjs +651 -0
- package/src/session-runtime/workspace/workspace-state.mjs +112 -0
- package/src/standalone/channel-binding.mjs +1 -2
- package/src/standalone/channel-transport/call-dispatch.mjs +4 -10
- package/src/standalone/channel-transport/http-routes.mjs +17 -0
- package/src/standalone/channel-transport.mjs +13 -1
- package/src/standalone/daemon-channels-loader.mjs +2 -3
- package/src/standalone/daemon-desktop-runtime.mjs +1 -0
- package/src/standalone/daemon-owner.mjs +372 -0
- package/src/standalone/daemon-stored-session-views.mjs +4 -1
- package/src/standalone/daemon.mjs +139 -5
- package/src/standalone/desktop-service-registry.mjs +20 -0
- package/src/standalone/local-session-runtime.mjs +13 -78
- package/src/standalone/session-client/attachment-calls.mjs +3 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
- package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
- package/src/standalone/session-client.mjs +104 -13
- package/src/standalone/session-protocol.mjs +17 -0
- package/src/standalone/session-proxy.mjs +3 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
- package/src/standalone/session-service/projection/revisions.mjs +2 -0
- package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
- package/src/standalone/session-service/projection/workspace.mjs +14 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
- package/src/standalone/session-transport/call-route.mjs +13 -0
- package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
- package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
- package/src/standalone/session-transport/client-registry.mjs +20 -3
- package/src/standalone/session-transport/routes.mjs +79 -2
- package/src/standalone/session-transport/stack.mjs +2 -0
- package/src/standalone/session-transport.mjs +24 -1
- package/src/tui/app/message-selector.mjs +2 -1
- package/src/tui/app/model-options.mjs +35 -5
- package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
- package/src/tui/app/model-picker.mjs +4 -2
- package/src/tui/dist/index.mjs +332 -36
- package/src/tui/index.jsx +10 -2
- package/src/tui/session/compaction-view.mjs +147 -0
- package/src/tui/session/context-state.mjs +6 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/quota-resume.mjs +2 -1
- package/src/tui/session/session-api/commands.mjs +10 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +12 -8
- package/src/tui/session/session-api/intake/rewind.mjs +2 -1
- package/src/tui/session/session-api/integrations.mjs +18 -0
- package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
- package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
- package/src/tui/session/session-api/routes.mjs +10 -1
- package/src/tui/session/session-api/settings.mjs +2 -0
- package/src/tui/session/session-api/shared.mjs +10 -6
- package/src/tui/session/session-api/worktree.mjs +60 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +31 -12
- package/src/tui/session/session-flow/drain.mjs +15 -1
- package/src/tui/session/session-flow/session-reset.mjs +4 -3
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/transcript-intake.mjs +14 -5
- package/src/tui/session/turn-stream.mjs +32 -4
- package/src/tui/session/turn-tool-cards.mjs +10 -2
- package/src/tui/session/turn.mjs +39 -10
- package/src/runtime/channels/tool-defs.mjs +0 -17
|
@@ -13,6 +13,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
13
13
|
import { updateJsonAtomicSync, writeJsonAtomicSync, withFileLock } from '../../../shared/atomic-file.mjs';
|
|
14
14
|
import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
|
|
15
15
|
import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
16
|
+
import { modelProxyClientMode, serverModelCredentialsDisabledError } from '../../../shared/model-proxy-mode.mjs';
|
|
16
17
|
import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
17
18
|
import { claudeCliUserAgent, resolveCliVersion } from './anthropic-oauth-client-version.mjs';
|
|
18
19
|
import {
|
|
@@ -148,6 +149,7 @@ export function _credentialsMaxMtime() {
|
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
export function loadCredentials() {
|
|
152
|
+
if (modelProxyClientMode()) return null;
|
|
151
153
|
const loaded = credentialCandidates().map(_loadCredentialsFile).filter(Boolean);
|
|
152
154
|
if (!loaded.length) return null;
|
|
153
155
|
loaded.sort((a, b) => (Number(b.expiresAt) || 0) - (Number(a.expiresAt) || 0));
|
|
@@ -382,6 +384,7 @@ async function _refreshOAuthCredentialsUnlocked(creds) {
|
|
|
382
384
|
}
|
|
383
385
|
|
|
384
386
|
export async function refreshOAuthCredentials(creds) {
|
|
387
|
+
if (modelProxyClientMode()) throw serverModelCredentialsDisabledError('Anthropic OAuth credentials');
|
|
385
388
|
if (isAnthropicOAuthRefreshDisabled()) {
|
|
386
389
|
throw new Error(
|
|
387
390
|
'Anthropic OAuth refresh is disabled in this process; ' +
|
|
@@ -48,6 +48,7 @@ import { createAnthropicOAuthRecovery } from './anthropic-oauth-recovery.mjs';
|
|
|
48
48
|
import { createMidState, createMidstreamRecovery } from './anthropic-oauth-midstream.mjs';
|
|
49
49
|
import { assertAnthropicStreamNotEmpty } from './anthropic-midstream-recovery.mjs';
|
|
50
50
|
import { applyAnthropicEffortToBody, shouldIncludeEffortBeta } from './anthropic-effort.mjs';
|
|
51
|
+
import { ANTHROPIC_EFFORT_LEVELS, withEffortRejectionFallback } from './effort-rejection.mjs';
|
|
51
52
|
import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
|
|
52
53
|
import {
|
|
53
54
|
applyAnthropicCacheMarkers,
|
|
@@ -408,6 +409,17 @@ export class AnthropicOAuthProvider {
|
|
|
408
409
|
}
|
|
409
410
|
|
|
410
411
|
async send(messages, model, tools, sendOpts) {
|
|
412
|
+
return withEffortRejectionFallback(
|
|
413
|
+
'anthropic-oauth',
|
|
414
|
+
model,
|
|
415
|
+
sendOpts || {},
|
|
416
|
+
undefined,
|
|
417
|
+
(opts) => this._sendOnce(messages, model, tools, opts),
|
|
418
|
+
{ levels: ANTHROPIC_EFFORT_LEVELS }
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async _sendOnce(messages, model, tools, sendOpts) {
|
|
411
423
|
await warmCliVersion();
|
|
412
424
|
// Re-warm the kept-alive socket before the turn. preconnect() is a
|
|
413
425
|
// best-effort no-op while a socket is still hot (TTL gate), but after an
|
|
@@ -625,7 +637,9 @@ export class AnthropicOAuthProvider {
|
|
|
625
637
|
// without them (the request keeps its turn effort).
|
|
626
638
|
if (isEffortConfigurationRejection(response.status, scrubbedText) && !opts._effortConfigurationRetry) {
|
|
627
639
|
markEffortConfigurationUnsupported('anthropic-oauth', useModel);
|
|
628
|
-
process.stderr.write(
|
|
640
|
+
process.stderr.write(
|
|
641
|
+
`[anthropic-oauth] ${useModel} rejected the mid-conversation effort update; retrying once without it\n`
|
|
642
|
+
);
|
|
629
643
|
return this.send(messages, useModel, tools, {
|
|
630
644
|
...opts,
|
|
631
645
|
effortConfigurationEnabled: false,
|
|
@@ -8,6 +8,7 @@ import { ANTHROPIC_MAX_MIDSTREAM_RETRIES, parseSSEStream } from './anthropic-sse
|
|
|
8
8
|
import { buildAnthropicBetaHeaders } from './anthropic-betas.mjs';
|
|
9
9
|
import { buildAnthropicApiRequest } from './anthropic-api-request.mjs';
|
|
10
10
|
import { isCustomProviderName } from './custom-reasoning.mjs';
|
|
11
|
+
import { ANTHROPIC_EFFORT_LEVELS, withEffortRejectionFallback } from './effort-rejection.mjs';
|
|
11
12
|
import { armFirstByteWatchdog, createAnthropicApiTransport } from './anthropic-api-transport.mjs';
|
|
12
13
|
import { createAnthropicApiRecovery } from './anthropic-api-recovery.mjs';
|
|
13
14
|
import { buildAnthropicTurnResult } from './anthropic-turn-result.mjs';
|
|
@@ -106,10 +107,21 @@ export class AnthropicProvider {
|
|
|
106
107
|
throw err;
|
|
107
108
|
}
|
|
108
109
|
}
|
|
110
|
+
// The `anthropic` API-key provider and every custom `anthropic`-protocol
|
|
111
|
+
// provider (a subclass of this one) share this send path.
|
|
109
112
|
async _doSend(messages, model, tools, sendOpts) {
|
|
113
|
+
return withEffortRejectionFallback(
|
|
114
|
+
this.name,
|
|
115
|
+
model,
|
|
116
|
+
sendOpts || {},
|
|
117
|
+
this.config?.reasoningEffort,
|
|
118
|
+
(opts) => this._sendOnce(messages, model, tools, opts),
|
|
119
|
+
{ levels: ANTHROPIC_EFFORT_LEVELS }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
async _sendOnce(messages, model, tools, opts) {
|
|
110
123
|
if (!model) throw new Error(`[${this.name}] model is required — pass it from the caller preset`);
|
|
111
124
|
const useModel = model;
|
|
112
|
-
const opts = sendOpts || {};
|
|
113
125
|
// Wire request (system/cache layout, tools, effort, fast mode, images)
|
|
114
126
|
// and the per-request beta headers derived from that final body —
|
|
115
127
|
// anthropic-api-request.mjs. Fast mode latches on the provider instance.
|
|
@@ -18,6 +18,7 @@ import { join, resolve } from 'node:path';
|
|
|
18
18
|
import { getPluginData } from '../config.mjs';
|
|
19
19
|
import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
|
|
20
20
|
import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
|
|
21
|
+
import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
|
|
21
22
|
import {
|
|
22
23
|
accountIdentityFields,
|
|
23
24
|
normalizeExpiresAtMs as _normalizeExpiresAt,
|
|
@@ -192,6 +193,7 @@ function mtimeMs(path) {
|
|
|
192
193
|
export { _normalizeExpiresAt };
|
|
193
194
|
|
|
194
195
|
export function loadTokens() {
|
|
196
|
+
if (modelProxyClientMode()) return null;
|
|
195
197
|
const path = getOwnTokenPath();
|
|
196
198
|
if (!existsSync(path)) return null;
|
|
197
199
|
try {
|
|
@@ -4,6 +4,7 @@ import { join, resolve } from 'node:path';
|
|
|
4
4
|
import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
5
5
|
import { writeJsonAtomicSync, withFileLock } from '../../../shared/atomic-file.mjs';
|
|
6
6
|
import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
|
|
7
|
+
import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
|
|
7
8
|
import { openInBrowser } from '../../../shared/open-url.mjs';
|
|
8
9
|
import { createOAuthPkce } from './lib/oauth-pkce.mjs';
|
|
9
10
|
import { accountIdentityFields, normalizeAccountIdentity } from './lib/oauth-token-utils.mjs';
|
|
@@ -75,6 +76,7 @@ export function cursorTokenIdentity(token) {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
function loadStoredCredentials() {
|
|
79
|
+
if (modelProxyClientMode()) return null;
|
|
78
80
|
if (process.env.CURSOR_ACCESS_TOKEN && !boundProviderAuthPath('cursor-oauth')) {
|
|
79
81
|
return {
|
|
80
82
|
access_token: process.env.CURSOR_ACCESS_TOKEN,
|
|
@@ -83,8 +83,9 @@ export function normalizeModels(models) {
|
|
|
83
83
|
name: isCursorAutoModelId(rawId)
|
|
84
84
|
? 'Auto'
|
|
85
85
|
: model.displayName || model.displayNameShort || model.displayModelId || aliases[0] || id,
|
|
86
|
+
// GetUsableModels publishes no window; the provider default applies
|
|
87
|
+
// after any described window instead of a seeded constant.
|
|
86
88
|
reasoning: Boolean(model.thinkingDetails),
|
|
87
|
-
contextWindow: 200_000,
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -43,10 +43,18 @@ export function markEffortConfigurationUnsupported(provider, model) {
|
|
|
43
43
|
|
|
44
44
|
/** True for a provider error that names the mid-conversation effort update. */
|
|
45
45
|
export function isEffortConfigurationRejection(status, text) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
46
|
+
const message = String(text || '');
|
|
47
|
+
if (status !== 400) return false;
|
|
48
|
+
if (/mid-conversation|configuration_update|reasoning_effort_update/i.test(message)) return true;
|
|
49
|
+
// A rejected `output_config.effort` VALUE (the error quotes a level) belongs
|
|
50
|
+
// to the effort-rejection fallback (effort-rejection.mjs); a rejected field
|
|
51
|
+
// ("output_config.effort is not supported") is still the update case.
|
|
52
|
+
if (
|
|
53
|
+
/output_config\.effort\b(?!_)/i.test(message) &&
|
|
54
|
+
/['"`](?:none|minimal|low|medium|high|xhigh|max|ultra)['"`]/i.test(message)
|
|
55
|
+
)
|
|
56
|
+
return false;
|
|
57
|
+
return /output_config/i.test(message);
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
/**
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parseJsonText, updateJsonAtomicSync } from '../../../shared/atomic-file.mjs';
|
|
4
|
+
import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
5
|
+
|
|
6
|
+
// An OpenAI-compatible endpoint that rejects the requested reasoning effort
|
|
7
|
+
// (HTTP 400 naming the value) is replayed once at the nearest accepted level,
|
|
8
|
+
// and the rejection is remembered per provider+model so the level stops being
|
|
9
|
+
// sent (clamped before the request) and stops being offered (the sync reader).
|
|
10
|
+
|
|
11
|
+
const EFFORT_ORDER = Object.freeze(['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
|
|
12
|
+
const FILE_NAME = 'effort-rejections.json';
|
|
13
|
+
// Per-provider request fields that can carry the effort next to `opts.effort`.
|
|
14
|
+
const EFFORT_OPT_KEYS = ['xaiReasoningEffort', 'openRouterReasoningEffort', 'deepseekReasoningEffort'];
|
|
15
|
+
const LEVEL_RE = new RegExp(`\\b(${EFFORT_ORDER.join('|')})\\b`, 'gi');
|
|
16
|
+
const LIST_RE =
|
|
17
|
+
/\b(?:supported|allowed|valid|accepted|available)\s+(?:values?|levels?|options?|efforts?)|(?:must|should|expected to) be one of|expected one of|one of|choose from|input should be/i;
|
|
18
|
+
const REJECT_RE =
|
|
19
|
+
/invalid|unsupported|not supported|does not support|doesn't support|not allowed|not a valid|unrecognized|must be one of|expected one of|supported values/i;
|
|
20
|
+
// Anthropic's `invalid_request_error` words a bad enum value as "Input should be 'low', ...".
|
|
21
|
+
const ANTHROPIC_REJECT_RE = /input should be|should be one of|not one of/i;
|
|
22
|
+
const EFFORT_RE = /reasoning[._ ]?effort|\beffort\b/i;
|
|
23
|
+
const REASONING_RE = /\breasoning\b/i;
|
|
24
|
+
// A request field other than the effort: `reasoning.summary`, `reasoning_content`,
|
|
25
|
+
// `thinking.type`, `budget_tokens`, ...
|
|
26
|
+
const OTHER_FIELD_RE = /\breasoning[._[](?!effort)\w+|(?:^|[\s'"`(])(?:thinking\.\w+|budget_tokens)\b/i;
|
|
27
|
+
// The mid-conversation effort update rejection belongs to effort-configuration.mjs.
|
|
28
|
+
const EFFORT_CONFIGURATION_RE = /mid-conversation|configuration_update|reasoning_effort_update/i;
|
|
29
|
+
/** The effort levels the Anthropic Messages wire accepts (`output_config.effort`). */
|
|
30
|
+
export const ANTHROPIC_EFFORT_LEVELS = Object.freeze(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
31
|
+
|
|
32
|
+
const cache = new Map();
|
|
33
|
+
|
|
34
|
+
const filePath = () => join(resolvePluginData(), FILE_NAME);
|
|
35
|
+
const entryKey = (provider, model) =>
|
|
36
|
+
`${String(provider || '').trim()}:${String(model || '')
|
|
37
|
+
.trim()
|
|
38
|
+
.toLowerCase()}`;
|
|
39
|
+
const levelOf = (value) => {
|
|
40
|
+
const level = String(value ?? '')
|
|
41
|
+
.trim()
|
|
42
|
+
.toLowerCase();
|
|
43
|
+
return EFFORT_ORDER.includes(level) ? level : null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// A rejection persisted by another process changes the file; the cached copy is
|
|
47
|
+
// only trusted while the file's mtime and size are unchanged.
|
|
48
|
+
function signature(file) {
|
|
49
|
+
try {
|
|
50
|
+
const stat = statSync(file);
|
|
51
|
+
return `${stat.mtimeMs}:${stat.size}`;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function load(file) {
|
|
58
|
+
const sig = signature(file);
|
|
59
|
+
const cached = cache.get(file);
|
|
60
|
+
if (cached && cached.sig === sig) return cached.data;
|
|
61
|
+
let data = null;
|
|
62
|
+
try {
|
|
63
|
+
data = parseJsonText(readFileSync(file, 'utf8'));
|
|
64
|
+
} catch {}
|
|
65
|
+
const valid =
|
|
66
|
+
data?.version === 1 && data.models && typeof data.models === 'object' ? data : { version: 1, models: {} };
|
|
67
|
+
cache.set(file, { sig, data: valid });
|
|
68
|
+
return valid;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function addRejection(data, key, level, allowed) {
|
|
72
|
+
const models = { ...data.models };
|
|
73
|
+
const prev = models[key] || {};
|
|
74
|
+
const rejected = new Set(prev.rejected || []);
|
|
75
|
+
if (level) rejected.add(level);
|
|
76
|
+
const nextAllowed = allowed?.length ? allowed : prev.allowed;
|
|
77
|
+
models[key] = { rejected: [...rejected], ...(nextAllowed?.length ? { allowed: nextAllowed } : {}) };
|
|
78
|
+
return { version: 1, models };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function entry(provider, model) {
|
|
82
|
+
return load(filePath()).models[entryKey(provider, model)] || null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Levels this provider+model is known not to accept (rejected, or outside a
|
|
86
|
+
* list the endpoint named). Sync, for the runtime effort catalog. */
|
|
87
|
+
export function rejectedEffortLevelsSync(provider, model) {
|
|
88
|
+
const found = entry(provider, model);
|
|
89
|
+
const rejected = new Set(found?.rejected || []);
|
|
90
|
+
if (found?.allowed?.length) for (const level of EFFORT_ORDER) if (!found.allowed.includes(level)) rejected.add(level);
|
|
91
|
+
return rejected;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Remember a rejected level (and the allowed list the endpoint named, if any). */
|
|
95
|
+
export function recordEffortRejection(provider, model, level, allowed = null) {
|
|
96
|
+
const file = filePath();
|
|
97
|
+
const key = entryKey(provider, model);
|
|
98
|
+
const normalized = levelOf(level);
|
|
99
|
+
try {
|
|
100
|
+
const data = updateJsonAtomicSync(
|
|
101
|
+
file,
|
|
102
|
+
(cur) =>
|
|
103
|
+
addRejection(cur?.version === 1 && cur.models ? cur : { version: 1, models: {} }, key, normalized, allowed),
|
|
104
|
+
{ lock: true, timeoutMs: 1000 }
|
|
105
|
+
);
|
|
106
|
+
cache.set(file, { sig: signature(file), data });
|
|
107
|
+
} catch {
|
|
108
|
+
cache.set(file, { sig: signature(file), data: addRejection(load(file), key, normalized, allowed) });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Forget what was loaded from disk (a process restart, for tests). */
|
|
113
|
+
export function resetEffortRejectionCache() {
|
|
114
|
+
cache.clear();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const collect = (error) => {
|
|
118
|
+
const nested = error?.error?.error && typeof error.error.error === 'object' ? error.error.error : null;
|
|
119
|
+
return [error, error?.error, nested].filter((part) => part && typeof part === 'object');
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const hasLevel = (text) => new RegExp(LEVEL_RE.source, 'i').test(text);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A 400-class provider error that rejects the reasoning effort, as
|
|
126
|
+
* `{ allowed }` (levels the message lists, or null), else null. It must be
|
|
127
|
+
* effort-specific: the `param` names the effort field, or the message names the
|
|
128
|
+
* effort field together with a level, or (given the `requested` level) the
|
|
129
|
+
* message calls that level invalid while talking about reasoning. A rejection
|
|
130
|
+
* of another reasoning field (`reasoning.summary`, `reasoning.include`) never matches.
|
|
131
|
+
*/
|
|
132
|
+
export function detectEffortRejection(error, requested = null) {
|
|
133
|
+
const status = [error?.status, error?.httpStatus, error?.response?.status].map(Number).find((value) => value > 0);
|
|
134
|
+
if (status !== 400 && status !== 422) return null;
|
|
135
|
+
const parts = collect(error);
|
|
136
|
+
const param = parts.map((part) => String(part.param ?? '')).join(' ');
|
|
137
|
+
const text = parts
|
|
138
|
+
.flatMap((part) => [part.message, part.code, part.param])
|
|
139
|
+
.filter(Boolean)
|
|
140
|
+
.join(' ');
|
|
141
|
+
if (EFFORT_CONFIGURATION_RE.test(text)) return null;
|
|
142
|
+
const anthropic = parts.some((part) => part.type === 'invalid_request_error') || /invalid_request_error/.test(text);
|
|
143
|
+
if (!REJECT_RE.test(text) && !(anthropic && ANTHROPIC_REJECT_RE.test(text))) return null;
|
|
144
|
+
const level = levelOf(requested);
|
|
145
|
+
const targeted =
|
|
146
|
+
/effort/i.test(param) ||
|
|
147
|
+
(!OTHER_FIELD_RE.test(`${param} ${text}`) &&
|
|
148
|
+
((EFFORT_RE.test(text) && hasLevel(text)) ||
|
|
149
|
+
(level && REASONING_RE.test(text) && new RegExp(`\\b${level}\\b`, 'i').test(text))));
|
|
150
|
+
if (!targeted) return null;
|
|
151
|
+
const list = LIST_RE.exec(text);
|
|
152
|
+
const allowed = list
|
|
153
|
+
? [...new Set([...text.slice(list.index + list[0].length).matchAll(LEVEL_RE)].map((m) => m[1].toLowerCase()))]
|
|
154
|
+
: [];
|
|
155
|
+
return { allowed: allowed.length ? allowed : null };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The nearest lower level not in `rejected`; with `allowHigher` (the accepted
|
|
160
|
+
* set is known) the nearest higher one when nothing lower is left.
|
|
161
|
+
*/
|
|
162
|
+
export function nearestAcceptedEffort(requested, rejected = new Set(), { allowHigher = false } = {}) {
|
|
163
|
+
const from = EFFORT_ORDER.indexOf(requested);
|
|
164
|
+
if (from < 0) return null;
|
|
165
|
+
const open = EFFORT_ORDER.filter((level, index) => index !== from && !rejected.has(level));
|
|
166
|
+
const lower = open.filter((level) => EFFORT_ORDER.indexOf(level) < from).at(-1);
|
|
167
|
+
return lower ?? (allowHigher ? (open[0] ?? null) : null);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const requestedEffort = (opts, configEffort) => levelOf(opts?.effort ?? configEffort);
|
|
171
|
+
|
|
172
|
+
function withEffort(opts, level) {
|
|
173
|
+
const next = { ...opts, effort: level };
|
|
174
|
+
for (const key of EFFORT_OPT_KEYS) if (opts?.[key] !== undefined) next[key] = level;
|
|
175
|
+
return next;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// The rejected set seen by a transport that only takes `levels` of the ladder.
|
|
179
|
+
function rejectedWithin(provider, model, levels) {
|
|
180
|
+
const rejected = rejectedEffortLevelsSync(provider, model);
|
|
181
|
+
if (levels) for (const level of EFFORT_ORDER) if (!levels.includes(level)) rejected.add(level);
|
|
182
|
+
return rejected;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** `opts` with a level already known to be rejected stepped down; unchanged otherwise. */
|
|
186
|
+
export function clampEffortOpts(provider, model, opts, configEffort, levels = null) {
|
|
187
|
+
const requested = requestedEffort(opts, configEffort);
|
|
188
|
+
if (!requested || (levels && !levels.includes(requested))) return opts;
|
|
189
|
+
const rejected = rejectedWithin(provider, model, levels);
|
|
190
|
+
if (!rejected.has(requested)) return opts;
|
|
191
|
+
const level = nearestAcceptedEffort(requested, rejected, { allowHigher: Boolean(entry(provider, model)?.allowed) });
|
|
192
|
+
return level ? withEffort(opts, level) : opts;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const STREAM_CALLBACKS = ['onStreamDelta', 'onTextDelta', 'onToolCall'];
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Runs `send(opts)` with known-rejected levels clamped first; when the model
|
|
199
|
+
* rejects the effort, remembers it and replays the request once at the nearest
|
|
200
|
+
* accepted level. Never replays after output reached the caller, and never
|
|
201
|
+
* replays the replay. `levels` limits the ladder to what the transport's wire
|
|
202
|
+
* accepts. A send re-entered from inside a wrapped send (a transport's own
|
|
203
|
+
* replays call `send` again) passes straight through: the outer wrapper owns it.
|
|
204
|
+
*/
|
|
205
|
+
export async function withEffortRejectionFallback(provider, model, opts, configEffort, send, { levels = null } = {}) {
|
|
206
|
+
if (opts?._effortRejectionActive) return send(opts);
|
|
207
|
+
let streamed = false;
|
|
208
|
+
const tracked = { ...opts, _effortRejectionActive: true };
|
|
209
|
+
for (const name of STREAM_CALLBACKS) {
|
|
210
|
+
const callback = opts?.[name];
|
|
211
|
+
if (typeof callback === 'function') {
|
|
212
|
+
tracked[name] = (...args) => {
|
|
213
|
+
streamed = true;
|
|
214
|
+
return callback(...args);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const first = clampEffortOpts(provider, model, tracked, configEffort, levels);
|
|
219
|
+
try {
|
|
220
|
+
return await send(first);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
const requested = requestedEffort(first, configEffort);
|
|
223
|
+
const rejection = detectEffortRejection(error, requested);
|
|
224
|
+
const emitted =
|
|
225
|
+
streamed || error?.liveTextEmitted === true || error?.emittedToolCall === true || error?.unsafeToRetry === true;
|
|
226
|
+
if (!rejection || !requested || (levels && !levels.includes(requested))) throw error;
|
|
227
|
+
recordEffortRejection(provider, model, requested, rejection.allowed);
|
|
228
|
+
if (emitted) throw error;
|
|
229
|
+
const level = nearestAcceptedEffort(requested, rejectedWithin(provider, model, levels), {
|
|
230
|
+
allowHigher: Boolean(entry(provider, model)?.allowed),
|
|
231
|
+
});
|
|
232
|
+
if (!level) throw error;
|
|
233
|
+
process.stderr.write(
|
|
234
|
+
`[${provider}] ${model} rejected reasoning effort '${requested}'; retrying once at '${level}'\n`
|
|
235
|
+
);
|
|
236
|
+
try {
|
|
237
|
+
return await send(withEffort(first, level));
|
|
238
|
+
} catch (retryError) {
|
|
239
|
+
const again = detectEffortRejection(retryError, level);
|
|
240
|
+
if (again) recordEffortRejection(provider, model, level, again.allowed);
|
|
241
|
+
throw retryError;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -3,7 +3,7 @@ import { createNpmVersionSource, maxSemver } from './npm-cli-version.mjs';
|
|
|
3
3
|
// Grok CLI client version for the cli-chat-proxy version gate (HTTP 426).
|
|
4
4
|
// Effective = env override, else max(floor, learned-from-426, live npm latest
|
|
5
5
|
// of the official @xai-official/grok CLI).
|
|
6
|
-
export const GROK_CLI_VERSION_FLOOR = '1.0.
|
|
6
|
+
export const GROK_CLI_VERSION_FLOOR = '1.0.52';
|
|
7
7
|
export const GROK_CLI_NPM_PACKAGE = '@xai-official/grok';
|
|
8
8
|
|
|
9
9
|
const live = createNpmVersionSource(GROK_CLI_NPM_PACKAGE, { persistKey: 'grok-cli' });
|
|
@@ -10,6 +10,7 @@ import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs
|
|
|
10
10
|
import { createTimeoutSignal } from '../stall-policy.mjs';
|
|
11
11
|
import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
12
12
|
import { grokClientVersionHeaders } from './grok-client-version.mjs';
|
|
13
|
+
import { modelProxyClientMode, serverModelCredentialsDisabledError } from '../../../shared/model-proxy-mode.mjs';
|
|
13
14
|
import {
|
|
14
15
|
accountIdentityFields,
|
|
15
16
|
decodeJwtPayload,
|
|
@@ -174,6 +175,7 @@ export function _mtimeMs(path) {
|
|
|
174
175
|
// mixdog's own login store (grok-oauth.json). Single writer, accurate
|
|
175
176
|
// numeric expires_at from refresh.
|
|
176
177
|
export function _loadOwnTokens() {
|
|
178
|
+
if (modelProxyClientMode()) return null;
|
|
177
179
|
const path = getOwnTokenPath();
|
|
178
180
|
if (!existsSync(path)) return null;
|
|
179
181
|
try {
|
|
@@ -375,6 +377,7 @@ async function _postRefreshWithRetry(tokens) {
|
|
|
375
377
|
// single-use. Hold a cross-process lease across the re-read, exchange, and
|
|
376
378
|
// atomic save so only one process can spend a generation.
|
|
377
379
|
export async function refreshTokens(tokens, { force = false } = {}) {
|
|
380
|
+
if (modelProxyClientMode()) throw serverModelCredentialsDisabledError('Grok OAuth credentials');
|
|
378
381
|
if (!tokens?.refresh_token) {
|
|
379
382
|
throw new Error('[grok-oauth] refresh token not available — open /providers in mixdog to sign in again');
|
|
380
383
|
}
|
|
@@ -6,6 +6,7 @@ import { scrubOAuthSecrets } from './lib/oauth-token-utils.mjs';
|
|
|
6
6
|
import { PROVIDER_MAX_BEFORE_WARN_MS, STALL_ABORT_S, STALL_TICK_MS } from '../stall-policy.mjs';
|
|
7
7
|
import { PACKAGE_VERSION } from '../../../web-search/lib/package-version.mjs';
|
|
8
8
|
import { DEFAULT_MODEL } from './mimo-oauth-tokens.mjs';
|
|
9
|
+
import { MIMO_SEED_MODELS, mimoSeedRow as seedRow } from '../../../shared/llm/provider-seed-rosters.mjs';
|
|
9
10
|
|
|
10
11
|
const MIMO_EFFORT_LEVELS = Object.freeze(['minimal', 'low', 'medium', 'high']);
|
|
11
12
|
const MIMO_DEFAULT_TEMPERATURE = 1.0;
|
|
@@ -15,25 +16,12 @@ const MIMO_DEFAULT_MAX_OUTPUT_TOKENS = 128_000;
|
|
|
15
16
|
export const MIMO_FIRST_BYTE_TIMEOUT_MS = Math.min(300_000, PROVIDER_MAX_BEFORE_WARN_MS);
|
|
16
17
|
export const MIMO_SEMANTIC_IDLE_TIMEOUT_MS = Math.min(480_000, Math.max(10_000, STALL_ABORT_S * 1000 - STALL_TICK_MS));
|
|
17
18
|
|
|
18
|
-
// Local roster used when /models fails or comes back empty.
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
export const SEED_MODELS = Object.freeze(
|
|
22
|
-
[
|
|
23
|
-
['mimo-v2.6-pro', 'MiMo-V2.6-Pro', SEED_CONTEXT],
|
|
24
|
-
['mimo-v2.6-flash', 'MiMo-V2.6-Flash', SEED_CONTEXT],
|
|
25
|
-
['mimo-v2.6-pro-ultraspeed', 'MiMo-V2.6-Pro-Ultraspeed', SEED_CONTEXT],
|
|
26
|
-
['mimo-v2.5-pro', 'MiMo-V2.5-Pro', SEED_CONTEXT],
|
|
27
|
-
['mimo-v2.5', 'MiMo-V2.5', SEED_CONTEXT],
|
|
28
|
-
['mimo-v2-pro', 'MiMo-V2-Pro', SEED_CONTEXT],
|
|
29
|
-
['mimo-v2-omni', 'MiMo-V2-Omni', 262_144],
|
|
30
|
-
].map(([id, display_name, context_window]) =>
|
|
31
|
-
Object.freeze({ id, display_name, context_window, max_output_tokens: SEED_OUTPUT })
|
|
32
|
-
)
|
|
33
|
-
);
|
|
19
|
+
// Local roster used when /models fails or comes back empty. Its limits are not
|
|
20
|
+
// discovered data: decorateModelRows applies them only where nothing fetched did.
|
|
21
|
+
export const SEED_MODELS = MIMO_SEED_MODELS;
|
|
34
22
|
|
|
35
23
|
export function seedModelItems() {
|
|
36
|
-
return SEED_MODELS.map((
|
|
24
|
+
return SEED_MODELS.map(({ id, display_name }) => ({ id, display_name }));
|
|
37
25
|
}
|
|
38
26
|
|
|
39
27
|
// Speech/transcription ids cannot serve a text chat turn.
|
|
@@ -50,28 +38,8 @@ export function chooseDefaultModel(ids) {
|
|
|
50
38
|
return chat[0] || DEFAULT_MODEL;
|
|
51
39
|
}
|
|
52
40
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
function seedRow(model) {
|
|
56
|
-
const id = String(model || '').toLowerCase();
|
|
57
|
-
let seed = SEED_MODELS.find((item) => item.id === id) || null;
|
|
58
|
-
if (!seed) {
|
|
59
|
-
for (const item of SEED_MODELS) {
|
|
60
|
-
if (id.startsWith(`${item.id}-`) && (!seed || item.id.length > seed.id.length)) seed = item;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (!seed) return null;
|
|
64
|
-
return {
|
|
65
|
-
id: String(model),
|
|
66
|
-
contextWindow: seed.context_window,
|
|
67
|
-
outputTokens: seed.max_output_tokens,
|
|
68
|
-
reasoning: true,
|
|
69
|
-
vision: true,
|
|
70
|
-
inputModalities: ['text', 'image'],
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Fill the gaps a /models row leaves (names, limits, effort levels).
|
|
41
|
+
// Fill the gaps a /models row leaves (names, limits, effort levels); seeds
|
|
42
|
+
// never replace a value the row already carries.
|
|
75
43
|
export function decorateModelRows(rows, items) {
|
|
76
44
|
const display = new Map();
|
|
77
45
|
for (const item of items || []) {
|
|
@@ -89,7 +57,7 @@ export function decorateModelRows(rows, items) {
|
|
|
89
57
|
...(seed && row.reasoning === undefined ? { reasoning: seed.reasoning } : {}),
|
|
90
58
|
...(seed && row.vision === undefined ? { vision: seed.vision } : {}),
|
|
91
59
|
...(seed && row.inputModalities === undefined ? { inputModalities: seed.inputModalities } : {}),
|
|
92
|
-
reasoningLevels: [...MIMO_EFFORT_LEVELS],
|
|
60
|
+
reasoningLevels: row.reasoningLevels?.length ? row.reasoningLevels : [...MIMO_EFFORT_LEVELS],
|
|
93
61
|
reasoningContentField: 'reasoning_content',
|
|
94
62
|
};
|
|
95
63
|
});
|
|
@@ -8,6 +8,7 @@ import { getPluginData } from '../config.mjs';
|
|
|
8
8
|
import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
|
|
9
9
|
import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
|
|
10
10
|
import { accountIdentityFields, fileMtimeMs, scrubOAuthSecrets } from './lib/oauth-token-utils.mjs';
|
|
11
|
+
import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
|
|
11
12
|
|
|
12
13
|
const DEFAULT_PLATFORM_ORIGIN = 'https://platform.xiaomimimo.com';
|
|
13
14
|
const DEFAULT_BASE_URL = 'https://api.xiaomimimo.com/v1';
|
|
@@ -98,6 +99,7 @@ export function getKeyName() {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
export function loadTokens() {
|
|
102
|
+
if (modelProxyClientMode()) return null;
|
|
101
103
|
const path = getOwnTokenPath();
|
|
102
104
|
if (!existsSync(path)) return null;
|
|
103
105
|
try {
|
|
@@ -10,6 +10,7 @@ import { noteErrorUsage } from './lib/note-error-usage.mjs';
|
|
|
10
10
|
import { fileMtimeMs, scrubOAuthSecrets } from './lib/oauth-token-utils.mjs';
|
|
11
11
|
import { OpenAICompatProvider, preloadOpenAICompatRuntime } from './openai-compat.mjs';
|
|
12
12
|
import { fetchCompatModelItems, listCompatModels } from './openai-compat-models.mjs';
|
|
13
|
+
import { makeModelCache } from './model-cache.mjs';
|
|
13
14
|
import {
|
|
14
15
|
SOURCE_HEADER,
|
|
15
16
|
ensureTokenPlanBaseUrl,
|
|
@@ -29,6 +30,23 @@ import {
|
|
|
29
30
|
|
|
30
31
|
const isUnauthorized = (err) => Number(err?.httpStatus || err?.status) === 401;
|
|
31
32
|
|
|
33
|
+
// The discovered roster (what /models returned, never seed values) is
|
|
34
|
+
// persisted so the shared sync lookup sees what listModels found.
|
|
35
|
+
const mimoModelCache = makeModelCache({ fileName: 'mimo-oauth-models.json', ttlMs: 24 * 60 * 60 * 1000, version: 1 });
|
|
36
|
+
|
|
37
|
+
function rosterRow(item) {
|
|
38
|
+
const contextWindow = Number(item.context_window ?? item.max_context_window ?? item.context_length ?? 0);
|
|
39
|
+
const outputTokens = Number(item.max_output_tokens ?? item.output_tokens ?? 0);
|
|
40
|
+
const name = typeof item.display_name === 'string' ? item.display_name.trim() : '';
|
|
41
|
+
return {
|
|
42
|
+
id: item.id,
|
|
43
|
+
provider: 'mimo-oauth',
|
|
44
|
+
...(name ? { name } : {}),
|
|
45
|
+
...(contextWindow > 0 ? { contextWindow } : {}),
|
|
46
|
+
...(outputTokens > 0 ? { outputTokens } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
// Strip the key from an error (message plus any echoed gateway body).
|
|
33
51
|
function scrubErrorSecrets(err, apiKey) {
|
|
34
52
|
if (!err || typeof err !== 'object' || !apiKey) return err;
|
|
@@ -101,6 +119,7 @@ export class MimoOAuthProvider {
|
|
|
101
119
|
_inner = null;
|
|
102
120
|
_innerKey = null;
|
|
103
121
|
_rosterItems = [];
|
|
122
|
+
_rosterSeq = 0;
|
|
104
123
|
_defaultModelKey = null;
|
|
105
124
|
_defaultModel = null;
|
|
106
125
|
|
|
@@ -143,7 +162,7 @@ export class MimoOAuthProvider {
|
|
|
143
162
|
clientOptions: { organization: null, project: null },
|
|
144
163
|
extraHeaders: { ...SOURCE_HEADER, Accept: 'application/json' },
|
|
145
164
|
});
|
|
146
|
-
inner._fetchModelItems = (args) => this._fetchRoster(inner, args);
|
|
165
|
+
inner._fetchModelItems = async (args) => (await this._fetchRoster(inner, args)).items;
|
|
147
166
|
redactClientErrors(inner.client, apiKey);
|
|
148
167
|
this._inner = inner;
|
|
149
168
|
this._innerKey = key;
|
|
@@ -153,15 +172,28 @@ export class MimoOAuthProvider {
|
|
|
153
172
|
// Chat models from /models; a failed or empty listing falls back to the local
|
|
154
173
|
// seed roster. Only a rejected key is surfaced.
|
|
155
174
|
async _fetchRoster(inner, args) {
|
|
175
|
+
// Only the latest request, fetched with the still-active key, publishes.
|
|
176
|
+
const apiKey = inner.apiKey;
|
|
177
|
+
const seq = ++this._rosterSeq;
|
|
178
|
+
const publish = mimoModelCache.beginSave(() => loadTokens()?.apiKey === apiKey);
|
|
156
179
|
let items = [];
|
|
157
180
|
try {
|
|
158
181
|
items = (await fetchCompatModelItems(inner, args)).filter((item) => isChatModelId(item?.id));
|
|
159
182
|
} catch (err) {
|
|
160
183
|
if (isUnauthorized(err)) throw err;
|
|
161
184
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
185
|
+
// `published` is false when disk kept a newer roster; the seed fallback
|
|
186
|
+
// publishes nothing. `_listRows` revalidates `seq` and the key before
|
|
187
|
+
// touching shared state, since enrichment runs after this returns.
|
|
188
|
+
let published = true;
|
|
189
|
+
if (items.length) published = publish(items.map(rosterRow));
|
|
190
|
+
else items = seedModelItems();
|
|
191
|
+
return { items, published, seq, apiKey };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Still the latest request, fetched with the still-active key.
|
|
195
|
+
_isCurrentRoster({ published, seq, apiKey }) {
|
|
196
|
+
return published && seq === this._rosterSeq && loadTokens()?.apiKey === apiKey;
|
|
165
197
|
}
|
|
166
198
|
|
|
167
199
|
// Shared send/list error shaping, also applied to credential setup failures.
|
|
@@ -180,19 +212,26 @@ export class MimoOAuthProvider {
|
|
|
180
212
|
// Configured model wins; otherwise pick from this credential's own roster.
|
|
181
213
|
async _pickDefaultModel(inner) {
|
|
182
214
|
if (this._defaultModel && this._defaultModelKey === this._innerKey) return this._defaultModel;
|
|
183
|
-
await this._listRows(inner);
|
|
184
|
-
this._defaultModel = chooseDefaultModel(
|
|
215
|
+
const rows = await this._listRows(inner);
|
|
216
|
+
this._defaultModel = chooseDefaultModel(rows.map((row) => row.id));
|
|
185
217
|
this._defaultModelKey = this._innerKey;
|
|
186
218
|
return this._defaultModel;
|
|
187
219
|
}
|
|
188
220
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
221
|
+
// The request keeps its own items and rows; only a request that is still
|
|
222
|
+
// current publishes them to the shared in-memory roster and metadata.
|
|
193
223
|
async _listRows(inner) {
|
|
194
|
-
|
|
195
|
-
|
|
224
|
+
let fetched = null;
|
|
225
|
+
const request = Object.create(inner);
|
|
226
|
+
request._fetchModelItems = async (args) => {
|
|
227
|
+
fetched = await this._fetchRoster(inner, args);
|
|
228
|
+
return fetched.items;
|
|
229
|
+
};
|
|
230
|
+
const rows = decorateModelRows(await listCompatModels(request, { throwOnError: true }), fetched.items);
|
|
231
|
+
if (this._isCurrentRoster(fetched)) {
|
|
232
|
+
this._rosterItems = fetched.items;
|
|
233
|
+
inner._enrichedModels = rows;
|
|
234
|
+
}
|
|
196
235
|
return rows;
|
|
197
236
|
}
|
|
198
237
|
|