@vellumai/assistant 0.11.6 → 0.11.7-staging.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 (478) hide show
  1. package/ARCHITECTURE.md +31 -19
  2. package/Dockerfile +1 -0
  3. package/docs/architecture/security.md +3 -3
  4. package/docs/flux-turn-detection-spike.md +8 -7
  5. package/docs/runbook-trusted-contacts.md +14 -0
  6. package/docs/stt-provider-onboarding.md +8 -4
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
  9. package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
  10. package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
  11. package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
  12. package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
  13. package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
  14. package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
  15. package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  19. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  20. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  21. package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
  22. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  23. package/node_modules/@vellumai/environments/package.json +2 -1
  24. package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
  25. package/node_modules/@vellumai/environments/src/shell.ts +65 -0
  26. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  27. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  28. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  29. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  30. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  31. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
  32. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
  33. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
  34. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
  35. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
  36. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
  37. package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
  38. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
  39. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
  40. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
  41. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
  42. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
  43. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
  44. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
  45. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  46. package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  47. package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  48. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  49. package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  50. package/openapi.yaml +419 -55
  51. package/package.json +4 -1
  52. package/scripts/sync-llm-catalog.ts +6 -0
  53. package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
  54. package/src/__tests__/approval-card-edit.test.ts +153 -0
  55. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  56. package/src/__tests__/available-skills.test.ts +54 -0
  57. package/src/__tests__/avatar-identity-sync.test.ts +5 -1
  58. package/src/__tests__/catalog-install-normalize.test.ts +9 -1
  59. package/src/__tests__/channel-policy.test.ts +1 -3
  60. package/src/__tests__/channel-readiness-discord.test.ts +9 -70
  61. package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +69 -0
  63. package/src/__tests__/config-schema.test.ts +2 -3
  64. package/src/__tests__/conversation-agent-loop.test.ts +238 -0
  65. package/src/__tests__/conversation-attachments.test.ts +0 -1
  66. package/src/__tests__/conversation-error.test.ts +44 -0
  67. package/src/__tests__/conversation-evictor.test.ts +19 -0
  68. package/src/__tests__/conversation-fork-referential.test.ts +73 -63
  69. package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
  70. package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
  71. package/src/__tests__/conversation-slash.test.ts +9 -0
  72. package/src/__tests__/conversation-title-service.test.ts +97 -0
  73. package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
  74. package/src/__tests__/credential-broker-server-use.test.ts +0 -218
  75. package/src/__tests__/credential-broker.test.ts +43 -420
  76. package/src/__tests__/credential-record-write-through.test.ts +78 -0
  77. package/src/__tests__/credential-routes.test.ts +17 -1
  78. package/src/__tests__/credential-security-invariants.test.ts +8 -9
  79. package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
  80. package/src/__tests__/delete-propagation.test.ts +92 -2
  81. package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
  82. package/src/__tests__/discord-channel-config.test.ts +237 -0
  83. package/src/__tests__/edit-propagation.test.ts +42 -2
  84. package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
  85. package/src/__tests__/fallback-breaker.test.ts +1335 -0
  86. package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
  87. package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
  88. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
  89. package/src/__tests__/image-conversion.test.ts +38 -4
  90. package/src/__tests__/install-skill-routing.test.ts +25 -0
  91. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  92. package/src/__tests__/llm-resolver.test.ts +74 -0
  93. package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
  94. package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
  95. package/src/__tests__/managed-profile-guard.test.ts +28 -0
  96. package/src/__tests__/managed-speech-defaults.test.ts +196 -1
  97. package/src/__tests__/mcp-cli.test.ts +23 -1
  98. package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
  99. package/src/__tests__/media-stream-stt-session.test.ts +2 -0
  100. package/src/__tests__/notification-decision-fallback.test.ts +106 -0
  101. package/src/__tests__/notification-discord-adapter.test.ts +162 -0
  102. package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
  103. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
  104. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  105. package/src/__tests__/platform.test.ts +19 -1
  106. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  107. package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
  108. package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
  109. package/src/__tests__/provider-usage-tracking.test.ts +56 -0
  110. package/src/__tests__/require-fresh-approval.test.ts +5 -2
  111. package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
  112. package/src/__tests__/schedule-retry.test.ts +183 -4
  113. package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
  115. package/src/__tests__/skill-load-tool.test.ts +34 -0
  116. package/src/__tests__/skill-tool-factory.test.ts +6 -1
  117. package/src/__tests__/skill-tool-manifest.test.ts +20 -0
  118. package/src/__tests__/skills.test.ts +25 -0
  119. package/src/__tests__/subagent-manager-notify.test.ts +38 -0
  120. package/src/__tests__/terminal-tools.test.ts +2 -0
  121. package/src/__tests__/tool-approval-handler.test.ts +146 -2
  122. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
  123. package/src/__tests__/tool-audit.test.ts +111 -40
  124. package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
  125. package/src/__tests__/ui-shape-teaching.test.ts +1 -1
  126. package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
  127. package/src/__tests__/voice-config-update.test.ts +43 -0
  128. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
  129. package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
  130. package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
  131. package/src/agent/loop.ts +31 -2
  132. package/src/approvals/guardian-card-withdrawal.ts +22 -0
  133. package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
  134. package/src/avatar/avatar-manifest.ts +48 -100
  135. package/src/avatar/avatar-store.ts +7 -7
  136. package/src/avatar/ensure-raster.ts +146 -0
  137. package/src/avatar/resvg-lazy.test.ts +0 -1
  138. package/src/avatar/traits-png-sync.ts +9 -10
  139. package/src/background-wake/wake-intent-hooks.test.ts +0 -1
  140. package/src/backup/__tests__/paths.test.ts +6 -2
  141. package/src/backup/paths.ts +6 -4
  142. package/src/bundler/app-compiler.ts +2 -2
  143. package/src/bundler/compiler-tools.test.ts +36 -0
  144. package/src/bundler/compiler-tools.ts +54 -18
  145. package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
  146. package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
  147. package/src/calls/media-stream-stt-session.ts +2 -1
  148. package/src/calls/telephony-synthesis-language.ts +13 -5
  149. package/src/calls/voice-session-bridge.ts +19 -4
  150. package/src/channels/config.ts +23 -12
  151. package/src/cli/AGENTS.md +3 -1
  152. package/src/cli/commands/backup.help.ts +10 -13
  153. package/src/cli/commands/contacts.help.ts +16 -4
  154. package/src/cli/commands/contacts.ts +5 -1
  155. package/src/cli/commands/credentials.help.ts +1 -1
  156. package/src/cli/commands/inference-providers.ts +18 -53
  157. package/src/cli/commands/mcp.help.ts +6 -5
  158. package/src/cli/commands/mcp.ts +1 -1
  159. package/src/cli/commands/memory/memory-retrospective.ts +1 -0
  160. package/src/cli/commands/stt.help.ts +2 -2
  161. package/src/config/__tests__/backup-schema.test.ts +1 -1
  162. package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
  163. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
  164. package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
  165. package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
  166. package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
  167. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
  168. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  169. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  170. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
  171. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
  172. package/src/config/bundled-skills/settings/TOOLS.json +11 -5
  173. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
  174. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
  175. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
  176. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
  177. package/src/config/default-profile-catalog.ts +195 -10
  178. package/src/config/default-profile-names.ts +41 -0
  179. package/src/config/feature-flag-registry.json +34 -2
  180. package/src/config/inference-profile-validation.ts +11 -2
  181. package/src/config/llm-resolver.ts +25 -0
  182. package/src/config/loader.ts +3 -0
  183. package/src/config/managed-speech-defaults.ts +160 -10
  184. package/src/config/schema.ts +2 -0
  185. package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
  186. package/src/config/schemas/__tests__/stt.test.ts +94 -0
  187. package/src/config/schemas/backup.ts +1 -1
  188. package/src/config/schemas/channels.ts +32 -0
  189. package/src/config/schemas/live-voice.ts +13 -4
  190. package/src/config/schemas/llm.ts +263 -15
  191. package/src/config/schemas/mcp.ts +10 -2
  192. package/src/config/schemas/memory-retrospective.ts +1 -19
  193. package/src/config/schemas/services.ts +24 -0
  194. package/src/config/schemas/stt.ts +128 -5
  195. package/src/config/seed-inference-profiles.ts +32 -3
  196. package/src/config/skill-state.ts +5 -0
  197. package/src/config/skills.ts +21 -0
  198. package/src/contacts/notify-contacts-changed.ts +5 -2
  199. package/src/context/compactor.ts +74 -1
  200. package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
  201. package/src/daemon/config-watcher.ts +2 -1
  202. package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
  203. package/src/daemon/conversation-agent-loop.ts +86 -10
  204. package/src/daemon/conversation-error.ts +25 -0
  205. package/src/daemon/conversation-evictor.ts +2 -4
  206. package/src/daemon/conversation-runtime-assembly.ts +19 -7
  207. package/src/daemon/conversation-skill-tools.ts +5 -1
  208. package/src/daemon/conversation-slash.ts +2 -2
  209. package/src/daemon/conversation-store.ts +8 -1
  210. package/src/daemon/conversation-tool-setup.ts +37 -0
  211. package/src/daemon/conversation.ts +0 -4
  212. package/src/daemon/handlers/channel-config-result.ts +30 -0
  213. package/src/daemon/handlers/config-discord-channel.ts +301 -0
  214. package/src/daemon/handlers/config-ingress.ts +9 -0
  215. package/src/daemon/handlers/config-slack-channel.ts +13 -15
  216. package/src/daemon/handlers/config-telegram.ts +2 -5
  217. package/src/daemon/handlers/conversations.ts +0 -2
  218. package/src/daemon/handlers/skills.ts +25 -7
  219. package/src/daemon/lifecycle.ts +3 -1
  220. package/src/daemon/message-types/messages.ts +0 -2
  221. package/src/daemon/tool-setup-types.ts +5 -3
  222. package/src/daemon/trust-context-types.test.ts +33 -0
  223. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  224. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
  225. package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
  226. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
  227. package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
  228. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
  229. package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
  230. package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
  231. package/src/live-voice/__tests__/protocol.test.ts +224 -0
  232. package/src/live-voice/live-voice-credential-preflight.ts +5 -1
  233. package/src/live-voice/live-voice-session-manager.ts +39 -2
  234. package/src/live-voice/live-voice-session.ts +224 -9
  235. package/src/live-voice/protocol.ts +187 -1
  236. package/src/messaging/providers/discord/api.ts +2 -1
  237. package/src/messaging/providers/discord/withdraw.ts +62 -0
  238. package/src/messaging/read-provider-metadata.ts +32 -0
  239. package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
  240. package/src/monitoring/__tests__/process-memory.test.ts +34 -1
  241. package/src/monitoring/db-integrity-sample.ts +1 -0
  242. package/src/monitoring/file-descriptors.ts +39 -6
  243. package/src/monitoring/process-memory.ts +37 -5
  244. package/src/monitoring/resource-sampler.ts +25 -8
  245. package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
  246. package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
  247. package/src/notifications/__tests__/connected-channels.test.ts +12 -0
  248. package/src/notifications/__tests__/copy-composer.test.ts +83 -0
  249. package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
  250. package/src/notifications/activity-failed-dedupe.ts +95 -0
  251. package/src/notifications/adapters/discord.ts +132 -0
  252. package/src/notifications/adapters/shared.ts +16 -0
  253. package/src/notifications/adapters/slack.ts +6 -1
  254. package/src/notifications/adapters/telegram.ts +5 -8
  255. package/src/notifications/approval-card-data.ts +94 -1
  256. package/src/notifications/background-failure-signal.ts +116 -0
  257. package/src/notifications/broadcaster.ts +3 -9
  258. package/src/notifications/copy-composer.ts +41 -8
  259. package/src/notifications/decision-engine.ts +55 -0
  260. package/src/notifications/destination-resolver.ts +45 -5
  261. package/src/notifications/emit-signal.ts +19 -5
  262. package/src/notifications/guardian-question-mode.ts +57 -0
  263. package/src/notifications/types.ts +2 -8
  264. package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
  265. package/src/oauth/connect-orchestrator.ts +16 -0
  266. package/src/oauth/seed-providers.ts +262 -0
  267. package/src/permissions/checker.test.ts +21 -0
  268. package/src/permissions/checker.ts +6 -0
  269. package/src/permissions/gateway-threshold-reader.test.ts +184 -0
  270. package/src/permissions/gateway-threshold-reader.ts +144 -10
  271. package/src/permissions/prompter.ts +0 -6
  272. package/src/permissions/types.ts +5 -4
  273. package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
  274. package/src/persistence/bulk-write-gate.ts +9 -10
  275. package/src/persistence/conversation-crud.ts +88 -134
  276. package/src/persistence/conversation-title-service.ts +43 -29
  277. package/src/persistence/embeddings/embedding-local.ts +14 -85
  278. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
  279. package/src/persistence/lifecycle-events-store.test.ts +50 -0
  280. package/src/persistence/lifecycle-events-store.ts +75 -5
  281. package/src/platform/platform-patch-queue.test.ts +368 -0
  282. package/src/platform/platform-patch-queue.ts +190 -0
  283. package/src/platform/sync-avatar.test.ts +527 -0
  284. package/src/platform/sync-avatar.ts +209 -0
  285. package/src/platform/sync-identity.test.ts +75 -0
  286. package/src/platform/sync-identity.ts +17 -110
  287. package/src/plugin-api/model-profiles.test.ts +2 -2
  288. package/src/plugin-api/model-profiles.ts +7 -7
  289. package/src/plugin-api/transcription-session.ts +1 -1
  290. package/src/plugins/defaults/compaction/window-manager.ts +4 -0
  291. package/src/plugins/defaults/image-recovery/recover.ts +2 -2
  292. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
  293. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
  294. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
  295. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
  296. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
  297. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
  298. package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
  299. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
  300. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
  301. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
  302. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
  303. package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
  304. package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
  305. package/src/plugins/defaults/memory/worker.ts +7 -0
  306. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  307. package/src/providers/__tests__/retry-callsite.test.ts +55 -0
  308. package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
  309. package/src/providers/connection-resolution.ts +2 -17
  310. package/src/providers/fallback-breaker.ts +389 -0
  311. package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
  312. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
  313. package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
  314. package/src/providers/inference/adapter-factory.ts +246 -3
  315. package/src/providers/inference/auth.ts +15 -14
  316. package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
  317. package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
  318. package/src/providers/inference/resolve-auth.ts +1 -1
  319. package/src/providers/media-resolve.ts +2 -3
  320. package/src/providers/model-catalog.ts +100 -0
  321. package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
  322. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
  323. package/src/providers/openai/api-error-normalization.ts +13 -0
  324. package/src/providers/openai/chat-completions-provider.ts +84 -0
  325. package/src/providers/opencode/client.test.ts +150 -0
  326. package/src/providers/opencode/client.ts +61 -0
  327. package/src/providers/platform-proxy/constants.ts +5 -0
  328. package/src/providers/provider-resolvability.ts +20 -0
  329. package/src/providers/provider-secret-catalog.ts +3 -2
  330. package/src/providers/retry.ts +956 -68
  331. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
  332. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
  333. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
  334. package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
  335. package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
  336. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
  337. package/src/providers/speech-to-text/provider-catalog.ts +191 -3
  338. package/src/providers/speech-to-text/resolve.ts +132 -24
  339. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
  340. package/src/providers/types.ts +39 -0
  341. package/src/providers/usage-tracking.ts +12 -1
  342. package/src/providers/vellum/client.ts +29 -0
  343. package/src/providers/vellum-model-routing.test.ts +2 -0
  344. package/src/providers/vellum-model-routing.ts +3 -3
  345. package/src/runtime/AGENTS.md +1 -2
  346. package/src/runtime/__tests__/agent-wake.test.ts +105 -3
  347. package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
  348. package/src/runtime/agent-wake.ts +44 -5
  349. package/src/runtime/background-job-runner.ts +68 -50
  350. package/src/runtime/channel-readiness-service.ts +0 -39
  351. package/src/runtime/http-server.ts +12 -4
  352. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
  353. package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
  354. package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
  355. package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
  356. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
  357. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
  358. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
  359. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
  360. package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
  361. package/src/runtime/routes/approval-routes.ts +0 -12
  362. package/src/runtime/routes/avatar-routes.ts +17 -30
  363. package/src/runtime/routes/backup-routes.ts +2 -2
  364. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
  365. package/src/runtime/routes/contact-prompt-routes.ts +65 -6
  366. package/src/runtime/routes/contact-routes.ts +20 -15
  367. package/src/runtime/routes/conversation-query-routes.ts +98 -10
  368. package/src/runtime/routes/conversation-routes.ts +2 -2
  369. package/src/runtime/routes/credential-routes.ts +2 -1
  370. package/src/runtime/routes/guardian-approval-interception.ts +12 -9
  371. package/src/runtime/routes/inbound-message-handler.ts +47 -48
  372. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
  373. package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
  374. package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
  375. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
  376. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  377. package/src/runtime/routes/index.ts +2 -0
  378. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  379. package/src/runtime/routes/inference-profiles-routes.ts +12 -7
  380. package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
  381. package/src/runtime/routes/inference-send-routes.ts +2 -2
  382. package/src/runtime/routes/ingress-status-routes.ts +2 -2
  383. package/src/runtime/routes/integrations/discord.ts +119 -0
  384. package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
  385. package/src/runtime/routes/mcp-auth-routes.ts +12 -6
  386. package/src/runtime/routes/migration-routes.ts +3 -1
  387. package/src/runtime/routes/oauth-pending-flows.ts +136 -0
  388. package/src/runtime/routes/secret-routes.ts +7 -0
  389. package/src/runtime/routes/stt-routes.ts +38 -6
  390. package/src/runtime/routes/wake-conversation-routes.ts +8 -1
  391. package/src/runtime/routes/watch-routes.ts +9 -5
  392. package/src/runtime/sync/resource-sync-events.ts +2 -0
  393. package/src/runtime/trust-verdict-consumer.ts +3 -3
  394. package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
  395. package/src/schedule/retry-policy.ts +8 -8
  396. package/src/schedule/run-script.ts +14 -23
  397. package/src/schedule/schedule-recovery.ts +0 -1
  398. package/src/schedule/scheduler.ts +89 -51
  399. package/src/schedule/worker.ts +57 -0
  400. package/src/security/__tests__/openai-device-auth.test.ts +355 -0
  401. package/src/security/ces-rpc-record-backend.ts +123 -0
  402. package/src/security/oauth2.ts +7 -0
  403. package/src/security/openai-device-auth.ts +364 -0
  404. package/src/security/secure-keys.ts +15 -0
  405. package/src/skills/available-skills.ts +10 -1
  406. package/src/skills/catalog-install.ts +19 -1
  407. package/src/skills/inline-command-runner.ts +6 -2
  408. package/src/skills/platform-compatibility.test.ts +49 -0
  409. package/src/skills/platform-compatibility.ts +65 -0
  410. package/src/skills/tool-manifest.ts +20 -0
  411. package/src/stt/__tests__/roles.test.ts +152 -0
  412. package/src/stt/daemon-batch-transcriber.ts +4 -1
  413. package/src/stt/roles.ts +180 -0
  414. package/src/stt/stt-stream-session.ts +3 -2
  415. package/src/stt/types.ts +33 -3
  416. package/src/subagent/manager.ts +12 -0
  417. package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
  418. package/src/telemetry/live-voice-funnel.ts +12 -2
  419. package/src/telemetry/telemetry-wire.generated.ts +6 -0
  420. package/src/telemetry/tool-audit.ts +83 -30
  421. package/src/telemetry/tool-executed-events-store.test.ts +0 -1
  422. package/src/telemetry/turn-outcome.ts +75 -4
  423. package/src/tools/client-os.ts +115 -0
  424. package/src/tools/computer-use/definitions.ts +11 -7
  425. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
  426. package/src/tools/credentials/broker-types.ts +0 -71
  427. package/src/tools/credentials/broker.ts +6 -201
  428. package/src/tools/credentials/metadata-store.ts +65 -16
  429. package/src/tools/credentials/store.ts +2 -0
  430. package/src/tools/executor.ts +2 -8
  431. package/src/tools/host-shell.test.ts +75 -0
  432. package/src/tools/host-shell.ts +24 -0
  433. package/src/tools/host-terminal/host-shell.ts +15 -44
  434. package/src/tools/mcp/mcp-tool-factory.ts +44 -9
  435. package/src/tools/permission-checker.ts +73 -21
  436. package/src/tools/policy-context.ts +1 -0
  437. package/src/tools/shared/filesystem/image-read.ts +1 -1
  438. package/src/tools/skills/find-similar-skills.ts +10 -2
  439. package/src/tools/skills/load.ts +41 -6
  440. package/src/tools/skills/sandbox-runner.ts +8 -16
  441. package/src/tools/skills/skill-tool-factory.ts +1 -0
  442. package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
  443. package/src/tools/terminal/safe-env.ts +39 -9
  444. package/src/tools/terminal/shell.ts +12 -31
  445. package/src/tools/tool-approval-handler.ts +64 -10
  446. package/src/tools/tool-types.ts +10 -0
  447. package/src/tools/types.ts +19 -3
  448. package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
  449. package/src/tools/workflows/manage-workflows.ts +2 -2
  450. package/src/tools/workflows/run-workflow.test.ts +2 -1
  451. package/src/util/__tests__/file-use.test.ts +61 -0
  452. package/src/util/__tests__/process-table.test.ts +66 -0
  453. package/src/util/__tests__/process-tree.test.ts +46 -0
  454. package/src/util/errors.ts +1 -0
  455. package/src/util/file-use.ts +91 -0
  456. package/src/util/host-process.test.ts +17 -0
  457. package/src/util/host-process.ts +75 -0
  458. package/src/util/image-conversion.ts +39 -7
  459. package/src/util/platform.ts +32 -13
  460. package/src/util/process-table.ts +249 -0
  461. package/src/util/process-tree.ts +85 -129
  462. package/src/util/provider-error-patterns.ts +20 -0
  463. package/src/util/spawn.ts +9 -2
  464. package/src/watch/__tests__/watch-retro.test.ts +48 -1
  465. package/src/watch/watch-retro.ts +46 -4
  466. package/src/workspace/git-service.ts +4 -14
  467. package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
  468. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
  469. package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
  470. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
  471. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
  472. package/src/workspace/migrations/registry.ts +8 -0
  473. package/src/__tests__/permission-types.test.ts +0 -17
  474. package/src/channels/gateway-discord-admission.ts +0 -32
  475. package/src/persistence/fork-message-copy.ts +0 -239
  476. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
  477. package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
  478. package/src/security/oauth2-device-code.ts +0 -312
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Managed Flux STT through the gateway's speech relay
3
+ * (gateway -> velay -> Deepgram Flux; velay contact is gateway-only).
4
+ *
5
+ * Composes {@link DeepgramFluxRealtimeTranscriber} against the relay's
6
+ * managed STT v2 endpoint with `contract=flux`, so Flux's own turn events
7
+ * arrive verbatim. Without that opt-in velay translates them into the
8
+ * released v1 Deepgram dialect, which drops `EagerEndOfTurn` and
9
+ * `TurnResumed` entirely and leaves live voice with no provider turn end to
10
+ * act on.
11
+ *
12
+ * This wrapper owns what is relay-specific, mirroring
13
+ * `VellumManagedRealtimeTranscriber` on the v1 path:
14
+ *
15
+ * - Dialing the gateway (`/v2/speech/stt/stream`, `?key=` carries a
16
+ * daemon-minted service token, no `model` param since velay derives and
17
+ * prices the Flux model from `language`).
18
+ * - Surfacing `velay_error` control frames as categorized SttErrors instead
19
+ * of generic socket failures.
20
+ * - Transparent re-dial on velay's 30-minute session cap, so one live-voice
21
+ * session can outlive it.
22
+ *
23
+ * There is deliberately no `finalizeUtterance`: Flux commits turns itself,
24
+ * and the interface makes the method optional so live voice feature-detects
25
+ * its absence and never asks for a flush.
26
+ */
27
+
28
+ import {
29
+ type StreamingTranscriber,
30
+ SttError,
31
+ type SttStreamServerEvent,
32
+ } from "../../stt/types.js";
33
+ import { getLogger } from "../../util/logger.js";
34
+ import { DeepgramFluxRealtimeTranscriber } from "./deepgram-flux-realtime.js";
35
+ import {
36
+ mapVelayError,
37
+ probeVelayRejection,
38
+ type SpeechRelayConnection,
39
+ type VelayErrorInfo,
40
+ } from "./vellum-speech-relay-connection.js";
41
+
42
+ const log = getLogger("vellum-managed-flux-realtime");
43
+
44
+ /** Managed STT v2: velay resolves this to Deepgram's Flux `/v2/listen`. */
45
+ const STT_STREAM_PATH = "/v2/speech/stt/stream";
46
+
47
+ export interface VellumManagedFluxRealtimeOptions {
48
+ /** Audio sample rate in Hz (default: 16000). */
49
+ sampleRate?: number;
50
+ /**
51
+ * BCP-47 language code (or `"multi"`), forwarded to the relay. This is
52
+ * how velay picks between `flux-general-en` and `flux-general-multi`, so
53
+ * it is the only lever over the Flux model from this side.
54
+ */
55
+ language?: string;
56
+ }
57
+
58
+ export class VellumManagedFluxRealtimeTranscriber implements StreamingTranscriber {
59
+ readonly providerId = "vellum-flux" as const;
60
+ readonly boundaryId = "daemon-streaming" as const;
61
+
62
+ private readonly connection: SpeechRelayConnection;
63
+ private readonly options: VellumManagedFluxRealtimeOptions;
64
+
65
+ private onEvent: ((event: SttStreamServerEvent) => void) | null = null;
66
+ /** The current relay session; null while re-dialing after a session cap. */
67
+ private inner: DeepgramFluxRealtimeTranscriber | null = null;
68
+ /**
69
+ * The most recent `velay_error` frame from the current session. Set just
70
+ * before velay closes the socket; consumed when the inner adapter turns
71
+ * that close into an error event.
72
+ */
73
+ private pendingRelayError: VelayErrorInfo | null = null;
74
+ /**
75
+ * Set when the current session hit velay's duration cap: the swap to a
76
+ * fresh session is deferred to the capped session's `closed` event so its
77
+ * close cleanup drains through first.
78
+ */
79
+ private redialOnClose = false;
80
+ private stopping = false;
81
+ private closedEmitted = false;
82
+
83
+ constructor(
84
+ connection: SpeechRelayConnection,
85
+ options: VellumManagedFluxRealtimeOptions = {},
86
+ ) {
87
+ this.connection = connection;
88
+ this.options = options;
89
+ }
90
+
91
+ async start(onEvent: (event: SttStreamServerEvent) => void): Promise<void> {
92
+ if (this.onEvent) {
93
+ throw new Error(
94
+ "VellumManagedFluxRealtimeTranscriber: start() called twice",
95
+ );
96
+ }
97
+ this.onEvent = onEvent;
98
+ try {
99
+ const inner = await this.dial();
100
+ if (this.stopping) {
101
+ // stop() raced the initial dial: it saw no inner to tear down and
102
+ // already emitted closed, so the freshly opened relay session must
103
+ // not be kept (a leaked session keeps velay metering).
104
+ inner.stop();
105
+ return;
106
+ }
107
+ this.inner = inner;
108
+ } catch (err) {
109
+ // A failed WebSocket upgrade exposes no HTTP details; replay the
110
+ // request as a plain GET to recover the relay's {code, detail}
111
+ // rejection.
112
+ const rejection = await probeVelayRejection(this.probeUrl());
113
+ if (rejection) {
114
+ const mapped = mapVelayError(rejection);
115
+ throw new SttError(mapped.category, mapped.message, {
116
+ userFacing: true,
117
+ });
118
+ }
119
+ throw err;
120
+ }
121
+ }
122
+
123
+ sendAudio(audio: Buffer, mimeType: string): void {
124
+ // Audio arriving during a session-cap re-dial (sub-second) is dropped,
125
+ // mirroring the inner adapter's backpressure-drop semantics.
126
+ this.inner?.sendAudio(audio, mimeType);
127
+ }
128
+
129
+ stop(): void {
130
+ if (this.stopping) {
131
+ return;
132
+ }
133
+ this.stopping = true;
134
+ if (this.inner) {
135
+ this.inner.stop();
136
+ return;
137
+ }
138
+ // Mid-redial stop: there is no session to drain.
139
+ this.emitClosedOnce();
140
+ }
141
+
142
+ // -- Relay session management ------------------------------------------
143
+
144
+ private async dial(): Promise<DeepgramFluxRealtimeTranscriber> {
145
+ this.pendingRelayError = null;
146
+ // Fresh per dial: a session-cap re-dial happens ~30 minutes in, long
147
+ // after any previously minted token expired.
148
+ const inner = new DeepgramFluxRealtimeTranscriber(
149
+ this.connection.mintServiceToken(),
150
+ {
151
+ baseUrl: this.connection.wsBaseUrl,
152
+ path: STT_STREAM_PATH,
153
+ queryAuth: true,
154
+ omitModelParam: true,
155
+ nativeContract: true,
156
+ ...(this.options.sampleRate !== undefined
157
+ ? { sampleRate: this.options.sampleRate }
158
+ : {}),
159
+ ...(this.options.language ? { language: this.options.language } : {}),
160
+ onUnhandledFrame: (frame) => this.handleRelayFrame(frame),
161
+ },
162
+ );
163
+ await inner.start((event) => this.handleInnerEvent(inner, event));
164
+ return inner;
165
+ }
166
+
167
+ private probeUrl(): string {
168
+ const key = encodeURIComponent(this.connection.mintServiceToken());
169
+ return `${this.connection.httpBaseUrl}${STT_STREAM_PATH}?key=${key}`;
170
+ }
171
+
172
+ private handleRelayFrame(frame: Record<string, unknown>): void {
173
+ if (frame.type !== "velay_error" || typeof frame.code !== "string") {
174
+ return;
175
+ }
176
+ this.pendingRelayError = {
177
+ code: frame.code,
178
+ ...(typeof frame.detail === "string" ? { detail: frame.detail } : {}),
179
+ };
180
+ log.info(
181
+ { code: frame.code, detail: frame.detail },
182
+ "Received velay_error frame on managed Flux relay",
183
+ );
184
+ }
185
+
186
+ private handleInnerEvent(
187
+ source: DeepgramFluxRealtimeTranscriber,
188
+ event: SttStreamServerEvent,
189
+ ): void {
190
+ // Events from a superseded session (the one replaced by a re-dial) must
191
+ // not leak through: its close/error was already accounted for.
192
+ if (source !== this.inner) {
193
+ return;
194
+ }
195
+
196
+ if (event.type === "error") {
197
+ const relayError = this.pendingRelayError;
198
+ if (relayError?.code === "session_duration_exceeded") {
199
+ // Velay's 30-minute cap, not a failure. Swallow the error but keep
200
+ // the capped session current so its close cleanup drains first; the
201
+ // re-dial happens on its `closed` event.
202
+ this.redialOnClose = !this.stopping;
203
+ return;
204
+ }
205
+ if (relayError) {
206
+ const mapped = mapVelayError(relayError);
207
+ this.emit({
208
+ type: "error",
209
+ category: mapped.category,
210
+ message: mapped.message,
211
+ });
212
+ return;
213
+ }
214
+ this.emit(event);
215
+ return;
216
+ }
217
+
218
+ if (event.type === "closed") {
219
+ if (this.redialOnClose && !this.stopping) {
220
+ this.redialOnClose = false;
221
+ this.beginRedial();
222
+ return;
223
+ }
224
+ this.emitClosedOnce();
225
+ return;
226
+ }
227
+
228
+ this.emit(event);
229
+ }
230
+
231
+ /**
232
+ * Replace the capped session with a fresh one. Detached: audio sent during
233
+ * the swap is dropped (see {@link sendAudio}); a dial failure is a real
234
+ * error and ends the stream.
235
+ */
236
+ private beginRedial(): void {
237
+ log.info("Managed Flux relay session hit velay's duration cap, re-dialing");
238
+ this.inner = null;
239
+ void (async () => {
240
+ try {
241
+ const inner = await this.dial();
242
+ if (this.stopping) {
243
+ inner.stop();
244
+ return;
245
+ }
246
+ this.inner = inner;
247
+ log.info("Managed Flux relay re-dial complete; session continues");
248
+ } catch (err) {
249
+ if (this.stopping) {
250
+ return;
251
+ }
252
+ const rejection = await probeVelayRejection(this.probeUrl());
253
+ if (rejection) {
254
+ const mapped = mapVelayError(rejection);
255
+ this.emit({
256
+ type: "error",
257
+ category: mapped.category,
258
+ message: mapped.message,
259
+ });
260
+ } else {
261
+ this.emit({
262
+ type: "error",
263
+ category: "provider-error",
264
+ message: `Managed Flux relay re-dial failed after the session cap: ${
265
+ err instanceof Error ? err.message : String(err)
266
+ }`,
267
+ });
268
+ }
269
+ this.emitClosedOnce();
270
+ }
271
+ })();
272
+ }
273
+
274
+ // -- Event emission -----------------------------------------------------
275
+
276
+ private emit(event: SttStreamServerEvent): void {
277
+ if (!this.onEvent || this.closedEmitted) {
278
+ return;
279
+ }
280
+ if (event.type === "closed") {
281
+ this.closedEmitted = true;
282
+ }
283
+ try {
284
+ this.onEvent(event);
285
+ } catch (err) {
286
+ log.warn(
287
+ { error: err },
288
+ "Listener error in vellum managed Flux realtime adapter",
289
+ );
290
+ }
291
+ }
292
+
293
+ private emitClosedOnce(): void {
294
+ this.emit({ type: "closed" });
295
+ }
296
+ }
@@ -1,6 +1,16 @@
1
1
  import type { ToolDefinition } from "../tools/tool-types.js";
