@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,92 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { OpenAIChatCompletionsProvider } from "../../openai/chat-completions-provider.js";
4
+ import {
5
+ OPENCODE_GO_BASE_URL,
6
+ OPENCODE_ZEN_BASE_URL,
7
+ OpenCodeProvider,
8
+ } from "../../opencode/client.js";
9
+ import {
10
+ buildProviderAdapter,
11
+ createAdapterFromConnection,
12
+ } from "../adapter-factory.js";
13
+ import type { ProviderConnection, ResolvedAuth } from "../auth.js";
14
+
15
+ describe("opencode adapter factory", () => {
16
+ test("buildProviderAdapter returns an OpenCodeProvider", () => {
17
+ const adapter = buildProviderAdapter("opencode", {
18
+ apiKey: "sk-opencode-test",
19
+ model: "mimo-v2.5-free",
20
+ streamTimeoutMs: 60_000,
21
+ useNativeWebSearch: false,
22
+ });
23
+ expect(adapter).toBeInstanceOf(OpenCodeProvider);
24
+ expect(adapter).toBeInstanceOf(OpenAIChatCompletionsProvider);
25
+ expect(adapter?.name).toBe("opencode");
26
+ });
27
+
28
+ test("defaults to OpenCode Zen when no baseURL is set", () => {
29
+ const adapter = buildProviderAdapter("opencode", {
30
+ apiKey: "sk-opencode-test",
31
+ model: "mimo-v2.5-free",
32
+ streamTimeoutMs: 60_000,
33
+ useNativeWebSearch: false,
34
+ }) as OpenCodeProvider;
35
+ const client = (
36
+ adapter as unknown as { client: { baseURL?: string } }
37
+ ).client;
38
+ expect(client.baseURL).toBe(OPENCODE_ZEN_BASE_URL);
39
+ });
40
+
41
+ test("createAdapterFromConnection wires a custom OpenCode Go base URL", () => {
42
+ const connection: ProviderConnection = {
43
+ name: "opencode-go",
44
+ provider: "opencode",
45
+ auth: { type: "api_key", credential: "cred-opencode" },
46
+ label: "OpenCode Go",
47
+ baseUrl: OPENCODE_GO_BASE_URL,
48
+ models: [{ id: "mimo-v2.5-free" }],
49
+ createdAt: Date.now(),
50
+ updatedAt: Date.now(),
51
+ isManaged: false,
52
+ };
53
+
54
+ const resolvedAuth: ResolvedAuth = {
55
+ kind: "header",
56
+ headers: { Authorization: "Bearer sk-opencode-key" },
57
+ baseUrl: OPENCODE_GO_BASE_URL,
58
+ };
59
+
60
+ const adapter = createAdapterFromConnection(connection, resolvedAuth, {
61
+ model: "mimo-v2.5-free",
62
+ streamTimeoutMs: 60_000,
63
+ });
64
+
65
+ expect(adapter).not.toBeNull();
66
+ });
67
+
68
+ test("createAdapterFromConnection rejects none auth for opencode", () => {
69
+ const connection: ProviderConnection = {
70
+ name: "opencode-keyless",
71
+ provider: "opencode",
72
+ auth: { type: "none" },
73
+ label: null,
74
+ baseUrl: OPENCODE_ZEN_BASE_URL,
75
+ models: [{ id: "mimo-v2.5-free" }],
76
+ createdAt: Date.now(),
77
+ updatedAt: Date.now(),
78
+ isManaged: false,
79
+ };
80
+
81
+ const adapter = createAdapterFromConnection(
82
+ connection,
83
+ { kind: "none" },
84
+ {
85
+ model: "mimo-v2.5-free",
86
+ streamTimeoutMs: 60_000,
87
+ },
88
+ );
89
+
90
+ expect(adapter).toBeNull();
91
+ });
92
+ });
@@ -184,6 +184,22 @@ describe("base_url provider-type gate (create)", () => {
184
184
  );
185
185
  });
186
186
 
