@vellumai/assistant 0.12.0 → 0.12.1-dev.202609141913.5e16607

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 (441) hide show
  1. package/AGENTS.md +5 -1
  2. package/ARCHITECTURE.md +11 -12
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/openapi.yaml +292 -50
  6. package/package.json +1 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/app-compiler.test.ts +6 -0
  10. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  11. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  12. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  13. package/src/__tests__/computer-use-tools.test.ts +17 -5
  14. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  15. package/src/__tests__/config-schema.test.ts +28 -29
  16. package/src/__tests__/config-watcher.test.ts +23 -8
  17. package/src/__tests__/connection-model-compat.test.ts +2 -2
  18. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  19. package/src/__tests__/conversation-pairing.test.ts +26 -0
  20. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  21. package/src/__tests__/conversation-queue.test.ts +48 -207
  22. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  23. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  24. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  25. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  26. package/src/__tests__/conversation-title-service.test.ts +14 -10
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  28. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  29. package/src/__tests__/credential-record-retire.test.ts +171 -1
  30. package/src/__tests__/credential-routes.test.ts +212 -3
  31. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  32. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  33. package/src/__tests__/document-sync-tags.test.ts +25 -0
  34. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  35. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  36. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  37. package/src/__tests__/git-porcelain.test.ts +47 -0
  38. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  39. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  40. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  41. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  42. package/src/__tests__/host-file-proxy.test.ts +11 -23
  43. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  44. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  45. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  46. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  47. package/src/__tests__/mcp-health-check.test.ts +8 -11
  48. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  49. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  50. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  51. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  52. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  53. package/src/__tests__/model-intents.test.ts +3 -0
  54. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  55. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  56. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  57. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  58. package/src/__tests__/plugin-import-boundary-guard.test.ts +13 -0
  59. package/src/__tests__/resolve-app.test.ts +135 -0
  60. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  61. package/src/__tests__/service-account-token.test.ts +247 -0
  62. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  63. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  64. package/src/__tests__/source-fingerprint.test.ts +184 -0
  65. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  66. package/src/__tests__/subagent-tools.test.ts +3 -2
  67. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  68. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  69. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  70. package/src/__tests__/workspace-git-service.test.ts +26 -0
  71. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  72. package/src/acp/__tests__/agent-process.test.ts +258 -4
  73. package/src/acp/__tests__/auth-required.test.ts +96 -0
  74. package/src/acp/__tests__/client-handler.test.ts +90 -2
  75. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  76. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  77. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  78. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  79. package/src/acp/__tests__/session-manager.test.ts +895 -4
  80. package/src/acp/agent-process.test.ts +4 -1
  81. package/src/acp/agent-process.ts +94 -9
  82. package/src/acp/auth-required.ts +50 -1
  83. package/src/acp/auto-install.test.ts +9 -7
  84. package/src/acp/client-handler.ts +16 -3
  85. package/src/acp/model-config.test.ts +166 -0
  86. package/src/acp/model-config.ts +138 -0
  87. package/src/acp/resolve-agent.test.ts +220 -6
  88. package/src/acp/resolve-agent.ts +88 -23
  89. package/src/acp/session-manager.test.ts +196 -3
  90. package/src/acp/session-manager.ts +441 -18
  91. package/src/acp/types.ts +50 -5
  92. package/src/api/events/acp-session-model-update.test.ts +88 -0
  93. package/src/api/events/acp-session-model-update.ts +46 -0
  94. package/src/api/events/app-files-changed.ts +4 -3
  95. package/src/api/events/notification-intent.ts +2 -0
  96. package/src/api/index.ts +6 -0
  97. package/src/apps/app-store.ts +1 -1
  98. package/src/apps/resolve-app.ts +81 -0
  99. package/src/apps/source-fingerprint.ts +266 -0
  100. package/src/bundler/app-compiler.ts +15 -0
  101. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  102. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  103. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  104. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  105. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  106. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  107. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  108. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  109. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  110. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  111. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  112. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  113. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  114. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  115. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  116. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  117. package/src/calls/barge-in-guard.ts +118 -0
  118. package/src/calls/call-constants.ts +24 -4
  119. package/src/calls/call-controller.ts +499 -199
  120. package/src/calls/call-transport.ts +16 -0
  121. package/src/calls/media-stream-output.ts +32 -0
  122. package/src/calls/media-stream-server.ts +82 -30
  123. package/src/calls/media-stream-stt-session.ts +15 -0
  124. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  125. package/src/calls/resolve-call-tts-provider.ts +33 -2
  126. package/src/calls/tts-call-strategy.ts +9 -5
  127. package/src/calls/types.ts +2 -0
  128. package/src/calls/voice-control-protocol.ts +53 -21
  129. package/src/calls/voice-leg-coordinator.ts +189 -0
  130. package/src/calls/voice-progress-cadence.ts +423 -0
  131. package/src/calls/voice-session-bridge.ts +59 -46
  132. package/src/calls/voice-triage-escalate.ts +175 -56
  133. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  134. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  135. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  136. package/src/cli/commands/apps.help.ts +67 -13
  137. package/src/cli/commands/apps.ts +183 -0
  138. package/src/cli/commands/bash.help.ts +8 -13
  139. package/src/cli/commands/bash.ts +7 -25
  140. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  141. package/src/cli/commands/channels/index.help.ts +70 -2
  142. package/src/cli/commands/channels/index.ts +16 -3
  143. package/src/cli/commands/channels/send.ts +128 -0
  144. package/src/cli/commands/credentials.help.ts +6 -2
  145. package/src/cli/commands/credentials.ts +2 -2
  146. package/src/cli/commands/mcp.help.ts +8 -8
  147. package/src/cli/commands/mcp.ts +1 -1
  148. package/src/cli/commands/roadmap.help.ts +5 -1
  149. package/src/cli/commands/roadmap.ts +1 -1
  150. package/src/cli/lib/bundled-marketplace.json +103 -90
  151. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  152. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  153. package/src/config/__tests__/platform-identity.test.ts +255 -0
  154. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  155. package/src/config/acp-agent-write.ts +43 -0
  156. package/src/config/acp-defaults.test.ts +13 -3
  157. package/src/config/acp-defaults.ts +23 -6
  158. package/src/config/acp-schema.test.ts +81 -0
  159. package/src/config/acp-schema.ts +30 -2
  160. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  161. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  162. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  163. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  164. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  165. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  166. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  167. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  168. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  169. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  170. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  171. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  172. package/src/config/default-profile-catalog.ts +1 -1
  173. package/src/config/env.ts +2 -2
  174. package/src/config/feature-flag-registry.json +24 -12
  175. package/src/config/loader.ts +1 -0
  176. package/src/config/memory-v3-gate.test.ts +31 -0
  177. package/src/config/memory-v3-gate.ts +20 -16
  178. package/src/config/platform-identity.ts +201 -0
  179. package/src/config/platform-rehydration.ts +11 -27
  180. package/src/config/schema.ts +2 -2
  181. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  182. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  183. package/src/config/schemas/calls.ts +1 -28
  184. package/src/config/schemas/live-voice.ts +1 -173
  185. package/src/config/schemas/mcp.ts +1 -1
  186. package/src/config/schemas/memory-retrospective.ts +10 -0
  187. package/src/config/schemas/services.ts +8 -0
  188. package/src/config/schemas/skills.ts +0 -14
  189. package/src/config/schemas/voice.ts +150 -0
  190. package/src/credential-execution/process-manager.test.ts +18 -19
  191. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  192. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  193. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  194. package/src/daemon/app-change-notify.ts +45 -0
  195. package/src/daemon/config-watcher.ts +13 -194
  196. package/src/daemon/conversation-process.ts +35 -64
  197. package/src/daemon/conversation-skill-tools.ts +13 -31
  198. package/src/daemon/conversation-store.ts +1 -1
  199. package/src/daemon/conversation-surfaces.ts +10 -8
  200. package/src/daemon/conversation-tool-setup.ts +37 -43
  201. package/src/daemon/conversation.ts +0 -3
  202. package/src/daemon/host-bash-proxy.ts +8 -12
  203. package/src/daemon/host-browser-proxy.ts +15 -1
  204. package/src/daemon/host-file-proxy.ts +8 -10
  205. package/src/daemon/host-transfer-proxy.ts +14 -18
  206. package/src/daemon/lifecycle.ts +0 -5
  207. package/src/daemon/mcp-reload-service.ts +5 -5
  208. package/src/daemon/message-types/sync.ts +1 -0
  209. package/src/daemon/orphan-reaper.ts +1 -1
  210. package/src/daemon/providers-setup.ts +1 -1
  211. package/src/daemon/shutdown-handlers.ts +1 -9
  212. package/src/daemon/skill-memory-refresh.ts +64 -0
  213. package/src/daemon/tool-side-effects.ts +7 -18
  214. package/src/desktop/__tests__/fake-desktop.ts +119 -0
  215. package/src/desktop/desktop-chrome-policy.test.ts +71 -0
  216. package/src/desktop/desktop-chrome-policy.ts +42 -0
  217. package/src/desktop/desktop-dependencies.test.ts +102 -0
  218. package/src/desktop/desktop-dependencies.ts +306 -0
  219. package/src/desktop/desktop-feature.test.ts +18 -0
  220. package/src/desktop/desktop-feature.ts +19 -0
  221. package/src/desktop/desktop-panel-config.test.ts +135 -0
  222. package/src/desktop/desktop-panel-config.ts +149 -0
  223. package/src/desktop/desktop-session-manager.test.ts +627 -0
  224. package/src/desktop/desktop-session-manager.ts +814 -0
  225. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  226. package/src/desktop/desktop-stream-bridge.ts +214 -0
  227. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  228. package/src/desktop/desktop-wallpaper.ts +113 -0
  229. package/src/desktop/desktop-window-theme.ts +170 -0
  230. package/src/home/__tests__/feed-writer.test.ts +22 -0
  231. package/src/home/feed-writer.ts +11 -2
  232. package/src/hooks/types.ts +33 -0
  233. package/src/i18n/AGENTS.md +22 -0
  234. package/src/i18n/__tests__/i18n.test.ts +108 -0
  235. package/src/i18n/index.ts +16 -0
  236. package/src/i18n/locales.ts +93 -0
  237. package/src/i18n/messages.ts +61 -0
  238. package/src/i18n/title.ts +25 -0
  239. package/src/inbound/platform-callback-registration.ts +4 -5
  240. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  241. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  242. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  243. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  244. package/src/live-voice/live-voice-session.ts +213 -693
  245. package/src/live-voice/protocol.ts +0 -2
  246. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  247. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  248. package/src/mcp/effective-config.ts +3 -3
  249. package/src/mcp/mcp-header-store.ts +29 -45
  250. package/src/mcp/spec-schema.ts +111 -0
  251. package/src/mcp/startup.ts +8 -4
  252. package/src/mcp/workspace-mcp-config.ts +188 -0
  253. package/src/messaging/provider.ts +10 -3
  254. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  255. package/src/messaging/providers/callback-routing.ts +18 -0
  256. package/src/messaging/providers/channel-transport.ts +56 -0
  257. package/src/messaging/providers/discord/transport.ts +17 -0
  258. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  259. package/src/messaging/providers/slack/adapter.ts +7 -44
  260. package/src/messaging/providers/slack/transport.ts +16 -0
  261. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  262. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  263. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  264. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  265. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  266. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  267. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  268. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  269. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  270. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  271. package/src/notifications/adapters/macos.ts +3 -0
  272. package/src/notifications/assistant-reply-producer.ts +8 -3
  273. package/src/notifications/conversation-pairing.ts +36 -16
  274. package/src/notifications/delivered-post-record.ts +11 -1
  275. package/src/notifications/guardian-feed-projection.ts +125 -5
  276. package/src/notifications/home-feed-side-effect.ts +55 -7
  277. package/src/notifications/schedule-result-producer.ts +66 -22
  278. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  279. package/src/oauth/byo-connection.test.ts +45 -0
  280. package/src/oauth/byo-connection.ts +36 -6
  281. package/src/oauth/connection-resolver.test.ts +73 -0
  282. package/src/oauth/connection-resolver.ts +95 -2
  283. package/src/oauth/seed-providers.ts +165 -0
  284. package/src/outbound-proxy/certs.ts +3 -5
  285. package/src/permissions/checker.test.ts +11 -6
  286. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  287. package/src/persistence/conversation-crud.ts +71 -63
  288. package/src/persistence/conversation-lineage.ts +12 -4
  289. package/src/persistence/conversation-title-placeholders.ts +23 -0
  290. package/src/persistence/conversation-title-service.ts +9 -26
  291. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  292. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  293. package/src/persistence/message-cursor.ts +78 -0
  294. package/src/persistence/message-reads.ts +30 -0
  295. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  296. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  297. package/src/persistence/schema/memory-core.ts +5 -0
  298. package/src/platform/client.test.ts +4 -2
  299. package/src/platform/client.ts +2 -10
  300. package/src/plugin-api/constants.ts +2 -0
  301. package/src/plugin-api/index.ts +1 -0
  302. package/src/plugin-api/types.ts +2 -0
  303. package/src/plugins/defaults/index.ts +2 -0
  304. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  305. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  306. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  307. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  308. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  309. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  310. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  311. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  312. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  313. package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
  314. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  315. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  316. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  317. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  318. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  319. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  320. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  321. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  322. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  323. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  324. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  325. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  326. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  327. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  328. package/src/plugins/defaults/memory/startup.ts +6 -1
  329. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  330. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  331. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
  332. package/src/plugins/defaults/memory/worker.ts +6 -0
  333. package/src/plugins/mcp-servers.ts +15 -75
  334. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  335. package/src/providers/gemini/client.ts +9 -23
  336. package/src/providers/gemini-thought-signature.test.ts +63 -0
  337. package/src/providers/gemini-thought-signature.ts +44 -0
  338. package/src/providers/inference/auth.ts +2 -3
  339. package/src/providers/inference/connection-availability.ts +27 -8
  340. package/src/providers/inference/resolve-auth.ts +22 -4
  341. package/src/providers/inference/service-account-token.ts +248 -0
  342. package/src/providers/model-catalog.ts +34 -0
  343. package/src/providers/model-intents.ts +10 -5
  344. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  345. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  346. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  347. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  348. package/src/providers/openai/chat-completions-provider.ts +192 -78
  349. package/src/providers/openai/codex-models.ts +0 -4
  350. package/src/providers/openai/google-thought-signature.ts +320 -0
  351. package/src/runtime/AGENTS.md +2 -0
  352. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  353. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  354. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  355. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  356. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  357. package/src/runtime/auth/same-actor.ts +38 -3
  358. package/src/runtime/channel-send.ts +316 -0
  359. package/src/runtime/http-server.ts +160 -151
  360. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  361. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  362. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  363. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  364. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  365. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  366. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  367. package/src/runtime/routes/acp-routes.test.ts +15 -0
  368. package/src/runtime/routes/acp-routes.ts +72 -9
  369. package/src/runtime/routes/app-management-routes.ts +73 -0
  370. package/src/runtime/routes/app-routes.ts +1 -1
  371. package/src/runtime/routes/channel-send-routes.ts +85 -0
  372. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  373. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  374. package/src/runtime/routes/credential-routes.ts +19 -10
  375. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  376. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  377. package/src/runtime/routes/diagnostics-routes.ts +55 -3
  378. package/src/runtime/routes/index.ts +4 -2
  379. package/src/runtime/routes/log-export-routes.ts +2 -0
  380. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  381. package/src/runtime/routes/migration-routes.ts +6 -20
  382. package/src/runtime/routes/platform-routes.ts +18 -15
  383. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  384. package/src/runtime/services/conversation-serializer.ts +16 -3
  385. package/src/runtime/sync/resource-sync-events.ts +2 -1
  386. package/src/schedule/worker.ts +1 -1
  387. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  388. package/src/security/ces-http-record-backend.ts +120 -0
  389. package/src/security/ces-rpc-record-backend.ts +7 -0
  390. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  391. package/src/skills/workspace-skill-md-signature.ts +50 -0
  392. package/src/subagent/consult-context.ts +12 -25
  393. package/src/tools/acp/list-agents.test.ts +3 -1
  394. package/src/tools/acp/spawn.test.ts +146 -11
  395. package/src/tools/acp/spawn.ts +21 -3
  396. package/src/tools/acp/status.test.ts +94 -0
  397. package/src/tools/acp/status.ts +40 -3
  398. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  399. package/src/tools/capability-offer.test.ts +22 -2
  400. package/src/tools/capability-offer.ts +16 -1
  401. package/src/tools/computer-use/definitions.ts +4 -2
  402. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  403. package/src/tools/credentials/metadata-store.ts +100 -0
  404. package/src/tools/host-filesystem/edit.test.ts +2 -1
  405. package/src/tools/host-filesystem/edit.ts +2 -2
  406. package/src/tools/host-filesystem/read.test.ts +2 -1
  407. package/src/tools/host-filesystem/read.ts +2 -2
  408. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  409. package/src/tools/host-filesystem/transfer.ts +2 -2
  410. package/src/tools/host-filesystem/write.test.ts +2 -1
  411. package/src/tools/host-filesystem/write.ts +2 -2
  412. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  413. package/src/tools/host-terminal/host-shell.ts +4 -20
  414. package/src/tools/policy-context.ts +6 -7
  415. package/src/tools/subagent/spawn.ts +5 -8
  416. package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
  417. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  418. package/src/tools/terminal/safe-env.ts +23 -3
  419. package/src/tools/terminal/sanitized-bash.ts +105 -0
  420. package/src/tools/watch/watch-retro-report.ts +0 -8
  421. package/src/util/__tests__/language-subtag.test.ts +23 -1
  422. package/src/util/ca-bundle.ts +13 -0
  423. package/src/util/language-subtag.ts +16 -0
  424. package/src/util/platform.ts +8 -0
  425. package/src/workspace/git-porcelain.ts +66 -0
  426. package/src/workspace/git-service.ts +5 -54
  427. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  428. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  429. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  430. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  431. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  432. package/src/workspace/migrations/registry.ts +6 -0
  433. package/src/__tests__/app-source-watcher.test.ts +0 -218
  434. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  435. package/src/__tests__/speaker-identification.test.ts +0 -56
  436. package/src/calls/speaker-identification.ts +0 -233
  437. package/src/daemon/app-source-watcher.ts +0 -218
  438. package/src/messaging/providers/whatsapp/client.ts +0 -29
  439. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  440. package/src/util/fs-watcher-error.ts +0 -36
  441. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -39,6 +39,7 @@ import type {
39
39
  SendMessageOptions,
40
40
  } from "@vellumai/plugin-api";
