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.
Files changed (326) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/gate-local.mjs +172 -61
  4. package/scripts/lib/gate-scheduler.mjs +138 -32
  5. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  6. package/scripts/lib/run-node-tests.mjs +54 -29
  7. package/scripts/lib/scoped-temp.mjs +1 -0
  8. package/scripts/lib/test-failure-records.mjs +14 -1
  9. package/scripts/lib/test-failure-summary.mjs +16 -4
  10. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  11. package/scripts/pptx-kit-index.mjs +5 -6
  12. package/scripts/suite-health.mjs +110 -28
  13. package/scripts/test.mjs +9 -14
  14. package/src/agents/advisor/AGENT.md +4 -4
  15. package/src/agents/advisor/agent.json +1 -1
  16. package/src/agents/explore/AGENT.md +22 -0
  17. package/src/agents/explore/agent.json +6 -0
  18. package/src/cli.mjs +7 -0
  19. package/src/defaults/agents.json +7 -0
  20. package/src/defaults/shipped-history.json +18 -2
  21. package/src/defaults/skills/browser-use/SKILL.md +4 -1
  22. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  23. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  24. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  25. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  26. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  27. package/src/defaults/skills/computer-use/SKILL.md +12 -2
  28. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  29. package/src/defaults/skills/setup/references/actions.md +12 -6
  30. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  31. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  32. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  33. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  34. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  35. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  36. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  37. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  38. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  39. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  40. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  41. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  42. package/src/defaults/skills/visualize/SKILL.md +6 -2
  43. package/src/lib/rules-builder.cjs +6 -30
  44. package/src/rules/lead/LEAD.md +3 -3
  45. package/src/rules/shared/20-research.md +3 -6
  46. package/src/rules/shared/40-editing.md +1 -0
  47. package/src/rules/shared/60-verification.md +10 -9
  48. package/src/rules/shared/70-delivery.md +4 -3
  49. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  50. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +130 -21
  52. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  53. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  54. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  55. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +7 -7
  56. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  57. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  58. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  59. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  60. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  61. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  62. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  63. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +83 -52
  64. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  65. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +43 -8
  66. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +30 -28
  67. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  69. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  74. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  75. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  78. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  79. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  80. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +48 -31
  81. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  82. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  83. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  85. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +26 -28
  88. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +3 -1
  89. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  92. package/src/runtime/agent/orchestrator/providers/registry.mjs +33 -2
  93. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  94. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  95. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  96. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  97. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  98. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +2 -1
  99. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  100. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  102. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  103. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  104. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  105. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +94 -10
  106. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +3 -3
  107. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +10 -5
  108. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +5 -5
  109. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +11 -1
  110. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  111. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  112. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +5 -3
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +21 -2
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  116. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  117. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  118. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +3 -4
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  123. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -14
  124. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +17 -14
  125. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  126. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  127. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -3
  128. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +47 -29
  129. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  130. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  131. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +1 -1
  132. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +243 -38
  133. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  134. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  135. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  136. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  137. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -26
  138. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +3 -5
  139. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  141. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  142. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  143. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  144. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  145. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  147. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  148. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  149. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  150. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  151. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  154. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  155. package/src/runtime/channels/lib/whisper-port.mjs +19 -10
  156. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  157. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  158. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  159. package/src/runtime/explore/explorer.mjs +93 -0
  160. package/src/runtime/explore/tool-defs.mjs +35 -0
  161. package/src/runtime/explore/tool.mjs +123 -0
  162. package/src/runtime/memory/index.mjs +4 -0
  163. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  164. package/src/runtime/memory/lib/embedding-worker.mjs +4 -0
  165. package/src/runtime/office/quality/assurance-structure-docx.mjs +1 -1
  166. package/src/runtime/shared/agent-route-config.mjs +17 -2
  167. package/src/runtime/shared/atomic-file.mjs +103 -2
  168. package/src/runtime/shared/background-tasks.mjs +15 -6
  169. package/src/runtime/shared/connector-accounts.mjs +5 -5
  170. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  171. package/src/runtime/shared/connector-catalog.mjs +60 -52
  172. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  173. package/src/runtime/shared/file-has-data.mjs +26 -2
  174. package/src/runtime/shared/file-lock.mjs +13 -15
  175. package/src/runtime/shared/git-launch.d.mts +9 -0
  176. package/src/runtime/shared/git-launch.mjs +213 -0
  177. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  178. package/src/runtime/shared/llm/cost.mjs +6 -1
  179. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  180. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  181. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  182. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  183. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  184. package/src/runtime/shared/provider-accounts.mjs +52 -2
  185. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  186. package/src/runtime/shared/staged-update.mjs +47 -2
  187. package/src/runtime/shared/tool-surface.mjs +110 -36
  188. package/src/runtime/shared/tool-work-units.mjs +4 -0
  189. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  190. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  191. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  192. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  193. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  194. package/src/runtime/shared/worktrees/git.mjs +57 -0
  195. package/src/runtime/shared/worktrees/manager.mjs +205 -137
  196. package/src/runtime/tidy/install.mjs +95 -15
  197. package/src/runtime/tidy/languages.mjs +17 -15
  198. package/src/runtime/tidy/process.mjs +7 -4
  199. package/src/runtime/tidy/resolve.mjs +19 -16
  200. package/src/runtime/tidy/runners/biome.mjs +3 -3
  201. package/src/runtime/tidy/tool.mjs +1 -1
  202. package/src/runtime/tidy/worktree.mjs +10 -6
  203. package/src/runtime/visualize-bridge/client.mjs +4 -1
  204. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  205. package/src/runtime/web-search/index.mjs +1 -1
  206. package/src/session-runtime/agent-task-sessions.mjs +410 -353
  207. package/src/session-runtime/boot/tools.mjs +2 -0
  208. package/src/session-runtime/bot-status.mjs +103 -103
  209. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  210. package/src/session-runtime/conversation-import/discovery.mjs +159 -40
  211. package/src/session-runtime/conversation-import/service.mjs +9 -3
  212. package/src/session-runtime/handoff-attachments.mjs +1 -1
  213. package/src/session-runtime/held-requests.mjs +3 -1
  214. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  215. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  216. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +143 -116
  217. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  218. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  219. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  220. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  221. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  222. package/src/session-runtime/provider-auth/login.mjs +2 -2
  223. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  224. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  225. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  226. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  227. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  228. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  229. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  230. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  231. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  232. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  233. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  234. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  235. package/src/session-runtime/services/provider-admin.mjs +281 -10
  236. package/src/session-runtime/session-turn-api.mjs +2 -0
  237. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  238. package/src/session-runtime/settings-system-api.mjs +1 -1
  239. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  240. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  241. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  242. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  243. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  244. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  245. package/src/session-runtime/workflow-agents-api/agent-route.mjs +10 -1
  246. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  247. package/src/session-runtime/workspace/session-view.mjs +149 -0
  248. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  249. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  250. package/src/session-runtime/workspace/transitions.mjs +108 -1066
  251. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  252. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  253. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  254. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  255. package/src/session-runtime/workspace/worktree-tool.mjs +54 -54
  256. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  257. package/src/standalone/daemon-session-control.mjs +151 -136
  258. package/src/standalone/daemon.mjs +7 -0
  259. package/src/standalone/desktop-service-registry.mjs +9 -0
  260. package/src/standalone/local-session-runtime.mjs +5 -1
  261. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  262. package/src/standalone/session-client.mjs +5 -1
  263. package/src/standalone/session-frame-stream.mjs +83 -11
  264. package/src/standalone/session-protocol.mjs +4 -0
  265. package/src/standalone/session-service/agent-tree.mjs +45 -9
  266. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  267. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  268. package/src/standalone/session-service/stored-reader.mjs +127 -40
  269. package/src/tui/App.jsx +154 -164
  270. package/src/tui/app/app-view/floating-panel.jsx +3 -0
  271. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  272. package/src/tui/app/clipboard.mjs +21 -10
  273. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  274. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  275. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  276. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  277. package/src/tui/app/provider-setup/oauth-actions.mjs +25 -6
  278. package/src/tui/app/provider-setup-picker.mjs +20 -13
  279. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  280. package/src/tui/app/slash-commands.mjs +1 -0
  281. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  282. package/src/tui/app/slash-dispatch.mjs +1 -1
  283. package/src/tui/app/use-btw-panel.mjs +29 -0
  284. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  285. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  286. package/src/tui/app/use-mouse-input.mjs +11 -0
  287. package/src/tui/app/use-onboarding-start.mjs +8 -3
  288. package/src/tui/app/use-panel-transition.mjs +10 -1
  289. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  290. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  291. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  292. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  293. package/src/tui/app/use-selection-focus.mjs +11 -2
  294. package/src/tui/app/use-selection-geometry.mjs +18 -9
  295. package/src/tui/app/use-selection-paint.mjs +4 -4
  296. package/src/tui/app/use-selection-stitch.mjs +3 -3
  297. package/src/tui/app/use-slash-palette.mjs +62 -0
  298. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  299. package/src/tui/app/use-transcript-activity.mjs +47 -32
  300. package/src/tui/app/use-transcript-follow.mjs +12 -4
  301. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  302. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  303. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  304. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  305. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  306. package/src/tui/app/use-transcript-window.mjs +21 -11
  307. package/src/tui/app/use-tui-ready.mjs +18 -0
  308. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  309. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  310. package/src/tui/components/Picker.jsx +27 -3
  311. package/src/tui/components/SignInUrl.jsx +68 -0
  312. package/src/tui/components/TextEntryPanel.jsx +76 -46
  313. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  314. package/src/tui/dist/index.mjs +1896 -1341
  315. package/src/tui/session/goal-turn-state.mjs +6 -2
  316. package/src/tui/session/session-api/controls.mjs +4 -0
  317. package/src/tui/session/session-api/extensions.mjs +8 -0
  318. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  319. package/src/tui/session/session-api/intake.mjs +2 -0
  320. package/src/tui/session/session-api/integrations.mjs +2 -2
  321. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  322. package/src/tui/session/session-api/settings.mjs +2 -2
  323. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  324. package/src/tui/session/transcript-spill.mjs +92 -4
  325. package/src/tui/session/transcript-store.mjs +2 -0
  326. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -0,0 +1,185 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { currentUsageContext } from '../../../shared/llm/usage-context.mjs';
