@vellumai/assistant 0.11.3 → 0.11.4-staging.1

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 (324) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/docs/architecture/memory.md +11 -0
  3. package/docs/architecture/turn-actor.md +70 -0
  4. package/docs/flux-turn-detection-spike.md +243 -0
  5. package/docs/stt-provider-onboarding.md +3 -1
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  8. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  10. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  11. package/openapi.yaml +140 -38
  12. package/package.json +1 -1
  13. package/scripts/voice-ttft-spike.ts +3 -3
  14. package/src/__tests__/app-compiler.test.ts +38 -3
  15. package/src/__tests__/attachments-store.test.ts +21 -12
  16. package/src/__tests__/byok-default-profile-ensure.test.ts +2 -0
  17. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  18. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  19. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  20. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  21. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  22. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  23. package/src/__tests__/config-schema.test.ts +25 -10
  24. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  26. package/src/__tests__/conversation-agent-loop.test.ts +28 -20
  27. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  28. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  29. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  30. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  31. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  32. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  33. package/src/__tests__/conversation-queue.test.ts +55 -4
  34. package/src/__tests__/conversation-runtime-assembly.test.ts +134 -102
  35. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  36. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  37. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  38. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  39. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  40. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  41. package/src/__tests__/edit-propagation.test.ts +0 -7
  42. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  43. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  44. package/src/__tests__/injector-chain.test.ts +63 -41
  45. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  46. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  47. package/src/__tests__/llm-schema.test.ts +5 -2
  48. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  49. package/src/__tests__/memory-retrieval-hook.test.ts +6 -5
  50. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  51. package/src/__tests__/mtime-cache.test.ts +1 -1
  52. package/src/__tests__/non-member-access-request.test.ts +0 -20
  53. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  54. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -0
  55. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  56. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  57. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  58. package/src/__tests__/run-conversation-turn-persistence.test.ts +434 -105
  59. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  60. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  61. package/src/__tests__/skills.test.ts +32 -0
  62. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  63. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  64. package/src/__tests__/subagent-spawn-and-await.test.ts +14 -10
  65. package/src/__tests__/turn-events-store.test.ts +43 -0
  66. package/src/__tests__/user-plugin-loader.test.ts +1 -1
  67. package/src/__tests__/visible-app-context.test.ts +16 -9
  68. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  69. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  70. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  71. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  72. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  73. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  74. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  75. package/src/acp/__tests__/auth-required.test.ts +161 -0
  76. package/src/acp/acp-claude-oauth.ts +19 -2
  77. package/src/acp/agent-process.test.ts +100 -0
  78. package/src/acp/agent-process.ts +29 -26
  79. package/src/acp/auth-required.ts +102 -0
  80. package/src/acp/session-manager.test.ts +119 -0
  81. package/src/acp/session-manager.ts +68 -2
  82. package/src/api/events/acp-auth-required.ts +55 -0
  83. package/src/api/index.ts +7 -0
  84. package/src/apps/app-store.ts +3 -0
  85. package/src/bundler/package-resolver.ts +2 -30
  86. package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
  87. package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
  88. package/src/calls/call-controller.ts +9 -2
  89. package/src/calls/call-setup-flow.ts +0 -1
  90. package/src/calls/media-stream-stt-session.ts +15 -0
  91. package/src/calls/voice-session-bridge.ts +71 -16
  92. package/src/calls/voice-triage-escalate.ts +104 -2
  93. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  94. package/src/channels/config.ts +13 -0
  95. package/src/channels/plugin-channel-declarations.ts +108 -0
  96. package/src/channels/types.ts +30 -0
  97. package/src/cli/AGENTS.md +5 -2
  98. package/src/cli/commands/credentials.help.ts +2 -2
  99. package/src/cli/commands/inference-providers.ts +1 -1
  100. package/src/cli/commands/mcp.help.ts +13 -4
  101. package/src/cli/commands/mcp.ts +9 -0
  102. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  103. package/src/cli/commands/memory/index.help.ts +43 -1
  104. package/src/cli/commands/memory/memory-v3.ts +64 -0
  105. package/src/cli/commands/stt.help.ts +27 -2
  106. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  107. package/src/cli/lib/bundled-marketplace.json +13 -0
  108. package/src/cli/lib/upgrade-plugin.ts +42 -0
  109. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  110. package/src/config/__tests__/default-provider.test.ts +6 -1
  111. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  112. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  113. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  114. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  115. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  116. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  117. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  118. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  119. package/src/config/call-site-defaults.ts +4 -5
  120. package/src/config/default-profile-catalog.ts +82 -11
  121. package/src/config/default-profile-names.ts +4 -1
  122. package/src/config/default-provider-resolution.ts +4 -0
  123. package/src/config/llm-context-resolution.ts +11 -3
  124. package/src/config/llm-resolver.ts +28 -1
  125. package/src/config/profile-materialization.ts +70 -22
  126. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  127. package/src/config/schemas/call-site-catalog.ts +4 -4
  128. package/src/config/schemas/live-voice.ts +57 -23
  129. package/src/config/schemas/llm.ts +59 -32
  130. package/src/config/schemas/mcp.ts +23 -0
  131. package/src/config/schemas/plugin-updates.ts +6 -2
  132. package/src/config/schemas/stt.ts +1 -0
  133. package/src/context/outbound-sanitize.ts +96 -1
  134. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  135. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  136. package/src/daemon/conversation-agent-loop.ts +17 -6
  137. package/src/daemon/conversation-messaging.ts +5 -1
  138. package/src/daemon/conversation-notifiers.ts +9 -1
  139. package/src/daemon/conversation-process.ts +9 -6
  140. package/src/daemon/conversation-runtime-assembly.ts +3 -4
  141. package/src/daemon/conversation-tool-setup.ts +1 -2
  142. package/src/daemon/conversation.ts +48 -0
  143. package/src/daemon/mcp-reload-service.ts +36 -6
  144. package/src/daemon/process-message.ts +13 -3
  145. package/src/daemon/providers-setup.ts +6 -3
  146. package/src/daemon/trust-context-types.ts +29 -0
  147. package/src/daemon/wake-conversation-ops.ts +3 -2
  148. package/src/documents/document-store.ts +138 -5
  149. package/src/hooks/hook-loader.ts +3 -3
  150. package/src/hooks/registry.ts +50 -6
  151. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  152. package/src/inbound/oauth-callback-url.ts +61 -0
  153. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  154. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  155. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
  156. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  157. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  158. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  159. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  160. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  161. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  162. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  163. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  164. package/src/live-voice/live-voice-archive.ts +2 -0
  165. package/src/live-voice/live-voice-metrics.ts +57 -32
  166. package/src/live-voice/live-voice-photo.ts +1 -2
  167. package/src/live-voice/live-voice-session.ts +535 -314
  168. package/src/live-voice/progress-narration.ts +277 -0
  169. package/src/live-voice/protocol.ts +21 -1
  170. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  171. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  172. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  173. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  174. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  175. package/src/mcp/client.ts +16 -11
  176. package/src/mcp/effective-config.ts +113 -0
  177. package/src/mcp/manager.ts +11 -6
  178. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  179. package/src/mcp/mcp-oauth-provider.ts +205 -240
  180. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  181. package/src/monitoring/plugin-auto-update.ts +128 -24
  182. package/src/notifications/signal.ts +1 -0
  183. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  184. package/src/permissions/confirmation-guardian-request.ts +2 -2
  185. package/src/permissions/question-guardian-request.test.ts +14 -6
  186. package/src/permissions/question-guardian-request.ts +1 -2
  187. package/src/persistence/attachments-store.ts +8 -1
  188. package/src/persistence/bookmark-crud.ts +3 -7
  189. package/src/persistence/conversation-attention-store.ts +16 -45
  190. package/src/persistence/conversation-crud.ts +33 -4
  191. package/src/persistence/conversation-lineage.ts +9 -0
  192. package/src/persistence/conversation-queries.ts +108 -41
  193. package/src/persistence/delivery-crud.ts +38 -29
  194. package/src/persistence/external-conversation-store.ts +32 -4
  195. package/src/persistence/llm-request-log-store.ts +4 -10
  196. package/src/persistence/llm-usage-store.ts +8 -3
  197. package/src/persistence/message-reads.test.ts +197 -0
  198. package/src/persistence/message-reads.ts +211 -0
  199. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  200. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  201. package/src/persistence/real-user-turn-filter.ts +27 -3
  202. package/src/persistence/steps.ts +9 -0
  203. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  204. package/src/plugin-api/conversation-turn.ts +168 -5
  205. package/src/plugin-api/index.ts +21 -5
  206. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  207. package/src/plugins/defaults/memory/AGENTS.md +4 -0
  208. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  209. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  210. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  211. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  212. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  213. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  214. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  215. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  216. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  217. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
  218. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  219. package/src/plugins/defaults/memory/indexer.ts +3 -1
  220. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  221. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  222. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  223. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
  224. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
  225. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
  226. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  227. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
  228. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  229. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  230. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  231. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  232. package/src/plugins/defaults/memory/worker.ts +6 -3
  233. package/src/plugins/external-plugin-loader.ts +47 -0
  234. package/src/plugins/mcp-servers.ts +361 -0
  235. package/src/plugins/mtime-cache.ts +23 -49
  236. package/src/plugins/worker-plugin-surface.ts +33 -0
  237. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  238. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  239. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  240. package/src/providers/__tests__/retry-callsite.test.ts +5 -2
  241. package/src/providers/call-site-routing.ts +30 -3
  242. package/src/providers/connection-resolution.ts +194 -11
  243. package/src/providers/inference/auth.ts +6 -0
  244. package/src/providers/inference/connection-availability.ts +24 -2
  245. package/src/providers/inference/connections.ts +2 -0
  246. package/src/providers/model-intents.ts +26 -6
  247. package/src/providers/openai/codex-models.ts +2 -1
  248. package/src/providers/provider-send-message.ts +32 -3
  249. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  250. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
  251. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  252. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  253. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  254. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
  255. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  256. package/src/providers/speech-to-text/resolve.ts +25 -2
  257. package/src/routes/worker.ts +17 -5
  258. package/src/runtime/access-request-helper.ts +9 -12
  259. package/src/runtime/agent-wake.ts +3 -3
  260. package/src/runtime/pre-first-message-gate.ts +4 -0
  261. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  262. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
  263. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  264. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  265. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  266. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  267. package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
  268. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  269. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
  270. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  271. package/src/runtime/routes/channel-route-shared.ts +0 -6
  272. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  273. package/src/runtime/routes/conversation-list-routes.ts +112 -1
  274. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  275. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  276. package/src/runtime/routes/default-provider-routes.ts +15 -0
  277. package/src/runtime/routes/inbound-message-handler.ts +17 -41
  278. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  279. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  280. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  281. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  282. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  283. package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
  284. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  285. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
  286. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
  287. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
  288. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  289. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  291. package/src/runtime/routes/log-export-routes.ts +3 -0
  292. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  293. package/src/runtime/routes/plugins-routes.ts +21 -3
  294. package/src/runtime/routes/stt-routes.ts +31 -25
  295. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  296. package/src/runtime/routes/user-route-dispatcher.ts +39 -14
  297. package/src/runtime/routes/user-route-import.ts +108 -0
  298. package/src/schedule/worker.ts +6 -0
  299. package/src/security/oauth2.ts +6 -22
  300. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  301. package/src/stt/__tests__/types.test.ts +94 -0
  302. package/src/stt/daemon-batch-transcriber.ts +10 -0
  303. package/src/stt/stt-stream-session.ts +8 -4
  304. package/src/stt/types.ts +103 -0
  305. package/src/subagent/manager.ts +1 -3
  306. package/src/subagent/types.ts +7 -6
  307. package/src/tools/acp/spawn.test.ts +97 -0
  308. package/src/tools/acp/spawn.ts +32 -0
  309. package/src/tools/document/document-tool.ts +12 -3
  310. package/src/tools/registry.ts +2 -1
  311. package/src/tools/workflows/run-workflow.ts +1 -2
  312. package/src/tts/__tests__/reasoning-tag-filter.test.ts +63 -0
  313. package/src/tts/reasoning-tag-filter.ts +110 -0
  314. package/src/workspace/byok-default-profile-ensure.ts +61 -18
  315. package/src/workspace/custom-profile-ensure.ts +4 -24
  316. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  317. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  318. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  319. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  320. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  321. package/src/workspace/migrations/registry.ts +8 -0
  322. package/src/workspace/provider-commit-message-generator.ts +7 -5
  323. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  324. package/src/live-voice/front-decision.ts +0 -476