41
41
 
42
+ import { OpenRouterProvider } from "../../../../../providers/openrouter/client.js";
42
43
  import { ProviderError } from "../../../../../util/errors.js";
43
44
  import { sectionHeadLine } from "../sections.js";
44
45
  import type { MemoryRoutingTurn, Section } from "../types.js";
@@ -843,3 +844,91 @@ describe("selectPool: sections and keyword-in-context snippets", () => {
843
844
  );
844
845
  });
845
846
  });
847
+
848
+ // ---------------------------------------------------------------------------
849
+ // selectPool: cataloged thinking and forced-tool compatibility.
850
+ // ---------------------------------------------------------------------------
851
+
852
+ describe("selectPool: cataloged thinking and forced-tool compatibility", () => {
853
+ test("OpenRouter Kimi K2.6 omits the forced choice and yields a structured selection", async () => {
854
+ // A real OpenAI chat-completions provider stands in for the cataloged
855
+ // OpenRouter Kimi profile. The request succeeds without a reactive retry
856
+ // because the forced select_pages choice is omitted before dispatch.
857
+ const wireRequests: unknown[] = [];
858
+ const kimi = new OpenRouterProvider(
859
+ "test-key",
860
+ "moonshotai/kimi-k2.6-20260420",
861
+ );
862
+ (kimi as unknown as { client: unknown }).client = {
863
+ chat: {
864
+ completions: {
865
+ create: async (params: unknown) => {
866
+ wireRequests.push(JSON.parse(JSON.stringify(params)));
867
+ return {
868
+ async *[Symbol.asyncIterator]() {
869
+ yield {
870
+ choices: [
871
+ {
872
+ delta: {
873
+ tool_calls: [
874
+ {
875
+ index: 0,
876
+ id: "call-1",
877
+ function: {
878
+ name: "select_pages",
879
+ arguments: '{"ids":[3]}',
880
+ },
881
+ },
882
+ ],
883
+ },
884
+ finish_reason: "tool_calls",
885
+ },
886
+ ],
887
+ usage: { prompt_tokens: 10, completion_tokens: 2 },
888
+ };
889
+ },
890
+ };
891
+ },
892
+ },
893
+ },
894
+ };
895
+
896
+ // The profile layer injects the thinking effort onto the call config in
897
+ // production; emulate that here so the forced tool_choice rides alongside
898
+ // reasoning on the wire.
899
+ providerStub = {
900
+ name: "kimi-openai-compat",
901
+ sendMessage: (messages: Message[], options?: SendMessageOptions) =>
902
+ kimi.sendMessage(messages, {
903
+ ...options,
904
+ config: {
905
+ ...options?.config,
906
+ effort: "high",
907
+ thinking: { enabled: true },
908
+ },
909
+ }),
910
+ };
911
+
912
+ const selection = await selectPool(makePool(), makeTurn("rollout?"));
913
+
914
+ // The preflight path avoids an incompatible first request, and the
915
+ // pool-level re-prompt loop never engages.
916
+ expect(wireRequests).toHaveLength(1);
917
+ const first = wireRequests[0] as {
918
+ tool_choice?: unknown;
919
+ reasoning?: { effort?: string; summary?: string };
920
+ };
921
+ expect(first.tool_choice).toBeUndefined();
922
+ expect(first.reasoning).toMatchObject({
923
+ effort: "high",
924
+ summary: "detailed",
925
+ });
926
+ expect(
927
+ warnPayloads().filter((p) => p.reason === "provider_error"),
928
+ ).toEqual([]);
929
+
930
+ // Structured selection survived: ids [3] is the topic-x finder line.
931
+ expect(selection.keptAll).toBe(false);
932
+ expect(selection.pages).toEqual([{ slug: "topic-x", sections: [] }]);
933
+ });
934
+ });
@@ -16,6 +16,10 @@ import { getConfig } from "../../../config/loader.js";
16
16
  import { isMemoryEnabled } from "../../../config/memory-v3-gate.js";
