mixdog 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/scripts/build-shipped-history.mjs +115 -33
- package/scripts/gate-local.mjs +172 -61
- package/scripts/lib/gate-scheduler.mjs +138 -32
- package/scripts/lib/gate-timing-reporter.mjs +16 -0
- package/scripts/lib/run-node-tests.mjs +54 -29
- package/scripts/lib/scoped-temp.mjs +1 -0
- package/scripts/lib/test-failure-records.mjs +14 -1
- package/scripts/lib/test-failure-summary.mjs +16 -4
- package/scripts/lib/test-spawn-ledger.mjs +12 -3
- package/scripts/pptx-kit-index.mjs +5 -6
- package/scripts/suite-health.mjs +110 -28
- package/scripts/test.mjs +9 -14
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/explore/AGENT.md +22 -0
- package/src/agents/explore/agent.json +6 -0
- package/src/cli.mjs +7 -0
- package/src/defaults/agents.json +7 -0
- package/src/defaults/shipped-history.json +18 -2
- package/src/defaults/skills/browser-use/SKILL.md +4 -1
- package/src/defaults/skills/code-tidy/SKILL.md +71 -102
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
- package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
- package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
- package/src/defaults/skills/computer-use/SKILL.md +12 -2
- package/src/defaults/skills/goal-management/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +12 -6
- package/src/defaults/skills/setup/references/surfaces.md +2 -2
- package/src/defaults/skills/setup-guide/SKILL.md +5 -2
- package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
- package/src/defaults/skills/skill-creator/SKILL.md +85 -190
- package/src/defaults/skills/skill-creator/references/design.md +70 -162
- package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
- package/src/defaults/skills/test-suite/SKILL.md +194 -0
- package/src/defaults/skills/test-suite/references/case-management.md +51 -0
- package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
- package/src/defaults/skills/test-suite/references/reliability.md +49 -0
- package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
- package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
- package/src/defaults/skills/visualize/SKILL.md +6 -2
- package/src/lib/rules-builder.cjs +6 -30
- package/src/rules/lead/LEAD.md +3 -3
- package/src/rules/shared/20-research.md +3 -6
- package/src/rules/shared/40-editing.md +1 -0
- package/src/rules/shared/60-verification.md +10 -9
- package/src/rules/shared/70-delivery.md +4 -3
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +130 -21
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
- package/src/runtime/agent/orchestrator/config.mjs +12 -7
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +7 -7
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
- package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
- package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +83 -52
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +43 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +30 -28
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +48 -31
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +26 -28
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +33 -2
- package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +94 -10
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +10 -5
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +21 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +3 -4
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -14
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +17 -14
- package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +47 -29
- package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +243 -38
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -26
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +3 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/channels/lib/whisper-port.mjs +19 -10
- package/src/runtime/channels/lib/whisper-server.mjs +10 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
- package/src/runtime/connectors/gmail-app-password.mjs +3 -3
- package/src/runtime/explore/explorer.mjs +93 -0
- package/src/runtime/explore/tool-defs.mjs +35 -0
- package/src/runtime/explore/tool.mjs +123 -0
- package/src/runtime/memory/index.mjs +4 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +4 -0
- package/src/runtime/office/quality/assurance-structure-docx.mjs +1 -1
- package/src/runtime/shared/agent-route-config.mjs +17 -2
- package/src/runtime/shared/atomic-file.mjs +103 -2
- package/src/runtime/shared/background-tasks.mjs +15 -6
- package/src/runtime/shared/connector-accounts.mjs +5 -5
- package/src/runtime/shared/connector-catalog.d.mts +11 -4
- package/src/runtime/shared/connector-catalog.mjs +60 -52
- package/src/runtime/shared/custom-provider-keys.mjs +20 -0
- package/src/runtime/shared/file-has-data.mjs +26 -2
- package/src/runtime/shared/file-lock.mjs +13 -15
- package/src/runtime/shared/git-launch.d.mts +9 -0
- package/src/runtime/shared/git-launch.mjs +213 -0
- package/src/runtime/shared/git-runner-worker.mjs +2 -1
- package/src/runtime/shared/llm/cost.mjs +6 -1
- package/src/runtime/shared/llm/http-agent.mjs +313 -29
- package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
- package/src/runtime/shared/llm/model-catalog.mjs +46 -6
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
- package/src/runtime/shared/llm/system-proxy.mjs +452 -0
- package/src/runtime/shared/provider-accounts.mjs +52 -2
- package/src/runtime/shared/staged-install-worker.mjs +5 -0
- package/src/runtime/shared/staged-update.mjs +47 -2
- package/src/runtime/shared/tool-surface.mjs +110 -36
- package/src/runtime/shared/tool-work-units.mjs +4 -0
- package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
- package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
- package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
- package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
- package/src/runtime/shared/worktrees/git.mjs +57 -0
- package/src/runtime/shared/worktrees/manager.mjs +205 -137
- package/src/runtime/tidy/install.mjs +95 -15
- package/src/runtime/tidy/languages.mjs +17 -15
- package/src/runtime/tidy/process.mjs +7 -4
- package/src/runtime/tidy/resolve.mjs +19 -16
- package/src/runtime/tidy/runners/biome.mjs +3 -3
- package/src/runtime/tidy/tool.mjs +1 -1
- package/src/runtime/tidy/worktree.mjs +10 -6
- package/src/runtime/visualize-bridge/client.mjs +4 -1
- package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
- package/src/runtime/web-search/index.mjs +1 -1
- package/src/session-runtime/agent-task-sessions.mjs +410 -353
- package/src/session-runtime/boot/tools.mjs +2 -0
- package/src/session-runtime/bot-status.mjs +103 -103
- package/src/session-runtime/connector-merge-migration.mjs +18 -15
- package/src/session-runtime/conversation-import/discovery.mjs +159 -40
- package/src/session-runtime/conversation-import/service.mjs +9 -3
- package/src/session-runtime/handoff-attachments.mjs +1 -1
- package/src/session-runtime/held-requests.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +143 -116
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
- package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
- package/src/session-runtime/mixy-heartbeat.mjs +1 -1
- package/src/session-runtime/provider-auth/login.mjs +2 -2
- package/src/session-runtime/resource-mcp-api.mjs +98 -35
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
- package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
- package/src/session-runtime/resource-mcp-servers.mjs +5 -4
- package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
- package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
- package/src/session-runtime/services/daemon-candidate.mjs +4 -1
- package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
- package/src/session-runtime/services/plugin-admin.mjs +10 -6
- package/src/session-runtime/services/provider-admin.mjs +281 -10
- package/src/session-runtime/session-turn-api.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
- package/src/session-runtime/settings-system-api.mjs +1 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
- package/src/session-runtime/setup-tool/executor.mjs +6 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
- package/src/session-runtime/turn/agent-ops.mjs +23 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +10 -1
- package/src/session-runtime/workspace/activity-guards.mjs +176 -0
- package/src/session-runtime/workspace/session-view.mjs +149 -0
- package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
- package/src/session-runtime/workspace/transition-runner.mjs +188 -0
- package/src/session-runtime/workspace/transitions.mjs +108 -1066
- package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
- package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
- package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
- package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +54 -54
- package/src/standalone/daemon-desktop-runtime.mjs +2 -0
- package/src/standalone/daemon-session-control.mjs +151 -136
- package/src/standalone/daemon.mjs +7 -0
- package/src/standalone/desktop-service-registry.mjs +9 -0
- package/src/standalone/local-session-runtime.mjs +5 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
- package/src/standalone/session-client.mjs +5 -1
- package/src/standalone/session-frame-stream.mjs +83 -11
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/standalone/session-service/agent-tree.mjs +45 -9
- package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
- package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
- package/src/standalone/session-service/stored-reader.mjs +127 -40
- package/src/tui/App.jsx +154 -164
- package/src/tui/app/app-view/floating-panel.jsx +3 -0
- package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
- package/src/tui/app/clipboard.mjs +21 -10
- package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
- package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
- package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
- package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
- package/src/tui/app/provider-setup/oauth-actions.mjs +25 -6
- package/src/tui/app/provider-setup-picker.mjs +20 -13
- package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
- package/src/tui/app/slash-dispatch.mjs +1 -1
- package/src/tui/app/use-btw-panel.mjs +29 -0
- package/src/tui/app/use-context-panel-refresh.mjs +31 -0
- package/src/tui/app/use-model-catalog-cache.mjs +13 -10
- package/src/tui/app/use-mouse-input.mjs +11 -0
- package/src/tui/app/use-onboarding-start.mjs +8 -3
- package/src/tui/app/use-panel-transition.mjs +10 -1
- package/src/tui/app/use-pasted-buffers.mjs +28 -20
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
- package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
- package/src/tui/app/use-prompt-queue-history.mjs +3 -2
- package/src/tui/app/use-selection-focus.mjs +11 -2
- package/src/tui/app/use-selection-geometry.mjs +18 -9
- package/src/tui/app/use-selection-paint.mjs +4 -4
- package/src/tui/app/use-selection-stitch.mjs +3 -3
- package/src/tui/app/use-slash-palette.mjs +62 -0
- package/src/tui/app/use-terminal-chrome.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +47 -32
- package/src/tui/app/use-transcript-follow.mjs +12 -4
- package/src/tui/app/use-transcript-scroll.mjs +46 -17
- package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
- package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
- package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
- package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
- package/src/tui/app/use-transcript-window.mjs +21 -11
- package/src/tui/app/use-tui-ready.mjs +18 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
- package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
- package/src/tui/components/Picker.jsx +27 -3
- package/src/tui/components/SignInUrl.jsx +68 -0
- package/src/tui/components/TextEntryPanel.jsx +76 -46
- package/src/tui/components/sign-in-url-layout.mjs +24 -0
- package/src/tui/dist/index.mjs +1896 -1341
- package/src/tui/session/goal-turn-state.mjs +6 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +8 -0
- package/src/tui/session/session-api/intake/abort.mjs +2 -1
- package/src/tui/session/session-api/intake.mjs +2 -0
- package/src/tui/session/session-api/integrations.mjs +2 -2
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +2 -2
- package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
- package/src/tui/session/transcript-spill.mjs +92 -4
- package/src/tui/session/transcript-store.mjs +2 -0
- package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { getPluginData } from '../config.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { updateJsonAtomic, writeJsonAtomicAsync } from '../../../shared/atomic-file.mjs';
|
|
5
|
+
|
|
6
|
+
// Disk writes never block the event loop: they run on the async atomic-file
|
|
7
|
+
// primitives, one at a time per cache file, in the order they were requested
|
|
8
|
+
// (a later write is never overtaken by an earlier one finishing late). Every
|
|
9
|
+
// instance of one file shares these module-level tables, since several
|
|
10
|
+
// instances and several providers can address the same path.
|
|
11
|
+
const writeTails = new Map(); // path → tail of that file's write chain
|
|
12
|
+
// path → JSON text of the newest save() whose write has not finished. Sync
|
|
13
|
+
// readers see it, so a save is visible at once although its disk write is not.
|
|
14
|
+
const unsettledSaves = new Map();
|
|
15
|
+
|
|
16
|
+
function enqueueWrite(p, task) {
|
|
17
|
+
const run = (writeTails.get(p) ?? Promise.resolve()).then(task);
|
|
18
|
+
writeTails.set(p, run);
|
|
19
|
+
run.then(() => {
|
|
20
|
+
if (writeTails.get(p) === run) writeTails.delete(p);
|
|
21
|
+
});
|
|
22
|
+
return run;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Resolves once every cache write requested so far has settled (shutdown, tests). */
|
|
26
|
+
export async function flushModelCacheWrites() {
|
|
27
|
+
while (writeTails.size) await Promise.all([...writeTails.values()]);
|
|
28
|
+
}
|
|
5
29
|
|
|
6
30
|
// Shared model-catalog disk-cache CRUD, parameterized over the small bits each
|
|
7
31
|
// provider varies: cache file name, TTL, an optional schema-version gate, and an
|
|
@@ -15,9 +39,10 @@ export function makeModelCache({ fileName, ttlMs, version = null, onSave = null
|
|
|
15
39
|
// allowStale skips the TTL only; the schema-version gate still applies.
|
|
16
40
|
function loadEntrySync({ allowStale = false } = {}) {
|
|
17
41
|
const p = path();
|
|
18
|
-
|
|
42
|
+
const unsettled = unsettledSaves.get(p);
|
|
43
|
+
if (unsettled === undefined && !existsSync(p)) return null;
|
|
19
44
|
try {
|
|
20
|
-
const raw = JSON.parse(readFileSync(p, 'utf-8'));
|
|
45
|
+
const raw = JSON.parse(unsettled ?? readFileSync(p, 'utf-8'));
|
|
21
46
|
if (version != null && raw?.version !== version) return null;
|
|
22
47
|
if (!raw?.fetchedAt || !Array.isArray(raw.models)) return null;
|
|
23
48
|
if (!allowStale && Date.now() - raw.fetchedAt > ttlMs) return null;
|
|
@@ -31,56 +56,98 @@ export function makeModelCache({ fileName, ttlMs, version = null, onSave = null
|
|
|
31
56
|
return loadEntrySync(options)?.models ?? null;
|
|
32
57
|
}
|
|
33
58
|
|
|
59
|
+
// The in-memory state (sync readers, onSave mirror) updates at once; the
|
|
60
|
+
// returned promise settles, never rejecting, when the disk write has.
|
|
34
61
|
function save(models, extra = {}) {
|
|
62
|
+
const p = path();
|
|
63
|
+
// The value as of this call: later mutation of `models` must not change
|
|
64
|
+
// what lands on disk (the old synchronous write captured it the same way).
|
|
65
|
+
let text;
|
|
66
|
+
let record;
|
|
67
|
+
try {
|
|
68
|
+
text = JSON.stringify({
|
|
69
|
+
...extra,
|
|
70
|
+
...(version != null ? { version } : {}),
|
|
71
|
+
fetchedAt: Date.now(),
|
|
72
|
+
models,
|
|
73
|
+
});
|
|
74
|
+
record = JSON.parse(text);
|
|
75
|
+
unsettledSaves.set(p, text);
|
|
76
|
+
} catch {
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
}
|
|
35
79
|
try {
|
|
36
|
-
writeJsonAtomicSync(
|
|
37
|
-
path(),
|
|
38
|
-
{
|
|
39
|
-
...extra,
|
|
40
|
-
...(version != null ? { version } : {}),
|
|
41
|
-
fetchedAt: Date.now(),
|
|
42
|
-
models,
|
|
43
|
-
},
|
|
44
|
-
{ lock: true, fsyncDir: true }
|
|
45
|
-
);
|
|
46
80
|
if (onSave) onSave(models);
|
|
47
81
|
} catch {
|
|
48
82
|
/* best-effort */
|
|
49
83
|
}
|
|
84
|
+
return enqueueWrite(p, async () => {
|
|
85
|
+
try {
|
|
86
|
+
await writeJsonAtomicAsync(p, record, { lock: true, fsyncDir: true });
|
|
87
|
+
} catch {
|
|
88
|
+
/* best-effort */
|
|
89
|
+
} finally {
|
|
90
|
+
if (unsettledSaves.get(p) === text) unsettledSaves.delete(p);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
50
93
|
}
|
|
51
94
|
|
|
52
95
|
// A discovery request claims a generation when it starts. Its roster is
|
|
53
96
|
// written only if no newer request started meanwhile — in this process
|
|
54
97
|
// (generation) or another one (`requestedAt` checked under the file lock) —
|
|
55
98
|
// and `isCurrent()` (the credential it was fetched with is still active) holds.
|
|
99
|
+
// The returned publish() resolves, never rejecting, to whether the roster was
|
|
100
|
+
// written; the cross-process check runs under the file lock, so the answer
|
|
101
|
+
// is only known once the queued write has run.
|
|
56
102
|
let generation = 0;
|
|
57
103
|
function beginSave(isCurrent) {
|
|
58
104
|
const claimed = ++generation;
|
|
59
105
|
const requestedAt = Date.now();
|
|
106
|
+
const stillWanted = () => claimed === generation && isCurrent();
|
|
60
107
|
return (models, extra = {}) => {
|
|
61
|
-
if (
|
|
62
|
-
|
|
108
|
+
if (!stillWanted()) return Promise.resolve(false);
|
|
109
|
+
const p = path();
|
|
110
|
+
// The roster as of this call, not as mutated while the write waits.
|
|
111
|
+
let snapshot;
|
|
63
112
|
try {
|
|
64
|
-
|
|
65
|
-
path(),
|
|
66
|
-
(cur) => {
|
|
67
|
-
if (Number(cur?.requestedAt) > requestedAt) return undefined;
|
|
68
|
-
written = true;
|
|
69
|
-
return {
|
|
70
|
-
...extra,
|
|
71
|
-
...(version != null ? { version } : {}),
|
|
72
|
-
fetchedAt: Date.now(),
|
|
73
|
-
requestedAt,
|
|
74
|
-
models,
|
|
75
|
-
};
|
|
76
|
-
},
|
|
77
|
-
{ fsyncDir: true }
|
|
78
|
-
);
|
|
113
|
+
snapshot = JSON.parse(JSON.stringify({ extra, models }));
|
|
79
114
|
} catch {
|
|
80
|
-
|
|
115
|
+
return Promise.resolve(false);
|
|
81
116
|
}
|
|
82
|
-
|
|
83
|
-
|
|
117
|
+
return enqueueWrite(p, async () => {
|
|
118
|
+
// A newer request may have started while this write waited its turn.
|
|
119
|
+
if (!stillWanted()) return false;
|
|
120
|
+
let written = false;
|
|
121
|
+
try {
|
|
122
|
+
await updateJsonAtomic(
|
|
123
|
+
p,
|
|
124
|
+
(cur) => {
|
|
125
|
+
if (Number(cur?.requestedAt) > requestedAt) return undefined;
|
|
126
|
+
// The lock wait is asynchronous: a newer request or a credential
|
|
127
|
+
// change may have landed since the checks above. Re-decide now,
|
|
128
|
+
// right before accepting the data.
|
|
129
|
+
if (!stillWanted()) return undefined;
|
|
130
|
+
written = true;
|
|
131
|
+
return {
|
|
132
|
+
...snapshot.extra,
|
|
133
|
+
...(version != null ? { version } : {}),
|
|
134
|
+
fetchedAt: Date.now(),
|
|
135
|
+
requestedAt,
|
|
136
|
+
models: snapshot.models,
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
{ fsyncDir: true }
|
|
140
|
+
);
|
|
141
|
+
} catch {
|
|
142
|
+
/* best-effort */
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
if (written && onSave) onSave(models);
|
|
146
|
+
} catch {
|
|
147
|
+
/* best-effort */
|
|
148
|
+
}
|
|
149
|
+
return written;
|
|
150
|
+
});
|
|
84
151
|
};
|
|
85
152
|
}
|
|
86
153
|
|
|
@@ -435,7 +435,7 @@ export class MuseCodeOAuthProvider {
|
|
|
435
435
|
});
|
|
436
436
|
const rows = discovered.map((row) => museRow(row.id, row));
|
|
437
437
|
// A superseded or other-credential response never replaces the live roster either.
|
|
438
|
-
if (publish(discovered)) inner._enrichedModels = rows;
|
|
438
|
+
if (await publish(discovered)) inner._enrichedModels = rows;
|
|
439
439
|
return rows;
|
|
440
440
|
}
|
|
441
441
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// The authorization code a test login sends: redacted by value, since `code`
|
|
2
|
+
// alone is also an error/status field.
|
|
3
|
+
export const AUTH_CODE = 'SECRET-CODE';
|
|
4
|
+
|
|
5
|
+
// Every secret field a token endpoint might echo, each with a distinct value
|
|
6
|
+
// that a test can search for in pages, errors and logs.
|
|
7
|
+
export const SECRET_FIELDS = {
|
|
8
|
+
access_token: 'SECRET-ACCESS',
|
|
9
|
+
refresh_token: 'SECRET-REFRESH',
|
|
10
|
+
id_token: 'SECRET-ID',
|
|
11
|
+
client_secret: 'SECRET-CLIENT',
|
|
12
|
+
code: AUTH_CODE,
|
|
13
|
+
code_verifier: 'SECRET-VERIFIER',
|
|
14
|
+
code_challenge: 'SECRET-CHALLENGE',
|
|
15
|
+
device_code: 'SECRET-DEVICE',
|
|
16
|
+
accessToken: 'SECRET-CAMEL-ACCESS',
|
|
17
|
+
refreshToken: 'SECRET-CAMEL-REFRESH',
|
|
18
|
+
idToken: 'SECRET-CAMEL-ID',
|
|
19
|
+
clientSecret: 'SECRET-CAMEL-CLIENT',
|
|
20
|
+
token: 'SECRET-TOKEN',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const SECRET_RE = /SECRET-/;
|
|
24
|
+
|
|
25
|
+
/** Valid JSON whose secret keys are written with escapes, nested in objects and arrays. */
|
|
26
|
+
export const ESCAPED_SECRET_BODY = String.raw`{"error":{"code":400,"details":[{"client\u005fsecret":"SECRET-ESC-CLIENT","Refresh\u005FToken":"SECRET-ESC-REFRESH"},["x",{"\u0069d_token":"SECRET-ESC-ID"}]],"meta":{"api\u005Fkey":"SECRET-ESC-KEY","acc\u0065ss\u005ftoken":"SECRET-ESC-ACCESS"}}}`;
|
|
27
|
+
|
|
28
|
+
/** Valid JSON whose secrets sit inside string values: form-encoded, a query string, nested JSON. */
|
|
29
|
+
export const STRING_SECRET_BODY = JSON.stringify({
|
|
30
|
+
error: 'invalid_request',
|
|
31
|
+
detail: 'access_token=SECRET-FORM-ACCESS&client_secret=SECRET-FORM-CLIENT',
|
|
32
|
+
error_uri: 'https://auth.example/cb?client_secret=SECRET-QUERY-CLIENT&state=ok',
|
|
33
|
+
nested: JSON.stringify({ refresh_token: 'SECRET-NESTED', inner: String.raw`{"client\u005fsecret":"SECRET-DEEP"}` }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/** Token-endpoint failure bodies carrying every secret field (JSON, escaped JSON, strings, form-encoded). */
|
|
37
|
+
export const SECRET_BODIES = [
|
|
38
|
+
JSON.stringify({ error: 'invalid_grant', ...SECRET_FIELDS }),
|
|
39
|
+
ESCAPED_SECRET_BODY,
|
|
40
|
+
STRING_SECRET_BODY,
|
|
41
|
+
new URLSearchParams({ error: 'invalid_grant', ...SECRET_FIELDS }).toString(),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/** A 200 body that is not JSON but carries tokens (the parser would quote it). */
|
|
45
|
+
export const MALFORMED_TOKEN_BODY = '{"access_token":"SECRET-ACCESS","refresh_token":"SECRET-REFRESH" oops';
|
|
@@ -12,6 +12,7 @@ import { createServer } from 'node:http';
|
|
|
12
12
|
import { randomBytes } from 'node:crypto';
|
|
13
13
|
import { createOAuthPkce, parseOAuthCodeInput } from './lib/oauth-pkce.mjs';
|
|
14
14
|
import { OAUTH_PAGE_CONTENT_TYPE, oauthErrorHtml, oauthSuccessHtml } from './lib/oauth-page.mjs';
|
|
15
|
+
import { oauthFailureReason, oauthHttpError, oauthRequestError, oauthResponseJson } from './lib/oauth-token-utils.mjs';
|
|
15
16
|
import { openLoginUrl } from './lib/open-login-url.mjs';
|
|
16
17
|
import { sleepWithAbort } from './retry-backoff.mjs';
|
|
17
18
|
|
|
@@ -28,14 +29,6 @@ const TOKEN_TIMEOUT_MS = 30_000;
|
|
|
28
29
|
const DEVICE_LOGIN_TIMEOUT_MS = 15 * 60_000;
|
|
29
30
|
const DEFAULT_DEVICE_INTERVAL_S = 5;
|
|
30
31
|
|
|
31
|
-
function _scrubOAuthLoginBody(text) {
|
|
32
|
-
return String(text || '')
|
|
33
|
-
.replace(/"access_token"\s*:\s*"[^"]+"/g, '"access_token":"[REDACTED]"')
|
|
34
|
-
.replace(/"refresh_token"\s*:\s*"[^"]+"/g, '"refresh_token":"[REDACTED]"')
|
|
35
|
-
.replace(/"id_token"\s*:\s*"[^"]+"/g, '"id_token":"[REDACTED]"')
|
|
36
|
-
.replace(/[A-Za-z0-9_-]{32,}\.[A-Za-z0-9._-]+/g, '[REDACTED]');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
32
|
/**
|
|
40
33
|
* @param {object} deps
|
|
41
34
|
* @param {string} deps.clientId
|
|
@@ -72,26 +65,31 @@ export function createOpenAIOAuthLogin(deps) {
|
|
|
72
65
|
signal?.throwIfAborted();
|
|
73
66
|
const cleanCode = String(code || '').trim();
|
|
74
67
|
if (!cleanCode) throw new Error('[openai-oauth] authorization code is required');
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
:
|
|
89
|
-
|
|
68
|
+
let tokenRes;
|
|
69
|
+
try {
|
|
70
|
+
tokenRes = await fetch(tokenUrl, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
73
|
+
body: new URLSearchParams({
|
|
74
|
+
grant_type: 'authorization_code',
|
|
75
|
+
code: cleanCode,
|
|
76
|
+
redirect_uri: redirectUri,
|
|
77
|
+
client_id: clientId,
|
|
78
|
+
code_verifier: pkce.verifier,
|
|
79
|
+
}),
|
|
80
|
+
redirect: 'error',
|
|
81
|
+
signal: signal
|
|
82
|
+
? AbortSignal.any([signal, AbortSignal.timeout(TOKEN_TIMEOUT_MS)])
|
|
83
|
+
: AbortSignal.timeout(TOKEN_TIMEOUT_MS),
|
|
84
|
+
});
|
|
85
|
+
} catch (err) {
|
|
86
|
+
throw oauthRequestError('[openai-oauth] token exchange', err);
|
|
87
|
+
}
|
|
90
88
|
if (!tokenRes.ok) {
|
|
91
89
|
const text = await tokenRes.text().catch(() => '');
|
|
92
|
-
throw
|
|
90
|
+
throw oauthHttpError('[openai-oauth] token exchange', tokenRes.status, text, [cleanCode, pkce.verifier]);
|
|
93
91
|
}
|
|
94
|
-
const json = await tokenRes
|
|
92
|
+
const json = await oauthResponseJson(tokenRes, '[openai-oauth] token exchange');
|
|
95
93
|
if (!json.access_token || !json.refresh_token) {
|
|
96
94
|
throw new Error('[openai-oauth] token exchange response missing access_token or refresh_token');
|
|
97
95
|
}
|
|
@@ -292,10 +290,10 @@ export function createOpenAIOAuthLogin(deps) {
|
|
|
292
290
|
// written; do not leave a keep-alive connection bound to it.
|
|
293
291
|
res.writeHead(200, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE, Connection: 'close' });
|
|
294
292
|
res.end(oauthSuccessHtml('OpenAI'));
|
|
295
|
-
} catch {
|
|
296
|
-
// The shared exchange reports its error through waitForCallback.
|
|
293
|
+
} catch (err) {
|
|
294
|
+
// The shared exchange also reports its error through waitForCallback.
|
|
297
295
|
res.writeHead(500, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE, Connection: 'close' });
|
|
298
|
-
res.end(oauthErrorHtml('OpenAI could not complete the token exchange.'));
|
|
296
|
+
res.end(oauthErrorHtml(oauthFailureReason('OpenAI could not complete the token exchange.', err)));
|
|
299
297
|
}
|
|
300
298
|
});
|
|
301
299
|
timeout = setTimeout(() => finish(null), LOGIN_TIMEOUT_MS);
|
|
@@ -40,7 +40,9 @@ export function createWsEventSwitch({
|
|
|
40
40
|
// progress only once the model has produced output; before that the silent
|
|
41
41
|
// thinking follows it.
|
|
42
42
|
function onItemAdded() {
|
|
43
|
-
|
|
43
|
+
let kind = null;
|
|
44
|
+
if (response.toolInFlight) kind = 'tool';
|
|
45
|
+
else if (watchdogs.semanticSeen()) kind = 'semantic';
|
|
44
46
|
if (kind) watchdogs.bumpSemantic();
|
|
45
47
|
progress(kind ?? 'transport');
|
|
46
48
|
}
|
|
@@ -93,6 +93,7 @@ export function createWsStreamWatchdogs({
|
|
|
93
93
|
// synthesized completion. Attach the streamed partial state so
|
|
94
94
|
// the loop's partial/tool recovery can finalize it explicitly.
|
|
95
95
|
err.streamStalled = true;
|
|
96
|
+
err.code = 'ESTREAMSTALL';
|
|
96
97
|
attachPartialState(err);
|
|
97
98
|
fail(err, 'inter_chunk_timeout');
|
|
98
99
|
}, interChunkMs);
|
|
@@ -228,7 +228,7 @@ export class OpenCodeGoOAuthProvider {
|
|
|
228
228
|
});
|
|
229
229
|
const response = await consoleGetJson(CONFIG_URL, tokens, { fetchFn: this.config.fetchImpl, refresh: true });
|
|
230
230
|
const remote = goConfigFromResponse(response, listsGoModels(response) ? null : await loadModelsDevGoModels());
|
|
231
|
-
publish(remote.models, {
|
|
231
|
+
await publish(remote.models, {
|
|
232
232
|
baseURL: remote.baseURL,
|
|
233
233
|
headers: remote.headers,
|
|
234
234
|
accountId: tokens.account_id,
|
|
@@ -170,7 +170,9 @@ export class OpenCodeGoProvider {
|
|
|
170
170
|
const { OpenCodeGoGeminiEngine } = await import('./opencode-go-gemini.mjs');
|
|
171
171
|
this.gemini = new OpenCodeGoGeminiEngine({
|
|
172
172
|
...this.config,
|
|
173
|
-
|
|
173
|
+
// The OAuth org config names the chat base `.../go/openai/v1`; the
|
|
174
|
+
// generateContent routes live under `.../go/v1` and 404 on it.
|
|
175
|
+
baseURL: String(this.openai.baseURL).replace(/\/openai\/v1$/i, '/v1'),
|
|
174
176
|
extraHeaders: {
|
|
175
177
|
...GO_GATEWAY_PRESET.extraHeaders,
|
|
176
178
|
...(this.config.extraHeaders || {}),
|
|
@@ -3,7 +3,9 @@ import { oauthCredentialProbeState } from './oauth-credential-probes.mjs';
|
|
|
3
3
|
import { providerAvailable } from './provider-availability.mjs';
|
|
4
4
|
import { refreshCatalog as refreshMetadataCatalog } from './model-catalog.mjs';
|
|
5
5
|
import { wrapProviderAdmission } from './admission-scheduler.mjs';
|
|
6
|
-
import { createAccountPoolProvider } from './account-pool.mjs';
|
|
6
|
+
import { createAccountPoolProvider, createCustomKeyPoolProvider } from './account-pool.mjs';
|
|
7
|
+
import { customKeyEngine } from './custom-provider-keys.mjs';
|
|
8
|
+
import { customProviderKeyIds } from '../../../shared/provider-accounts.mjs';
|
|
7
9
|
import { isInclusiveProvider } from '../../../shared/llm/cost.mjs';
|
|
8
10
|
import { noEnabledProvidersError } from './registry-errors.mjs';
|
|
9
11
|
import { providerTakesNativePdf } from '../../../attachments/store.mjs';
|
|
@@ -182,6 +184,7 @@ async function loadProviderExport(cacheKey, spec, exportName, signal = null) {
|
|
|
182
184
|
const PROVIDER_MODULES = {
|
|
183
185
|
anthropic: ['./anthropic.mjs', 'AnthropicProvider'],
|
|
184
186
|
gemini: ['./gemini.mjs', 'GeminiProvider'],
|
|
187
|
+
'vertex-ai': ['./vertex-ai.mjs', 'VertexAIProvider'],
|
|
185
188
|
'openai-oauth': ['./openai-oauth.mjs', 'OpenAIOAuthProvider'],
|
|
186
189
|
'anthropic-oauth': ['./anthropic-oauth.mjs', 'AnthropicOAuthProvider'],
|
|
187
190
|
'grok-oauth': ['./grok-oauth.mjs', 'GrokOAuthProvider'],
|
|
@@ -227,13 +230,39 @@ async function instantiateProvider(name, Ctor, cfg, proxyChannel = null) {
|
|
|
227
230
|
const { createModelProxyProvider } = await import('./model-proxy-provider.mjs');
|
|
228
231
|
return wrapProviderAdmission(createModelProxyProvider(name, cfg, proxyChannel ?? getModelProxyChannel()), name);
|
|
229
232
|
}
|
|
230
|
-
if (isCustomProviderConfig(name, cfg))
|
|
233
|
+
if (isCustomProviderConfig(name, cfg)) {
|
|
234
|
+
const { [CUSTOM_KEY_IDS]: keyIds, ...settings } = cfg;
|
|
235
|
+
const primary = wrapProviderAdmission(await Ctor(name, settings), name);
|
|
236
|
+
// Only a provider with additional keys sends through the key roster.
|
|
237
|
+
if (!keyIds?.length) return primary;
|
|
238
|
+
return createCustomKeyPoolProvider(name, primary, (id) => customKeyEngine(primary, name, id));
|
|
239
|
+
}
|
|
231
240
|
if (Object.hasOwn(OPENAI_COMPAT_PRESETS, name) && !Object.hasOwn(PROVIDER_MODULES, name)) {
|
|
232
241
|
return wrapProviderAdmission(new Ctor(name, cfg), name);
|
|
233
242
|
}
|
|
234
243
|
return createAccountPoolProvider(name, () => wrapProviderAdmission(new Ctor(cfg), name));
|
|
235
244
|
}
|
|
236
245
|
|
|
246
|
+
// A custom provider's additional keys live in its key roster, not the config.
|
|
247
|
+
// Their ids are read once per init and join its entry (and so its reuse
|
|
248
|
+
// signature): a save that changes them rebuilds the provider, which is wrapped
|
|
249
|
+
// in the key pool only while it has some. A single-key provider's entry and
|
|
250
|
+
// sends stay as they were, with no roster read per send.
|
|
251
|
+
const CUSTOM_KEY_IDS = 'customKeyIds';
|
|
252
|
+
|
|
253
|
+
function withCustomKeyIds(config) {
|
|
254
|
+
if (!config || typeof config !== 'object') return config;
|
|
255
|
+
let out = config;
|
|
256
|
+
for (const [name, cfg] of Object.entries(config)) {
|
|
257
|
+
if (!isCustomProviderConfig(name, cfg) || cfg.auth === 'none') continue;
|
|
258
|
+
const ids = customProviderKeyIds(name);
|
|
259
|
+
if (!ids.length) continue;
|
|
260
|
+
if (out === config) out = { ...config };
|
|
261
|
+
out[name] = { ...cfg, [CUSTOM_KEY_IDS]: ids };
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
|
|
237
266
|
let _initProvidersCalls = 0;
|
|
238
267
|
/** Test seam: how many times initProviders was called in this process. */
|
|
239
268
|
export function _initProvidersCallCountForTest() {
|
|
@@ -249,6 +278,8 @@ export async function initProviders(config, { signal = null } = {}) {
|
|
|
249
278
|
if (proxyChannel) {
|
|
250
279
|
const { loadModelProxyProviderConfig } = await import('./model-proxy-provider.mjs');
|
|
251
280
|
config = await awaitWithAbort(loadModelProxyProviderConfig({ channel: proxyChannel }), signal);
|
|
281
|
+
} else {
|
|
282
|
+
config = withCustomKeyIds(config);
|
|
252
283
|
}
|
|
253
284
|
const sig = reuseSignature(config, proxyChannel);
|
|
254
285
|
// Coalesce: an identical config is already mid-init — attach to it.
|