@@ -101,7 +101,6 @@ import {
101
101
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
102
102
  import { deliverChannelReply } from "../gateway-client.js";
103
103
  import { trustContextFromVerdict } from "../trust-verdict-consumer.js";
104
- import { canonicalChannelAssistantId } from "./channel-route-shared.js";
105
104
  import { BadRequestError } from "./errors.js";
106
105
  import { handleApprovalInterception } from "./guardian-approval-interception.js";
107
106
  import {
@@ -371,16 +370,6 @@ export async function handleChannelInbound({
371
370
  throw new BadRequestError("content or attachmentIds is required");
372
371
  }
373
372
 
374
- // Canonicalize the assistant ID so all DB-facing operations use the
375
- // consistent 'self' key regardless of what the gateway sent.
376
- const canonicalAssistantId = canonicalChannelAssistantId(assistantId);
377
- if (canonicalAssistantId !== assistantId) {
378
- log.debug(
379
- { raw: assistantId, canonical: canonicalAssistantId },
380
- "Canonicalized channel assistant ID",
381
- );
382
- }
383
-
384
373
  // Coerce actorExternalId to a string at the boundary — the field
385
374
  // comes from unvalidated JSON and may be a number, object, or other
386
375
  // non-string type. Non-string truthy values would throw inside
@@ -438,7 +427,6 @@ export async function handleChannelInbound({
438
427
  sourceInterface,
439
428
  conversationExternalId,
440
429
  externalMessageId,
441
- canonicalAssistantId,
442
430
  rawSenderId,
443
431
  canonicalSenderId,
444
432
  actorDisplayName: body.actorDisplayName,
@@ -486,7 +474,6 @@ export async function handleChannelInbound({
486
474
  rawSenderId,
487
475
  sourceChannel,
488
476
  conversationExternalId,
489
- canonicalAssistantId,
490
477
  trimmedContent,
491
478
  sourceMetadata: body.sourceMetadata,
492
479
  actorDisplayName: body.actorDisplayName,
@@ -709,7 +696,6 @@ export async function handleChannelInbound({
709
696
  externalMessageId,
710
697
  sourceMessageId,
711
698
  sourceThreadId: channelThreadId,
712
- canonicalAssistantId,
713
699
  assistantId,
714
700
  content,
715
701
  channelId: resolvedMember?.channelId,
@@ -723,28 +709,24 @@ export async function handleChannelInbound({
723
709
  externalMessageId,
724
710
  {
725
711
  sourceMessageId,
726
- assistantId: canonicalAssistantId,
727
712
  sourceThreadId: channelThreadId,
728
713
  },
729
714
  );
730
715
 
731
716
  const replyCallbackUrl = body.replyCallbackUrl;
732
717
 
733
- // external_conversation_bindings is assistant-agnostic. Restrict writes to
734
- // self so assistant-scoped legacy routes do not overwrite each other's
735
- // channel binding metadata for the same chat.
736
- if (canonicalAssistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
737
- upsertBinding({
738
- conversationId: result.conversationId,
739
- sourceChannel,
740
- externalChatId: conversationExternalId,
741
- externalChatName: slackChannelName,
742
- externalThreadId: channelThreadId ?? null,
743
- externalUserId: canonicalSenderId ?? rawSenderId ?? null,
744
- displayName: body.actorDisplayName ?? null,
745
- username: body.actorUsername ?? null,
746
- });
747
- }
718
+ // `external_conversation_bindings` is assistant-agnostic: one row per chat,
719
+ // whichever caller writes it.
720
+ upsertBinding({
721
+ conversationId: result.conversationId,
722
+ sourceChannel,
723
+ externalChatId: conversationExternalId,
724
+ externalChatName: slackChannelName,
725
+ externalThreadId: channelThreadId ?? null,
726
+ externalUserId: canonicalSenderId ?? rawSenderId ?? null,
727
+ displayName: body.actorDisplayName ?? null,
728
+ username: body.actorUsername ?? null,
729
+ });
748
730
 
749
731
  // ── Actor role resolution ──
750
732
  // Built from the gateway-stamped trust verdict (ACL + identity). An absent
@@ -844,7 +826,6 @@ export async function handleChannelInbound({
844
826
  if (isCallbackInteraction) {
845
827
  if (floorSenderId) {
846
828
  handshakeInProgress = await isApprovalHandshakeInProgress({
847
- canonicalAssistantId,
848
829
  sourceChannel,
849
830
  actorExternalId: floorSenderId,
850
831
  });
@@ -852,7 +833,6 @@ export async function handleChannelInbound({
852
833
  } else {
853
834
  try {
854
835
  const accessResult = await notifyGuardianOfAccessRequest({
855
- canonicalAssistantId,
856
836
  sourceChannel,
857
837
  conversationExternalId,
858
838
  actorExternalId: floorSenderId,
@@ -925,7 +905,7 @@ export async function handleChannelInbound({
925
905
  const replyPayload: Parameters<typeof deliverChannelReply>[1] = {
926
906
  chatId: conversationExternalId,
927
907
  text: replyText,
928
- assistantId: canonicalAssistantId,
908
+ assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
929
909
  };
930
910
  if (sourceChannel === "slack" && (canonicalSenderId ?? rawSenderId)) {
931
911
  replyPayload.ephemeral = true;
@@ -987,7 +967,7 @@ export async function handleChannelInbound({
987
967
  const replyPayload: Parameters<typeof deliverChannelReply>[1] = {
988
968
  chatId: conversationExternalId,
989
969
  text: DISK_PRESSURE_REMOTE_BLOCK_REPLY,
990
- assistantId: canonicalAssistantId,
970
+ assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
991
971
  };
992
972
  if (sourceChannel === "slack" && (canonicalSenderId ?? rawSenderId)) {
993
973
  replyPayload.ephemeral = true;
@@ -1059,7 +1039,6 @@ export async function handleChannelInbound({
1059
1039
  isDuplicate: result.duplicate,
1060
1040
  commandIntent,
1061
1041
  rawSenderId,
1062
- canonicalAssistantId,
1063
1042
  sourceChannel,
1064
1043
  conversationExternalId,
1065
1044
  eventId: result.eventId,
@@ -1081,7 +1060,6 @@ export async function handleChannelInbound({
1081
1060
  callbackData: body.callbackData,
1082
1061
  rawSenderId,
1083
1062
  canonicalSenderId,
1084
- canonicalAssistantId,
1085
1063
  sourceChannel,
1086
1064
  conversationExternalId,
1087
1065
  conversationId: result.conversationId,
@@ -1123,7 +1101,7 @@ export async function handleChannelInbound({
1123
1101
  actorExternalId: canonicalSenderId ?? rawSenderId,
1124
1102
  replyCallbackUrl,
1125
1103
  trustCtx,
1126
- assistantId: canonicalAssistantId,
1104
+ assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
1127
1105
  approvalCopyGenerator,
1128
1106
  approvalConversationGenerator,
1129
1107
  approvalMessageTs,
@@ -1219,7 +1197,7 @@ export async function handleChannelInbound({
1219
1197
  chatId: conversationExternalId,
1220
1198
  text: "This approval request has been resolved.",
1221
1199
  messageTs: approvalMessageTs,
1222
- assistantId: canonicalAssistantId,
1200
+ assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
1223
1201
  }).catch((err) => {
1224
1202
  log.error(
1225
1203
  { err, conversationId: result.conversationId },
@@ -1255,7 +1233,6 @@ export async function handleChannelInbound({
1255
1233
  actorUsername: body.actorUsername,
1256
1234
  trustCtx,
1257
1235
  replyCallbackUrl,
1258
- canonicalAssistantId,
1259
1236
  });
1260
1237
 
1261
1238
  if (ingressResult.blocked) {
@@ -1296,7 +1273,6 @@ export async function handleChannelInbound({
1296
1273
  const admittedSenderId = canonicalSenderId ?? rawSenderId;
1297
1274
  if (admittedSenderId) {
1298
1275
  void maybeNotifyGuardianOfAdmittedContact({
1299
- canonicalAssistantId,
1300
1276
  sourceChannel,
1301
1277
  conversationExternalId,
1302
1278
  actorExternalId: admittedSenderId,
@@ -1497,7 +1473,7 @@ export async function handleChannelInbound({
1497
1473
  commandIntent,
1498
1474
  sourceLanguageCode,
1499
1475
  replyCallbackUrl,
1500
- assistantId: canonicalAssistantId,
1476
+ assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
1501
1477
  approvalCopyGenerator,
1502
1478
  chatType: sourceChatType,
1503
1479
  clientTimezone: inboundClientTimezone,
@@ -98,7 +98,6 @@ function makeParams(
98
98
  rawSenderId: "sender-1",
99
99
  sourceChannel: "telegram",
100
100
  conversationExternalId: "chat-1",
101
- canonicalAssistantId: "assistant-1",
102
101
  trimmedContent: "hello",
103
102
  sourceMetadata: undefined,
104
103
  actorDisplayName: "Sender One",
@@ -90,7 +90,6 @@ export interface AclEnforcementParams {
90
90
  rawSenderId: string | undefined;
91
91
  sourceChannel: ChannelId;
92
92
  conversationExternalId: string;
93
- canonicalAssistantId: string;
94
93
  trimmedContent: string;
95
94
  sourceMetadata: SourceMetadata | undefined;
96
95
  actorDisplayName: string | undefined;
@@ -190,7 +189,6 @@ export async function enforceIngressAcl(
190
189
  rawSenderId,
191
190
  sourceChannel,
192
191
  conversationExternalId,
193
- canonicalAssistantId,
194
192
  trimmedContent,
195
193
  sourceMetadata,
196
194
  actorDisplayName,
@@ -426,7 +424,6 @@ export async function enforceIngressAcl(
426
424
  // Still notify the guardian about the access attempt
427
425
  try {
428
426
  await notifyGuardianOfAccessRequest({
429
- canonicalAssistantId,
430
427
  sourceChannel,
431
428
  conversationExternalId,
432
429
  actorExternalId: canonicalSenderId ?? rawSenderId,
@@ -509,7 +506,6 @@ export async function enforceIngressAcl(
509
506
  if (emailVerifyResult.initiated) {
510
507
  try {
511
508
  await notifyGuardianOfAccessRequest({
512
- canonicalAssistantId,
513
509
  sourceChannel,
514
510
  conversationExternalId,
515
511
  actorExternalId: canonicalSenderId ?? rawSenderId,
@@ -554,14 +550,12 @@ export async function enforceIngressAcl(
554
550
  let handshakeInProgress = false;
555
551
  if (isCallbackInteraction) {
556
552
  handshakeInProgress = await isApprovalHandshakeInProgress({
557
- canonicalAssistantId,
558
553
  sourceChannel,
559
554
  actorExternalId: (canonicalSenderId ?? rawSenderId)!,
560
555
  });
561
556
  } else {
562
557
  try {
563
558
  const accessResult = await notifyGuardianOfAccessRequest({
564
- canonicalAssistantId,
565
559
  sourceChannel,
566
560
  conversationExternalId,
567
561
  actorExternalId: canonicalSenderId ?? rawSenderId,
@@ -730,7 +724,6 @@ export async function enforceIngressAcl(
730
724
  if (slackVerifyResult.initiated) {
731
725
  try {
732
726
  await notifyGuardianOfAccessRequest({
733
- canonicalAssistantId,
734
727
  sourceChannel,
735
728
  conversationExternalId,
736
729
  actorExternalId: canonicalSenderId ?? rawSenderId,
@@ -803,14 +796,12 @@ export async function enforceIngressAcl(
803
796
  if (!terminallyKeptOut) {
804
797
  if (isCallbackInteraction) {
805
798
  handshakeInProgress = await isApprovalHandshakeInProgress({
806
- canonicalAssistantId,
807
799
  sourceChannel,
808
800
  actorExternalId: (canonicalSenderId ?? rawSenderId)!,
809
801
  });
810
802
  } else {
811
803
  try {
812
804
  const accessResult = await notifyGuardianOfAccessRequest({
813
- canonicalAssistantId,
814
805
  sourceChannel,
815
806
  conversationExternalId,
816
807
  actorExternalId: canonicalSenderId ?? rawSenderId,
@@ -27,6 +27,7 @@ import {
27
27
  ADMISSION_FLOOR,
28
28
  type AdmissionPolicy,
29
29
  isAdmissionPolicyExemptChannel,
30
+ TRUST_CLASS_RANK,
30
31
  } from "@vellumai/gateway-client";
31
32
 
32
33
  import type { ChannelId } from "../../../channels/types.js";
@@ -73,23 +74,6 @@ export type AdmissionPolicyResult =
73
74
  // Public API
74
75
  // ---------------------------------------------------------------------------
75
76
 
76
- /**
77
- * Trust-class ordinal compared against {@link ADMISSION_FLOOR} to make the
78
- * admission decision (`rank >= floor`). Higher rank = more trusted.
79
- * Blocked/revoked never reach this comparison — they short-circuit to deny on
80
- * member status above — so they carry no rank here.
81
- *
82
- * The two halves of the floor check: this table is keyed by `TrustClass`,
83
- * `ADMISSION_FLOOR` (from `@vellumai/gateway-client`) by `AdmissionPolicy`.
84
- * They are only ever read together, here, so they live next to their use.
85
- */
86
- const TRUST_CLASS_RANK: Record<TrustClass, number> = {
87
- guardian: 4,
88
- trusted_contact: 3,
89
- unverified_contact: 2,
90
- unknown: 1,
91
- };
92
-
93
77
  /**
94
78
  * Policies under which completing verification could lift the sender past
95
79
  * the floor. Used to decide whether to fire the upgrade UX on deny.
@@ -54,7 +54,6 @@ function makeParams(
54
54
  isDuplicate: false,
55
55
  commandIntent: { type: "start", payload: "gv_token123" },
56
56
  rawSenderId: "user-42",
57
- canonicalAssistantId: "self",
58
57
  sourceChannel: "telegram",
59
58
  conversationExternalId: "chat-123",
60
59
  eventId: "event-1",
@@ -24,6 +24,7 @@ import {
24
24
  import type { ChannelId } from "../../../channels/types.js";
25
25
  import { sendTelegramReply } from "../../../messaging/providers/telegram-bot/send.js";
26
26
  import { getLogger } from "../../../util/logger.js";
27
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../assistant-scope.js";
27
28
  import { RESEND_COOLDOWN_MS } from "../../verification-outbound-actions.js";
28
29
  import {
29
30
  composeVerificationText,
@@ -40,7 +41,6 @@ export interface BootstrapInterceptParams {
40
41
  isDuplicate: boolean;
41
42
  commandIntent: Record<string, unknown> | undefined;
42
43
  rawSenderId: string | undefined;
43
- canonicalAssistantId: string;
44
44
  sourceChannel: ChannelId;
45
45
  conversationExternalId: string;
46
46
  eventId: string;
@@ -96,7 +96,6 @@ export async function handleBootstrapIntercept(
96
96
  isDuplicate,
97
97
  commandIntent,
98
98
  rawSenderId,
99
- canonicalAssistantId,
100
99
  sourceChannel,
101
100
  conversationExternalId,
102
101
  eventId,
@@ -208,7 +207,7 @@ export async function handleBootstrapIntercept(
208
207
  deliverBootstrapVerificationTelegram(
209
208
  conversationExternalId,
210
209
  telegramBody,
211
- canonicalAssistantId,
210
+ DAEMON_INTERNAL_ASSISTANT_ID,
212
211
  );
213
212
 
214
213
  // Update delivery tracking (best-effort — the code is already on its way)
@@ -45,7 +45,6 @@ export interface EditInterceptParams {
45
45
  externalMessageId: string;
46
46
  sourceMessageId: string;
47
47
  sourceThreadId?: string;
48
- canonicalAssistantId: string;
49
48
  assistantId: string;
50
49
  content: string | undefined;
51
50
  /** Channel ID for channel-level interaction tracking. */
@@ -68,7 +67,6 @@ export async function handleEditIntercept(
68
67
  externalMessageId,
69
68
  sourceMessageId,
70
69
  sourceThreadId,
71
- canonicalAssistantId,
72
70
  assistantId,
73
71
  content,
74
72
  } = params;
@@ -78,7 +76,7 @@ export async function handleEditIntercept(
78
76
  sourceChannel,
79
77
  conversationExternalId,
80
78
  externalMessageId,
81
- { sourceMessageId, sourceThreadId, assistantId: canonicalAssistantId },
79
+ { sourceMessageId, sourceThreadId },
82
80
  );
83
81
 
84
82
  if (editResult.duplicate) {
@@ -51,7 +51,6 @@ function makeParams(
51
51
  callbackData: undefined,
52
52
  rawSenderId: "user-42",
53
53
  canonicalSenderId: "user-42",
54
- canonicalAssistantId: "self",
55
54
  sourceChannel: "slack" as const,
56
55
  conversationExternalId: "chat-123",
57
56
  conversationId: "conv-abc",
@@ -15,6 +15,7 @@ import {
15
15
  } from "../../../channels/gateway-guardian-requests.js";
16
16
  import type { ChannelId } from "../../../channels/types.js";
17
17
  import { getLogger } from "../../../util/logger.js";
18
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../assistant-scope.js";
18
19
  import { deliverChannelReply } from "../../gateway-client.js";
19
20
  import {
20
21
  type GuardianPendingScope,
@@ -41,7 +42,6 @@ export interface GuardianReplyInterceptParams {
41
42
  reactedMessageTs?: string;
42
43
  rawSenderId: string | undefined;
43
44
  canonicalSenderId: string | null;
44
- canonicalAssistantId: string;
45
45
  sourceChannel: ChannelId;
46
46
  conversationExternalId: string;
47
47
  conversationId: string;
@@ -77,7 +77,6 @@ export async function handleGuardianReplyIntercept(
77
77
  reactedMessageTs,
78
78
  rawSenderId,
79
79
  canonicalSenderId,
80
- canonicalAssistantId,
81
80
  sourceChannel,
82
81
  conversationExternalId,
83
82
  conversationId,
@@ -178,7 +177,7 @@ export async function handleGuardianReplyIntercept(
178
177
  channelDeliveryContext: {
179
178
  replyCallbackUrl,
180
179
  guardianChatId: conversationExternalId,
181
- assistantId: canonicalAssistantId,
180
+ assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
182
181
  },
183
182
  });
184
183
 
@@ -188,7 +187,7 @@ export async function handleGuardianReplyIntercept(
188
187
  const routerReplyPayload: Parameters<typeof deliverChannelReply>[1] = {
189
188
  chatId: conversationExternalId,
190
189
  text: routerResult.replyText,
191
- assistantId: canonicalAssistantId,
190
+ assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
192
191
  };
193
192
  // On Slack, send guardian management replies (disambiguation, pending
194
193
  // request lists, etc.) as ephemeral so only the guardian sees them.
@@ -172,7 +172,6 @@ function buildParams(overrides: {
172
172
  sourceInterface: "slack" as const,
173
173
  conversationExternalId: SLACK_CHANNEL_ID,
174
174
  externalMessageId: `${SLACK_CHANNEL_ID}:1700000000.1:${msgCounter}`,
175
- canonicalAssistantId: "assistant-1",
176
175
  rawSenderId: overrides.rawSenderId,
177
176
  canonicalSenderId: overrides.rawSenderId,
178
177
  actorDisplayName: "Reactor",
@@ -35,7 +35,6 @@ import {
35
35
  import { markProcessed } from "../../../persistence/delivery-status.js";
36
36
  import { upsertBinding } from "../../../persistence/external-conversation-store.js";
37
37
  import { getLogger } from "../../../util/logger.js";
38
- import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../assistant-scope.js";
39
38
  import type { ApprovalConversationGenerator } from "../../http-types.js";
40
39
  import {
41
40
  actorTrustContextFromVerdict,
@@ -100,7 +99,6 @@ export interface ReactionInterceptParams {
100
99
  sourceInterface: InterfaceId | undefined;
101
100
  conversationExternalId: string;
102
101
  externalMessageId: string;
103
- canonicalAssistantId: string;
104
102
  rawSenderId: string | undefined;
105
103
  canonicalSenderId: string | null;
106
104
  actorDisplayName: string | undefined;
@@ -126,7 +124,6 @@ export async function handleSlackReactionIntercept(
126
124
  sourceInterface,
127
125
  conversationExternalId,
128
126
  externalMessageId,
129
- canonicalAssistantId,
130
127
  rawSenderId,
131
128
  canonicalSenderId,
132
129
  actorDisplayName,
@@ -182,7 +179,6 @@ export async function handleSlackReactionIntercept(
182
179
  externalMessageId,
183
180
  {
184
181
  sourceMessageId: reactedMessageTs,
185
- assistantId: canonicalAssistantId,
186
182
  sourceThreadId: threadTs,
187
183
  },
188
184
  );
@@ -216,21 +212,17 @@ export async function handleSlackReactionIntercept(
216
212
  };
217
213
  }
218
214
 
219
- // Maintain the conversation binding, matching the message pipeline. Scoped to
220
- // the daemon's own assistant so assistant-scoped legacy routes don't clobber
221
- // each other's binding metadata.
222
- if (canonicalAssistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
223
- upsertBinding({
224
- conversationId: result.conversationId,
225
- sourceChannel,
226
- externalChatId: conversationExternalId,
227
- externalChatName: slackChannelName,
228
- externalThreadId: threadTs ?? null,
229
- externalUserId: canonicalSenderId ?? rawSenderId ?? null,
230
- displayName: actorDisplayName ?? null,
231
- username: actorUsername ?? null,
232
- });
233
- }
215
+ // Maintain the conversation binding, matching the message pipeline.
216
+ upsertBinding({
217
+ conversationId: result.conversationId,
218
+ sourceChannel,
219
+ externalChatId: conversationExternalId,
220
+ externalChatName: slackChannelName,
221
+ externalThreadId: threadTs ?? null,
222
+ externalUserId: canonicalSenderId ?? rawSenderId ?? null,
223
+ displayName: actorDisplayName ?? null,
224
+ username: actorUsername ?? null,
225
+ });
234
226
 
235
227
  // Guardian approval-by-reaction → guardian decision pipeline, exactly like
236
228
  // buttons and text replies. Only `reaction:` (added) expresses intent;
@@ -247,7 +239,6 @@ export async function handleSlackReactionIntercept(
247
239
  reactedMessageTs,
248
240
  rawSenderId,
249
241
  canonicalSenderId,
250
- canonicalAssistantId,
251
242
  sourceChannel,
252
243
  conversationExternalId,
253
244
  conversationId: result.conversationId,
@@ -14,6 +14,7 @@ import {
14
14
  } from "../../../persistence/delivery-crud.js";
15
15
  import { checkIngressForSecrets } from "../../../security/secret-ingress.js";
16
16
  import { getLogger } from "../../../util/logger.js";
17
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../assistant-scope.js";
17
18
 
18
19
  const log = getLogger("runtime-http");
19
20
 
@@ -36,7 +37,6 @@ export interface SecretIngressCheckParams {
36
37
  actorUsername: string | undefined;
37
38
  trustCtx: TrustContext;
38
39
  replyCallbackUrl: string | undefined;
39
- canonicalAssistantId: string;
40
40
  }
41
41
 
42
42
  export interface SecretIngressCheckResult {
@@ -69,7 +69,6 @@ export function runSecretIngressCheck(
69
69
  actorUsername,
70
70
  trustCtx,
71
71
  replyCallbackUrl,
72
- canonicalAssistantId,
73
72
  } = params;
74
73
 
75
74
  // Persist the raw payload so dead-lettered events can always be replayed.
@@ -85,7 +84,7 @@ export function runSecretIngressCheck(
85
84
  senderUsername: actorUsername,
86
85
  trustCtx,
87
86
  replyCallbackUrl,
88
- assistantId: canonicalAssistantId,
87
+ assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
89
88
  });
90
89
 
91
90
  // ── Secret ingress scan ──
@@ -29,11 +29,14 @@ import {
29
29
  loadRawConfig,
30
30
  } from "../../config/loader.js";
31
31
  import {
32
- LLMProvider,
33
32
  ProfileEntry,
34
33
  routingIdentityModelIssue,
35
34
  } from "../../config/schemas/llm.js";
36
35
  import { getDb } from "../../persistence/db-connection.js";
36
+ import {
37
+ resolveEntryProviderKind,
38
+ writableProfileProviderIssue,
39
+ } from "../../providers/connection-resolution.js";
37
40
  import { ROUTING_IDENTITY_PROVIDERS } from "../../providers/inference/auth.js";
38
41
  import type { ConnectionAvailability } from "../../providers/inference/connection-availability.js";
39
42
  import {
@@ -150,10 +153,9 @@ const updateRequestSchema = z.object({
150
153
  // ---------------------------------------------------------------------------
151
154
 
152
155
  function assertValidProvider(provider: string): void {
153
- if (!LLMProvider.safeParse(provider).success) {
154
- throw new BadRequestError(
155
- `Invalid provider "${provider}". Valid providers: ${LLMProvider.options.join(", ")}.`,
156
- );
156
+ const issue = writableProfileProviderIssue(provider);
157
+ if (issue) {
158
+ throw new BadRequestError(issue);
157
159
  }
158
160
  }
159
161
 
@@ -182,31 +184,38 @@ function validateModel(
182
184
  }
183
185
  return [];
184
186
  }
185
- if (isModelInCatalog(provider, model)) {
187
+ // An entry-name provider validates against its row's dispatchable kind,
188
+ // the same translation dispatch uses; the entry itself also serves as the
189
+ // model-list connection for custom endpoints below.
190
+ const entryKind = resolveEntryProviderKind(provider, model);
191
+ const catalogProvider = entryKind ?? provider;
192
+ if (isModelInCatalog(catalogProvider, model)) {
186
193
  return [];
187
194
  }
188
195
  // The named connection's advertised model list is authoritative for models
189
196
  // the code-owned catalog doesn't know — a custom (openai-compatible)
190
197
  // endpoint declares its own models at connection-create time.
191
- if (connectionName) {
192
- const connection = getConnection(getDb(), connectionName);
198
+ const modelListConnection =
199
+ connectionName ?? (entryKind !== null ? provider : undefined);
200
+ if (modelListConnection) {
201
+ const connection = getConnection(getDb(), modelListConnection);
193
202
  if (connection?.models?.some((m) => m.id === model)) {
194
203
  return [];
195
204
  }
196
205
  }
197
206
  if (!allowUnlisted) {
198
207
  const remedy =
199
- provider === "openai-compatible"
208
+ catalogProvider === "openai-compatible"
200
209
  ? `Pass allowUnlisted to create it anyway, or declare the model on the connection ` +
201
210
  `("assistant inference providers update <name> --model ${model}").`
202
211
  : `Pass allowUnlisted to create it anyway, or run ` +
203
212
  `"assistant inference models list --provider ${provider}" to see valid ids.`;
204
213
  throw new BadRequestError(
205
- `Model "${model}" is not in the catalog for provider "${provider}". ${remedy}`,
214
+ `Model "${model}" is not in the catalog for provider "${catalogProvider}". ${remedy}`,
206
215
  );
207
216
  }
208
217
  return [
209
- `Model "${model}" is not in the catalog for provider "${provider}" — created anyway (allowUnlisted).`,
218
+ `Model "${model}" is not in the catalog for provider "${catalogProvider}"; created anyway (allowUnlisted).`,
210
219
  ];
211
220
  }
212
221