3
+ import { noteErrorUsage } from './lib/note-error-usage.mjs';
4
+ import { OpenAICompatProvider } from './openai-compat.mjs';
5
+ import { vertexHttpStatus } from './vertex-ai-models.mjs';
6
+
7
+ // Gives an OpenAI-compatible engine the current access token of one account
8
+ // (from the Vertex token manager) as its API key. The engine never resolves a
9
+ // key of its own, so a stored credential is never sent as a bearer.
10
+ export class ApiKeySetter {
11
+ constructor(auth) {
12
+ this.auth = auth;
13
+ }
14
+
15
+ async set(engine, identity) {
16
+ const token = await this.auth.getAccessToken({ identity });
17
+ this.auth.assertIdentity(identity);
18
+ if (engine.apiKey !== token) {
19
+ engine.apiKey = token;
20
+ engine.config = { ...engine.config, apiKey: token };
21
+ engine.client = engine._createClient(token);
22
+ }
23
+ return token;
24
+ }
25
+ }
26
+
27
+ // Vertex usage is cache-exclusive (VertexAIProvider.inputExcludesCache): input
28
+ // is the uncached remainder. Gemini's promptTokenCount and Chat Completions'
29
+ // prompt_tokens both include the cached prefix, so both routes convert here.
30
+ // One source object always converts to the same object, and a converted one
31
+ // passes through unchanged, so an abandoned attempt stays one record.
32
+ const exclusiveUsage = new WeakSet();
33
+ const convertedUsage = new WeakMap();
34
+
35
+ export function toVertexExclusiveUsage(usage) {
36
+ if (!usage || typeof usage !== 'object' || exclusiveUsage.has(usage)) return usage;
37
+ if (convertedUsage.has(usage)) return convertedUsage.get(usage);
38
+ const input = Number(usage.inputTokens) || 0;
39
+ const cached = (Number(usage.cachedTokens) || 0) + (Number(usage.cacheWriteTokens) || 0);
40
+ const converted = {
41
+ ...usage,
42
+ inputTokens: Math.max(0, input - cached),
43
+ promptTokens: Math.max(Number(usage.promptTokens) || 0, input),
44
+ };
45
+ exclusiveUsage.add(converted);
46
+ convertedUsage.set(usage, converted);
47
+ return converted;
48
+ }
49
+
50
+ function exclusiveError(error) {
51
+ if (error && typeof error === 'object') {
52
+ if (error.usage) error.usage = toVertexExclusiveUsage(error.usage);
53
+ if (error.partialUsage) error.partialUsage = toVertexExclusiveUsage(error.partialUsage);
54
+ }
55
+ return error;
56
+ }
57
+
58
+ const emittedOutput = (error) =>
59
+ error?.liveTextEmitted === true || error?.emittedToolCall === true || error?.unsafeToRetry === true;
60
+
61
+ /**
62
+ * Vertex may answer an error with a JSON array of error objects
63
+ * (`[{"error":{…}}]`); the first entry is the usual `{"error":{…}}` body.
64
+ * Any other text is returned unchanged.
65
+ */
66
+ export function vertexErrorBody(text) {
67
+ try {
68
+ const parsed = JSON.parse(text);
69
+ if (Array.isArray(parsed) && parsed[0]?.error) return JSON.stringify(parsed[0]);
70
+ } catch {}
71
+ return text;
72
+ }
73
+
74
+ // The account one send targets ({ identity, token }), read by its engine's
75
+ // fetch on every HTTP attempt, the engine's own retries included.
76
+ const sendAccount = new AsyncLocalStorage();
77
+
78
+ // Each HTTP attempt carries the send's account's current token, and a
79
+ // credential switch since the send began aborts it before anything is sent.
80
+ // The OpenAI SDK reads an array error body as "no body", so it is unwrapped.
81
+ function vertexEngineFetch(fetchFn, auth) {
82
+ return async (url, init) => {
83
+ const account = sendAccount.getStore();
84
+ let request = init;
85
+ if (account) {
86
+ account.token = await auth.getAccessToken({ identity: account.identity });
87
+ auth.assertIdentity(account.identity);
88
+ const headers = new Headers(init?.headers || {});
89
+ headers.set('Authorization', `Bearer ${account.token}`);
90
+ request = { ...init, headers };
91
+ }
92
+ const response = await fetchFn(url, request);
93
+ if (response.ok || !/json/i.test(response.headers.get('content-type') || '')) return response;
94
+ const body = vertexErrorBody(await response.text());
95
+ return new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers });
96
+ };
97
+ }
98
+
99
+ // Open-model reasoning controls (Thinking for open models, checked
100
+ // 2026-10-28): gpt-oss takes `reasoning_effort` low/medium/high. Grok
101
+ // reasoning models reject it; the others document no effort parameter.
102
+ const GPT_OSS_EFFORTS = new Set(['low', 'medium', 'high']);
103
+ const MAAS_CHAT_PROFILE = Object.freeze({
104
+ applyParams(params, { opts, config }) {
105
+ if (!/^openai\/gpt-oss-/i.test(String(params.model || ''))) return;
106
+ const effort = String(opts?.effort ?? config?.reasoningEffort ?? '')
107
+ .trim()
108
+ .toLowerCase();
109
+ if (GPT_OSS_EFFORTS.has(effort)) params.reasoning_effort = effort;
110
+ },
111
+ // The SDK wraps a fetch failure as a (retryable) connection error; a
112
+ // credential switch surfaces as itself, so no attempt retries it.
113
+ refineError(error) {
114
+ return error?.cause?.code === 'EVERTEXAUTH' ? error.cause : error;
115
+ },
116
+ });
117
+
118
+ // The OpenAI-compatible transport on a Vertex bearer: its own key lookup and
119
+ // 401 re-read would reach the stored service-account JSON, so the route sends
120
+ // through _doSend and owns the credential.
121
+ class VertexMaasEngine extends OpenAICompatProvider {
122
+ reloadApiKey() {}
123
+ }
124
+
125
+ /**
126
+ * Open and partner models (every publisher but Google's Gemini and Anthropic)
127
+ * on `projects/P/locations/L/endpoints/openapi/chat/completions`.
128
+ */
129
+ export class VertexMaasRoute {
130
+ constructor({ name, config, auth, fetchFn }) {
131
+ this.name = name;
132
+ this.config = config;
133
+ this.auth = auth;
134
+ this.fetchFn = fetchFn;
135
+ this.setter = new ApiKeySetter(auth);
136
+ this.engines = new Map();
137
+ }
138
+
139
+ _engine(baseURL, token) {
140
+ let engine = this.engines.get(baseURL);
141
+ if (!engine) {
142
+ const { apiKey: _key, apiKeyEnv: _env, fetchFn: _fetch, now: _now, sleepFn: _sleep, ...config } = this.config;
143
+ engine = new VertexMaasEngine(this.name, {
144
+ ...config,
145
+ baseURL,
146
+ apiKey: token,
147
+ chatProfile: MAAS_CHAT_PROFILE,
148
+ // Explicit nulls keep OPENAI_ORG_ID / OPENAI_PROJECT_ID out of the requests.
149
+ clientOptions: {
150
+ organization: null,
151
+ project: null,
152
+ fetch: vertexEngineFetch(this.fetchFn || ((url, init) => fetch(url, init)), this.auth),
153
+ },
154
+ });
155
+ this.engines.set(baseURL, engine);
156
+ }
157
+ return engine;
158
+ }
159
+
160
+ /** `baseURL` and `identity` are the account the caller built the URL for. */
161
+ async send(messages, model, tools, opts, { baseURL, identity }) {
162
+ const identityContext = currentUsageContext();
163
+ if (identityContext) identityContext.normalizeAbandonedUsage = toVertexExclusiveUsage;
164
+ const token = await this.auth.getAccessToken({ identity });
165
+ const engine = this._engine(baseURL, token);
166
+ const attempt = async () => {
167
+ const account = { identity, token: await this.setter.set(engine, identity) };
168
+ try {
169
+ return { result: await sendAccount.run(account, () => engine._doSend(messages, model, tools, opts)) };
170
+ } catch (error) {
171
+ noteErrorUsage(error);
172
+ // The token the last HTTP attempt actually carried.
173
+ return { error, token: account.token };
174
+ }
175
+ };
176
+ let outcome = await attempt();
177
+ if (outcome.error && vertexHttpStatus(outcome.error) === 401 && !emittedOutput(outcome.error)) {
178
+ this.auth.invalidate(outcome.token);
179
+ outcome = await attempt();
180
+ }
181
+ if (outcome.error) throw exclusiveError(outcome.error);
182
+ const { result } = outcome;
183
+ return result?.usage ? { ...result, usage: toVertexExclusiveUsage(result.usage) } : result;
184
+ }
185
+ }
@@ -0,0 +1,445 @@
1
+ import { geminiBareModelId } from './gemini-cache-policy.mjs';
2
+
3
+ // Vertex AI model catalog and per-model endpoint rules (checked 2026-10-28):
4
+ // - Gemini lifecycle: https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/model-versions
5
+ // - Claude ids: https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/partner-models/claude/use-claude
6
+ // - Partner endpoints: https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/partner-models/use-partner-models
7
+ // - Open models (MaaS): https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/maas/use-open-models
8
+ // - Flex / Priority PayGo: .../models/flex-paygo, .../models/priority-paygo
9
+ // - Pricing: https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing
10
+
11
+ export const VERTEX_SERVICE_TIER_PARAMETER = 'serviceTier';
12
+ export const VERTEX_FLEX_TIER = 'flex';
13
+ export const VERTEX_PRIORITY_TIER = 'priority';
14
+
15
+ // Multi-region locations, served by representative endpoints.
16
+ export const VERTEX_MULTI_REGIONS = new Set(['us', 'eu']);
17
+ const PRIORITY_LOCATIONS = new Set(['global', 'us', 'eu']);
18
+
19
+ // Claude serving rules are enumerated over the finite set of OLDER models
20
+ // (use-partner-models and use-claude docs, Anthropic's model list); an id in
21
+ // no set is a newer model and gets the newer-model rules, never a guess from
22
+ // its text.
23
+ // Every Claude model before 4.5.
24
+ const EARLIER_CLAUDE = new Set([
25
+ 'claude-3-sonnet',
26
+ 'claude-3-opus',
27
+ 'claude-3-haiku',
28
+ 'claude-3-5-sonnet',
29
+ 'claude-3-5-sonnet-v2',
30
+ 'claude-3-5-haiku',
31
+ 'claude-3-7-sonnet',
32
+ 'claude-sonnet-4',
33
+ 'claude-opus-4',
34
+ 'claude-opus-4-1',
35
+ ]);
36
+ // Of those, the ones the global endpoint serves (use-partner-models: global list).
37
+ const GLOBAL_EARLIER_CLAUDE = new Set(['claude-3-7-sonnet', 'claude-3-5-sonnet-v2']);
38
+ // 4.5 and 4.6: served on global, not on the multi-region endpoints (4.7 and later only).
39
+ const CLAUDE_4_5_TO_4_6 = new Set([
40
+ 'claude-opus-4-5',
41
+ 'claude-sonnet-4-5',
42
+ 'claude-haiku-4-5',
43
+ 'claude-opus-4-6',
44
+ 'claude-sonnet-4-6',
45
+ ]);
46
+ // Models that gain nothing from the interleaved-thinking beta (Anthropic
47
+ // extended-thinking doc): Haiku 4.5 does not support it and Opus 4.6 has no
48
+ // manual-mode interleaving; 4.7 and later reject manual thinking and
49
+ // interleave adaptively. Vertex accepts the header and ignores it wherever it
50
+ // is unsupported, so every other model, unknown ids included, is sent it when
51
+ // the request itself uses manual thinking with tools.
52
+ const NO_INTERLEAVED_BETA_CLAUDE = new Set([
53
+ // The beta is documented for Claude 4 models; the 3.x models never got it.
54
+ 'claude-3-sonnet',
55
+ 'claude-3-opus',
56
+ 'claude-3-haiku',
57
+ 'claude-3-5-sonnet',
58
+ 'claude-3-5-sonnet-v2',
59
+ 'claude-3-5-haiku',
60
+ 'claude-3-7-sonnet',
61
+ 'claude-haiku-4-5',
62
+ 'claude-opus-4-6',
63
+ 'claude-opus-4-7',
64
+ 'claude-opus-4-8',
65
+ 'claude-opus-5',
66
+ 'claude-opus-5-5',
67
+ 'claude-fable-5',
68
+ 'claude-fable-5-1',
69
+ 'claude-sonnet-5',
70
+ 'claude-sonnet-5-5',
71
+ 'claude-haiku-5-5',
72
+ ]);
73
+
74
+ // The environment's region when no location is configured, in precedence order.
75
+ const LOCATION_ENV = Object.freeze([
76
+ 'GOOGLE_VERTEX_LOCATION',
77
+ 'GOOGLE_CLOUD_LOCATION',
78
+ 'CLOUD_ML_REGION',
79
+ 'VERTEX_LOCATION',
80
+ 'VERTEXAI_LOCATION',
81
+ ]);
82
+
83
+ // Flex PayGo: global endpoint only.
84
+ const FLEX_MODELS = new Set([
85
+ 'gemini-3.8-flash-cyber',
86
+ 'gemini-3.8-flash',
87
+ 'gemini-3.7-flash',
88
+ 'gemini-3.6-flash',
89
+ 'gemini-3.5-flash-lite',
90
+ 'gemini-3.1-flash-lite-image',
91
+ 'gemini-3-pro-image',
92
+ 'gemini-3.1-flash-image',
93
+ 'gemini-3.5-flash',
94
+ 'gemini-3.1-flash-lite',
95
+ 'gemini-3.1-pro-preview',
96
+ 'gemini-3-flash-preview',
97
+ ]);
98
+
99
+ // Priority PayGo: global, us and eu endpoints (retired Gemini 2.5 left out).
100
+ const PRIORITY_MODELS = new Set([
101
+ 'gemini-4-argon',
102
+ 'gemini-4-argon-cyber-permissive',
103
+ 'gemini-3.8-flash-cyber',
104
+ 'gemini-3.8-flash',
105
+ 'gemini-3.7-flash',
106
+ 'gemini-3.6-flash',
107
+ 'gemini-3.5-flash-lite',
108
+ 'gemini-3.1-flash-lite-image',
109
+ 'gemini-3-pro-image',
110
+ 'gemini-nano-banana-2.1',
111
+ 'gemini-3.1-flash-image',
112
+ 'gemini-3.5-flash',
113
+ 'gemini-3.1-flash-lite',
114
+ 'gemini-3.1-pro-preview',
115
+ 'gemini-3-flash-preview',
116
+ ]);
117
+
118
+ // Generally available Gemini 3+ models billed 10% above the global rate on
119
+ // regional and multi-region endpoints since 2026-07-01 (the pricing page's
120
+ // "Non-global" rows). Preview models carry global rates only.
121
+ const REGIONAL_PREMIUM_GEMINI = new Set([
122
+ 'gemini-4-argon',
123
+ 'gemini-4-argon-cyber-permissive',
124
+ 'gemini-3.8-flash-cyber',
125
+ 'gemini-3.8-flash',
126
+ 'gemini-3.7-flash',
127
+ 'gemini-3.6-flash',
128
+ 'gemini-3.5-flash',
129
+ 'gemini-3.5-flash-lite',
130
+ 'gemini-3.1-flash-lite',
131
+ ]);
132
+
133
+ // Vertex bills Claude and the Gemini models above on regional and multi-region
134
+ // endpoints 10% above the global list price; such requests carry this pricing
135
+ // SKU suffix (model-catalog.mjs prices it from the base SKU).
136
+ export const VERTEX_REGIONAL_PRICING_SUFFIX = '@regional';
137
+
138
+ const google = (id, name, contextWindow) => ({ id, name, ...(contextWindow ? { contextWindow } : {}) });
139
+ const claude = (id, name, family, contextWindow) => ({
140
+ id,
141
+ name,
142
+ family,
143
+ ...(contextWindow ? { contextWindow } : {}),
144
+ });
145
+ // Open and partner models on the OpenAI-compatible endpoint. `locations` is
146
+ // the model card's availability (checked 2026-10-28); a listed model is sent
147
+ // only there. A model with no documented locations is not listed (it stays
148
+ // usable as a free-form id, which is not checked).
149
+ const open = (id, name, locations) => ({ id, name, locations });
150
+
151
+ export const VERTEX_MODELS = Object.freeze([
152
+ google('gemini-4-argon', 'Gemini 4 Argon', 1048576),
153
+ google('gemini-4-argon-cyber-permissive', 'Gemini 4 Argon Cyber Permissive'),
154
+ google('gemini-3.8-flash', 'Gemini 3.8 Flash', 1048576),
155
+ google('gemini-3.8-flash-cyber', 'Gemini 3.8 Flash Cyber'),
156
+ google('gemini-3.7-flash', 'Gemini 3.7 Flash'),
157
+ google('gemini-3.6-flash', 'Gemini 3.6 Flash'),
158
+ google('gemini-3.5-flash', 'Gemini 3.5 Flash'),
159
+ google('gemini-3.5-flash-lite', 'Gemini 3.5 Flash Lite', 1048576),
160
+ google('gemini-3.1-flash-lite', 'Gemini 3.1 Flash Lite'),
161
+ google('gemini-3.1-pro-preview', 'Gemini 3.1 Pro Preview', 1048576),
162
+ google('gemini-3-flash-preview', 'Gemini 3 Flash Preview'),
163
+ claude('claude-opus-5-5', 'Claude Opus 5.5', 'opus', 1000000),
164
+ claude('claude-fable-5-1', 'Claude Fable 5.1', 'fable', 1000000),
165
+ claude('claude-sonnet-5-5', 'Claude Sonnet 5.5', 'sonnet', 1000000),
166
+ claude('claude-haiku-5-5', 'Claude Haiku 5.5', 'haiku'),
167
+ claude('claude-opus-5', 'Claude Opus 5', 'opus'),
168
+ claude('claude-fable-5', 'Claude Fable 5', 'fable'),
169
+ claude('claude-sonnet-5', 'Claude Sonnet 5', 'sonnet'),
170
+ claude('claude-opus-4-8', 'Claude Opus 4.8', 'opus'),
171
+ claude('claude-opus-4-7', 'Claude Opus 4.7', 'opus'),
172
+ claude('claude-opus-4-6', 'Claude Opus 4.6', 'opus'),
173
+ claude('claude-sonnet-4-6', 'Claude Sonnet 4.6', 'sonnet'),
174
+ claude('claude-opus-4-5', 'Claude Opus 4.5', 'opus'),
175
+ claude('claude-sonnet-4-5', 'Claude Sonnet 4.5', 'sonnet'),
176
+ claude('claude-haiku-4-5', 'Claude Haiku 4.5', 'haiku'),
177
+ claude('claude-3-7-sonnet', 'Claude 3.7 Sonnet', 'sonnet'),
178
+ claude('claude-3-5-sonnet-v2', 'Claude 3.5 Sonnet v2', 'sonnet'),
179
+ claude('claude-3-5-sonnet', 'Claude 3.5 Sonnet', 'sonnet'),
180
+ claude('claude-3-5-haiku', 'Claude 3.5 Haiku', 'haiku'),
181
+ open('deepseek-ai/deepseek-v4.1-flash-maas', 'DeepSeek V4.1 Flash', ['global']),
182
+ open('deepseek-ai/deepseek-v3.2-maas', 'DeepSeek V3.2', ['global']),
183
+ open('deepseek-ai/deepseek-v3.1-maas', 'DeepSeek V3.1', ['us-central1']),
184
+ open('deepseek-ai/deepseek-r1-0528-maas', 'DeepSeek R1 (0528)', ['us-central1']),
185
+ open('qwen/qwen3-coder-480b-a35b-instruct-maas', 'Qwen3 Coder', ['global', 'us-south1']),
186
+ open('qwen/qwen3-next-80b-a3b-instruct-maas', 'Qwen3-Next 80B Instruct', ['global']),
187
+ open('qwen/qwen3-next-80b-a3b-thinking-maas', 'Qwen3-Next 80B Thinking', ['global']),
188
+ open('zai-org/glm-5.2-maas', 'GLM 5.2', ['global']),
189
+ open('zai-org/glm-5-maas', 'GLM 5', ['global']),
190
+ open('zai-org/glm-4.7-maas', 'GLM 4.7', ['global']),
191
+ open('moonshotai/kimi-k2-thinking-maas', 'Kimi K2 Thinking', ['global']),
192
+ open('minimaxai/minimax-m2-maas', 'MiniMax M2', ['global']),
193
+ // Card: availability `global`, quotas for `global` and `us-central1`.
194
+ open('openai/gpt-oss-120b-maas', 'gpt-oss 120B', ['global', 'us-central1']),
195
+ open('openai/gpt-oss-20b-maas', 'gpt-oss 20B', ['us-central1']),
196
+ open('meta/llama-4-maverick-17b-128e-instruct-maas', 'Llama 4 Maverick 17B-128E', ['us-east5']),
197
+ open('meta/llama-4-scout-17b-16e-instruct-maas', 'Llama 4 Scout 17B-16E', ['us-east5']),
198
+ open('google/gemma-4-26b-a4b-it-maas', 'Gemma 4 26B A4B IT', ['global']),
199
+ open('xai/grok-4.7', 'Grok 4.7', ['global', 'us']),
200
+ open('xai/grok-4.6', 'Grok 4.6', ['global', 'us']),
201
+ open('xai/grok-4.3', 'Grok 4.3', ['global']),
202
+ open('xai/grok-4.20-reasoning', 'Grok 4.20 (Reasoning)', ['global']),
203
+ open('xai/grok-4.20-non-reasoning', 'Grok 4.20 (Non-reasoning)', ['global']),
204
+ ]);
205
+
206
+ const LISTED_LOCATIONS = new Map(
207
+ VERTEX_MODELS.filter((model) => model.locations).map((model) => [model.id, model.locations])
208
+ );
209
+
210
+ // A Claude model id without its `@version` (or `@default`) and Anthropic's `-YYYYMMDD` snapshot suffix.
211
+ function claudeBase(id) {
212
+ return String(id)
213
+ .replace(/@.*$/, '')
214
+ .replace(/-\d{8}$/, '');
215
+ }
216
+
217
+ /** Whether a manual-thinking request with tools needs the interleaved-thinking beta on this Claude model. */
218
+ export function claudeNeedsInterleavedBeta(id) {
219
+ return !NO_INTERLEAVED_BETA_CLAUDE.has(claudeBase(id));
220
+ }
221
+
222
+ const PUBLISHER = /^[a-z0-9][a-z0-9-]{0,62}$/;
223
+ const MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._@:-]{0,127}$/;
224
+
225
+ /**
226
+ * The publisher, model id and wire of a model: `gemini` (generateContent),
227
+ * `claude` (Anthropic rawPredict) or `maas` (the OpenAI-compatible endpoint
228
+ * every other publisher, and Google's `-maas` open models, answer on).
229
+ * Accepts `publishers/P/models/M`, `P/M` and the bare Gemini / Claude / Grok ids.
230
+ */
231
+ export function vertexRoute(model) {
232
+ const raw = String(model || '').trim();
233
+ if (!raw) throw new Error('[vertex-ai] model is required');
234
+ const path = /^publishers\/([^/]+)\/models\/([^/]+)$/.exec(raw) || /^([^/]+)\/([^/]+)$/.exec(raw);
235
+ let publisher;
236
+ let id;
237
+ if (path) {
238
+ publisher = path[1].toLowerCase();
239
+ id = path[2];
240
+ } else if (/^gemini-/i.test(raw)) {
241
+ publisher = 'google';
242
+ id = geminiBareModelId(raw);
243
+ } else if (/^claude-/i.test(raw)) {
244
+ publisher = 'anthropic';
245
+ id = raw;
246
+ } else if (/^grok-/i.test(raw)) {
247
+ publisher = 'xai';
248
+ id = raw;
249
+ } else {
250
+ throw new Error(
251
+ `[vertex-ai] cannot determine the publisher of model "${raw}"; use "publisher/model" (for example "deepseek-ai/deepseek-v3.2-maas")`
252
+ );
253
+ }
254
+ if (!PUBLISHER.test(publisher) || !MODEL_ID.test(id)) throw new Error(`[vertex-ai] model "${raw}" is invalid`);
255
+ // Anthropic answers rawPredict; Google answers generateContent except its
256
+ // open models, whose ids carry the `-maas` suffix (Google's MaaS naming rule)
257
+ // or are listed in the catalog; every other publisher is MaaS.
258
+ let wire = 'maas';
259
+ if (publisher === 'anthropic') wire = 'claude';
260
+ else if (publisher === 'google' && !/-maas$/i.test(id) && !LISTED_LOCATIONS.has(`${publisher}/${id}`))
261
+ wire = 'gemini';
262
+ return { publisher, id, wire };
263
+ }
264
+
265
+ /** [major, minor] of a Claude id (claude-opus-4-8, claude-sonnet-5, claude-3-7-sonnet), or null. */
266
+ export function claudeVersion(id) {
267
+ const bare = String(id).replace(/@.*$/, '');
268
+ const match =
269
+ /^claude-[a-z]+-(\d+)(?:-(\d{1,2}))?(?:-|$)/.exec(bare) || /^claude-(\d+)(?:-(\d{1,2}))?-[a-z]/.exec(bare);
270
+ return match ? [Number(match[1]), Number(match[2] || 0)] : null;
271
+ }
272
+
273
+ // A Claude model's own region variables: VERTEX_REGION_CLAUDE_4_5_SONNET, VERTEX_REGION_CLAUDE_HAIKU_4_5.
274
+ function claudeRegionEnv(id) {
275
+ const version = claudeVersion(id);
276
+ const family = /^claude-(?:\d+-(?:\d{1,2}-)?)?([a-z]+)/.exec(String(id))?.[1]?.toUpperCase();
277
+ if (!version || !family) return [];
278
+ const [major, minor] = version;
279
+ return [`VERTEX_REGION_CLAUDE_${major}_${minor}_${family}`, `VERTEX_REGION_CLAUDE_${family}_${major}_${minor}`];
280
+ }
281
+
282
+ /**
283
+ * The region the environment names ('' when none), for use only when no
284
+ * location is configured: a Claude model's own VERTEX_REGION_CLAUDE_* first.
285
+ */
286
+ export function vertexEnvLocation(env = process.env, claudeId = '') {
287
+ for (const name of [...(claudeId ? claudeRegionEnv(claudeId) : []), ...LOCATION_ENV]) {
288
+ const value = String(env[name] || '').trim();
289
+ if (value) return value;
290
+ }
291
+ return '';
292
+ }
293
+
294
+ /** The PayGo tier a send asks for: 'flex', 'priority' or '' (standard). */
295
+ export function vertexRequestedTier(opts) {
296
+ const tier = String(opts?.modelParameters?.[VERTEX_SERVICE_TIER_PARAMETER] || '').toLowerCase();
297
+ return tier === VERTEX_FLEX_TIER || tier === VERTEX_PRIORITY_TIER ? tier : '';
298
+ }
299
+
300
+ /** Why Vertex does not serve a model / location / tier combination (an error message), or '' when it does. */
301
+ export function vertexServingProblem(route, location, tier) {
302
+ if (tier === VERTEX_FLEX_TIER) {
303
+ if (route.wire !== 'gemini' || !FLEX_MODELS.has(route.id)) {
304
+ return `[vertex-ai] Flex PayGo is not available for ${route.id}`;
305
+ }
306
+ if (location !== 'global') return '[vertex-ai] Flex PayGo is only available with the global location';
307
+ }
308
+ if (tier === VERTEX_PRIORITY_TIER) {
309
+ if (route.wire !== 'gemini' || !PRIORITY_MODELS.has(route.id)) {
310
+ return `[vertex-ai] Priority PayGo is not available for ${route.id}`;
311
+ }
312
+ if (!PRIORITY_LOCATIONS.has(location)) {
313
+ return '[vertex-ai] Priority PayGo is only available with the global, us or eu location';
314
+ }
315
+ }
316
+ const served = route.wire === 'maas' ? LISTED_LOCATIONS.get(`${route.publisher}/${route.id}`) : null;
317
+ if (served && !served.includes(location)) {
318
+ return `[vertex-ai] ${route.publisher}/${route.id} is served only in: ${served.join(', ')} (location is ${location})`;
319
+ }
320
+ const claudeId = route.wire === 'claude' ? claudeBase(route.id) : '';
321
+ if (VERTEX_MULTI_REGIONS.has(location) && (EARLIER_CLAUDE.has(claudeId) || CLAUDE_4_5_TO_4_6.has(claudeId))) {
322
+ return `[vertex-ai] ${route.id} is not served on the ${location} multi-region endpoint (Claude 4.7 and later only); use the global location or a region`;
323
+ }
324
+ if (location === 'global' && EARLIER_CLAUDE.has(claudeId) && !GLOBAL_EARLIER_CLAUDE.has(claudeId)) {
325
+ return (
326
+ `[vertex-ai] ${route.id} is not served on the global endpoint; set the location to a region that serves it ` +
327
+ `(for example us-east5), or with no location set, ${claudeRegionEnv(claudeId)[0]}`
328
+ );
329
+ }
330
+ return '';
331
+ }
332
+
333
+ /** Rejects a model / location / tier combination Vertex does not serve, before any request. */
334
+ export function assertVertexServing(route, location, tier) {
335
+ const problem = vertexServingProblem(route, location, tier);
336
+ if (problem) throw new Error(problem);
337
+ }
338
+
339
+ // The premium covers Sonnet 4.5, Haiku 4.5, Opus 4.5 and later: every Claude
340
+ // model that is not one of the enumerated earlier ones.
341
+ function claudeRegionalPremium(route) {
342
+ return route.wire === 'claude' && !EARLIER_CLAUDE.has(claudeBase(route.id));
343
+ }
344
+
345
+ /** The pricing SKU of a request, when it differs from the served model id. */
346
+ export function vertexPricingModel(route, location) {
347
+ // Open-model rates are the same on every endpoint; their catalog rows are keyed `publisher/model`.
348
+ if (route.wire === 'maas') return `${route.publisher}/${route.id}`;
349
+ const regional = location !== 'global' && (claudeRegionalPremium(route) || REGIONAL_PREMIUM_GEMINI.has(route.id));
350
+ if (regional) return `${route.id}${VERTEX_REGIONAL_PRICING_SUFFIX}`;
351
+ // Claude bills as the requested publisher model: the stream reports an
352
+ // Anthropic-style dated id (claude-sonnet-4-5-20250929) no Vertex row prices.
353
+ return route.wire === 'claude' ? route.id : undefined;
354
+ }
355
+
356
+ const TIER_OPTIONS = Object.freeze({
357
+ [VERTEX_FLEX_TIER]: Object.freeze({ value: VERTEX_FLEX_TIER, label: 'Flex (50% off, slower)' }),
358
+ [VERTEX_PRIORITY_TIER]: Object.freeze({ value: VERTEX_PRIORITY_TIER, label: 'Priority (faster, higher price)' }),
359
+ });
360
+
361
+ /** The service-tier parameter a listed model offers, or null. */
362
+ export function vertexTierOption(id) {
363
+ const tiers = [
364
+ ...(FLEX_MODELS.has(id) ? [TIER_OPTIONS[VERTEX_FLEX_TIER]] : []),
365
+ ...(PRIORITY_MODELS.has(id) ? [TIER_OPTIONS[VERTEX_PRIORITY_TIER]] : []),
366
+ ];
367
+ if (!tiers.length) return null;
368
+ return {
369
+ id: VERTEX_SERVICE_TIER_PARAMETER,
370
+ label: 'Service tier',
371
+ kind: 'enum',
372
+ options: [{ value: 'standard', label: 'Standard' }, ...tiers],
373
+ };
374
+ }
375
+
376
+ /**
377
+ * The structured part of a Google API error (`google.rpc.Status`): the body's
378
+ * `status`, `message`, and every `google.rpc.ErrorInfo` `reason` / `metadata`
379
+ * in its `details`. Read from the parsed body an error carries (`error.error`,
380
+ * or `error.data.error`) and its `details`.
381
+ */
382
+ export function googleErrorInfo(error) {
383
+ const body = [error?.error, error?.data?.error].find((value) => value && typeof value === 'object');
384
+ const details = [body?.details, body?.errorDetails, error?.details].find(Array.isArray) || [];
385
+ const infos = details.filter((entry) => String(entry?.['@type'] || '').endsWith('google.rpc.ErrorInfo'));
386
+ return {
387
+ status: typeof body?.status === 'string' ? body.status : '',
388
+ message: typeof body?.message === 'string' ? body.message : '',
389
+ reasons: new Set(infos.map((entry) => entry.reason).filter(Boolean)),
390
+ metadata: Object.assign({}, ...infos.map((entry) => entry.metadata)),
391
+ };
392
+ }
393
+
394
+ /** The HTTP status an error carries (0 when none). */
395
+ export function vertexHttpStatus(error) {
396
+ return (
397
+ [error?.status, error?.httpStatus, error?.response?.status]
398
+ .map(Number)
399
+ .find((value) => Number.isFinite(value) && value > 0) || 0
400
+ );
401
+ }
402
+
403
+ // Google's own IAM message for a missing permission (an IAM decision, not a model-enablement one).
404
+ export const PERMISSION_TEXT = /Permission '[^']+' denied/;
405
+
406
+ const MODEL_GARDEN = 'https://console.cloud.google.com/agent-platform/publishers';
407
+ const LOCATIONS_DOC = 'https://docs.cloud.google.com/gemini-enterprise-agent-platform/resources/locations';
408
+
409
+ /**
410
+ * A 404, or a 403 that names the model's enablement, means the model is not
411
+ * enabled for the project (or the id is wrong): the error says so and where to
412
+ * enable it. Status and the other classification fields stay as they were.
413
+ */
414
+ export function vertexModelAccessError(error, route, { project, location }) {
415
+ if (!error || typeof error !== 'object') return error;
416
+ const status = vertexHttpStatus(error);
417
+ const detail = String(error.message || '');
418
+ const info = googleErrorInfo(error);
419
+ const text = `${detail} ${info.message}`;
420
+ // An IAM decision (the caller lacks a permission such as
421
+ // aiplatform.endpoints.predict) stays as it is, whatever resource it names:
422
+ // the ErrorInfo reason, else Google's own IAM message.
423
+ const iamDenied = info.reasons.has('IAM_PERMISSION_DENIED') || PERMISSION_TEXT.test(text);
424
+ // The API gives no structured reason for a project that may not use a
425
+ // model, only this FAILED_PRECONDITION / PERMISSION_DENIED message.
426
+ const enablementText = (text) => text.includes('is not allowed to use Publisher Model');
427
+ const notEnabled =
428
+ !iamDenied &&
429
+ // NOT_FOUND: "Publisher Model `…` was not found or your project does not have access to it".
430
+ (status === 404 ||
431
+ // FAILED_PRECONDITION: "Project `…` is not allowed to use Publisher Model `…`".
432
+ (status === 400 && info.status === 'FAILED_PRECONDITION' && enablementText(text)) ||
433
+ (status === 403 && enablementText(text)));
434
+ if (!notEnabled || error.code === 'VERTEX_MODEL_NOT_ENABLED') return error;
435
+ // NOT_FOUND is also what a location that does not serve the model answers.
436
+ const elsewhere = location === 'global' ? 'a region' : 'the global location or a region';
437
+ const region =
438
+ status === 404 ? `If ${location} does not serve it, use ${elsewhere} that does: ${LOCATIONS_DOC} ` : '';
439
+ error.message =
440
+ `[vertex-ai] ${route.publisher}/${route.id} was not found or is not enabled for project ${project} in ${location}. ` +
441
+ `Check the model id and enable the model in Model Garden: ${MODEL_GARDEN}/${route.publisher}/model-garden/${route.id}. ` +
442
+ `${region}(HTTP ${status}: ${detail.slice(0, 200)})`;
443
+ error.code = 'VERTEX_MODEL_NOT_ENABLED';
444
+ return error;
445
+ }