17
17
  import { rehydratePlatformCredentials } from "../../../config/platform-rehydration.js";
18
18
  import { startConversationEvictor } from "../../../daemon/conversation-evictor.js";
19
+ import {
20
+ startWorkspaceSkillMdMtimePoll,
21
+ stopWorkspaceSkillMdMtimePoll,
22
+ } from "../../../daemon/skill-memory-refresh.js";
19
23
  import { resetDb } from "../../../persistence/db-connection.js";
20
24
  import {
21
25
  EMBEDDING_SHUTDOWN_BUDGET_MS,
@@ -108,6 +112,7 @@ async function main(): Promise<void> {
108
112
  shuttingDown = true;
109
113
  log.info({ signal }, "Memory worker process shutting down");
110
114
  worker?.stop();
115
+ stopWorkspaceSkillMdMtimePoll();
111
116
  if (keepAlive != null) {
112
117
  clearInterval(keepAlive);
113
118
  }
@@ -161,6 +166,7 @@ async function main(): Promise<void> {
161
166
  }
162
167
 
163
168
  worker = startMemoryJobsWorkerLoop();
169
+ startWorkspaceSkillMdMtimePoll();
164
170
 
165
171
  // Keep-alive: the worker's setTimeout timers are unref'd, so without
166
172
  // this interval the process would exit immediately.
@@ -14,7 +14,7 @@
14
14
  * This module reads those files and projects each entry onto the
15
15
  * assistant's own `McpServerConfig` shape, so a plugin-declared server can
16
16
  * flow through the same surfaces as one configured in the workspace
17
- * `config.json`.
17
+ * `mcp.json`.
18
18
  *
19
19
  * Failure isolation follows the spec: an invalid top-level `mcp.json`
20
20
  * disables MCP for that plugin only, and an invalid individual server
@@ -39,17 +39,17 @@
39
39
  import { existsSync, readFileSync } from "node:fs";
40
40
  import { join } from "node:path";
41
41
 
42
- import { z } from "zod";
43
-
44
42
  import {
45
43
  type AllPluginInfo,
46
44
  listAllPlugins,
47
45
  type ListInstalledPluginsOptions,
48
46
  } from "../cli/lib/list-installed-plugins.js";
49
- import type {
50
- McpTransport,
51
- ResolvedMcpServerConfig,
52
- } from "../config/schemas/mcp.js";
47
+ import type { ResolvedMcpServerConfig } from "../config/schemas/mcp.js";
48
+ import {
49
+ projectSpecServerToTransport,
50
+ SpecMcpDocumentSchema,
51
+ SpecMcpServerSchema,
52
+ } from "../mcp/spec-schema.js";
53
53
  import { getLogger } from "../util/logger.js";
54
54
 
55
55
  const log = getLogger("plugin-mcp-servers");
@@ -77,45 +77,6 @@ export const PLUGIN_MCP_MANIFEST = "mcp.json";
77
77
  * origin risk.
78
78
  */
79
79
 
80
- // ---------------------------------------------------------------------------
81
- // Wire schema (Agent Plugins 1.0.0)
82
- // ---------------------------------------------------------------------------
83
-
84
- const PluginStdioServerSchema = z.object({
85
- type: z.literal("stdio"),
86
- command: z.string().min(1),
87
- args: z.array(z.string()).optional(),
88
- env: z.record(z.string(), z.string()).optional(),
89
- cwd: z.string().optional(),
90
- });
91
-
92
- /**
93
- * Remote MCP entry. Agent Plugins 1.0.0 requires `type` and names no
94
- * default. MCP's current remote transport is Streamable HTTP, so this
95
- * host fills omitted `type` and the Claude-style `http` alias as
96
- * `streamable-http`. `sse` stays explicit.
97
- */
98
- const PluginHttpServerSchema = z.object({
99
- type: z
100
- .union([
101
- z.literal("streamable-http"),
102
- z.literal("sse"),
103
- z.literal("http").transform(() => "streamable-http" as const),
104
- ])
105
- .default("streamable-http"),
106
- url: z.string().min(1),
107
- headers: z.record(z.string(), z.string()).optional(),
108
- });
109
-
110
- const PluginMcpServerSchema = z.union([
111
- PluginStdioServerSchema,
112
- PluginHttpServerSchema,
113
- ]);
114
-
115
- const PluginMcpManifestSchema = z.object({
116
- mcpServers: z.record(z.string(), z.unknown()),
117
- });
118
-
119
80
  // ---------------------------------------------------------------------------
120
81
  // Public shape
121
82
  // ---------------------------------------------------------------------------
@@ -251,7 +212,7 @@ export function readPluginMcpServers(
251
212
  continue;
252
213
  }
253
214
 
254
- const entry = PluginMcpServerSchema.safeParse(raw);
215
+ const entry = SpecMcpServerSchema.safeParse(raw);
255
216
  if (!entry.success) {
256
217
  issues.push({
257
218
  pluginName: plugin.name,
@@ -325,7 +286,7 @@ function parseManifest(
325
286
  };
326
287
  }
327
288
 
328
- const manifest = PluginMcpManifestSchema.safeParse(json);
289
+ const manifest = SpecMcpDocumentSchema.safeParse(json);
329
290
  if (!manifest.success) {
330
291
  return {
331
292
  error: `${PLUGIN_MCP_MANIFEST} is missing a valid "mcpServers" object`,
@@ -334,33 +295,12 @@ function parseManifest(
334
295
  return { mcpServers: manifest.data.mcpServers };
335
296
  }
336
297
 
337
- /**
338
- * Project one spec-shaped entry onto the assistant's transport union. The
339
- * two vocabularies already agree on type names and required fields, so this
340
- * is a field copy plus path interpolation for the stdio case.
341
- */
342
298
  function projectTransport(
343
- entry: z.infer<typeof PluginMcpServerSchema>,
299
+ entry: Parameters<typeof projectSpecServerToTransport>[0],
344
300
  pluginRoot: string,
345
- ): McpTransport {
346
- if (entry.type === "stdio") {
347
- const pluginData = join(pluginRoot, "data");
348
- const expand = (v: string): string =>
349
- interpolatePluginPaths(v, pluginRoot, pluginData);
350
- return {
351
- type: "stdio",
352
- command: entry.command,
353
- args: (entry.args ?? []).map(expand),
354
- ...(entry.env && {
355
- env: Object.fromEntries(
356
- Object.entries(entry.env).map(([k, v]) => [k, expand(v)]),
357
- ),
358
- }),
359
- };
360
- }
361
- return {
362
- type: entry.type,
363
- url: entry.url,
364
- ...(entry.headers && { headers: entry.headers }),
365
- };
301
+ ) {
302
+ const pluginData = join(pluginRoot, "data");
303
+ return projectSpecServerToTransport(entry, (value) =>
304
+ interpolatePluginPaths(value, pluginRoot, pluginData),
305
+ );
366
306
  }
@@ -50,6 +50,8 @@ describe("isConnectionCompatibleWithModel", () => {
50
50
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.4-nano")).toBe(false);
51
51
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.5-pro")).toBe(false);
52
52
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.3-codex")).toBe(false);
53
+ expect(isConnectionCompatibleWithModel(conn, "gpt-5.4")).toBe(false);
54
+ expect(isConnectionCompatibleWithModel(conn, "gpt-5.4-mini")).toBe(false);
53
55
  });
54
56
 
55
57
  test("oauth_subscription connection is compatible with a Codex model", () => {
@@ -59,8 +61,6 @@ describe("isConnectionCompatibleWithModel", () => {
59
61
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-terra")).toBe(true);
60
62
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-luna")).toBe(true);
61
63
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.5")).toBe(true);
62
- expect(isConnectionCompatibleWithModel(conn, "gpt-5.4")).toBe(true);
63
- expect(isConnectionCompatibleWithModel(conn, "gpt-5.4-mini")).toBe(true);
64
64
  });
65
65
 
66
66
  test("undefined model applies no gating (compatible)", () => {
@@ -165,7 +165,7 @@ describe("auto-resolution skips oauth_subscription connections for non-Codex mod
165
165
 
166
166
  test("Codex model can select the oauth_subscription connection", async () => {
167
167
  registerConnections([OPENAI_CODEX, OPENAI_KEY]);
168
- setOpenAiProfile("gpt-5.4");
168
+ setOpenAiProfile("gpt-5.5");
169
169
 
170
170
  const result = await getConfiguredProvider("mainAgent", {
171
171
  overrideProfile: "openai-any",
@@ -15,6 +15,7 @@ import {
15
15
  keepFileAsWorkspaceRef,
16
16
  } from "../content-block-size.js";
17
17
  import { fileBlockToProviderText } from "../file-block-text.js";
18
+ import { unsignedThoughtSignatureFallback } from "../gemini-thought-signature.js";
18
19
  import { base64Source, resolveMediaReferences } from "../media-resolve.js";
19
20
  import { PROVIDER_CATALOG } from "../model-catalog.js";
20
21
  import { recordProviderRequestDiagnostics } from "../request-diagnostics.js";
@@ -45,13 +46,6 @@ import {
45
46
  const GEMINI_CONTEXT_OVERFLOW_TOKEN_PATTERNS =
46
47
  /token.?count.*exceeds|exceeds.*maximum.*tokens|prompt.?is.?too.?long|too.?many.?(?:input.?)?tokens|input.?too.?long|context.?length.?exceeded/i;
47
48
 
48
- const GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE =
49
- "context_engineering_is_the_way_to_go";
50
-
51
- function isGemini3Model(model: string): boolean {
52
- return model.startsWith("gemini-3") || model.startsWith("models/gemini-3");
53
- }
54
-
55
49
  const THINKING_LEVEL_BY_NAME: Record<ThinkingLevelName, ThinkingLevel> = {
56
50
  minimal: ThinkingLevel.MINIMAL,
57
51
  low: ThinkingLevel.LOW,
@@ -435,7 +429,8 @@ export class GeminiProvider implements Provider {
435
429
  const maxTokens = configObj?.max_tokens as number | undefined;
436
430
  const modelOverride = configObj?.model as string | undefined;
437
431
  const usageAttributionHeaders = configObj?.usageAttributionHeaders as
438
- Record<string, string> | undefined;
432
+ | Record<string, string>
433
+ | undefined;
439
434
  const activeModel = modelOverride ?? this.model;
440
435
  const thinkingConfig = geminiModelSupportsThinking(activeModel)
441
436
  ? buildThinkingConfig(
@@ -902,28 +897,19 @@ export class GeminiProvider implements Provider {
902
897
  parts: genai.Part[],
903
898
  model: string,
904
899
  ): void {
905
- if (!isGemini3Model(model)) {
906
- return;
907
- }
908
-
909
900
  const functionCallParts = parts.filter((part) => part.functionCall);
910
- if (functionCallParts.length === 0) {
911
- return;
912
- }
913
-
914
- const hasRealThoughtSignature = functionCallParts.some((part) =>
915
- Boolean(part.thoughtSignature),
901
+ const fallback = unsignedThoughtSignatureFallback(
902
+ functionCallParts.map((part) => part.thoughtSignature),
903
+ { model },
916
904
  );
917
- if (hasRealThoughtSignature) {
905
+ if (!fallback) {
918
906
  return;
919
907
  }
920
-
921
- const firstFunctionCallPart = functionCallParts[0];
908
+ const firstFunctionCallPart = functionCallParts[fallback.index];
922
909
  if (!firstFunctionCallPart) {
923
910
  return;
924
911
  }
925
- firstFunctionCallPart.thoughtSignature =
926
- GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE;
912
+ firstFunctionCallPart.thoughtSignature = fallback.signature;
927
913
  }
928
914
 
929
915
  private supportsGeminiInlineFile(mimeType: string): boolean {
@@ -0,0 +1,63 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
5
+ isGemini3Model,
6
+ unsignedThoughtSignatureFallback,
7
+ } from "./gemini-thought-signature.js";
8
+
9
+ describe("isGemini3Model", () => {
10
+ test("matches native Gemini 3 ids", () => {
11
+ expect(isGemini3Model("gemini-3.7-flash")).toBe(true);
12
+ expect(isGemini3Model("models/gemini-3.1-pro-preview")).toBe(true);
13
+ });
14
+
15
+ test("matches OpenAI-compatible catalog prefixes", () => {
16
+ expect(isGemini3Model("google/gemini-3.7-flash")).toBe(true);
17
+ });
18
+
19
+ test("does not match Gemini 2.5 or unrelated ids", () => {
20
+ expect(isGemini3Model("gemini-2.5-flash")).toBe(false);
21
+ expect(isGemini3Model("gpt-5.2")).toBe(false);
22
+ });
23
+ });
24
+
25
+ describe("unsignedThoughtSignatureFallback", () => {
26
+ test("stamps the dummy on the first unsigned Gemini 3 call", () => {
27
+ expect(
28
+ unsignedThoughtSignatureFallback([undefined, undefined], {
29
+ model: "gemini-3.7-flash",
30
+ }),
31
+ ).toEqual({
32
+ index: 0,
33
+ signature: GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
34
+ });
35
+ });
36
+
37
+ test("does not stamp when any call already has a signature", () => {
38
+ expect(
39
+ unsignedThoughtSignatureFallback(["signed-thought-1", undefined], {
40
+ model: "gemini-3.7-flash",
41
+ }),
42
+ ).toBeUndefined();
43
+ });
44
+
45
+ test("does not stamp for non-Gemini-3 models when a model id is provided", () => {
46
+ expect(
47
+ unsignedThoughtSignatureFallback([undefined], { model: "gpt-5.2" }),
48
+ ).toBeUndefined();
49
+ });
50
+
51
+ test("stamps unsigned calls when no model gate is supplied", () => {
52
+ expect(unsignedThoughtSignatureFallback([undefined])).toEqual({
53
+ index: 0,
54
+ signature: GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
55
+ });
56
+ });
57
+
58
+ test("does not stamp an empty function-call list", () => {
59
+ expect(
60
+ unsignedThoughtSignatureFallback([], { model: "gemini-3.7-flash" }),
61
+ ).toBeUndefined();
62
+ });
63
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Gemini 3.x requires a thought signature on the first function-call part of
3
+ * each step. When history has no captured signature (unsigned tool_use from an
4
+ * earlier provider, or a client-injected call), Google documents these dummy
5
+ * values to skip validation.
6
+ */
7
+ export const GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE =
8
+ "context_engineering_is_the_way_to_go";
9
+
10
+ /**
11
+ * True for Gemini 3.x model ids, including the native `models/` prefix and
12
+ * OpenAI-compatible catalog prefixes such as `google/gemini-3.7-flash`.
13
+ */
14
+ export function isGemini3Model(model: string): boolean {
15
+ if (model.startsWith("gemini-3") || model.startsWith("models/gemini-3")) {
16
+ return true;
17
+ }
18
+ return /\/gemini-3/.test(model);
19
+ }
20
+
21
+ /**
22
+ * Decide whether unsigned function-call history should receive Google's
23
+ * documented dummy thought signature. Returns the first call's index when
24
+ * every call is unsigned. Pass `model` to restrict this to Gemini 3.x;
25
+ * omit it for retry backfills that already observed a thought-signature 4xx.
26
+ */
27
+ export function unsignedThoughtSignatureFallback(
28
+ signatures: ReadonlyArray<string | undefined | null>,
29
+ options?: { model?: string },
30
+ ): { index: 0; signature: string } | undefined {
31
+ if (options?.model !== undefined && !isGemini3Model(options.model)) {
32
+ return undefined;
33
+ }
34
+ if (signatures.length === 0) {
35
+ return undefined;
36
+ }
37
+ if (signatures.some((signature) => Boolean(signature))) {
38
+ return undefined;
39
+ }
40
+ return {
41
+ index: 0,
42
+ signature: GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
43
+ };
44
+ }
@@ -15,9 +15,8 @@ import { VELLUM_MANAGED_PROVIDER } from "../vellum-model-routing.js";
15
15
  * - platform: route via Vellum managed proxy; no client-side credential.
16
16
  * - none: no auth (e.g. Ollama running locally).
17
17
  * - oauth_subscription: OAuth-based subscription auth (e.g. ChatGPT Codex).
18
- *
19
- * Schema-accepted variants (runtime rejects with a clear "not yet shipped" error):
20
- * - service_account: service-account credentials (Vertex AI, Bedrock).
18
+ * - service_account: Google service-account JSON key (Vertex AI); signs a
19
+ * JWT and exchanges it for a bearer token at the embedded token_uri.
21
20
  */
22
21
  export const AuthSchema = z
23
22
  .discriminatedUnion("type", [
@@ -178,14 +178,33 @@ export async function computeConnectionAvailability(
178
178
  }
179
179
 
180
180
  switch (connection.auth.type) {
181
- // Schema-accepted but not dispatchable: `resolveAuth` returns
182
- // not_implemented for service_account, so a stored credential still
183
- // cannot serve inference.
184
- case "service_account":
185
- return {
186
- status: "unsupported_auth",
187
- message: `Connection "${resolvedConnectionName}" uses service-account auth, which inference does not support yet. Pick a connection with a different auth type.`,
188
- };
181
+ case "service_account": {
182
+ const result = await getSecureKeyResultAsync(connection.auth.credential);
183
+ if (result.unreachable) {
184
+ return {
185
+ status: "unknown",
186
+ message: `The credential store is unreachable, so the credential for connection "${resolvedConnectionName}" could not be verified. Try again shortly.`,
187
+ };
188
+ }
189
+ if (result.value == null) {
190
+ return {
191
+ status: "missing_credential",
192
+ message: `Connection "${resolvedConnectionName}" has no service-account credential stored. Add one ${SETTINGS_HINT}.`,
193
+ };
194
+ }
195
+ // Validate the JSON payload before reporting ok: a stored value that is
196
+ // not a well-formed service-account object will be rejected by dispatch,
197
+ // so surface the problem here rather than letting a turn fail silently.
198
+ const { parseServiceAccountKey } =
199
+ await import("./service-account-token.js");
200
+ if (!parseServiceAccountKey(result.value)) {
201
+ return {
202
+ status: "missing_credential",
203
+ message: `Connection "${resolvedConnectionName}" has an invalid service-account credential (expected JSON with client_email, private_key, and token_uri). Update it ${SETTINGS_HINT}.`,
204
+ };
205
+ }
206
+ return { status: "ok" };
207
+ }
189
208
  case "api_key":
190
209
  case "oauth_subscription": {
191
210
  const result = await getSecureKeyResultAsync(connection.auth.credential);
@@ -6,7 +6,7 @@
6
6
  * - platform → build managed proxy URL and fetch the platform API key
7
7
  * - none → pass through with no auth headers
8
8
  * - oauth_subscription → fetch OAuth token from vault (with auto-refresh) → inject as bearer header
9
- * - service_account → reject (v2 not yet shipped)
9
+ * - service_account → sign JWT and exchange for bearer token at token_uri (Google Vertex AI)
10
10
  */
11
11
 
12
12
  import { getSecureKeyAsync } from "../../security/secure-keys.js";
@@ -119,10 +119,28 @@ export async function resolveAuth(
119
119
  };
120
120
  }
121
121
 
122
- case "service_account":
122
+ case "service_account": {
123
+ const { getValidServiceAccountToken } =
124
+ await import("./service-account-token.js");
125
+ const result = await getValidServiceAccountToken(auth.credential);
126
+ if (!result.ok) {
127
+ if (result.reason === "exchange_failed") {
128
+ return { ok: false, error: { code: "platform_unavailable" } };
129
+ }
130
+ // not_found or invalid_config: the credential needs to be supplied or fixed.
131
+ return {
132
+ ok: false,
133
+ error: { code: "credential_not_found", credential: auth.credential },
134
+ };
135
+ }
123
136
  return {
124
- ok: false,
125
- error: { code: "not_implemented", authType: auth.type },
137
+ ok: true,
138
+ resolved: {
139
+ kind: "header",
140
+ headers: { Authorization: `Bearer ${result.token}` },
141
+ ...(safeBaseUrl ? { baseUrl: safeBaseUrl } : {}),
142
+ },
126
143
  };
144
+ }
127
145
  }
128
146
  }