187
+ test("accepts base_url on opencode provider", async () => {
188
+ mockResolvedAddresses = ["93.184.216.34"];
189
+ const result = await handleCreate({
190
+ body: {
191
+ name: "valid-opencode-go",
192
+ provider: "opencode",
193
+ auth: { type: "api_key", credential: "cred-opencode" },
194
+ base_url: "https://opencode.ai/zen/go/v1",
195
+ },
196
+ });
197
+ expect(result).toBeDefined();
198
+ expect((result as { baseUrl: string }).baseUrl).toBe(
199
+ "https://opencode.ai/zen/go/v1",
200
+ );
201
+ });
202
+
187
203
  test("accepts base_url on ollama provider", async () => {
188
204
  mockResolvedAddresses = ["93.184.216.34"];
189
205
  const result = await handleCreate({
@@ -174,6 +174,18 @@ describe("resolveAuth defense-in-depth", () => {
174
174
  }
175
175
  });
176
176
 
177
+ test("preserves baseUrl for opencode provider", async () => {
178
+ const result = await resolveAuth(
179
+ { type: "api_key", credential: "cred-test" },
180
+ "opencode",
181
+ { baseUrl: "https://opencode.ai/zen/go/v1" },
182
+ );
183
+ expect(result.ok).toBe(true);
184
+ if (result.ok && result.resolved.kind === "header") {
185
+ expect(result.resolved.baseUrl).toBe("https://opencode.ai/zen/go/v1");
186
+ }
187
+ });
188
+
177
189
  test("preserves baseUrl for ollama none-auth", async () => {
178
190
  const result = await resolveAuth({ type: "none" }, "ollama", {
179
191
  baseUrl: "http://192.168.1.50:11434/v1",
@@ -18,7 +18,18 @@
18
18
  * 3. Register the client in `ADAPTER_FACTORIES` below.
19
19
  */
20
20
 
21
+ import { resolveDefaultProfileForProvider } from "../../config/default-profile-catalog.js";
22
+ import {
23
+ FALLBACK_PROFILE_BY_KEY,
24
+ isDefaultProfileKey,
25
+ } from "../../config/default-profile-names.js";
26
+ import {
27
+ resolveCallSiteConfigWithProfile,
28
+ selectWinningProfile,
29
+ } from "../../config/llm-resolver.js";
30
+ import { getConfig } from "../../config/loader.js";
21
31
  import { normalizeCredentialRef } from "../../security/credential-key.js";
32
+ import { getLogger } from "../../util/logger.js";
22
33
  import { AnthropicProvider } from "../anthropic/client.js";
23
34
  import { AtlasCloudProvider } from "../atlascloud/client.js";
24
35
  import { BasetenProvider } from "../baseten/client.js";
@@ -29,13 +40,21 @@ import { PROVIDER_CATALOG } from "../model-catalog.js";
29
40
  import { OllamaProvider } from "../ollama/client.js";
30
41
  import { OpenAIChatCompletionsProvider } from "../openai/chat-completions-provider.js";
31
42
  import { OpenAIResponsesProvider } from "../openai/responses-provider.js";
43
+ import { OpenCodeProvider } from "../opencode/client.js";
32
44
  import { OpenRouterProvider } from "../openrouter/client.js";
33
45
  import { PoolsideProvider } from "../poolside/client.js";
46
+ import { dispatchProviderResolvable } from "../provider-resolvability.js";
34
47
  import { RetryProvider } from "../retry.js";
35
48
  import { TogetherProvider } from "../together/client.js";
36
- import type { Provider } from "../types.js";
49
+ import type { Provider, SendMessageOptions } from "../types.js";
37
50
  import { UsageTrackingProvider } from "../usage-tracking.js";
38
- import { isVellumManagedConnection } from "../vellum-model-routing.js";
51
+ import { VellumProvider } from "../vellum/client.js";
52
+ import {
53
+ getManagedUpstream,
54
+ isVellumManagedConnection,
55
+ MANAGED_ROUTABLE_PROVIDERS,
56
+ VELLUM_MANAGED_PROVIDER,
57
+ } from "../vellum-model-routing.js";
39
58
  import { VercelAIGatewayProvider } from "../vercel-ai-gateway/client.js";
40
59
  import type { ResolvedAuth } from "./auth.js";
41
60
  import type { ProviderConnection } from "./auth.js";
@@ -58,6 +77,15 @@ export interface AdapterCreateOpts {
58
77
 
59
78
  type AdapterFactory = (opts: AdapterCreateOpts) => Provider;
60
79
 
80
+ const log = getLogger("adapter-factory");
81
+
82
+ /** The backup route `RetryProvider` escalates to; see {@link makeFallbackRouteResolver}. */
83
+ interface FallbackRoute {
84
+ provider: Provider;
85
+ overrideProfile: string;
86
+ forwardUsageAttributionHeaders: boolean;
87
+ }
88
+
61
89
  /**
62
90
  * Catalog-keyed factory table. Each entry takes a unified
63
91
  * `AdapterCreateOpts` and constructs the underlying provider client. The
@@ -138,6 +166,11 @@ const ADAPTER_FACTORIES: Record<string, AdapterFactory> = {
138
166
  assistantReasoningField: "reasoning_content",
139
167
  ...(baseURL ? { baseURL } : {}),
140
168
  }),
169
+ opencode: ({ apiKey, model, streamTimeoutMs, baseURL }) =>
170
+ new OpenCodeProvider(apiKey, model, {
171
+ streamTimeoutMs,
172
+ ...(baseURL ? { baseURL } : {}),
173
+ }),
141
174
  // Keyless openai-compatible endpoints (e.g. LM Studio) ignore the key; the
142
175
  // placeholder satisfies the OpenAI SDK, which requires a non-empty key.
143
176
  "openai-compatible": ({ apiKey, model, streamTimeoutMs, baseURL }) =>
@@ -172,6 +205,11 @@ const ADAPTER_FACTORIES: Record<string, AdapterFactory> = {
172
205
  streamTimeoutMs,
173
206
  ...(baseURL ? { baseURL } : {}),
174
207
  }),
208
+ vellum: ({ apiKey, model, streamTimeoutMs, baseURL }) =>
209
+ new VellumProvider(apiKey, model, {
210
+ streamTimeoutMs,
211
+ ...(baseURL ? { baseURL } : {}),
212
+ }),
175
213
  };
176
214
 
177
215
  /**
@@ -275,6 +313,195 @@ export function createAdapterFromConnection(
275
313
  };
276
314
  }
277
315
 
316
+ /**
317
+ * Resolve the backup-profile route for a request whose primary route just
318
+ * failed with an outage-shaped error, so `RetryProvider` can serve it on a
319
+ * different provider instead of surfacing the outage.
320
+ *
321
+ * The winning profile of the FAILED call is re-resolved from the same
322
+ * inputs `normalizeSendMessageOptions` uses (call site plus the request's
323
+ * own override/seed fields), and its `fallbackProfile` pointer names the
324
+ * backup. The upstream comes from the backup's resolved provider; only the
325
+ * `vellum` routing identity carries no upstream of its own, and there the
326
+ * backup's model determines it.
327
+ *
328
+ * Returns a RAW adapter, exactly like `makeCredentialRefresher` above:
329
+ * `RetryProvider` hands the backup route options it has ALREADY normalized
330
+ * (call site consumed, wire params resolved from the backup profile, usage
331
+ * attribution headers stamped). A second `RetryProvider` in between would
332
+ * re-normalize that call-site-less config and strip those headers, leaving
333
+ * degraded traffic unattributed on the platform's billing events.
334
+ *
335
+ * Never throws: a broken backup lookup must surface the original provider
336
+ * error, not a new one.
337
+ */
338
+ function makeFallbackRouteResolver(): (
339
+ failedOptions: SendMessageOptions | undefined,
340
+ ) => Promise<FallbackRoute | null> {
341
+ return async (failedOptions): Promise<FallbackRoute | null> => {
342
+ const failedConfig = failedOptions?.config;
343
+ const callSite = failedConfig?.callSite;
344
+ if (callSite === undefined) {
345
+ return null;
346
+ }
347
+ try {
348
+ const { llm } = getConfig();
349
+ const winner = selectWinningProfile(callSite, llm, {
350
+ overrideProfile: failedConfig?.overrideProfile,
351
+ forceOverrideProfile: failedConfig?.forceOverrideProfile,
352
+ selectionSeed: failedConfig?.selectionSeed,
353
+ isResolvableProvider: dispatchProviderResolvable,
354
+ });
355
+ const primaryProfile =
356
+ winner.profileName ??
357
+ (winner.source === "default" ? "balanced" : undefined);
358
+ if (
359
+ primaryProfile === undefined ||
360
+ !isDefaultProfileKey(primaryProfile)
361
+ ) {
362
+ return null;
363
+ }
364
+ const expectedFallback = FALLBACK_PROFILE_BY_KEY[primaryProfile];
365
+ if (
366
+ winner.entry?.source !== "managed" ||
367
+ winner.entry.fallbackProfile !== expectedFallback
368
+ ) {
369
+ return null;
370
+ }
371
+ const overrideProfile = expectedFallback;
372
+ // Resolved the provider-aware way the runtime resolver resolves every
373
+ // profile name: the backups are companions of the managed column and
374
+ // must not materialize under a BYOK or ChatGPT default provider.
375
+ const backup = resolveDefaultProfileForProvider(
376
+ llm.profiles,
377
+ overrideProfile,
378
+ llm.defaultProvider ?? null,
379
+ );
380
+ if (backup?.model == null) {
381
+ log.warn(
382
+ { connectionName: connection.name, overrideProfile },
383
+ "Backup profile does not resolve on this default provider; keeping the original error",
384
+ );
385
+ return null;
386
+ }
387
+ const { config: resolvedBackup, profileName: resolvedBackupProfile } =
388
+ resolveCallSiteConfigWithProfile(callSite, llm, {
389
+ overrideProfile,
390
+ forceOverrideProfile: true,
391
+ selectionSeed: failedConfig?.selectionSeed,
392
+ isResolvableProvider: dispatchProviderResolvable,
393
+ });
394
+ if (resolvedBackupProfile !== overrideProfile) {
395
+ log.warn(
396
+ {
397
+ connectionName: connection.name,
398
+ overrideProfile,
399
+ selectedProfile: resolvedBackupProfile,
400
+ },
401
+ "Backup profile is not dispatch-resolvable; keeping the original error",
402
+ );
403
+ return null;
404
+ }
405
+ // The model the adapter serves must be the model the fallback send
406
+ // carries, so it comes from the same forced-profile resolution.
407
+ // This callback authenticates the backup with the managed connection
408
+ // it was built for, so it can only serve a backup that routes through
409
+ // the managed column. Refusing any other route keeps managed billing
410
+ // and credentials from being applied to user-owned traffic.
411
+ // Keyed on the connection, not the provider: a call-site fragment that
412
+ // pins a concrete upstream over a managed winner keeps the managed
413
+ // routing and is stamped with this connection's name by the resolver
414
+ // (see `llm-resolver.ts`), so a concrete `provider` here is still
415
+ // managed traffic. Only a profile that names a different connection,
416
+ // or a concrete provider carrying no managed connection at all (a BYOK
417
+ // winner), routes somewhere this callback cannot authenticate.
418
+ const backupConnection = resolvedBackup.provider_connection;
419
+ const routesThroughThisConnection =
420
+ backupConnection === undefined
421
+ ? resolvedBackup.provider === VELLUM_MANAGED_PROVIDER
422
+ : backupConnection === connection.name;
423
+ if (!routesThroughThisConnection) {
424
+ log.warn(
425
+ {
426
+ connectionName: connection.name,
427
+ overrideProfile,
428
+ backupProvider: resolvedBackup.provider,
429
+ backupConnection,
430
+ },
431
+ "Backup profile routes outside the managed connection; keeping the original error",
432
+ );
433
+ return null;
434
+ }
435
+ // The upstream is the resolved provider, the same way normal dispatch
436
+ // picks it for this connection: `connection-resolution.ts` threads the
437
+ // resolved provider through as `providerOverride` for a `vellum`
438
+ // connection and derives the upstream from the model only when the
439
+ // provider is the routing-identity sentinel (`resolveRoutingIdentity`,
440
+ // and the `isVellum && !expectedProvider` branch). Deriving it from the
441
+ // model here regardless would send a provider-pinned call-site fragment
442
+ // to a different upstream than a primary send of the same resolution,
443
+ // and would refuse a managed model the catalog does not list yet even
444
+ // though normal dispatch serves it. A provider the managed proxy cannot
445
+ // front (a non-routable id, or another routing identity such as
446
+ // `chatgpt`) is refused rather than routed.
447
+ const declaredProvider = resolvedBackup.provider;
448
+ const upstream =
449
+ declaredProvider === VELLUM_MANAGED_PROVIDER
450
+ ? getManagedUpstream(resolvedBackup.model)
451
+ : MANAGED_ROUTABLE_PROVIDERS.has(declaredProvider)
452
+ ? declaredProvider
453
+ : null;
454
+ if (upstream === null) {
455
+ log.warn(
456
+ {
457
+ connectionName: connection.name,
458
+ overrideProfile,
459
+ backupProvider: declaredProvider,
460
+ backupModel: resolvedBackup.model,
461
+ },
462
+ "Backup profile does not resolve to a managed upstream; keeping the original error",
463
+ );
464
+ return null;
465
+ }
466
+ // Re-resolve auth for the BACKUP upstream: the managed proxy's base URL
467
+ // is per-upstream, so the primary's resolved auth cannot be reused.
468
+ const backupAuth = await resolveAuth(
469
+ effectiveConnectionAuth(connection),
470
+ upstream,
471
+ { baseUrl: connection.baseUrl },
472
+ );
473
+ if (!backupAuth.ok) {
474
+ return null;
475
+ }
476
+ const backupAdapter = buildConnectionAdapter(
477
+ connection,
478
+ backupAuth.resolved,
479
+ // `useNativeWebSearch` carries over from the primary: it is a
480
+ // property of this request's web-search config, and every managed
481
+ // backup pin either supports native search or ignores the flag.
482
+ { ...opts, model: resolvedBackup.model, provider: upstream },
483
+ );
484
+ if (backupAdapter === null) {
485
+ return null;
486
+ }
487
+ return {
488
+ provider: backupAdapter,
489
+ overrideProfile,
490
+ // Always true: this callback is wired only on the managed proxy, so
491
+ // the backup route runs through it too and its `X-Vellum-*` billing
492
+ // metadata reaches our own backend, never a third party.
493
+ forwardUsageAttributionHeaders: true,
494
+ };
495
+ } catch (err) {
496
+ log.warn(
497
+ { err, connectionName: connection.name, callSite },
498
+ "Failed to resolve a backup profile route; keeping the original error",
499
+ );
500
+ return null;
501
+ }
502
+ };
503
+ }
504
+
278
505
  // Usage-attribution headers (`X-Vellum-*`) are only meaningful when the
279
506
  // request is routed through the Vellum-managed proxy. They carry billing
280
507
  // metadata for our own backend. Forwarding them to a third-party endpoint
@@ -295,8 +522,15 @@ export function createAdapterFromConnection(
295
522
  ? "no-auth"
296
523
  : undefined,
297
524
  connectionName: connection.name,
525
+ // Both escalations are managed-route only. A BYOK, oauth-subscription,
526
+ // or no-auth connection must never fall back: the install may hold no
527
+ // credential for the backup profile's provider, and only the managed
528
+ // column carries `fallbackProfile` pointers in the first place.
298
529
  ...(isManagedProxy
299
- ? { refreshCredentialProvider: makeCredentialRefresher() }
530
+ ? {
531
+ refreshCredentialProvider: makeCredentialRefresher(),
532
+ resolveFallbackRoute: makeFallbackRouteResolver(),
533
+ }
300
534
  : {}),
301
535
  }),
302
536
  );
@@ -323,6 +557,15 @@ function buildConnectionAdapter(
323
557
  },
324
558
  ): Provider | null {
325
559
  const provider = opts.provider ?? connection.provider;
560
+ // The connection's own `vellum` column is a routing sentinel. Dispatch
561
+ // only when the caller names an upstream, including Vellum-hosted GPU
562
+ // models whose catalog id is also `vellum`.
563
+ if (
564
+ connection.provider === VELLUM_MANAGED_PROVIDER &&
565
+ opts.provider === undefined
566
+ ) {
567
+ return null;
568
+ }
326
569
  const entry = PROVIDER_CATALOG.find((e) => e.id === provider);
327
570
  if (!entry) {
328
571
  return null;
@@ -118,20 +118,20 @@ export type ResolvedAuth =
118
118
  // providers at runtime.
119
119
 
120
120
  export const VALID_CONNECTION_PROVIDERS: readonly string[] = [
121
- ...PROVIDER_CATALOG.map((p) => p.id),
122
- // The provider-agnostic Vellum-managed connection stores this sentinel in its
123
- // `provider` column. It is intentionally not a PROVIDER_CATALOG entry (it
124
- // names no single upstream), so it must be allowlisted explicitly or the DB
125
- // loaders (getConnection/listConnections) and the create route would reject
126
- // persisted `vellum` rows the routing threaded in via `providerOverride`
127
- // never runs on a row that fails to load.
128
- VELLUM_MANAGED_PROVIDER,
129
- // The ChatGPT-subscription row stores the "chatgpt" routing identity in its
130
- // `provider` column for the same reason: the row IS the subscription route
131
- // (auth modality = provider identity), and dispatch derives the openai
132
- // upstream per-request. Allowlisted explicitly or the DB loaders would drop
133
- // the persisted row.
134
- "chatgpt",
121
+ ...new Set([
122
+ ...PROVIDER_CATALOG.map((p) => p.id),
123
+ // The provider-agnostic Vellum-managed connection stores this sentinel in
124
+ // its `provider` column. The same id owns Vellum-hosted GPU models in the
125
+ // catalog. Keep it allowlisted explicitly so a future catalog rename
126
+ // cannot drop persisted `vellum` rows from the DB loaders.
127
+ VELLUM_MANAGED_PROVIDER,
128
+ // The ChatGPT-subscription row stores the "chatgpt" routing identity in
129
+ // its `provider` column: the row IS the subscription route (auth
130
+ // modality = provider identity), and dispatch derives the openai
131
+ // upstream per-request. Allowlisted explicitly or the DB loaders would
132
+ // drop the persisted row.
133
+ "chatgpt",
134
+ ]),
135
135
  ];
136
136
 
137
137
  export type ConnectionProvider = string;
@@ -190,6 +190,7 @@ export const PROVIDERS_REQUIRING_BASE_URL_AND_MODELS: ReadonlySet<string> =
190
190
  export const PROVIDERS_ALLOWING_CUSTOM_BASE_URL: ReadonlySet<string> = new Set([
191
191
  ...PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
192
192
  "ollama",
193
+ "opencode",
193
194
  ]);
194
195
 
195
196
  // ---------------------------------------------------------------------------
@@ -0,0 +1,70 @@
1
+ /**
2
+ * In-daemon completion of the "ChatGPT Subscription" sign-in: store the tokens
3
+ * and point the provider connection at them in one step.
4
+ *
5
+ * The OAuth client config and the credential writes live in
6
+ * `chatgpt-subscription-credentials.ts`, which depends on nothing but the
7
+ * credential store so the CLI can import it without pulling in the database.
8
+ */
9
+
10
+ import { getDb } from "../../persistence/db-connection.js";
11
+ import type { OAuth2TokenResult } from "../../security/oauth2.js";
12
+ import { getLogger } from "../../util/logger.js";
13
+ import {
14
+ CHATGPT_SUBSCRIPTION_AUTH_INPUT,
15
+ CHATGPT_SUBSCRIPTION_CONNECTION_NAME,
16
+ storeChatgptSubscriptionCredentials,
17
+ } from "./chatgpt-subscription-credentials.js";
18
+ import {
19
+ createConnection,
20
+ getConnection,
21
+ updateConnection,
22
+ } from "./connections.js";
23
+
24
+ const log = getLogger("chatgpt-subscription-auth");
25
+
26
+ /**
27
+ * Store the subscription's tokens and point the `chatgpt-subscription` provider
28
+ * connection at them, creating the connection when it does not exist yet.
29
+ */
30
+ export async function storeChatgptSubscriptionTokens(
31
+ tokens: OAuth2TokenResult,
32
+ ): Promise<void> {
33
+ await storeChatgptSubscriptionCredentials(tokens);
34
+
35
+ const db = getDb();
36
+ const existing = getConnection(db, CHATGPT_SUBSCRIPTION_CONNECTION_NAME);
37
+
38
+ if (existing) {
39
+ // Stamp the provider with the auth: this row IS the subscription route
40
+ // (auth modality = provider identity), and dispatch derives the openai
41
+ // upstream from the identity per-request. Stamping also heals a
42
+ // claiming row whose provider would otherwise misroute the fresh token.
43
+ const updateResult = updateConnection(
44
+ db,
45
+ CHATGPT_SUBSCRIPTION_CONNECTION_NAME,
46
+ { auth: CHATGPT_SUBSCRIPTION_AUTH_INPUT, provider: "chatgpt" },
47
+ );
48
+ if (!updateResult.ok) {
49
+ log.error(
50
+ { error: updateResult.error },
51
+ "Failed to update chatgpt-subscription connection",
52
+ );
53
+ throw new Error("Failed to update connection");
54
+ }
55
+ return;
56
+ }
57
+
58
+ const createResult = createConnection(db, {
59
+ name: CHATGPT_SUBSCRIPTION_CONNECTION_NAME,
60
+ provider: "chatgpt",
61
+ auth: CHATGPT_SUBSCRIPTION_AUTH_INPUT,
62
+ });
63
+ if (!createResult.ok) {
64
+ log.error(
65
+ { error: createResult.error },
66
+ "Failed to create chatgpt-subscription connection",
67
+ );
68
+ throw new Error("Failed to create connection");
69
+ }
70
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * OAuth client config and credential writes for the "ChatGPT Subscription"
3
+ * sign-in.
4
+ *
5
+ * Every entry point into the flow (the daemon's device-code routes, the
6
+ * daemon's copy-paste PKCE routes, and the `assistant inference providers
7
+ * login-chatgpt` CLI command) authenticates against the same OAuth client and
8
+ * lands its tokens under the same credential prefix, so the client config and
9
+ * the credential writes live here rather than in each caller.
10
+ *
11
+ * This module deliberately depends on nothing but the credential store: the
12
+ * CLI is a separate process with no database handle, so it imports this leaf
13
+ * and pairs it with its own IPC connection upsert. In-daemon callers use
14
+ * `storeChatgptSubscriptionTokens` from `chatgpt-subscription-auth.ts`, which
15
+ * writes the credentials and the provider connection together.
16
+ */
17
+
18
+ import type { OAuth2Config, OAuth2TokenResult } from "../../security/oauth2.js";
19
+ import { setSecureKeyAsync } from "../../security/secure-keys.js";
20
+ import { getLogger } from "../../util/logger.js";
21
+ import { CHATGPT_SUBSCRIPTION_CONNECTION_NAME } from "./auth.js";
22
+
23
+ const log = getLogger("chatgpt-subscription-credentials");
24
+
25
+ export { CHATGPT_SUBSCRIPTION_CONNECTION_NAME };
26
+
27
+ /** OpenAI's Codex OAuth client. PKCE only, no client secret. */
28
+ export const OPENAI_OAUTH_CONFIG: OAuth2Config = {
29
+ authorizeUrl: "https://auth.openai.com/oauth/authorize",
30
+ tokenExchangeUrl: "https://auth.openai.com/oauth/token",
31
+ clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
32
+ scopes: ["openid", "profile", "email", "offline_access"],
33
+ scopeSeparator: " ",
34
+ authorizeParams: { id_token_add_organizations: "true" },
35
+ };
36
+
37
+ /** Credential-store prefix holding the subscription's access/refresh tokens. */
38
+ export const CHATGPT_CREDENTIAL_PREFIX = "credential/chatgpt";
39
+
40
+ export const CHATGPT_ACCESS_TOKEN_CREDENTIAL = `${CHATGPT_CREDENTIAL_PREFIX}/access_token`;
41
+
42
+ /** Auth block stamped on the `chatgpt-subscription` provider connection. */
43
+ export const CHATGPT_SUBSCRIPTION_AUTH_INPUT = {
44
+ type: "oauth_subscription" as const,
45
+ credential: CHATGPT_ACCESS_TOKEN_CREDENTIAL,
46
+ };
47
+
48
+ /**
49
+ * Write the subscription's tokens to the credential store.
50
+ *
51
+ * Throws when a write fails: a half-stored credential set leaves the provider
52
+ * connection pointing at a token the assistant cannot use.
53
+ */
54
+ export async function storeChatgptSubscriptionCredentials(
55
+ tokens: OAuth2TokenResult,
56
+ ): Promise<void> {
57
+ const accessStored = await setSecureKeyAsync(
58
+ CHATGPT_ACCESS_TOKEN_CREDENTIAL,
59
+ tokens.accessToken,
60
+ );
61
+ if (!accessStored) {
62
+ log.error("Failed to store ChatGPT access token in CES");
63
+ throw new Error("Failed to store access token");
64
+ }
65
+
66
+ if (tokens.refreshToken) {
67
+ const refreshStored = await setSecureKeyAsync(
68
+ `${CHATGPT_CREDENTIAL_PREFIX}/refresh_token`,
69
+ tokens.refreshToken,
70
+ );
71
+ if (!refreshStored) {
72
+ log.error("Failed to store ChatGPT refresh token in CES");
73
+ throw new Error("Failed to store refresh token");
74
+ }
75
+ }
76
+
77
+ if (tokens.expiresIn) {
78
+ const expiresAt = Math.floor(Date.now() / 1000 + tokens.expiresIn);
79
+ await setSecureKeyAsync(
80
+ `${CHATGPT_CREDENTIAL_PREFIX}/expires_at`,
81
+ String(expiresAt),
82
+ );
83
+ }
84
+ }
@@ -43,7 +43,7 @@ export async function resolveAuth(
43
43
  if (safeBaseUrl && !PROVIDERS_ALLOWING_CUSTOM_BASE_URL.has(provider)) {
44
44
  log.warn(
45
45
  { provider, baseUrl: safeBaseUrl },
46
- `Stripping baseUrl for provider "${provider}": base_url is only valid for openai-compatible and ollama providers.`,
46
+ `Stripping baseUrl for provider "${provider}": base_url is only valid for openai-compatible, ollama, and opencode providers.`,
47
47
  );
48
48
  safeBaseUrl = null;
49
49
  }
@@ -79,9 +79,8 @@ export function isUnsendableImageSource(source: MediaSource): boolean {
79
79
  * `image/heic` and `image/heif` directly
80
80
  * (https://ai.google.dev/gemini-api/docs/image-understanding), while
81
81
  * OpenAI-compatible and Anthropic endpoints answer HTTP 400 for the whole
82
- * request. HEIF bytes reach a provider whenever transcoding was unavailable
83
- * (`convertImageToJpeg` is backed by macOS `sips`), so a Linux-hosted assistant
84
- * on Gemini depends on them passing through.
82
+ * request. HEIF bytes reach a provider whenever transcoding cannot decode the
83
+ * input, so Gemini depends on them passing through.
85
84
  */
86
85
  export interface MediaResolveOptions {
87
86
  acceptsHeif?: boolean;