2
2
  export type { ToolDefinition };
3
3
 
4
+ /**
5
+ * The tool name that activates provider-native web search. Callers append a
6
+ * tool under this name only to instances reporting
7
+ * {@link Provider.supportsNativeWebSearch}; anywhere a request can change
8
+ * routes after that decision (see `RetryProvider`'s backup-profile
9
+ * escalation), the tool has to be dropped again when the new route cannot
10
+ * serve it, or the model answers with a tool call nothing can execute.
11
+ */
12
+ export const NATIVE_WEB_SEARCH_TOOL_NAME = "web_search";
13
+
4
14
  import type { LLMCallSite } from "../config/schemas/llm.js";
5
15
  import {
6
16
  ProviderError,
@@ -191,6 +201,15 @@ export interface ProviderResponse {
191
201
  model: string;
192
202
  /** Provider that actually produced this response, which may differ from a wrapper provider name. */
193
203
  actualProvider?: string;
204
+ /**
205
+ * Inference profile key that actually governed this response when a
206
+ * wrapper re-routed the request away from the caller's own resolution
207
+ * (`RetryProvider`'s fallback-route escalation). `UsageTrackingProvider`
208
+ * prefers this over re-resolving from the original request options, so a
209
+ * successful fallback serve is attributed to the backup profile rather
210
+ * than the failed primary's. Absent on the normal (non-rerouted) path.
211
+ */
212
+ actualInferenceProfile?: string;
194
213
  /**
195
214
  * Base URL the provider's HTTP client actually resolved to for this request,
196
215
  * read from the live SDK client instance rather than re-derived from config.
@@ -281,6 +300,18 @@ export interface SendMessageConfig {
281
300
  * request.
282
301
  */
283
302
  forceOverrideProfile?: boolean;
303
+ /**
304
+ * True when the caller appended {@link NATIVE_WEB_SEARCH_TOOL_NAME} purely
305
+ * to activate the route's provider-native web search, rather than passing an
306
+ * app-executed search tool of the same name (which is what runs when a
307
+ * search backend like Brave or the platform search proxy is configured).
308
+ * Only the caller can tell those apart, and a route change after that
309
+ * decision has to: `RetryProvider` drops the tool on a backup that runs no
310
+ * native search, and must not touch it when the daemon executes it itself.
311
+ * A resolution/routing-time concern only; stripped before any provider wire
312
+ * request.
313
+ */
314
+ nativeWebSearchSentinel?: boolean;
284
315
  /**
285
316
  * Per-conversation seed for deterministic `mix`-profile expansion. The agent
286
317
  * loop sets this to the conversation id so every resolver call this send
@@ -316,6 +347,14 @@ export interface SendMessageConfig {
316
347
  * JSON request bodies.
317
348
  */
318
349
  usageAttributionHeaders?: Record<string, string>;
350
+ /**
351
+ * Per-request HTTP headers merged onto the transport. `RetryProvider`
352
+ * stamps these for providers that need support-lookup headers (OpenCode
353
+ * `x-opencode-session` / `x-opencode-request`). Provider clients pass
354
+ * them through SDK request options only and must never include this
355
+ * object in provider JSON request bodies.
356
+ */
357
+ requestHeaders?: Record<string, string>;
319
358
  /**
320
359
  * Controls local usage-ledger writes for attributed provider calls.
321
360
  * Defaults to `auto`; conversation paths that aggregate usage separately
@@ -66,9 +66,20 @@ export class UsageTrackingProvider implements Provider {
66
66
  }
67
67
 
68
68
  try {
69
+ // A fallback-serving wrapper (`RetryProvider`'s escalation) stamps the
70
+ // profile that actually governed the response; prefer it over the
71
+ // original request's resolution so a degraded serve bills under the
72
+ // backup profile, not the failed primary's. `forceOverrideProfile`
73
+ // mirrors how the fallback dispatch floated that profile above the
74
+ // call-site layers.
75
+ const appliedOverride =
76
+ response.actualInferenceProfile ?? config.overrideProfile;
69
77
  const attribution = resolveUsageAttribution({
70
78
  callSite: config.callSite,
71
- overrideProfile: config.overrideProfile,
79
+ overrideProfile: appliedOverride,
80
+ ...(response.actualInferenceProfile !== undefined
81
+ ? { forceOverrideProfile: true }
82
+ : {}),
72
83
  });
73
84
  const providerName = response.actualProvider ?? this.inner.name;
74
85
  const pricingUsage = buildPricingUsageFromResponse(
@@ -0,0 +1,29 @@
1
+ import { OpenAIChatCompletionsProvider } from "../openai/chat-completions-provider.js";
2
+
3
+ export interface VellumProviderOptions {
4
+ apiKey?: string;
5
+ baseURL?: string;
6
+ streamTimeoutMs?: number;
7
+ }
8
+
9
+ /**
10
+ * OpenAI-compatible client for Vellum-hosted GPU inference (vLLM).
11
+ * Managed requests set `baseURL` to the platform `/v1/runtime-proxy/vellum`
12
+ * path and authenticate with the assistant API key. These models have no
13
+ * bring-your-own-key path.
14
+ */
15
+ export class VellumProvider extends OpenAIChatCompletionsProvider {
16
+ constructor(
17
+ apiKey: string,
18
+ model: string,
19
+ options: VellumProviderOptions = {},
20
+ ) {
21
+ super(apiKey || "not-needed", model, {
22
+ providerName: "vellum",
23
+ providerLabel: "Vellum",
24
+ streamTimeoutMs: options.streamTimeoutMs,
25
+ omitToolChoiceWhenReasoning: true,
26
+ ...(options.baseURL ? { baseURL: options.baseURL } : {}),
27
+ });
28
+ }
29
+ }
@@ -41,6 +41,7 @@ describe("vellum-model-routing", () => {
41
41
 
42
42
  test("getManagedUpstream resolves a bare catalog id to its owner", () => {
43
43
  expect(getManagedUpstream("claude-fable-5")).toBe("anthropic");
44
+ expect(getManagedUpstream("qwen/qwen3-8b")).toBe("vellum");
44
45
  });
45
46
 
46
47
  test("getManagedUpstream resolves a routing string by its prefix", () => {
@@ -72,6 +73,7 @@ describe("vellum-model-routing", () => {
72
73
  "gemini",
73
74
  "openai",
74
75
  "together",
76
+ "vellum",
75
77
  ]);
76
78
  });
77
79
  });
@@ -36,9 +36,9 @@ export const MANAGED_ROUTABLE_PROVIDERS: ReadonlySet<string> = new Set(
36
36
  * Sentinel provider id for the single, provider-agnostic Vellum-managed
37
37
  * connection. Unlike the per-provider `*-managed` connections, this one does
38
38
  * not name an upstream provider on its DB row — the upstream is determined
39
- * per-request from the resolving profile. This id is never a real catalog
40
- * entry, so routing code must substitute the effective provider before any
41
- * catalog/adapter lookup.
39
+ * per-request from the resolving profile. The same id is the catalog owner
40
+ * of Vellum-hosted GPU models. Dispatch still substitutes a concrete
41
+ * upstream (including `vellum` for those GPU models) before adapter lookup.
42
42
  */
43
43
  export const VELLUM_MANAGED_PROVIDER = "vellum";
44
44
 
@@ -41,9 +41,8 @@ Approvals are **orthogonal to message sending**. The assistant asks for approval
41
41
 
42
42
  - **Discovery**: Clients discover pending approvals via SSE events (`confirmation_request`, `secret_request`) which include a `requestId`.
43
43
  - **Resolution**: Clients respond via standalone endpoints keyed by `requestId`:
44
- - `POST /v1/confirm` — `{ requestId, decision, selectedPattern?, selectedScope? }`. Valid decisions: `"allow"`, `"allow_10m"`, `"allow_conversation"`, `"deny"`, `"always_allow"`, `"always_deny"`. For persistent decisions (`always_allow`, `always_deny`), `selectedPattern` and `selectedScope` are validated against the server-provided allowlist/scope options from the original confirmation request before trust rules are persisted.
44
+ - `POST /v1/confirm` — `{ requestId, decision }`. Valid decisions: `"allow"`, `"deny"`. The confirm route resolves the pending interaction and nothing else: durable Always-Allow rules are minted through the gateway trust-rules API (`POST /v1/trust-rules`, also served assistant-scoped), which carries the rule's tool, pattern, and risk. The daemon's own `trust-rules` surface is read-only (`GET /v1/trust-rules` list, `POST /v1/trust-rules/suggest`).
45
45
  - `POST /v1/secret` — `{ requestId, value, delivery }`
46
- - `POST /v1/trust-rules` — `{ requestId, pattern, scope, decision }`. Validates pattern/scope against server-provided options. Does not resolve the confirmation itself.
47
46
  - **Tracking**: The `pending-interactions` tracker (`assistant/src/runtime/pending-interactions.ts`) maps `requestId → conversation`. Use `register()` to track, `resolve()` to consume, `getByConversation()` to query.
48
47
 
49
48
  Do NOT couple approval handling to message sending. Do NOT add run/status tracking to the send path.
@@ -266,6 +266,7 @@ const recordRequestLogCalls: Array<{
266
266
  }> = [];
267
267
  const recordUsageCalls: Array<{
268
268
  conversationId: string;
269
+ providerName: string | undefined;
269
270
  inputTokens: number;
270
271
  outputTokens: number;
271
272
  model: string;
@@ -279,7 +280,7 @@ const recordUsageCalls: Array<{
279
280
  }> = [];
280
281
  mock.module("../../daemon/conversation-usage.js", () => ({
281
282
  recordUsage: (
282
- ctx: { conversationId: string },
283
+ ctx: { conversationId: string; providerName?: string },
283
284
  inputTokens: number,
284
285
  outputTokens: number,
285
286
  model: string,
@@ -300,6 +301,7 @@ mock.module("../../daemon/conversation-usage.js", () => ({
300
301
  ) => {
301
302
  recordUsageCalls.push({
302
303
  conversationId: ctx.conversationId,
304
+ providerName: ctx.providerName,
303
305
  inputTokens,
304
306
  outputTokens,
305
307
  model,
@@ -1748,7 +1750,13 @@ describe("wakeAgentForOpportunity", () => {
1748
1750
  { resolveTarget: async () => conversation },
1749
1751
  );
1750
1752
 
1751
- expect(result).toEqual({ invoked: true, producedToolCalls: false });
1753
+ // The result carries how the run ended, so a caller that treats output as
1754
+ // a finished answer can see that something else ended this one.
1755
+ expect(result).toEqual({
1756
+ invoked: true,
1757
+ producedToolCalls: false,
1758
+ exitReason: "error",
1759
+ });
1752
1760
  // The checkpoint's flush pushed + persisted the partial tail.
1753
1761
  expect(conversation.pushedMessages.length).toBeGreaterThan(0);
1754
1762
  expect(conversation.persistedTailCalls.length).toBeGreaterThan(0);
@@ -1770,7 +1778,11 @@ describe("wakeAgentForOpportunity", () => {
1770
1778
  { resolveTarget: async () => conversation },
1771
1779
  );
1772
1780
 
1773
- expect(result).toEqual({ invoked: true, producedToolCalls: false });
1781
+ expect(result).toEqual({
1782
+ invoked: true,
1783
+ producedToolCalls: false,
1784
+ exitReason: "no_tool_calls",
1785
+ });
1774
1786
  });
1775
1787
 
1776
1788
  test("reports no_output on a clean empty reply when the caller requires usable output", async () => {
@@ -2986,6 +2998,96 @@ describe("wakeAgentForOpportunity", () => {
2986
2998
  });
2987
2999
  });
2988
3000
 
3001
+ test("rerouted wake records usage under the profile that actually served", async () => {
3002
+ // The wake resolved its own profile before the run; the request then hit
3003
+ // an outage-shaped failure and `RetryProvider` escalated to a backup
3004
+ // profile on a different provider, stamping both `actualProvider` and
3005
+ // `actualInferenceProfile` on the response the loop reports.
3006
+ const usageEvent: AgentEvent = {
3007
+ type: "usage",
3008
+ inputTokens: 100,
3009
+ outputTokens: 5,
3010
+ model: "claude-sonnet-5",
3011
+ actualProvider: "anthropic",
3012
+ actualInferenceProfile: "backupProfile",
3013
+ providerDurationMs: 10,
3014
+ rawRequest: { request: "rerouted wake" },
3015
+ rawResponse: { response: "real reply" },
3016
+ };
3017
+ const conversation = makeWakeConversation({
3018
+ baseline: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
3019
+ scriptedEvents: [usageEvent],
3020
+ scriptedAssistant: {
3021
+ role: "assistant",
3022
+ content: [{ type: "text", text: "real reply" }],
3023
+ },
3024
+ });
3025
+
3026
+ await wakeAgentForOpportunity(
3027
+ {
3028
+ conversationId: conversation.conversationId,
3029
+ hint: "do reply",
3030
+ source: "unit-test",
3031
+ callSite: "memoryRetrospective",
3032
+ },
3033
+ { resolveTarget: async () => conversation },
3034
+ );
3035
+
3036
+ // All three attribution facets of the row must describe the same call.
3037
+ // Provider and model already followed the backup off the event; the
3038
+ // profile has to follow it too or the row contradicts itself.
3039
+ expect(recordUsageCalls).toHaveLength(1);
3040
+ expect(recordUsageCalls[0]).toMatchObject({
3041
+ providerName: "anthropic",
3042
+ model: "claude-sonnet-5",
3043
+ overrideProfile: "backupProfile",
3044
+ forceOverrideProfile: true,
3045
+ selectionSeed: conversation.conversationId,
3046
+ });
3047
+ });
3048
+
3049
+ test("non-rerouted wake keeps its own profile resolution", async () => {
3050
+ // No reroute: the event carries no `actualInferenceProfile`, so the wake's
3051
+ // own pre-run resolution stands and nothing is floated above the call site.
3052
+ const usageEvent: AgentEvent = {
3053
+ type: "usage",
3054
+ inputTokens: 100,
3055
+ outputTokens: 5,
3056
+ model: "test-model",
3057
+ actualProvider: "test-provider",
3058
+ providerDurationMs: 10,
3059
+ rawRequest: { request: "normal wake" },
3060
+ rawResponse: { response: "real reply" },
3061
+ };
3062
+ const conversation = makeWakeConversation({
3063
+ baseline: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
3064
+ scriptedEvents: [usageEvent],
3065
+ scriptedAssistant: {
3066
+ role: "assistant",
3067
+ content: [{ type: "text", text: "real reply" }],
3068
+ },
3069
+ });
3070
+
3071
+ await wakeAgentForOpportunity(
3072
+ {
3073
+ conversationId: conversation.conversationId,
3074
+ hint: "do reply",
3075
+ source: "unit-test",
3076
+ callSite: "memoryRetrospective",
3077
+ },
3078
+ { resolveTarget: async () => conversation },
3079
+ );
3080
+
3081
+ expect(recordUsageCalls).toHaveLength(1);
3082
+ expect(recordUsageCalls[0]).toMatchObject({
3083
+ providerName: "test-provider",
3084
+ model: "test-model",
3085
+ overrideProfile: null,
3086
+ forceOverrideProfile: false,
3087
+ selectionSeed: conversation.conversationId,
3088
+ });
3089
+ });
3090
+
2989
3091
  test("silent no-op wake still records usage even though its request log is dropped", async () => {
2990
3092
  const usageEvent: AgentEvent = {
2991
3093
  type: "usage",