mixdog 1.0.12 → 1.1.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 +75 -181
- package/package.json +4 -2
- package/scripts/build-shipped-history.mjs +131 -0
- package/scripts/lib/run-node-tests.mjs +235 -13
- package/scripts/lib/test-failure-records.mjs +5 -0
- package/scripts/lib/test-failure-summary.mjs +9 -3
- package/scripts/lib/test-spawn-ledger.mjs +19 -0
- package/scripts/prune-embedding-runtime.mjs +9 -5
- package/scripts/test-direct.mjs +16 -1
- package/src/agents/advisor/AGENT.md +38 -0
- package/src/agents/advisor/agent.json +6 -0
- package/src/agents/reviewer/AGENT.md +5 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +381 -0
- package/src/defaults/skills/goal-management/SKILL.md +11 -5
- package/src/defaults/skills/setup/SKILL.md +5 -8
- package/src/defaults/skills/setup/references/actions.md +61 -31
- package/src/defaults/skills/setup/references/surfaces.md +12 -10
- package/src/lib/rules-builder.cjs +1 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.d.mts +1 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +44 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +9 -1
- package/src/runtime/agent/orchestrator/config.mjs +102 -21
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +77 -26
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +44 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +388 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +92 -109
- package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +107 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +51 -9
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +51 -11
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +7 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +27 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +12 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -3
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +19 -35
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +0 -17
- package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +0 -3
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +0 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +18 -4
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -1
- package/src/runtime/agent/orchestrator/runtime-core/session-route-policy.mjs +16 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +2 -2
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +7 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +28 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +24 -11
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +2 -6
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +63 -3
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +1 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +18 -8
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +19 -20
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +6 -11
- package/src/runtime/agent/orchestrator/tools/builtin/file-version-test-helpers.mjs +11 -0
- package/src/runtime/agent/orchestrator/tools/builtin/file-version.mjs +13 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +8 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +25 -12
- package/src/runtime/agent/orchestrator/tools/builtin/raw-content-publication.mjs +17 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +136 -76
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +15 -17
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +4 -11
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-validation.mjs +16 -19
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +14 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +58 -8
- package/src/runtime/agent/orchestrator/tools/patch/content-guard.mjs +47 -0
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +155 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-proofs.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-snapshots.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +19 -16
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +13 -44
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/post-patch-excerpts.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +47 -7
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +72 -8
- package/src/runtime/agent/orchestrator/tools/patch/section-coalesce.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/patch/sequence/run.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +16 -4
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +35 -14
- package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +42 -4
- package/src/runtime/agent/orchestrator/tools/shell-codepage.mjs +296 -0
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +75 -24
- package/src/runtime/channels/lib/config.mjs +3 -8
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +7 -3
- package/src/{tui → runtime/channels}/lib/voice-setup.mjs +38 -22
- package/src/runtime/channels/lib/webhook/request-handler.mjs +42 -8
- package/src/runtime/computer-bridge/actions.mjs +2 -0
- package/src/runtime/effort-judge/judge-client.mjs +12 -3
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/effort-judge/previous-human-request.mjs +162 -0
- package/src/runtime/effort-judge/verified-context-input.mjs +10 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +12 -0
- package/src/runtime/memory/lib/http-router/session-routes.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +3 -2
- package/src/runtime/memory/lib/memory-embed.mjs +5 -0
- package/src/runtime/shared/agent-route-config.mjs +50 -2
- package/src/runtime/shared/bounded-file-read.mjs +166 -0
- package/src/runtime/shared/builtin-grandfathering.mjs +37 -0
- package/src/runtime/shared/config.mjs +24 -4
- package/src/runtime/shared/defaults-separation.mjs +263 -0
- package/src/runtime/shared/llm/anthropic-effort.mjs +4 -0
- package/src/runtime/shared/llm/model-catalog.mjs +23 -0
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +105 -1
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +72 -5
- package/src/runtime/shared/onnx-runtime-support.mjs +6 -0
- package/src/runtime/shared/orchestration.mjs +14 -6
- package/src/runtime/shared/projects.mjs +65 -48
- package/src/runtime/shared/provider-accounts.mjs +47 -6
- package/src/runtime/shared/quota-resume-store.mjs +255 -0
- package/src/runtime/shared/shipped-definitions.mjs +275 -0
- package/src/runtime/shared/tool-card-model.golden.json +3 -3
- package/src/runtime/shared/tool-result-summary.mjs +86 -15
- package/src/runtime/shared/transcript-metadata.mjs +2 -0
- package/src/runtime/shared/turn-review-reply.mjs +2 -0
- package/src/runtime/shared/turn-snapshot.mjs +65 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +202 -22
- package/src/session-runtime/boot/apis.mjs +19 -0
- package/src/session-runtime/boot/config.mjs +4 -0
- package/src/session-runtime/boot/facade.mjs +4 -1
- package/src/session-runtime/boot/infrastructure.mjs +21 -2
- package/src/session-runtime/boot/session.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +5 -0
- package/src/session-runtime/config-lifecycle/config-writers.mjs +3 -0
- package/src/session-runtime/config-lifecycle.mjs +31 -6
- package/src/session-runtime/goal-facade-api.mjs +4 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +28 -6
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +3 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -1
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +6 -0
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +14 -2
- package/src/session-runtime/media-api.mjs +32 -22
- package/src/session-runtime/model-recency.mjs +14 -0
- package/src/session-runtime/model-route/route-tuning.mjs +3 -5
- package/src/session-runtime/model-route/set-route.mjs +49 -21
- package/src/session-runtime/model-route-api.mjs +2 -1
- package/src/session-runtime/notification-bus.mjs +31 -6
- package/src/session-runtime/provider-auth/login.mjs +6 -0
- package/src/session-runtime/provider-models/catalog-load.mjs +3 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +15 -14
- package/src/session-runtime/quick-model-rows/model-meta.mjs +26 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +40 -14
- package/src/session-runtime/resource-api.mjs +8 -1
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/runtime-facade.mjs +20 -1
- package/src/session-runtime/runtime-review-api.mjs +12 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +4 -0
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +10 -15
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +2 -0
- package/src/session-runtime/services/channel-admin.mjs +87 -13
- package/src/session-runtime/services/provider-admin.mjs +256 -63
- package/src/session-runtime/services/seeds.mjs +73 -142
- package/src/session-runtime/services/usage-dashboard-model.mjs +29 -0
- package/src/session-runtime/services/usage-dashboard.mjs +7 -0
- package/src/session-runtime/session-address.mjs +125 -0
- package/src/session-runtime/session-create.mjs +57 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +8 -0
- package/src/session-runtime/settings-compaction-api.mjs +13 -0
- package/src/session-runtime/setup-tool/executor.mjs +92 -40
- package/src/session-runtime/setup-tool/extended-actions.mjs +64 -5
- package/src/session-runtime/setup-tool/settings-contract.mjs +44 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +12 -7
- package/src/session-runtime/tool-surface.mjs +16 -9
- package/src/session-runtime/turn/session-ops.mjs +3 -0
- package/src/session-runtime/turn/share-ops.mjs +4 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +6 -0
- package/src/session-runtime/usage-stats-api.mjs +15 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +31 -5
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +4 -0
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +27 -7
- package/src/standalone/daemon-boot-coordinator.mjs +10 -0
- package/src/standalone/daemon-drain-policy.mjs +9 -0
- package/src/standalone/daemon-stored-session-views.mjs +7 -8
- package/src/standalone/daemon.mjs +21 -5
- package/src/standalone/local-session-runtime.mjs +11 -0
- package/src/standalone/session-client.mjs +52 -7
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +2 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +2 -0
- package/src/standalone/session-service/entries/session-loads.mjs +9 -0
- package/src/standalone/session-service/projection/revisions.mjs +28 -4
- package/src/standalone/session-service/projection.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +140 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +2 -2
- package/src/standalone/session-service/session-calls.mjs +10 -2
- package/src/standalone/session-service/stored-reader.mjs +26 -1
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-transport/routes.mjs +2 -0
- package/src/tui/app/doctor.mjs +7 -11
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +3 -5
- package/src/tui/app/settings-picker/settings-rows.mjs +4 -6
- package/src/tui/app/settings-picker/settings-toggles/store-toggles.mjs +1 -1
- package/src/tui/app/slash-dispatch/route-commands.mjs +3 -2
- package/src/tui/components/ContextPanel.jsx +5 -1
- package/src/tui/components/Spinner.jsx +34 -319
- package/src/tui/components/SpinnerByline.jsx +256 -0
- package/src/tui/components/use-spinner-progress.mjs +113 -0
- package/src/tui/dist/index.mjs +15540 -14452
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +13 -1
- package/src/tui/session/agent-job-feed/notification-router.mjs +3 -0
- package/src/tui/session/agent-job-feed/pending-resume.mjs +14 -1
- package/src/tui/session/agent-job-feed.mjs +4 -0
- package/src/tui/session/context-state.mjs +13 -1
- package/src/tui/session/initial-state.mjs +6 -0
- package/src/tui/session/quota-resume.mjs +263 -0
- package/src/tui/session/session-api/intake/abort.mjs +39 -35
- package/src/tui/session/session-api/intake/submission.mjs +13 -21
- package/src/tui/session/session-api/intake.mjs +3 -3
- package/src/tui/session/session-api/integrations.mjs +5 -1
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -2
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +4 -0
- package/src/tui/session/session-api/lifecycle.mjs +71 -2
- package/src/tui/session/session-api/media.mjs +2 -2
- package/src/tui/session/session-api/quota-resume.mjs +21 -0
- package/src/tui/session/session-api/routes.mjs +2 -0
- package/src/tui/session/session-api/settings.mjs +1 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +14 -6
- package/src/tui/session/session-flow/drain.mjs +52 -5
- package/src/tui/session/session-flow/queue/take-entries.mjs +2 -3
- package/src/tui/session/session-flow/queue.mjs +3 -0
- package/src/tui/session/session-flow/steering.mjs +11 -0
- package/src/tui/session/session-flow.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +7 -2
- package/src/tui/session/transcript-intake.mjs +1 -0
- package/src/tui/session/turn.mjs +40 -3
- package/src/vendor/statusline/bin/statusline-route.mjs +3 -2
|
@@ -36,8 +36,11 @@ An attached Desktop or TUI may navigate and return `opened:true`.
|
|
|
36
36
|
- Settings → General: profile, Web Search exposure, display language, theme,
|
|
37
37
|
side panels, and notifications.
|
|
38
38
|
- Settings → Context: auto-compact and auto-clear.
|
|
39
|
-
- Settings → Output style: output style selection
|
|
40
|
-
|
|
39
|
+
- Settings → Output style: output style selection, plus the desktop-only
|
|
40
|
+
"Expand tool activity" choice (collapsed / commands and edits / everything),
|
|
41
|
+
saved per device; Ctrl+O toggles everything open for the current window.
|
|
42
|
+
- Settings → Providers: API-key, OAuth, custom provider registration, local
|
|
43
|
+
providers, and usage sign-in.
|
|
41
44
|
- Extensions → Plugin → Git & GitHub: Git/GitHub CLI installation and connection.
|
|
42
45
|
- Settings → Connection: web-app pairing and linked devices.
|
|
43
46
|
- Settings → System: update, keep-awake, run in background after the window
|
|
@@ -53,11 +56,12 @@ the user to Desktop.
|
|
|
53
56
|
|
|
54
57
|
## Desktop-backed settings
|
|
55
58
|
|
|
56
|
-
Setup can read and change Desktop appearance
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
Setup can read and change Desktop appearance (including markdown default
|
|
60
|
+
view, link previews, and tool activity expansion), keep-awake, run in
|
|
61
|
+
background, turn-completion notifications, notification volume (Windows),
|
|
62
|
+
usage pin, activity rail pins, Computer observe-only, Browser/Computer
|
|
63
|
+
installation and toggles, Projects, and linked-device revocation through the
|
|
64
|
+
attached Desktop. A receipt identifies `scope:desktop-host`.
|
|
61
65
|
|
|
62
66
|
Computer observe-only and background Memory cycles (`set_recap_enabled`)
|
|
63
67
|
have no settings control in either app; setup is their only supported route. Usage pin is the pin toggle on the activity rail usage card.
|
|
@@ -74,12 +78,10 @@ The setup tool does not mutate:
|
|
|
74
78
|
- web-app pairing credentials and OS permission dialogs;
|
|
75
79
|
- TUI-local theme palettes (use `/theme`; Desktop themes are separate).
|
|
76
80
|
|
|
77
|
-
Use `open` where a target exists, then let the user act.
|
|
78
|
-
missing or denied operation by editing storage or switching surfaces.
|
|
81
|
+
Use `open` where a target exists, then let the user act.
|
|
79
82
|
|
|
80
83
|
## TUI notes
|
|
81
84
|
|
|
82
85
|
The TUI settings hub exposes system shell in addition to common settings.
|
|
83
86
|
Desktop-host actions need the Desktop app; opening the TUI is not a substitute.
|
|
84
|
-
Use the schema and `status capabilities` as the supported API boundary.
|
|
85
87
|
|
|
@@ -366,7 +366,7 @@ function buildLeadRoleContent({ PLUGIN_ROOT, includeLeadBrief = true }) {
|
|
|
366
366
|
const RULES_DIR = path.join(PLUGIN_ROOT, 'rules');
|
|
367
367
|
const lead = readOptional(path.join(RULES_DIR, 'lead', 'LEAD.md'));
|
|
368
368
|
if (!lead) return '';
|
|
369
|
-
// Delegation-free
|
|
369
|
+
// Delegation-free sessions (headless, no delegatable agents) never expose the `agent` tool,
|
|
370
370
|
// so Lead guidance marked `<!-- tools: agent -->` (briefing, completion
|
|
371
371
|
// notifications) is dropped there and kept verbatim for delegating workflows.
|
|
372
372
|
return omitToolRoutes(lead, includeLeadBrief ? [] : ['agent']);
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
* Non-breakpoint providers:
|
|
31
31
|
* - OpenAI (public): prompt_cache_key plus model-specific retention when
|
|
32
32
|
* response storage is enabled (30m cache options or 24h retention).
|
|
33
|
-
* - OpenAI OAuth: prompt_cache_key only
|
|
33
|
+
* - OpenAI OAuth: prompt_cache_key only; the Codex backend keeps the
|
|
34
|
+
* prompt cache at least 30 minutes on GPT-5.6+ models, refreshed on use
|
|
35
|
+
* (the openai-oauth auto-clear window matches it)
|
|
34
36
|
* - Gemini: provider-managed explicit cachedContents with 5m default TTL, plus
|
|
35
37
|
* implicit caching as a fallback when the prefix is below cache minimums.
|
|
36
38
|
* - xAI: conversation routing for chat; Responses omits prompt_cache_key
|
|
@@ -76,6 +76,8 @@ export function prepareAgentSession({
|
|
|
76
76
|
schemaAllowedTools,
|
|
77
77
|
sessionId,
|
|
78
78
|
mcpScopeId,
|
|
79
|
+
contextPercent,
|
|
80
|
+
selectedContextWindow,
|
|
79
81
|
}) {
|
|
80
82
|
const effectivePermission = resolveAgentSessionPermission(agent, permission);
|
|
81
83
|
// Pass cwd through verbatim — null is the fixed agent sentinel meaning
|
|
@@ -112,6 +114,8 @@ export function prepareAgentSession({
|
|
|
112
114
|
compaction: compaction || undefined,
|
|
113
115
|
mcpScopeId: mcpScopeId || null,
|
|
114
116
|
};
|
|
117
|
+
if (contextPercent) sessionOpts.contextPercent = contextPercent;
|
|
118
|
+
if (selectedContextWindow) sessionOpts.selectedContextWindow = selectedContextWindow;
|
|
115
119
|
if (agentTag) sessionOpts.agentTag = agentTag;
|
|
116
120
|
if (effectivePermission) sessionOpts.permission = effectivePermission;
|
|
117
121
|
if (permissionMode) sessionOpts.permissionMode = permissionMode;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function withDefaultPresets(stored: unknown): unknown[];
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// Preset normalization and identity are independent of config storage.
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
|
|
2
4
|
const ANTHROPIC_FAMILY_MODEL = Object.freeze({
|
|
3
5
|
opus: 'claude-opus-4-8',
|
|
4
6
|
sonnet: 'claude-sonnet-4-6',
|
|
@@ -64,6 +66,48 @@ export const DEFAULT_PRESETS = Object.freeze([
|
|
|
64
66
|
}),
|
|
65
67
|
]);
|
|
66
68
|
|
|
69
|
+
const DEFAULT_PRESET_BY_ID = new Map(DEFAULT_PRESETS.map((preset) => [preset.id, preset]));
|
|
70
|
+
|
|
71
|
+
/** Effective presets: the stored entries in their stored order, then the
|
|
72
|
+
* shipped presets missing from storage in shipped order. */
|
|
73
|
+
export function withDefaultPresets(stored) {
|
|
74
|
+
const list = Array.isArray(stored) ? stored : [];
|
|
75
|
+
const present = new Set(list.map(presetKey));
|
|
76
|
+
return [...list, ...DEFAULT_PRESETS.filter((preset) => !present.has(preset.id)).map((preset) => ({ ...preset }))];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Stored form of `presets`: only entries that differ from the shipped default of the same id. */
|
|
80
|
+
export function presetsDelta(presets) {
|
|
81
|
+
return (Array.isArray(presets) ? presets : []).filter((preset) => {
|
|
82
|
+
const shipped = DEFAULT_PRESET_BY_ID.get(presetKey(preset));
|
|
83
|
+
return !shipped || !isDeepStrictEqual(normalizePreset(preset), { ...shipped });
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Stored form of `maintenance`: only slots that differ from the shipped route. */
|
|
88
|
+
export function maintenanceDelta(maintenance) {
|
|
89
|
+
const source = maintenance && typeof maintenance === 'object' ? maintenance : {};
|
|
90
|
+
return Object.fromEntries(
|
|
91
|
+
Object.entries(source).filter(([slot, route]) => !isDeepStrictEqual(route, DEFAULT_MAINTENANCE[slot]))
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Drop shipped presets and maintenance routes that equal their default; loadConfig restores them. */
|
|
96
|
+
export function stripStoredRouteDefaults(agent) {
|
|
97
|
+
const next = { ...agent };
|
|
98
|
+
if (Object.hasOwn(next, 'presets')) {
|
|
99
|
+
const presets = presetsDelta(next.presets);
|
|
100
|
+
if (presets.length) next.presets = presets;
|
|
101
|
+
else delete next.presets;
|
|
102
|
+
}
|
|
103
|
+
if (Object.hasOwn(next, 'maintenance')) {
|
|
104
|
+
const maintenance = maintenanceDelta(next.maintenance);
|
|
105
|
+
if (Object.keys(maintenance).length) next.maintenance = maintenance;
|
|
106
|
+
else delete next.maintenance;
|
|
107
|
+
}
|
|
108
|
+
return next;
|
|
109
|
+
}
|
|
110
|
+
|
|
67
111
|
const AGENT_PROVIDER_ALIASES = Object.freeze({
|
|
68
112
|
'openai-api': 'openai',
|
|
69
113
|
'gemini-api': 'gemini',
|
|
@@ -50,9 +50,17 @@ export function mergeStoredProviders({ raw, defaults, includeSecrets }) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
// Custom providers: the keychain account is the generated custom-<uuid> id.
|
|
53
|
+
// A keyless (auth 'none') custom provider never carries a key, not even a
|
|
54
|
+
// hand-edited plaintext one.
|
|
55
|
+
for (const [name, val] of Object.entries(mergedProviders)) {
|
|
56
|
+
if (val?.type === 'custom' && val.auth === 'none' && Object.hasOwn(val, 'apiKey')) {
|
|
57
|
+
const { apiKey: _dropped, ...rest } = val;
|
|
58
|
+
mergedProviders[name] = rest;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
53
61
|
if (includeSecrets) {
|
|
54
62
|
for (const [name, val] of Object.entries(mergedProviders)) {
|
|
55
|
-
if (val?.type !== 'custom' || !name.startsWith('custom-')) continue;
|
|
63
|
+
if (val?.type !== 'custom' || !name.startsWith('custom-') || val.auth === 'none') continue;
|
|
56
64
|
const kc = getAgentApiKey(name);
|
|
57
65
|
if (kc) mergedProviders[name] = { ...val, apiKey: kc, enabled: raw.providers?.[name]?.enabled !== false };
|
|
58
66
|
}
|
|
@@ -4,9 +4,29 @@ import { mergeStoredProviders } from './config-providers-merge.mjs';
|
|
|
4
4
|
import { normalizeExtensionScopes } from '../../shared/extension-scopes.mjs';
|
|
5
5
|
import { applyConfigPatch, diffConfig } from '../../shared/config-patch.mjs';
|
|
6
6
|
import { normalizeWorkflowSelection } from '../../shared/orchestration.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_DISABLED_AGENT_ROSTER,
|
|
9
|
+
canonicalizeAgentRouteStorage,
|
|
10
|
+
disabledAgentDelta,
|
|
11
|
+
disabledAgentIds,
|
|
12
|
+
effectiveDisabledAgents,
|
|
13
|
+
withEffectiveDisabledAgents,
|
|
14
|
+
withStoredDisabledAgents,
|
|
15
|
+
} from '../../shared/agent-route-config.mjs';
|
|
16
|
+
import {
|
|
17
|
+
DEFAULTS_VERSION,
|
|
18
|
+
ensureDefaultsSeparated,
|
|
19
|
+
storedDefaultsVersion,
|
|
20
|
+
stripAgentSectionDefaults,
|
|
21
|
+
} from '../../shared/defaults-separation.mjs';
|
|
8
22
|
import profileConfig from '../../shared/profile-config.cjs';
|
|
9
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
DEFAULT_MAINTENANCE,
|
|
25
|
+
DEFAULT_PRESETS,
|
|
26
|
+
normalizePreset,
|
|
27
|
+
stripStoredRouteDefaults,
|
|
28
|
+
withDefaultPresets,
|
|
29
|
+
} from './config-presets.mjs';
|
|
10
30
|
import {
|
|
11
31
|
agentConfigStorageNeedsMigration,
|
|
12
32
|
canonicalizeAgentStorage,
|
|
@@ -100,11 +120,10 @@ export function buildDefaultConfig(options = {}) {
|
|
|
100
120
|
providers['mixdog-local'] = { enabled: false };
|
|
101
121
|
return {
|
|
102
122
|
providers,
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
disabledAgents: [...DEFAULT_DISABLED_AGENT_IDS, 'maintainer'],
|
|
123
|
+
// Effective roster; the file stores only the user's delta against it.
|
|
124
|
+
disabledAgents: [...DEFAULT_DISABLED_AGENT_ROSTER],
|
|
106
125
|
workflow: { active: 'default' },
|
|
107
|
-
orchestrationMode: '
|
|
126
|
+
orchestrationMode: 'balanced',
|
|
108
127
|
};
|
|
109
128
|
}
|
|
110
129
|
|
|
@@ -170,9 +189,17 @@ function normalizeRecapConfig(rawRecap) {
|
|
|
170
189
|
|
|
171
190
|
export function loadConfig(options = {}) {
|
|
172
191
|
const includeSecrets = options.secrets !== false;
|
|
192
|
+
try {
|
|
193
|
+
ensureDefaultsSeparated({ stripRouteDefaults: separateStoredAgent });
|
|
194
|
+
} catch (err) {
|
|
195
|
+
process.stderr.write(`[config] defaults separation failed: ${err?.message}\n`);
|
|
196
|
+
}
|
|
173
197
|
const sectionRaw = readSection('agent');
|
|
174
198
|
if (hasKeys(sectionRaw)) {
|
|
175
199
|
try {
|
|
200
|
+
// A file that could not be separated still holds the legacy form:
|
|
201
|
+
// before version 1 `disabledAgents` is the full list, not a delta.
|
|
202
|
+
const legacyDisabledAgents = storedDefaultsVersion() < 1;
|
|
176
203
|
let raw = sectionRaw;
|
|
177
204
|
const storageNeedsMigration = agentConfigStorageNeedsMigration(raw);
|
|
178
205
|
raw = canonicalizeAgentStorage(raw);
|
|
@@ -206,7 +233,7 @@ export function loadConfig(options = {}) {
|
|
|
206
233
|
const loaded = canonicalizeAgentRouteStorage({
|
|
207
234
|
providers: mergedProviders,
|
|
208
235
|
mcpServers,
|
|
209
|
-
presets: normalizedPresets,
|
|
236
|
+
presets: withDefaultPresets(normalizedPresets),
|
|
210
237
|
default: raw.default || null,
|
|
211
238
|
maintenance: { ...DEFAULT_MAINTENANCE, ...normalizedMaint },
|
|
212
239
|
workflowRoutes,
|
|
@@ -214,9 +241,10 @@ export function loadConfig(options = {}) {
|
|
|
214
241
|
modelSettings,
|
|
215
242
|
onboarding: raw.onboarding && typeof raw.onboarding === 'object' ? raw.onboarding : {},
|
|
216
243
|
agents: raw.agents && typeof raw.agents === 'object' ? raw.agents : {},
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
disabledAgents:
|
|
244
|
+
// Effective "off" roster = code defaults + the stored user delta.
|
|
245
|
+
// canonicalizeAgentRouteStorage drops it when empty.
|
|
246
|
+
disabledAgents: legacyDisabledAgents ? disabledAgentIds(raw) : effectiveDisabledAgents(raw),
|
|
247
|
+
...(raw.memoryTools && typeof raw.memoryTools === 'object' ? { memoryTools: { ...raw.memoryTools } } : {}),
|
|
220
248
|
...normalizeWorkflowSelection(raw),
|
|
221
249
|
profile: normalizeProfileConfig(raw.profile),
|
|
222
250
|
skills: normalizeSkillsConfig(raw.skills),
|
|
@@ -241,7 +269,7 @@ export function loadConfig(options = {}) {
|
|
|
241
269
|
// Retired cross-section fields are no longer migrated by
|
|
242
270
|
// the shared config layer. Normalize only the locked
|
|
243
271
|
// current section; stale reads must not undo deletions.
|
|
244
|
-
persistAgentConfig(canonicalizeAgentStorage);
|
|
272
|
+
persistAgentConfig((current) => storedAgentForm(canonicalizeAgentStorage(current)));
|
|
245
273
|
} catch (err) {
|
|
246
274
|
process.stderr.write(`[config] persist canonical agent config failed: ${err?.message}\n`);
|
|
247
275
|
}
|
|
@@ -263,7 +291,7 @@ export function loadConfig(options = {}) {
|
|
|
263
291
|
onboarding: {},
|
|
264
292
|
agents: {},
|
|
265
293
|
workflow: { active: 'default' },
|
|
266
|
-
orchestrationMode: '
|
|
294
|
+
orchestrationMode: 'balanced',
|
|
267
295
|
profile: normalizeProfileConfig(null),
|
|
268
296
|
skills: normalizeSkillsConfig(null),
|
|
269
297
|
extensionScopes: normalizeExtensionScopes(null),
|
|
@@ -383,28 +411,81 @@ function buildAgentSaveBuilder(config) {
|
|
|
383
411
|
// Developer options (developer-options.mjs); an empty set stays absent.
|
|
384
412
|
if (Object.keys(developer).length) next.developer = developer;
|
|
385
413
|
else if (Object.hasOwn(config, 'developer')) delete next.developer;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
414
|
+
// Only the user's delta against the default roster is stored.
|
|
415
|
+
delete next.disabledAgents;
|
|
416
|
+
delete next.enabledAgents;
|
|
417
|
+
// An unseparated file keeps the legacy full list until the pass succeeds.
|
|
418
|
+
if (storedDefaultsVersion() < 1) {
|
|
419
|
+
if (canonicalRoutes.disabledAgents?.length) next.disabledAgents = canonicalRoutes.disabledAgents;
|
|
420
|
+
} else Object.assign(next, disabledAgentDelta(canonicalRoutes.disabledAgents));
|
|
421
|
+
if (config.memoryTools && typeof config.memoryTools === 'object') next.memoryTools = { ...config.memoryTools };
|
|
422
|
+
else delete next.memoryTools;
|
|
391
423
|
delete next.workflowRoutes;
|
|
392
|
-
|
|
424
|
+
// Defaults live in code: values equal to the current default are not stored.
|
|
425
|
+
return storedAgentForm(removeRetiredAgentFields(next));
|
|
393
426
|
};
|
|
394
427
|
}
|
|
428
|
+
|
|
429
|
+
/** A numeric `default` is a preset index; store the preset id it points at (index into the effective list). */
|
|
430
|
+
function resolveNumericDefault(agent) {
|
|
431
|
+
const value = agent?.default;
|
|
432
|
+
// The loader reads a falsy default (including 0) as "none".
|
|
433
|
+
if (!value || !(typeof value === 'number' || /^\d+$/.test(String(value)))) return agent;
|
|
434
|
+
const stored = (Array.isArray(agent.presets) ? agent.presets : [])
|
|
435
|
+
.map((preset) => normalizePreset(preset))
|
|
436
|
+
.filter(Boolean)
|
|
437
|
+
.filter((preset) => preset.id !== 'workflow-search');
|
|
438
|
+
const { presets } = canonicalizeAgentRouteStorage({ presets: withDefaultPresets(stored) });
|
|
439
|
+
const preset = presets[Number(value)];
|
|
440
|
+
return preset ? { ...agent, default: preset.id } : agent;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** The agent section as stored: defaults dropped. Route defaults go only once the file is separated. */
|
|
444
|
+
function storedAgentForm(agent) {
|
|
445
|
+
const stripped = stripAgentSectionDefaults(resolveNumericDefault(agent));
|
|
446
|
+
return storedDefaultsVersion() >= DEFAULTS_VERSION ? stripStoredRouteDefaults(stripped) : stripped;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Separation pass: normalize the compared sections first, then drop every default. */
|
|
450
|
+
function separateStoredAgent(agent) {
|
|
451
|
+
const full = canonicalizeAgentStorage(agent);
|
|
452
|
+
const next = { ...agent };
|
|
453
|
+
for (const key of ['autoClear', 'compaction', 'modules']) {
|
|
454
|
+
if (!Object.hasOwn(agent, key)) continue;
|
|
455
|
+
if (full[key] === undefined) delete next[key];
|
|
456
|
+
else next[key] = full[key];
|
|
457
|
+
}
|
|
458
|
+
return stripStoredRouteDefaults(stripAgentSectionDefaults(resolveNumericDefault(next)));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Patch the stored section; unseparated data keeps its form, a numeric default becomes an id first. */
|
|
462
|
+
function applyStoredPatch(current, changes) {
|
|
463
|
+
const base = resolveNumericDefault(current);
|
|
464
|
+
if (storedDefaultsVersion() >= 1) return applyConfigPatch(base, changes);
|
|
465
|
+
const delta = withStoredDisabledAgents({ ...base, disabledAgents: disabledAgentIds(base) });
|
|
466
|
+
return withEffectiveDisabledAgents(applyConfigPatch(delta, changes));
|
|
467
|
+
}
|
|
395
468
|
// Diff the persisted schema, not runtime defaults, aliases, or secret overlays.
|
|
469
|
+
// Patches always speak the delta domain; an unseparated file's full list is
|
|
470
|
+
// converted here and back in applyStoredPatch.
|
|
471
|
+
function patchForm(agent) {
|
|
472
|
+
if (storedDefaultsVersion() >= 1) return agent;
|
|
473
|
+
const { disabledAgents, ...rest } = agent;
|
|
474
|
+
return { ...rest, ...disabledAgentDelta(disabledAgents || []) };
|
|
475
|
+
}
|
|
476
|
+
|
|
396
477
|
export function createConfigPatch(before, after) {
|
|
397
|
-
return diffConfig(buildAgentSaveBuilder(before)({}), buildAgentSaveBuilder(after)({}));
|
|
478
|
+
return diffConfig(patchForm(buildAgentSaveBuilder(before)({})), patchForm(buildAgentSaveBuilder(after)({})));
|
|
398
479
|
}
|
|
399
480
|
|
|
400
481
|
export function saveConfigPatch(changes) {
|
|
401
482
|
if (!changes.length) return;
|
|
402
|
-
persistAgentConfig((current) =>
|
|
483
|
+
persistAgentConfig((current) => applyStoredPatch(current, changes));
|
|
403
484
|
}
|
|
404
485
|
|
|
405
486
|
export async function saveConfigPatchAsync(changes) {
|
|
406
487
|
if (!changes.length) return;
|
|
407
|
-
await persistAgentConfigAsync((current) =>
|
|
488
|
+
await persistAgentConfigAsync((current) => applyStoredPatch(current, changes));
|
|
408
489
|
}
|
|
409
490
|
|
|
410
491
|
// Whole-section replacement is reserved for explicit full saves. Read/edit/save
|
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
changeProviderAccounts,
|
|
7
7
|
blockProviderAccount,
|
|
8
8
|
providerAccountExhausted,
|
|
9
|
+
providerAccountsResetAt,
|
|
9
10
|
} from '../../../shared/provider-accounts.mjs';
|
|
10
11
|
import { withProviderAccount, hasExplicitProviderAuthBinding } from '../../../shared/provider-auth-binding.mjs';
|
|
11
12
|
import { retryAfterMsFromError, isExplicitUserAbortError, typedErrorCode } from './retry-classifier.mjs';
|
|
12
|
-
import { fetchOAuthUsageSnapshot } from './oauth-usage.mjs';
|
|
13
|
+
import { fetchOAuthUsageSnapshot, readCachedOAuthUsageSnapshot } from './oauth-usage.mjs';
|
|
13
14
|
|
|
14
15
|
// Only an explicit quota refusal advances the roster; a generic 429, auth
|
|
15
16
|
// refusal, policy denial or transport failure retains its existing recovery.
|
|
@@ -49,7 +50,7 @@ export function resetAccountProbePacing() {
|
|
|
49
50
|
async function probeRefusedAccounts(providerName, pool, model, account) {
|
|
50
51
|
const now = Date.now();
|
|
51
52
|
const due = pool.accounts
|
|
52
|
-
.filter((row) => providerAccountExhausted(row, now, model))
|
|
53
|
+
.filter((row) => providerAccountExhausted(row, now, model, { useCredits: pool.useCredits === true }))
|
|
53
54
|
.filter((row) => now - (lastProbeAt.get(`${providerName}:${row.id}`) || 0) >= PROBE_INTERVAL_MS)
|
|
54
55
|
.sort((a, b) => (a.usage?.checkedAt || 0) - (b.usage?.checkedAt || 0));
|
|
55
56
|
if (!due.length) return false;
|
|
@@ -64,12 +65,29 @@ async function probeRefusedAccounts(providerName, pool, model, account) {
|
|
|
64
65
|
return true;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
function accountsExhaustedError() {
|
|
68
|
-
const error = new Error(
|
|
68
|
+
function accountsExhaustedError(useCredits) {
|
|
69
|
+
const error = new Error(
|
|
70
|
+
useCredits
|
|
71
|
+
? 'All connected accounts have exhausted their quota. Check account usage and reset times.'
|
|
72
|
+
: 'The plan limit is reached and credit use is off. Turn on credit use or wait for the limit to reset.'
|
|
73
|
+
);
|
|
69
74
|
error.code = 'provider_accounts_exhausted';
|
|
70
75
|
return error;
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
// Structured proof that the send ended because no account can serve `model`
|
|
79
|
+
// (not merely because a generic 429 or a stopped loop surfaced an error).
|
|
80
|
+
function quotaExhausted(error, providerName, model, rows, options = {}) {
|
|
81
|
+
if (error && typeof error === 'object') {
|
|
82
|
+
error.quotaExhausted = {
|
|
83
|
+
provider: providerName,
|
|
84
|
+
model: String(model || ''),
|
|
85
|
+
resetAt: providerAccountsResetAt(rows, Date.now(), model, options),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return error;
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
// The send bound to one account: output callbacks are observed even when the
|
|
74
92
|
// caller does not subscribe, and account-bound connection/delta state stays
|
|
75
93
|
// apart without changing the visible Mixdog session, so no stale server state
|
|
@@ -114,18 +132,22 @@ function commitFallbackAccount(providerName, pool, row) {
|
|
|
114
132
|
async function accountQuotaExhausted(providerName, row, model, error, account) {
|
|
115
133
|
const status = Number(error?.status || error?.httpStatus || error?.response?.status || 0);
|
|
116
134
|
const delay = retryAfterMsFromError(error);
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
const typed = isAccountQuotaError(error);
|
|
136
|
+
let exhausted = typed || (status === 429 && delay > QUOTA_WINDOW_RETRY_AFTER_MS);
|
|
137
|
+
// Proof of a spent quota: a typed quota refusal or an exhausted usage window
|
|
138
|
+
// (a long Retry-After alone only rotates the roster, it proves nothing).
|
|
139
|
+
let evidence = typed;
|
|
140
|
+
if (!typed && status === 429) {
|
|
119
141
|
await fetchOAuthUsageSnapshot({ provider: providerName, accountId: row.id }, account(row.id), () => {}, {
|
|
120
142
|
force: true,
|
|
121
143
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
);
|
|
144
|
+
const pool = readProviderAccountPool(providerName);
|
|
145
|
+
const entry = pool.accounts.find((item) => item.id === row.id);
|
|
146
|
+
const options = { useCredits: pool.useCredits === true };
|
|
147
|
+
evidence = providerAccountExhausted({ ...entry, blockedUntil: 0 }, Date.now(), model, options);
|
|
148
|
+
exhausted = exhausted || evidence || providerAccountExhausted(entry, Date.now(), model, options);
|
|
127
149
|
}
|
|
128
|
-
return { exhausted, delay };
|
|
150
|
+
return { exhausted, delay, evidence };
|
|
129
151
|
}
|
|
130
152
|
|
|
131
153
|
// The pool's send: walks the eligible accounts until one answers, blocking
|
|
@@ -135,10 +157,43 @@ function poolSend(providerName, account) {
|
|
|
135
157
|
const attempted = new Set();
|
|
136
158
|
let lastError;
|
|
137
159
|
let probed = false;
|
|
160
|
+
const evidenced = new Set();
|
|
161
|
+
// Mark `error` only when every account in `ids` (default: the roster) is
|
|
162
|
+
// exhausted with quota evidence: observed in this send, or a full window.
|
|
163
|
+
const markExhausted = (error, ids) => {
|
|
164
|
+
const pool = readProviderAccountPool(providerName);
|
|
165
|
+
// Auto switching off: the eligible set is the selected account alone.
|
|
166
|
+
const scope = ids || (pool.auto === false ? [pool.selectedId] : null);
|
|
167
|
+
const rows = scope ? pool.accounts.filter((entry) => scope.includes(entry.id)) : pool.accounts;
|
|
168
|
+
const opts = { useCredits: pool.useCredits === true };
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
const all =
|
|
171
|
+
rows.length > 0 &&
|
|
172
|
+
rows.every((entry) =>
|
|
173
|
+
providerAccountExhausted(
|
|
174
|
+
evidenced.has(entry.id) || entry.blockedQuota === true ? entry : { ...entry, blockedUntil: 0 },
|
|
175
|
+
now,
|
|
176
|
+
model,
|
|
177
|
+
opts
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
return all ? quotaExhausted(error, providerName, model, rows, opts) : error;
|
|
181
|
+
};
|
|
138
182
|
while (attempted.size < 20) {
|
|
139
183
|
options.signal?.throwIfAborted();
|
|
140
184
|
const pool = readProviderAccountPool(providerName);
|
|
141
|
-
if (!pool.accounts.length)
|
|
185
|
+
if (!pool.accounts.length) {
|
|
186
|
+
if (pool.useCredits !== true) {
|
|
187
|
+
const stored = readCachedOAuthUsageSnapshot({ provider: providerName, model: '' });
|
|
188
|
+
if (stored?.quotaWindows?.length) {
|
|
189
|
+
const row = { usage: { windows: stored.quotaWindows, checkedAt: stored.cachedAt } };
|
|
190
|
+
if (providerAccountExhausted(row, Date.now(), model)) {
|
|
191
|
+
throw quotaExhausted(accountsExhaustedError(false), providerName, model, [row]);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return account('default').send(messages, model, tools, options);
|
|
196
|
+
}
|
|
142
197
|
const row = chooseProviderAccount(pool, attempted, Date.now(), model);
|
|
143
198
|
if (!row) {
|
|
144
199
|
// Once per send: a refusal built on a stale meter must not outlive the
|
|
@@ -148,8 +203,7 @@ function poolSend(providerName, account) {
|
|
|
148
203
|
probed = true;
|
|
149
204
|
if (await probeRefusedAccounts(providerName, pool, model, account)) continue;
|
|
150
205
|
}
|
|
151
|
-
|
|
152
|
-
throw accountsExhaustedError();
|
|
206
|
+
throw markExhausted(lastError || accountsExhaustedError(pool.useCredits === true));
|
|
153
207
|
}
|
|
154
208
|
attempted.add(row.id);
|
|
155
209
|
const bound = boundAccountSend(providerName, row, messages, options);
|
|
@@ -187,28 +241,25 @@ function poolSend(providerName, account) {
|
|
|
187
241
|
options.onStageChange?.('reconnecting', { message: 'Sign-in expired — switching to the next account' });
|
|
188
242
|
continue;
|
|
189
243
|
}
|
|
190
|
-
const { exhausted, delay } = await accountQuotaExhausted(providerName, row, model, error, account);
|
|
244
|
+
const { exhausted, delay, evidence } = await accountQuotaExhausted(providerName, row, model, error, account);
|
|
191
245
|
if (options.signal?.aborted || !exhausted) throw error;
|
|
246
|
+
if (evidence) evidenced.add(row.id);
|
|
192
247
|
blockProviderAccount(
|
|
193
248
|
providerName,
|
|
194
249
|
row.id,
|
|
195
250
|
Date.now() + (delay > 0 ? delay : 5 * 60_000),
|
|
196
251
|
model,
|
|
197
|
-
delay > 0 ? 'retry-after' : 'inferred'
|
|
252
|
+
delay > 0 ? 'retry-after' : 'inferred',
|
|
253
|
+
{ quota: evidence === true }
|
|
198
254
|
);
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
error.unsafeToRetry ||
|
|
203
|
-
error.liveTextEmitted ||
|
|
204
|
-
error.emittedToolCall
|
|
205
|
-
)
|
|
206
|
-
throw error;
|
|
255
|
+
if (bound.emitted() || error.unsafeToRetry || error.liveTextEmitted || error.emittedToolCall) throw error;
|
|
256
|
+
// Auto switching off: the eligible set is the selected account alone.
|
|
257
|
+
if (pool.auto === false) throw markExhausted(error, [row.id]);
|
|
207
258
|
lastError = error;
|
|
208
259
|
options.onStageChange?.('reconnecting', { message: 'Quota exhausted — switching to the next account' });
|
|
209
260
|
}
|
|
210
261
|
}
|
|
211
|
-
throw lastError || new Error('Provider account attempts exhausted.');
|
|
262
|
+
throw markExhausted(lastError || new Error('Provider account attempts exhausted.'));
|
|
212
263
|
};
|
|
213
264
|
}
|
|
214
265
|
|
|
@@ -14,6 +14,7 @@ import { buildAnthropicBetaHeaders, supportsAnthropicFastMode } from './anthropi
|
|
|
14
14
|
import { applyAnthropicServerFallback } from './anthropic-server-fallback.mjs';
|
|
15
15
|
import { fastModeAvailable } from './anthropic-fast-mode.mjs';
|
|
16
16
|
import { applyAnthropicEffortToBody, shouldIncludeEffortBeta } from './anthropic-effort.mjs';
|
|
17
|
+
import { isCustomProviderName, resolveCustomReasoning } from './custom-reasoning.mjs';
|
|
17
18
|
import {
|
|
18
19
|
applyAnthropicCacheMarkers,
|
|
19
20
|
clampAnthropicThinkingBudget as clampThinkingBudgetTokens,
|
|
@@ -44,7 +45,20 @@ import {
|
|
|
44
45
|
* @param {boolean} deps.fastModeLatched the provider's sticky fast-mode beta latch
|
|
45
46
|
* @returns {Promise<{ params: object, requestHeaders: object|null, knownToolNames: Set<string>, fastModeLatched: boolean }>}
|
|
46
47
|
*/
|
|
47
|
-
export async function buildAnthropicApiRequest({
|
|
48
|
+
export async function buildAnthropicApiRequest({
|
|
49
|
+
name,
|
|
50
|
+
config,
|
|
51
|
+
messages,
|
|
52
|
+
useModel,
|
|
53
|
+
tools,
|
|
54
|
+
opts,
|
|
55
|
+
fastModeLatched,
|
|
56
|
+
modelInfo = null,
|
|
57
|
+
}) {
|
|
58
|
+
// A user-defined provider's reasoning comes from its own model row only:
|
|
59
|
+
// the global by-model-id capability index and the Claude-id ladders are
|
|
60
|
+
// first-party knowledge and must not leak onto (or from) a custom endpoint.
|
|
61
|
+
const isCustom = isCustomProviderName(name);
|
|
48
62
|
const maxTokens = resolveMaxTokens(useModel);
|
|
49
63
|
const effortProjection = projectEffortConfiguration(messages, name, useModel, {
|
|
50
64
|
...config,
|
|
@@ -81,8 +95,10 @@ export async function buildAnthropicApiRequest({ name, config, messages, useMode
|
|
|
81
95
|
system: systemBlocks.length ? systemBlocks : undefined,
|
|
82
96
|
messages: await prepareAnthropicImages(anthropicMessages, { signal: opts.signal }),
|
|
83
97
|
};
|
|
98
|
+
// Server-side fallback and fast mode are decided from Claude model ids:
|
|
99
|
+
// first-party knowledge, never applied to a custom endpoint.
|
|
84
100
|
applyAnthropicServerFallback(params, useModel, {
|
|
85
|
-
enabled: config?.disableBetaHeaders !== true && opts.serverFallback !== false,
|
|
101
|
+
enabled: !isCustom && config?.disableBetaHeaders !== true && opts.serverFallback !== false,
|
|
86
102
|
});
|
|
87
103
|
if (requestTools.length) {
|
|
88
104
|
// No cache_control on tools — the system BP covers tools via
|
|
@@ -105,18 +121,28 @@ export async function buildAnthropicApiRequest({ name, config, messages, useMode
|
|
|
105
121
|
.map((t) => (t && typeof t.name === 'string' ? t.name : null))
|
|
106
122
|
.filter(Boolean)
|
|
107
123
|
);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
if (isCustom) {
|
|
125
|
+
const { fields } = resolveCustomReasoning({
|
|
126
|
+
protocol: 'anthropic',
|
|
127
|
+
model: modelInfo,
|
|
128
|
+
effort: opts.effort ?? config?.reasoningEffort,
|
|
129
|
+
});
|
|
130
|
+
if (fields.thinking) params.thinking = fields.thinking;
|
|
131
|
+
if (fields.output_config) params.output_config = { ...(params.output_config || {}), ...fields.output_config };
|
|
132
|
+
} else {
|
|
133
|
+
applyAnthropicEffortToBody(params, {
|
|
134
|
+
model: useModel,
|
|
135
|
+
opts: effortProjection ? { ...opts, effort: effortProjection.initialEffort } : opts,
|
|
136
|
+
maxTokens,
|
|
137
|
+
clampThinkingBudgetTokens,
|
|
138
|
+
logTag: name,
|
|
139
|
+
progressUpdates: config?.disableBetaHeaders !== true,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
116
142
|
// Fast mode → speed: "fast" on models Anthropic marks as speed-capable.
|
|
117
143
|
// Suppressed while the fast capacity pool is cooling down.
|
|
118
144
|
let fastLatched = fastModeLatched;
|
|
119
|
-
if (opts.fast === true && supportsAnthropicFastMode(useModel) && fastModeAvailable()) {
|
|
145
|
+
if (!isCustom && opts.fast === true && supportsAnthropicFastMode(useModel) && fastModeAvailable()) {
|
|
120
146
|
params.speed = 'fast';
|
|
121
147
|
fastLatched = true;
|
|
122
148
|
}
|
|
@@ -133,19 +159,23 @@ export async function buildAnthropicApiRequest({ name, config, messages, useMode
|
|
|
133
159
|
// too — otherwise opencode-go's anthropic-compatible routing
|
|
134
160
|
// (disableBetaHeaders:true) would still send beta strings that a
|
|
135
161
|
// third-party endpoint may reject.
|
|
162
|
+
// A custom endpoint gets no first-party base betas; only those its own
|
|
163
|
+
// request body needs (row-driven effort, deferred tools).
|
|
136
164
|
let betaHeaders = null;
|
|
137
165
|
if (!config?.disableBetaHeaders) {
|
|
138
166
|
const betas = [
|
|
139
167
|
buildAnthropicBetaHeaders({
|
|
168
|
+
...(isCustom ? { base: '' } : {}),
|
|
140
169
|
fastMode: fastLatched,
|
|
141
170
|
toolSearch: hasDeferredTools,
|
|
142
|
-
|
|
171
|
+
// Custom: the beta follows the resolver's output, not the model id.
|
|
172
|
+
effort: isCustom ? Boolean(params.output_config?.effort) : shouldIncludeEffortBeta(useModel, opts),
|
|
143
173
|
serverFallback: params.fallbacks === 'default',
|
|
144
174
|
thinkingDisplayUpdates: params.thinking?.display === 'updates',
|
|
145
175
|
}),
|
|
146
176
|
...(effortProjection ? [EFFORT_CONFIGURATION_BETA] : []),
|
|
147
|
-
];
|
|
148
|
-
betaHeaders = { 'anthropic-beta': betas.join(',') };
|
|
177
|
+
].filter(Boolean);
|
|
178
|
+
if (betas.length) betaHeaders = { 'anthropic-beta': betas.join(',') };
|
|
149
179
|
}
|
|
150
180
|
const requestHeaders =
|
|
151
181
|
betaHeaders || opts.requestHeaders ? { ...(betaHeaders || {}), ...(opts.requestHeaders || {}) } : null;
|