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
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import { resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
|
|
2
|
+
import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
3
|
+
import { sanitizeToolPairs } from '../session/context-utils.mjs';
|
|
4
|
+
import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
|
|
5
|
+
import { buildGeminiRequest } from './gemini-request-body.mjs';
|
|
6
|
+
import { geminiEndsWithFunctionResponse, runGeminiStreamAttempts } from './gemini-retry-policy.mjs';
|
|
7
|
+
import { consumeGeminiRestStreamResponse } from './gemini-stream.mjs';
|
|
8
|
+
import { geminiTextLeakGuardFor } from './gemini-sdk-request.mjs';
|
|
9
|
+
import { createGeminiReasoningRelay } from './gemini-reasoning-relay.mjs';
|
|
10
|
+
import {
|
|
11
|
+
geminiFailureUsage,
|
|
12
|
+
geminiIncompleteError,
|
|
13
|
+
geminiSendResult,
|
|
14
|
+
parseGeminiCandidate,
|
|
15
|
+
resolveGeminiUsage,
|
|
16
|
+
} from './gemini-response.mjs';
|
|
17
|
+
import { emitGeminiToolCalls } from './gemini-schema.mjs';
|
|
18
|
+
import { geminiRestError } from './gemini.mjs';
|
|
19
|
+
import {
|
|
20
|
+
ANTHROPIC_RETRY_BACKOFF_MS,
|
|
21
|
+
ANTHROPIC_RETRY_JITTER_RATIO,
|
|
22
|
+
anthropicMaxAttempts,
|
|
23
|
+
canFallbackNonStreaming,
|
|
24
|
+
emitProviderRetryStage,
|
|
25
|
+
withRetry,
|
|
26
|
+
} from './retry-classifier.mjs';
|
|
27
|
+
import { buildAnthropicApiRequest } from './anthropic-api-request.mjs';
|
|
28
|
+
import { applyAnthropicErrorContract } from './anthropic-error-contract.mjs';
|
|
29
|
+
import { createAnthropicMidState, assertAnthropicStreamNotEmpty } from './anthropic-midstream-recovery.mjs';
|
|
30
|
+
import { parseSSEStream } from './anthropic-sse.mjs';
|
|
31
|
+
import { buildAnthropicTurnResult } from './anthropic-turn-result.mjs';
|
|
32
|
+
import { streamCallbacks } from './lib/send-callbacks.mjs';
|
|
33
|
+
import { preparePdfTextForProvider } from './media-normalization.mjs';
|
|
34
|
+
import { VERTEX_API_KEY_MODE, VertexAccessTokenManager } from './vertex-ai-auth.mjs';
|
|
35
|
+
import { VertexMaasRoute, toVertexExclusiveUsage, vertexErrorBody } from './vertex-ai-maas.mjs';
|
|
36
|
+
import {
|
|
37
|
+
VERTEX_FLEX_TIER,
|
|
38
|
+
claudeNeedsInterleavedBeta,
|
|
39
|
+
VERTEX_MODELS,
|
|
40
|
+
VERTEX_MULTI_REGIONS,
|
|
41
|
+
VERTEX_PRIORITY_TIER,
|
|
42
|
+
assertVertexServing,
|
|
43
|
+
vertexEnvLocation,
|
|
44
|
+
vertexModelAccessError,
|
|
45
|
+
vertexPricingModel,
|
|
46
|
+
vertexRequestedTier,
|
|
47
|
+
vertexRoute,
|
|
48
|
+
vertexServingProblem,
|
|
49
|
+
vertexTierOption,
|
|
50
|
+
} from './vertex-ai-models.mjs';
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
VERTEX_FLEX_TIER,
|
|
54
|
+
VERTEX_MODELS,
|
|
55
|
+
VERTEX_PRIORITY_TIER,
|
|
56
|
+
VERTEX_REGIONAL_PRICING_SUFFIX,
|
|
57
|
+
VERTEX_SERVICE_TIER_PARAMETER,
|
|
58
|
+
} from './vertex-ai-models.mjs';
|
|
59
|
+
|
|
60
|
+
const VERTEX_ANTHROPIC_VERSION = 'vertex-2023-10-16';
|
|
61
|
+
|
|
62
|
+
// Vertex takes Anthropic betas in the body (`anthropic_beta`), never as a
|
|
63
|
+
// header, and only these (effort, fast mode, server fallback and the other
|
|
64
|
+
// first-party betas are not offered); tool search has its own name there.
|
|
65
|
+
const INTERLEAVED_THINKING_BETA = 'interleaved-thinking-2025-05-14';
|
|
66
|
+
const VERTEX_CLAUDE_BETAS = new Map([
|
|
67
|
+
[INTERLEAVED_THINKING_BETA, INTERLEAVED_THINKING_BETA],
|
|
68
|
+
['advanced-tool-use-2025-11-20', 'tool-search-tool-2025-10-19'],
|
|
69
|
+
['context-1m-2025-08-07', 'context-1m-2025-08-07'],
|
|
70
|
+
['context-management-2025-06-27', 'context-management-2025-06-27'],
|
|
71
|
+
['compact-2026-01-12', 'compact-2026-01-12'],
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
// Interleaved thinking needs the beta only for a manual thinking budget with
|
|
75
|
+
// tools on the models that document it (adaptive thinking interleaves on its own).
|
|
76
|
+
function needsInterleavedThinking(body, model) {
|
|
77
|
+
return (
|
|
78
|
+
body.thinking?.type === 'enabled' &&
|
|
79
|
+
Array.isArray(body.tools) &&
|
|
80
|
+
body.tools.length > 0 &&
|
|
81
|
+
claudeNeedsInterleavedBeta(model)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The Vertex names of the selected `betas` this request body needs. */
|
|
86
|
+
function vertexClaudeBetas(betas, body, model) {
|
|
87
|
+
const out = new Set();
|
|
88
|
+
for (const beta of betas || []) {
|
|
89
|
+
const mapped = VERTEX_CLAUDE_BETAS.get(beta);
|
|
90
|
+
if (!mapped || (mapped === INTERLEAVED_THINKING_BETA && !needsInterleavedThinking(body, model))) continue;
|
|
91
|
+
out.add(mapped);
|
|
92
|
+
}
|
|
93
|
+
return [...out];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Flex and Priority PayGo are selected per request with
|
|
97
|
+
// X-Vertex-AI-LLM-Shared-Request-Type (Provisioned Throughput is still used
|
|
98
|
+
// first when the project has it). The served tier is usageMetadata.trafficType:
|
|
99
|
+
// ON_DEMAND_FLEX, ON_DEMAND_PRIORITY, or ON_DEMAND for a Priority request
|
|
100
|
+
// downgraded to Standard PayGo (billed at Standard rates).
|
|
101
|
+
const SERVED_TIER = Object.freeze({
|
|
102
|
+
ON_DEMAND_FLEX: VERTEX_FLEX_TIER,
|
|
103
|
+
FLEX: VERTEX_FLEX_TIER,
|
|
104
|
+
BATCH: VERTEX_FLEX_TIER,
|
|
105
|
+
ON_DEMAND_PRIORITY: VERTEX_PRIORITY_TIER,
|
|
106
|
+
ON_DEMAND: 'default',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// A Flex request queues server-side and may sit silent for minutes before its
|
|
110
|
+
// first chunk (the docs allow request timeouts up to 30 minutes), so the shared
|
|
111
|
+
// 60s first-chunk window would kill and replay a healthy one. Kept under the
|
|
112
|
+
// 600s socket body timeout (MIXDOG_LLM_BODY_TIMEOUT_MS).
|
|
113
|
+
const VERTEX_FLEX_FIRST_CHUNK_TIMEOUT_MS = 570_000;
|
|
114
|
+
|
|
115
|
+
// Multi-region locations are served by representative endpoints, not by
|
|
116
|
+
// <location>-aiplatform.googleapis.com.
|
|
117
|
+
function vertexHost(location) {
|
|
118
|
+
if (location === 'global') return 'aiplatform.googleapis.com';
|
|
119
|
+
if (VERTEX_MULTI_REGIONS.has(location)) return `aiplatform.${location}.rep.googleapis.com`;
|
|
120
|
+
return `${location}-aiplatform.googleapis.com`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Express mode: an API key on the global endpoint, with no project or location in the path.
|
|
124
|
+
const EXPRESS_BASE = 'https://aiplatform.googleapis.com/v1';
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The request's location, and whether the user configured it (`pinned`):
|
|
128
|
+
* without one, the environment's region (for a Claude model, its own first),
|
|
129
|
+
* else global.
|
|
130
|
+
*/
|
|
131
|
+
function vertexLocation(config, claudeId = '') {
|
|
132
|
+
const configured = String(config?.location || '').trim();
|
|
133
|
+
const location = (configured || vertexEnvLocation(process.env, claudeId) || 'global').trim().toLowerCase();
|
|
134
|
+
// The location names the host the bearer token is sent to: only a region
|
|
135
|
+
// label (or global / a multi-region) may reach the host template.
|
|
136
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(location)) throw new Error('[vertex-ai] location is invalid');
|
|
137
|
+
return { location, pinned: Boolean(configured) };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const servesAt = (route, location, tier) => !vertexServingProblem(route, location, tier);
|
|
141
|
+
|
|
142
|
+
const isNotFound = (error) => [error?.status, error?.httpStatus, error?.response?.status].map(Number).includes(404);
|
|
143
|
+
|
|
144
|
+
function vertexProjectOverride(config) {
|
|
145
|
+
const projectId = String(config?.projectId || '').trim();
|
|
146
|
+
if (projectId && (!/^[a-zA-Z0-9._:-]{1,128}$/.test(projectId) || projectId.includes('/'))) {
|
|
147
|
+
throw new Error('[vertex-ai] project ID override is invalid');
|
|
148
|
+
}
|
|
149
|
+
return projectId;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function geminiCallbacks(opts) {
|
|
153
|
+
const reasoning = createGeminiReasoningRelay(opts);
|
|
154
|
+
return {
|
|
155
|
+
onStreamDelta: typeof opts.onStreamDelta === 'function' ? opts.onStreamDelta : null,
|
|
156
|
+
onToolCall: typeof opts.onToolCall === 'function' ? opts.onToolCall : null,
|
|
157
|
+
onTextDelta: typeof opts.onTextDelta === 'function' ? opts.onTextDelta : null,
|
|
158
|
+
onReasoningDelta: reasoning.onReasoningDelta,
|
|
159
|
+
retractReasoning: reasoning.retract,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Vertex rejects (400) functionCall.id / functionResponse.id, which the
|
|
164
|
+
// Gemini API request builder carries; calls pair by name and order here.
|
|
165
|
+
function withoutFunctionIds(contents) {
|
|
166
|
+
const strip = (key, part) => {
|
|
167
|
+
const { id: _id, ...rest } = part[key];
|
|
168
|
+
return { ...part, [key]: rest };
|
|
169
|
+
};
|
|
170
|
+
return (contents || []).map((content) =>
|
|
171
|
+
Array.isArray(content?.parts)
|
|
172
|
+
? {
|
|
173
|
+
...content,
|
|
174
|
+
parts: content.parts.map((part) => {
|
|
175
|
+
if (part?.functionCall && 'id' in part.functionCall) return strip('functionCall', part);
|
|
176
|
+
if (part?.functionResponse && 'id' in part.functionResponse) return strip('functionResponse', part);
|
|
177
|
+
return part;
|
|
178
|
+
}),
|
|
179
|
+
}
|
|
180
|
+
: content
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function geminiVertexBody(request) {
|
|
185
|
+
return {
|
|
186
|
+
contents: withoutFunctionIds(request.contents),
|
|
187
|
+
// Vertex accepts only a Content object here, never a bare string.
|
|
188
|
+
...(request.systemInstruction ? { systemInstruction: { parts: [{ text: request.systemInstruction }] } } : {}),
|
|
189
|
+
...(request.generationConfig ? { generationConfig: request.generationConfig } : {}),
|
|
190
|
+
...(request.geminiTools ? { tools: request.geminiTools } : {}),
|
|
191
|
+
...(request.toolConfig ? { toolConfig: request.toolConfig } : {}),
|
|
192
|
+
...(request.safetySettings ? { safetySettings: request.safetySettings } : {}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class VertexAIProvider {
|
|
197
|
+
static inputExcludesCache = true;
|
|
198
|
+
nativePdf = true;
|
|
199
|
+
name = 'vertex-ai';
|
|
200
|
+
|
|
201
|
+
constructor(config = {}) {
|
|
202
|
+
this.config = config;
|
|
203
|
+
const customFetch = typeof config.fetchFn === 'function' ? config.fetchFn : null;
|
|
204
|
+
this._fetch = customFetch ?? fetch;
|
|
205
|
+
this._auth = new VertexAccessTokenManager({
|
|
206
|
+
credentialResolver: () => resolveProviderApiKey('vertex-ai', this.config),
|
|
207
|
+
authMode: () => this.config.authMode,
|
|
208
|
+
fetchFn: this._fetch,
|
|
209
|
+
now: typeof config.now === 'function' ? config.now : () => Date.now(),
|
|
210
|
+
});
|
|
211
|
+
this._maas = new VertexMaasRoute({
|
|
212
|
+
name: this.name,
|
|
213
|
+
config,
|
|
214
|
+
auth: this._auth,
|
|
215
|
+
fetchFn: customFetch,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
_projectId() {
|
|
220
|
+
return vertexProjectOverride(this.config) || this._auth.projectId();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Only a Flex route has a budget; the shared window covers the rest. Vertex
|
|
224
|
+
// never marks transport progress at the response headers, so the transport
|
|
225
|
+
// deadline must already cover the headers wait plus the first-chunk wait.
|
|
226
|
+
firstResponseBudget(_model, { modelParameters } = {}) {
|
|
227
|
+
if (vertexRequestedTier({ modelParameters }) !== VERTEX_FLEX_TIER) return null;
|
|
228
|
+
const ms = 2 * VERTEX_FLEX_FIRST_CHUNK_TIMEOUT_MS;
|
|
229
|
+
return { transportMs: ms, semanticMs: ms };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
_express() {
|
|
233
|
+
return this.config.authMode === VERTEX_API_KEY_MODE;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_expressKey() {
|
|
237
|
+
const key = String(resolveProviderApiKey('vertex-ai', this.config) || '').trim();
|
|
238
|
+
if (!key) throw new Error('[vertex-ai] the Vertex AI API key is not configured');
|
|
239
|
+
return key;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** `target`: the send's { identity, project } and the location it goes to. */
|
|
243
|
+
_locationBase({ location, project }) {
|
|
244
|
+
if (this._express()) return EXPRESS_BASE;
|
|
245
|
+
return `https://${vertexHost(location)}/v1/projects/${encodeURIComponent(project)}/locations/${location}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
_url(publisher, model, method, target) {
|
|
249
|
+
// A version suffix (claude-…@default, @YYYYMMDD) stays a literal `@` in the path.
|
|
250
|
+
const id = encodeURIComponent(model).replace(/%40/g, '@');
|
|
251
|
+
return `${this._locationBase(target)}/publishers/${publisher}/models/${id}:${method}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* `identity` is the account `url` was built for, read in the same tick as
|
|
256
|
+
* the URL before the first attempt; every attempt (retries included) must
|
|
257
|
+
* still run as that account, so a switch in between aborts the request.
|
|
258
|
+
* Express mode sends its API key instead (its URL names no project).
|
|
259
|
+
*/
|
|
260
|
+
async _authorizedFetch(url, init, identity) {
|
|
261
|
+
if (this._express()) {
|
|
262
|
+
const headers = new Headers(init?.headers || {});
|
|
263
|
+
headers.set('x-goog-api-key', this._expressKey());
|
|
264
|
+
return await this._fetch(url, { ...init, headers, dispatcher: getLlmDispatcher() });
|
|
265
|
+
}
|
|
266
|
+
let token = '';
|
|
267
|
+
const run = async () => {
|
|
268
|
+
token = await this._auth.getAccessToken({ identity });
|
|
269
|
+
this._auth.assertIdentity(identity);
|
|
270
|
+
const headers = new Headers(init?.headers || {});
|
|
271
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
272
|
+
return await this._fetch(url, { ...init, headers, dispatcher: getLlmDispatcher() });
|
|
273
|
+
};
|
|
274
|
+
let response = await run();
|
|
275
|
+
if (response.status === 401) {
|
|
276
|
+
this._auth.invalidate(token);
|
|
277
|
+
response = await run();
|
|
278
|
+
}
|
|
279
|
+
return response;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async send(messages, model, tools, sendOpts = {}) {
|
|
283
|
+
const route = vertexRoute(model);
|
|
284
|
+
const tier = vertexRequestedTier(sendOpts);
|
|
285
|
+
const express = this._express();
|
|
286
|
+
if (express && route.wire !== 'gemini') {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`[vertex-ai] ${route.publisher}/${route.id} needs a service-account key or the gcloud login: ` +
|
|
289
|
+
'a Vertex AI express mode API key serves Gemini models only'
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
const requested = vertexLocation(this.config, route.wire === 'claude' ? route.id : '');
|
|
293
|
+
const { pinned } = requested;
|
|
294
|
+
if (express && pinned && requested.location !== 'global') {
|
|
295
|
+
throw new Error(
|
|
296
|
+
'[vertex-ai] a Vertex AI express mode API key serves only the global location; set the location to global'
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
// A configured location is never moved. A region from the environment
|
|
300
|
+
// gives way to global when it cannot serve the request (or for express mode).
|
|
301
|
+
let location = express ? 'global' : requested.location;
|
|
302
|
+
if (!pinned && location !== 'global' && !servesAt(route, location, tier) && servesAt(route, 'global', tier)) {
|
|
303
|
+
location = 'global';
|
|
304
|
+
}
|
|
305
|
+
assertVertexServing(route, location, tier);
|
|
306
|
+
await this._auth.ready();
|
|
307
|
+
let result;
|
|
308
|
+
try {
|
|
309
|
+
// One account for the whole send, the region fallback included: its
|
|
310
|
+
// identity and project are read in one tick, and every attempt (on any
|
|
311
|
+
// location) must still run as it.
|
|
312
|
+
const account = express
|
|
313
|
+
? { identity: null, project: '' }
|
|
314
|
+
: { identity: this._auth.identity(), project: this._projectId() };
|
|
315
|
+
const sendAt = (at) => {
|
|
316
|
+
const target = { ...account, location: at };
|
|
317
|
+
if (route.wire === 'gemini') return this._sendGemini(messages, route.id, tools, sendOpts, tier, target);
|
|
318
|
+
if (route.wire === 'claude') return this._sendClaude(messages, route.id, tools, sendOpts, target);
|
|
319
|
+
return this._sendMaas(messages, route, tools, sendOpts, target);
|
|
320
|
+
};
|
|
321
|
+
try {
|
|
322
|
+
result = await sendAt(location);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
// A region from the environment that does not have the model (404) is retried once on global.
|
|
325
|
+
if (pinned || location === 'global' || !isNotFound(error) || !servesAt(route, 'global', tier)) throw error;
|
|
326
|
+
location = 'global';
|
|
327
|
+
result = await sendAt(location);
|
|
328
|
+
}
|
|
329
|
+
} catch (error) {
|
|
330
|
+
let project = '(unknown project)';
|
|
331
|
+
try {
|
|
332
|
+
project = this._projectId();
|
|
333
|
+
} catch {
|
|
334
|
+
// No resolvable project: keep the placeholder; the original error is rethrown below.
|
|
335
|
+
}
|
|
336
|
+
throw this._auth.accessError(vertexModelAccessError(error, route, { project, location }), { project });
|
|
337
|
+
}
|
|
338
|
+
const pricingModel = vertexPricingModel(route, location);
|
|
339
|
+
if (pricingModel && result && typeof result === 'object') result.pricingModel = pricingModel;
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async _sendMaas(messages, route, tools, opts, target) {
|
|
344
|
+
// Built for the account it is sent with.
|
|
345
|
+
const baseURL = `${this._locationBase(target)}/endpoints/openapi`;
|
|
346
|
+
return await this._maas.send(messages, `${route.publisher}/${route.id}`, tools, opts, {
|
|
347
|
+
baseURL,
|
|
348
|
+
identity: target.identity,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async _sendGemini(messages, model, tools, opts, tier, target) {
|
|
353
|
+
const signal = opts.signal || null;
|
|
354
|
+
const headers = {
|
|
355
|
+
'Content-Type': 'application/json',
|
|
356
|
+
...(tier ? { 'X-Vertex-AI-LLM-Shared-Request-Type': tier } : {}),
|
|
357
|
+
};
|
|
358
|
+
const failureUsage = geminiFailureUsage(opts, null, model);
|
|
359
|
+
const vertexFailureUsage = {
|
|
360
|
+
...failureUsage,
|
|
361
|
+
normalize: (um) => toVertexExclusiveUsage(failureUsage.normalize(um)),
|
|
362
|
+
};
|
|
363
|
+
const callbacks = geminiCallbacks(opts);
|
|
364
|
+
const request = buildGeminiRequest(messages, model, tools, opts, null);
|
|
365
|
+
const body = geminiVertexBody(request);
|
|
366
|
+
// One account for every attempt and the unary fallback: the send's.
|
|
367
|
+
const { identity } = target;
|
|
368
|
+
const streamUrl = `${this._url('google', model, 'streamGenerateContent', target)}?alt=sse`;
|
|
369
|
+
const unaryUrl = this._url('google', model, 'generateContent', target);
|
|
370
|
+
const stream = { ...request, opts, signal, useModel: model, tools, callbacks, cachedContent: null };
|
|
371
|
+
try {
|
|
372
|
+
opts.onStageChange?.('requesting');
|
|
373
|
+
} catch {
|
|
374
|
+
// A throwing progress callback must not fail the request.
|
|
375
|
+
}
|
|
376
|
+
// Every attempt sends the same tier; the served trafficType settles the price.
|
|
377
|
+
noteRequestServiceTier(tier === VERTEX_PRIORITY_TIER ? tier : '');
|
|
378
|
+
|
|
379
|
+
let outcome;
|
|
380
|
+
try {
|
|
381
|
+
outcome = await runGeminiStreamAttempts({
|
|
382
|
+
signal,
|
|
383
|
+
sleepFn: this.config.sleepFn,
|
|
384
|
+
afterToolResult: geminiEndsWithFunctionResponse(request.contents),
|
|
385
|
+
failureUsage: vertexFailureUsage,
|
|
386
|
+
run: async ({ signal: attemptSignal, connected }) => {
|
|
387
|
+
callbacks.retractReasoning();
|
|
388
|
+
const res = await this._authorizedFetch(
|
|
389
|
+
streamUrl,
|
|
390
|
+
{ method: 'POST', headers, body: JSON.stringify(body), signal: attemptSignal },
|
|
391
|
+
identity
|
|
392
|
+
);
|
|
393
|
+
if (!res.ok) {
|
|
394
|
+
const text = vertexErrorBody(await res.text().catch(() => ''));
|
|
395
|
+
throw geminiRestError(res, text, 'Vertex AI Gemini streamGenerateContent');
|
|
396
|
+
}
|
|
397
|
+
connected();
|
|
398
|
+
try {
|
|
399
|
+
opts.onStageChange?.('streaming');
|
|
400
|
+
} catch {
|
|
401
|
+
// A throwing progress callback must not fail the request.
|
|
402
|
+
}
|
|
403
|
+
const textLeakGuard = geminiTextLeakGuardFor(stream);
|
|
404
|
+
const response = await consumeGeminiRestStreamResponse(res, {
|
|
405
|
+
signal: attemptSignal,
|
|
406
|
+
onStreamDelta: callbacks.onStreamDelta,
|
|
407
|
+
onTextDelta: callbacks.onTextDelta,
|
|
408
|
+
onReasoningDelta: callbacks.onReasoningDelta,
|
|
409
|
+
textLeakGuard,
|
|
410
|
+
label: 'Vertex AI Gemini streamGenerateContent',
|
|
411
|
+
failureUsage: vertexFailureUsage,
|
|
412
|
+
...(tier === VERTEX_FLEX_TIER
|
|
413
|
+
? { firstByteTimeoutMs: VERTEX_FLEX_FIRST_CHUNK_TIMEOUT_MS, idleAfterFirstChunk: true }
|
|
414
|
+
: {}),
|
|
415
|
+
});
|
|
416
|
+
return { response, textLeakGuard };
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
} catch (streamError) {
|
|
420
|
+
if (!canFallbackNonStreaming(streamError, { signal })) throw streamError;
|
|
421
|
+
callbacks.retractReasoning();
|
|
422
|
+
const res = await this._authorizedFetch(
|
|
423
|
+
unaryUrl,
|
|
424
|
+
{ method: 'POST', headers, body: JSON.stringify(body), signal },
|
|
425
|
+
identity
|
|
426
|
+
);
|
|
427
|
+
if (!res.ok) {
|
|
428
|
+
const text = vertexErrorBody(await res.text().catch(() => ''));
|
|
429
|
+
throw geminiRestError(res, text, 'Vertex AI Gemini generateContent');
|
|
430
|
+
}
|
|
431
|
+
outcome = { response: await res.json(), textLeakGuard: null };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const parsed = parseGeminiCandidate(outcome.response, outcome.textLeakGuard);
|
|
435
|
+
const usage = resolveGeminiUsage(outcome.response, opts, null, model, this.name);
|
|
436
|
+
const incomplete = geminiIncompleteError(outcome.response, parsed, model, usage);
|
|
437
|
+
if (incomplete) throw incomplete;
|
|
438
|
+
outcome.textLeakGuard?.releaseDeferredToolCalls?.();
|
|
439
|
+
emitGeminiToolCalls(parsed.nativeToolCalls, callbacks.onToolCall);
|
|
440
|
+
const result = geminiSendResult(parsed, model, opts, toVertexExclusiveUsage(usage));
|
|
441
|
+
// The served tier decides the price: a Flex or Priority request can be
|
|
442
|
+
// served by Provisioned Throughput first, and a Priority one downgraded.
|
|
443
|
+
const served = SERVED_TIER[usage?.raw?.trafficType];
|
|
444
|
+
if (served) result.serviceTier = served;
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async _sendClaude(messages, model, tools, opts, target) {
|
|
449
|
+
messages = sanitizeToolPairs(messages);
|
|
450
|
+
messages = await preparePdfTextForProvider(messages, this);
|
|
451
|
+
const requestOpts = { ...opts, fast: false, serverFallback: false };
|
|
452
|
+
const { params, betas, knownToolNames } = await buildAnthropicApiRequest({
|
|
453
|
+
name: this.name,
|
|
454
|
+
config: { ...this.config, disableBetaHeaders: true },
|
|
455
|
+
messages,
|
|
456
|
+
useModel: model,
|
|
457
|
+
tools,
|
|
458
|
+
opts: requestOpts,
|
|
459
|
+
fastModeLatched: false,
|
|
460
|
+
capabilityModel: model,
|
|
461
|
+
});
|
|
462
|
+
const { model: _model, ...body } = params;
|
|
463
|
+
body.anthropic_version = VERTEX_ANTHROPIC_VERSION;
|
|
464
|
+
const vertexBetas = vertexClaudeBetas(betas, body, model);
|
|
465
|
+
if (vertexBetas.length) body.anthropic_beta = vertexBetas;
|
|
466
|
+
const callbacks = streamCallbacks(opts);
|
|
467
|
+
try {
|
|
468
|
+
callbacks.onStageChange?.('requesting');
|
|
469
|
+
} catch {
|
|
470
|
+
// A throwing progress callback must not fail the request.
|
|
471
|
+
}
|
|
472
|
+
// One account for every attempt: the send's.
|
|
473
|
+
const { identity } = target;
|
|
474
|
+
const url = this._url('anthropic', model, 'streamRawPredict', target);
|
|
475
|
+
const payload = JSON.stringify(body);
|
|
476
|
+
// The initial response retries like the first-party Anthropic route: 429
|
|
477
|
+
// and 529 (and 5xx) on its backoff, Retry-After honored.
|
|
478
|
+
const response = await withRetry(
|
|
479
|
+
async ({ signal: attemptSignal }) => {
|
|
480
|
+
const res = await this._authorizedFetch(
|
|
481
|
+
url,
|
|
482
|
+
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload, signal: attemptSignal },
|
|
483
|
+
identity
|
|
484
|
+
);
|
|
485
|
+
if (res.ok) return res;
|
|
486
|
+
const text = await res.text().catch(() => '');
|
|
487
|
+
let data = null;
|
|
488
|
+
try {
|
|
489
|
+
data = JSON.parse(vertexErrorBody(text));
|
|
490
|
+
} catch {
|
|
491
|
+
// A non-JSON body carries no google.rpc.Status; the status code stands alone.
|
|
492
|
+
}
|
|
493
|
+
const error = Object.assign(
|
|
494
|
+
new Error(`Vertex AI Claude streamRawPredict ${res.status}: ${text.slice(0, 300)}`),
|
|
495
|
+
{
|
|
496
|
+
...(data ? { data } : {}),
|
|
497
|
+
status: res.status,
|
|
498
|
+
httpStatus: res.status,
|
|
499
|
+
headers: res.headers,
|
|
500
|
+
response: { status: res.status, headers: res.headers },
|
|
501
|
+
initialResponseError: true,
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
throw applyAnthropicErrorContract(error, { status: res.status, headers: res.headers, bodyText: text });
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
signal: opts.signal || null,
|
|
508
|
+
maxAttempts: anthropicMaxAttempts(),
|
|
509
|
+
backoffMs: ANTHROPIC_RETRY_BACKOFF_MS,
|
|
510
|
+
retryJitterRatio: ANTHROPIC_RETRY_JITTER_RATIO,
|
|
511
|
+
retryJitterMode: 'positive',
|
|
512
|
+
recoveryOwner: 'vertex-ai-claude-initial-response',
|
|
513
|
+
retry529: opts.retry529,
|
|
514
|
+
sleepFn: this.config.sleepFn,
|
|
515
|
+
onRetry: ({ attempt, maxAttempts, lastErr, delayMs }) =>
|
|
516
|
+
emitProviderRetryStage(callbacks.onStageChange, { attempt: attempt + 1, maxAttempts, lastErr, delayMs }),
|
|
517
|
+
}
|
|
518
|
+
);
|
|
519
|
+
try {
|
|
520
|
+
callbacks.onStageChange?.('streaming');
|
|
521
|
+
} catch {
|
|
522
|
+
// A throwing progress callback must not fail the request.
|
|
523
|
+
}
|
|
524
|
+
const controller = new AbortController();
|
|
525
|
+
const externalSignal = opts.signal || null;
|
|
526
|
+
const abort = () => controller.abort(externalSignal?.reason);
|
|
527
|
+
if (externalSignal) {
|
|
528
|
+
if (externalSignal.aborted) abort();
|
|
529
|
+
else externalSignal.addEventListener('abort', abort, { once: true });
|
|
530
|
+
}
|
|
531
|
+
const midState = createAnthropicMidState(0);
|
|
532
|
+
try {
|
|
533
|
+
const parsed = await parseSSEStream(
|
|
534
|
+
response,
|
|
535
|
+
controller.signal,
|
|
536
|
+
(reason) => controller.abort(reason),
|
|
537
|
+
callbacks.onStreamDelta,
|
|
538
|
+
callbacks.onToolCall,
|
|
539
|
+
midState,
|
|
540
|
+
callbacks.onTextDelta,
|
|
541
|
+
knownToolNames,
|
|
542
|
+
{ onStreamItemStart: callbacks.onStreamItemStart }
|
|
543
|
+
);
|
|
544
|
+
assertAnthropicStreamNotEmpty(midState, parsed, 'Vertex AI Claude');
|
|
545
|
+
return buildAnthropicTurnResult(parsed, { provider: this.name, useModel: model, opts });
|
|
546
|
+
} finally {
|
|
547
|
+
externalSignal?.removeEventListener?.('abort', abort);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async listModels() {
|
|
552
|
+
return VERTEX_MODELS.map((model) => {
|
|
553
|
+
const tierOption = vertexTierOption(model.id);
|
|
554
|
+
return { ...model, provider: this.name, ...(tierOption ? { modelParameterOptions: [tierOption] } : {}) };
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async isAvailable() {
|
|
559
|
+
try {
|
|
560
|
+
if (this._express()) return Boolean(this._expressKey());
|
|
561
|
+
await this._auth.ready();
|
|
562
|
+
return Boolean(this._projectId());
|
|
563
|
+
} catch {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
@@ -22,6 +22,7 @@ import { HEADLESS_MODEL_TOOL_NAMES, HEADLESS_TOOL_PROFILE, normalizeToolProfile
|
|
|
22
22
|
import { DEFERRED_DEFAULT_LEAD_TOOLS } from './tool-catalog-data.mjs';
|
|
23
23
|
import { gitExecutablePresent } from '../../../shared/path-executable.mjs';
|
|
24
24
|
import { onnxRuntimeSupported } from '../../../shared/onnx-runtime-support.mjs';
|
|
25
|
+
import { exploreRouteConfigured } from '../../../shared/agent-route-config.mjs';
|
|
25
26
|
import { setBuiltinInstalledInConfig, withGrandfatheredBuiltins } from '../../../shared/builtin-grandfathering.mjs';
|
|
26
27
|
|
|
27
28
|
// Browser Use / Computer Use have no install marker: the desktop app publishes
|
|
@@ -158,6 +159,7 @@ export function featureDisallowedToolsFor(
|
|
|
158
159
|
...(builtinFeatureActive(configLike, 'media') ? [] : ['media']),
|
|
159
160
|
...(builtinFeatureActive(configLike, 'tidy') ? [] : ['tidy']),
|
|
160
161
|
...(builtinFeatureActive(configLike, 'visualize') ? [] : ['visualize']),
|
|
162
|
+
...(exploreRouteConfigured(configLike) ? [] : ['explore']),
|
|
161
163
|
// Bots' emoji reactions (Settings → General → Reactions); on unless turned off.
|
|
162
164
|
...(configLike?.reactions === false ? ['react'] : []),
|
|
163
165
|
];
|
|
@@ -51,7 +51,8 @@ already holds — Lead makes them directly in every mode; the number of files or
|
|
|
51
51
|
scopes does not change this.
|
|
52
52
|
|
|
53
53
|
Dispatch all ready independent scopes in one turn. Only a scope that requires
|
|
54
|
-
another's output waits.
|
|
54
|
+
another's output waits. At most two building or testing scopes run at
|
|
55
|
+
once.
|
|
55
56
|
A new independent need goes to a new agent. When scopes touch the same file, name one owner for
|
|
56
57
|
that file in each brief.
|
|
57
58
|
Use only available agents; when none fits a role, Lead does that work itself
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Canonical route order — the single surface order wherever tools serialize:
|
|
2
2
|
// locator → path → content → symbol → read → edit → execute.
|
|
3
3
|
export const ROUTE_TOOL_ORDER = Object.freeze([
|
|
4
|
+
'explore',
|
|
4
5
|
'find',
|
|
5
6
|
'glob',
|
|
6
7
|
'list',
|
|
@@ -58,6 +59,8 @@ export const DEFERRED_DEFAULT_READONLY_TOOLS = Object.freeze([
|
|
|
58
59
|
'load_tool',
|
|
59
60
|
]);
|
|
60
61
|
export const DEFERRED_DEFAULT_LEAD_TOOLS = Object.freeze([
|
|
62
|
+
// Present only once Explore has a model (builtin-features.mjs).
|
|
63
|
+
'explore',
|
|
61
64
|
'find',
|
|
62
65
|
'glob',
|
|
63
66
|
'list',
|
|
@@ -240,6 +240,7 @@ const OFFICE_RUNTIME_TOOLS = ['office'];
|
|
|
240
240
|
const MEDIA_RUNTIME_TOOLS = ['media'];
|
|
241
241
|
const TIDY_RUNTIME_TOOLS = ['tidy'];
|
|
242
242
|
const VISUALIZE_RUNTIME_TOOLS = ['visualize'];
|
|
243
|
+
const EXPLORE_RUNTIME_TOOLS = ['explore'];
|
|
243
244
|
|
|
244
245
|
const admitted = (tools, names) => (tools || []).filter((tool) => names.includes(tool?.name));
|
|
245
246
|
|
|
@@ -262,6 +263,7 @@ export function collectStandaloneToolDefs({
|
|
|
262
263
|
mediaToolDefs = [],
|
|
263
264
|
tidyToolDefs = [],
|
|
264
265
|
visualizeToolDefs = [],
|
|
266
|
+
exploreToolDefs = [],
|
|
265
267
|
setupToolDefs = [],
|
|
266
268
|
askUserToolDefs = [],
|
|
267
269
|
handoffUserToolDefs = [],
|
|
@@ -285,6 +287,7 @@ export function collectStandaloneToolDefs({
|
|
|
285
287
|
...admitted(mediaToolDefs, MEDIA_RUNTIME_TOOLS),
|
|
286
288
|
...admitted(tidyToolDefs, TIDY_RUNTIME_TOOLS),
|
|
287
289
|
...admitted(visualizeToolDefs, VISUALIZE_RUNTIME_TOOLS),
|
|
290
|
+
...admitted(exploreToolDefs, EXPLORE_RUNTIME_TOOLS),
|
|
288
291
|
...setupToolDefs,
|
|
289
292
|
...askUserToolDefs,
|
|
290
293
|
...handoffUserToolDefs,
|
|
@@ -4,6 +4,8 @@ import { clean } from '../session-text.mjs';
|
|
|
4
4
|
import { configuredOrchestrationMode } from '../../../../shared/orchestration.mjs';
|
|
5
5
|
import { AGENT_MODE_DELEGATION, MIXY_CONTACTS_INTRO, orchestrationInstructions } from '../orchestration.mjs';
|
|
6
6
|
import { DEFAULT_WORKFLOW_ID, isContactAgentId, normalizeWorkflowId } from '../workflow-ids.mjs';
|
|
7
|
+
import { MIXY_AGENT_ID } from '../../session/agent-home.mjs';
|
|
8
|
+
import { mixyDisplayName } from '../../session/mixy-name.mjs';
|
|
7
9
|
|
|
8
10
|
export function createWorkflowContext({ packs, agents }) {
|
|
9
11
|
function workflowSummary(pack, { hasAgents = true } = {}) {
|
|
@@ -59,7 +61,9 @@ export function createWorkflowContext({ packs, agents }) {
|
|
|
59
61
|
// - coding: the orchestration mode block and the full agent catalog;
|
|
60
62
|
// - agent mode: the fixed delegation block (no per-agent setting) and the
|
|
61
63
|
// sub-agent catalog — identical for every agent, so their prompts share
|
|
62
|
-
// it — plus the contact-agent catalog: every bot but `agentId` itself
|
|
64
|
+
// it — plus the contact-agent catalog: every bot but `agentId` itself,
|
|
65
|
+
// and for any bot but Mixy, Mixy first under her display name, so a bot
|
|
66
|
+
// asked to hand work to her by that name finds her (session send lead).
|
|
63
67
|
function leadPromptParts(config, dir, { agentMode = false, agentId = '' } = {}) {
|
|
64
68
|
const mode = configuredOrchestrationMode(config);
|
|
65
69
|
const ids = agents.delegatableAgentIds(config, dir);
|
|
@@ -79,10 +83,15 @@ export function createWorkflowContext({ packs, agents }) {
|
|
|
79
83
|
dir,
|
|
80
84
|
ids.filter((id) => !isContact(id))
|
|
81
85
|
);
|
|
82
|
-
const contacts =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
const contacts = [
|
|
87
|
+
agentId === MIXY_AGENT_ID ? '' : `- ${mixyDisplayName()} (${MIXY_AGENT_ID}): the user's representative bot`,
|
|
88
|
+
agentCatalog(
|
|
89
|
+
dir,
|
|
90
|
+
ids.filter((id) => isContact(id) && id !== agentId)
|
|
91
|
+
),
|
|
92
|
+
]
|
|
93
|
+
.filter(Boolean)
|
|
94
|
+
.join('\n');
|
|
86
95
|
return {
|
|
87
96
|
// An agent-mode Lead without agents has no one to delegate to at all.
|
|
88
97
|
orchestrationContext: subAgents || contacts ? AGENT_MODE_DELEGATION : '',
|
|
@@ -7,6 +7,7 @@ export const AGENT_DELETED_MARKER = '.deleted';
|
|
|
7
7
|
export const FIXED_AGENT_SLOTS = Object.freeze([
|
|
8
8
|
// Short one-liners on purpose: these render inside the 260px sidebar rail.
|
|
9
9
|
{ id: 'maintainer', label: 'Maintainer', description: 'Memory and upkeep', workflowSlot: 'memory' },
|
|
10
|
+
{ id: 'explore', label: 'Explore', description: 'Codebase exploration', workflowSlot: 'explore' },
|
|
10
11
|
]);
|
|
11
12
|
export const AGENT_ROLE_IDS = new Set(FIXED_AGENT_SLOTS.map((agent) => agent.id));
|
|
12
13
|
// Slot-backed built-ins run through dedicated maintenance channels, so they are never
|