@vellumai/assistant 0.11.6 → 0.11.7-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 (447) 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/environments/package.json +2 -1
  21. package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
  22. package/node_modules/@vellumai/environments/src/shell.ts +65 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  24. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  25. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  26. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  27. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
  28. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
  29. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
  30. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
  31. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
  32. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
  33. package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
  34. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
  35. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
  36. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
  37. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
  38. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
  39. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
  40. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
  41. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  42. package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  43. package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  44. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  45. package/openapi.yaml +419 -55
  46. package/package.json +4 -1
  47. package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
  48. package/src/__tests__/approval-card-edit.test.ts +153 -0
  49. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  50. package/src/__tests__/available-skills.test.ts +54 -0
  51. package/src/__tests__/avatar-identity-sync.test.ts +5 -1
  52. package/src/__tests__/catalog-install-normalize.test.ts +9 -1
  53. package/src/__tests__/channel-policy.test.ts +1 -3
  54. package/src/__tests__/channel-readiness-discord.test.ts +9 -70
  55. package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
  56. package/src/__tests__/config-loader-backfill.test.ts +69 -0
  57. package/src/__tests__/config-schema.test.ts +2 -3
  58. package/src/__tests__/conversation-agent-loop.test.ts +238 -0
  59. package/src/__tests__/conversation-attachments.test.ts +0 -1
  60. package/src/__tests__/conversation-error.test.ts +44 -0
  61. package/src/__tests__/conversation-evictor.test.ts +19 -0
  62. package/src/__tests__/conversation-fork-referential.test.ts +73 -63
  63. package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
  64. package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
  65. package/src/__tests__/conversation-slash.test.ts +9 -0
  66. package/src/__tests__/conversation-title-service.test.ts +97 -0
  67. package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
  68. package/src/__tests__/credential-broker-server-use.test.ts +0 -218
  69. package/src/__tests__/credential-broker.test.ts +43 -420
  70. package/src/__tests__/credential-routes.test.ts +17 -1
  71. package/src/__tests__/credential-security-invariants.test.ts +8 -9
  72. package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
  73. package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
  74. package/src/__tests__/discord-channel-config.test.ts +237 -0
  75. package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
  76. package/src/__tests__/fallback-breaker.test.ts +1335 -0
  77. package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
  78. package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
  79. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
  80. package/src/__tests__/image-conversion.test.ts +38 -4
  81. package/src/__tests__/install-skill-routing.test.ts +25 -0
  82. package/src/__tests__/llm-resolver.test.ts +74 -0
  83. package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
  84. package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
  85. package/src/__tests__/managed-profile-guard.test.ts +28 -0
  86. package/src/__tests__/managed-speech-defaults.test.ts +196 -1
  87. package/src/__tests__/mcp-cli.test.ts +23 -1
  88. package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
  89. package/src/__tests__/media-stream-stt-session.test.ts +2 -0
  90. package/src/__tests__/notification-decision-fallback.test.ts +106 -0
  91. package/src/__tests__/notification-discord-adapter.test.ts +162 -0
  92. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
  93. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  94. package/src/__tests__/platform.test.ts +19 -1
  95. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  96. package/src/__tests__/provider-usage-tracking.test.ts +56 -0
  97. package/src/__tests__/require-fresh-approval.test.ts +5 -2
  98. package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
  99. package/src/__tests__/schedule-retry.test.ts +183 -4
  100. package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
  101. package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
  102. package/src/__tests__/skill-load-tool.test.ts +34 -0
  103. package/src/__tests__/skill-tool-factory.test.ts +6 -1
  104. package/src/__tests__/skill-tool-manifest.test.ts +20 -0
  105. package/src/__tests__/skills.test.ts +25 -0
  106. package/src/__tests__/subagent-manager-notify.test.ts +38 -0
  107. package/src/__tests__/terminal-tools.test.ts +2 -0
  108. package/src/__tests__/tool-approval-handler.test.ts +146 -2
  109. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
  110. package/src/__tests__/tool-audit.test.ts +111 -40
  111. package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
  112. package/src/__tests__/ui-shape-teaching.test.ts +1 -1
  113. package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
  114. package/src/__tests__/voice-config-update.test.ts +43 -0
  115. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
  116. package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
  117. package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
  118. package/src/agent/loop.ts +31 -2
  119. package/src/approvals/guardian-card-withdrawal.ts +22 -0
  120. package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
  121. package/src/avatar/avatar-manifest.ts +48 -100
  122. package/src/avatar/avatar-store.ts +7 -7
  123. package/src/avatar/ensure-raster.ts +146 -0
  124. package/src/avatar/resvg-lazy.test.ts +0 -1
  125. package/src/avatar/traits-png-sync.ts +9 -10
  126. package/src/background-wake/wake-intent-hooks.test.ts +0 -1
  127. package/src/backup/__tests__/paths.test.ts +6 -2
  128. package/src/backup/paths.ts +6 -4
  129. package/src/bundler/app-compiler.ts +2 -2
  130. package/src/bundler/compiler-tools.test.ts +36 -0
  131. package/src/bundler/compiler-tools.ts +54 -18
  132. package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +20 -0
  134. package/src/calls/media-stream-stt-session.ts +2 -1
  135. package/src/calls/telephony-synthesis-language.ts +13 -5
  136. package/src/calls/voice-session-bridge.ts +6 -4
  137. package/src/channels/config.ts +23 -12
  138. package/src/cli/AGENTS.md +3 -1
  139. package/src/cli/commands/backup.help.ts +10 -13
  140. package/src/cli/commands/contacts.help.ts +16 -4
  141. package/src/cli/commands/contacts.ts +5 -1
  142. package/src/cli/commands/credentials.help.ts +1 -1
  143. package/src/cli/commands/inference-providers.ts +18 -53
  144. package/src/cli/commands/mcp.help.ts +6 -5
  145. package/src/cli/commands/mcp.ts +1 -1
  146. package/src/cli/commands/memory/memory-retrospective.ts +1 -0
  147. package/src/cli/commands/stt.help.ts +2 -2
  148. package/src/config/__tests__/backup-schema.test.ts +1 -1
  149. package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
  150. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
  151. package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
  152. package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
  153. package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
  154. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
  155. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  156. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  157. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
  158. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
  159. package/src/config/bundled-skills/settings/TOOLS.json +11 -5
  160. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
  161. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
  162. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
  163. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
  164. package/src/config/default-profile-catalog.ts +195 -10
  165. package/src/config/default-profile-names.ts +41 -0
  166. package/src/config/feature-flag-registry.json +18 -2
  167. package/src/config/inference-profile-validation.ts +11 -2
  168. package/src/config/llm-resolver.ts +25 -0
  169. package/src/config/loader.ts +3 -0
  170. package/src/config/managed-speech-defaults.ts +160 -10
  171. package/src/config/schema.ts +2 -0
  172. package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
  173. package/src/config/schemas/__tests__/stt.test.ts +94 -0
  174. package/src/config/schemas/backup.ts +1 -1
  175. package/src/config/schemas/channels.ts +32 -0
  176. package/src/config/schemas/live-voice.ts +13 -4
  177. package/src/config/schemas/llm.ts +259 -11
  178. package/src/config/schemas/mcp.ts +10 -2
  179. package/src/config/schemas/memory-retrospective.ts +1 -19
  180. package/src/config/schemas/services.ts +24 -0
  181. package/src/config/schemas/stt.ts +128 -5
  182. package/src/config/seed-inference-profiles.ts +32 -3
  183. package/src/config/skill-state.ts +5 -0
  184. package/src/config/skills.ts +21 -0
  185. package/src/contacts/notify-contacts-changed.ts +5 -2
  186. package/src/context/compactor.ts +74 -1
  187. package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
  188. package/src/daemon/config-watcher.ts +2 -1
  189. package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
  190. package/src/daemon/conversation-agent-loop.ts +86 -10
  191. package/src/daemon/conversation-error.ts +25 -0
  192. package/src/daemon/conversation-evictor.ts +2 -4
  193. package/src/daemon/conversation-runtime-assembly.ts +19 -7
  194. package/src/daemon/conversation-skill-tools.ts +5 -1
  195. package/src/daemon/conversation-slash.ts +2 -2
  196. package/src/daemon/conversation-store.ts +8 -1
  197. package/src/daemon/conversation-tool-setup.ts +37 -0
  198. package/src/daemon/conversation.ts +0 -4
  199. package/src/daemon/handlers/channel-config-result.ts +30 -0
  200. package/src/daemon/handlers/config-discord-channel.ts +301 -0
  201. package/src/daemon/handlers/config-ingress.ts +9 -0
  202. package/src/daemon/handlers/config-slack-channel.ts +13 -15
  203. package/src/daemon/handlers/config-telegram.ts +2 -5
  204. package/src/daemon/handlers/conversations.ts +0 -2
  205. package/src/daemon/handlers/skills.ts +25 -7
  206. package/src/daemon/lifecycle.ts +3 -1
  207. package/src/daemon/message-types/messages.ts +0 -2
  208. package/src/daemon/tool-setup-types.ts +5 -3
  209. package/src/daemon/trust-context-types.test.ts +33 -0
  210. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  211. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
  212. package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
  213. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
  214. package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
  215. package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
  216. package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
  217. package/src/live-voice/__tests__/protocol.test.ts +224 -0
  218. package/src/live-voice/live-voice-credential-preflight.ts +5 -1
  219. package/src/live-voice/live-voice-session-manager.ts +39 -2
  220. package/src/live-voice/live-voice-session.ts +213 -8
  221. package/src/live-voice/protocol.ts +187 -1
  222. package/src/messaging/providers/discord/api.ts +2 -1
  223. package/src/messaging/providers/discord/withdraw.ts +62 -0
  224. package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
  225. package/src/monitoring/__tests__/process-memory.test.ts +34 -1
  226. package/src/monitoring/db-integrity-sample.ts +1 -0
  227. package/src/monitoring/file-descriptors.ts +39 -6
  228. package/src/monitoring/process-memory.ts +37 -5
  229. package/src/monitoring/resource-sampler.ts +25 -8
  230. package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
  231. package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
  232. package/src/notifications/__tests__/connected-channels.test.ts +12 -0
  233. package/src/notifications/__tests__/copy-composer.test.ts +83 -0
  234. package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
  235. package/src/notifications/activity-failed-dedupe.ts +95 -0
  236. package/src/notifications/adapters/discord.ts +132 -0
  237. package/src/notifications/adapters/shared.ts +16 -0
  238. package/src/notifications/adapters/slack.ts +6 -1
  239. package/src/notifications/adapters/telegram.ts +5 -8
  240. package/src/notifications/approval-card-data.ts +94 -1
  241. package/src/notifications/background-failure-signal.ts +116 -0
  242. package/src/notifications/broadcaster.ts +3 -9
  243. package/src/notifications/copy-composer.ts +41 -8
  244. package/src/notifications/decision-engine.ts +55 -0
  245. package/src/notifications/destination-resolver.ts +45 -5
  246. package/src/notifications/emit-signal.ts +19 -5
  247. package/src/notifications/guardian-question-mode.ts +57 -0
  248. package/src/notifications/types.ts +2 -8
  249. package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
  250. package/src/oauth/seed-providers.ts +261 -0
  251. package/src/permissions/checker.test.ts +21 -0
  252. package/src/permissions/checker.ts +6 -0
  253. package/src/permissions/gateway-threshold-reader.test.ts +184 -0
  254. package/src/permissions/gateway-threshold-reader.ts +144 -10
  255. package/src/permissions/prompter.ts +0 -6
  256. package/src/permissions/types.ts +5 -4
  257. package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
  258. package/src/persistence/bulk-write-gate.ts +9 -10
  259. package/src/persistence/conversation-crud.ts +85 -133
  260. package/src/persistence/conversation-title-service.ts +43 -29
  261. package/src/persistence/embeddings/embedding-local.ts +14 -85
  262. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
  263. package/src/persistence/lifecycle-events-store.test.ts +50 -0
  264. package/src/persistence/lifecycle-events-store.ts +75 -5
  265. package/src/platform/platform-patch-queue.test.ts +368 -0
  266. package/src/platform/platform-patch-queue.ts +190 -0
  267. package/src/platform/sync-avatar.test.ts +527 -0
  268. package/src/platform/sync-avatar.ts +209 -0
  269. package/src/platform/sync-identity.test.ts +75 -0
  270. package/src/platform/sync-identity.ts +17 -110
  271. package/src/plugin-api/model-profiles.test.ts +2 -2
  272. package/src/plugin-api/model-profiles.ts +7 -7
  273. package/src/plugin-api/transcription-session.ts +1 -1
  274. package/src/plugins/defaults/compaction/window-manager.ts +4 -0
  275. package/src/plugins/defaults/image-recovery/recover.ts +2 -2
  276. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
  277. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
  278. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
  279. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
  280. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
  281. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
  282. package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
  283. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
  284. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
  285. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
  286. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
  287. package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
  288. package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
  289. package/src/plugins/defaults/memory/worker.ts +7 -0
  290. package/src/providers/__tests__/retry-callsite.test.ts +55 -0
  291. package/src/providers/connection-resolution.ts +2 -17
  292. package/src/providers/fallback-breaker.ts +389 -0
  293. package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
  294. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
  295. package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
  296. package/src/providers/inference/adapter-factory.ts +231 -3
  297. package/src/providers/inference/auth.ts +1 -0
  298. package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
  299. package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
  300. package/src/providers/inference/resolve-auth.ts +1 -1
  301. package/src/providers/media-resolve.ts +2 -3
  302. package/src/providers/model-catalog.ts +75 -0
  303. package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
  304. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
  305. package/src/providers/openai/api-error-normalization.ts +13 -0
  306. package/src/providers/openai/chat-completions-provider.ts +84 -0
  307. package/src/providers/opencode/client.test.ts +150 -0
  308. package/src/providers/opencode/client.ts +61 -0
  309. package/src/providers/provider-resolvability.ts +20 -0
  310. package/src/providers/retry.ts +956 -68
  311. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
  312. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
  313. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
  314. package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
  315. package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
  316. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
  317. package/src/providers/speech-to-text/provider-catalog.ts +191 -3
  318. package/src/providers/speech-to-text/resolve.ts +132 -24
  319. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
  320. package/src/providers/types.ts +39 -0
  321. package/src/providers/usage-tracking.ts +12 -1
  322. package/src/runtime/AGENTS.md +1 -2
  323. package/src/runtime/__tests__/agent-wake.test.ts +105 -3
  324. package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
  325. package/src/runtime/agent-wake.ts +44 -5
  326. package/src/runtime/background-job-runner.ts +68 -50
  327. package/src/runtime/channel-readiness-service.ts +0 -39
  328. package/src/runtime/http-server.ts +12 -4
  329. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
  330. package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
  331. package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
  332. package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
  333. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
  334. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
  335. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
  336. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
  337. package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
  338. package/src/runtime/routes/approval-routes.ts +0 -12
  339. package/src/runtime/routes/avatar-routes.ts +17 -30
  340. package/src/runtime/routes/backup-routes.ts +2 -2
  341. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
  342. package/src/runtime/routes/contact-prompt-routes.ts +65 -6
  343. package/src/runtime/routes/contact-routes.ts +20 -15
  344. package/src/runtime/routes/conversation-query-routes.ts +98 -10
  345. package/src/runtime/routes/conversation-routes.ts +2 -2
  346. package/src/runtime/routes/credential-routes.ts +2 -1
  347. package/src/runtime/routes/guardian-approval-interception.ts +12 -9
  348. package/src/runtime/routes/inbound-message-handler.ts +21 -9
  349. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
  350. package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
  351. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
  352. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  353. package/src/runtime/routes/index.ts +2 -0
  354. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  355. package/src/runtime/routes/inference-profiles-routes.ts +12 -7
  356. package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
  357. package/src/runtime/routes/inference-send-routes.ts +2 -2
  358. package/src/runtime/routes/ingress-status-routes.ts +2 -2
  359. package/src/runtime/routes/integrations/discord.ts +119 -0
  360. package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
  361. package/src/runtime/routes/mcp-auth-routes.ts +12 -6
  362. package/src/runtime/routes/oauth-pending-flows.ts +136 -0
  363. package/src/runtime/routes/secret-routes.ts +7 -0
  364. package/src/runtime/routes/stt-routes.ts +38 -6
  365. package/src/runtime/routes/wake-conversation-routes.ts +8 -1
  366. package/src/runtime/routes/watch-routes.ts +9 -5
  367. package/src/runtime/sync/resource-sync-events.ts +2 -0
  368. package/src/runtime/trust-verdict-consumer.ts +3 -3
  369. package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
  370. package/src/schedule/retry-policy.ts +8 -8
  371. package/src/schedule/run-script.ts +14 -23
  372. package/src/schedule/schedule-recovery.ts +0 -1
  373. package/src/schedule/scheduler.ts +89 -51
  374. package/src/schedule/worker.ts +57 -0
  375. package/src/security/__tests__/openai-device-auth.test.ts +355 -0
  376. package/src/security/oauth2.ts +7 -0
  377. package/src/security/openai-device-auth.ts +364 -0
  378. package/src/skills/available-skills.ts +10 -1
  379. package/src/skills/catalog-install.ts +19 -1
  380. package/src/skills/inline-command-runner.ts +6 -2
  381. package/src/skills/platform-compatibility.test.ts +49 -0
  382. package/src/skills/platform-compatibility.ts +65 -0
  383. package/src/skills/tool-manifest.ts +20 -0
  384. package/src/stt/__tests__/roles.test.ts +152 -0
  385. package/src/stt/daemon-batch-transcriber.ts +4 -1
  386. package/src/stt/roles.ts +180 -0
  387. package/src/stt/stt-stream-session.ts +3 -2
  388. package/src/stt/types.ts +33 -3
  389. package/src/subagent/manager.ts +12 -0
  390. package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
  391. package/src/telemetry/live-voice-funnel.ts +12 -2
  392. package/src/telemetry/telemetry-wire.generated.ts +6 -0
  393. package/src/telemetry/tool-audit.ts +83 -30
  394. package/src/telemetry/tool-executed-events-store.test.ts +0 -1
  395. package/src/telemetry/turn-outcome.ts +75 -4
  396. package/src/tools/client-os.ts +115 -0
  397. package/src/tools/computer-use/definitions.ts +11 -7
  398. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
  399. package/src/tools/credentials/broker-types.ts +0 -71
  400. package/src/tools/credentials/broker.ts +6 -201
  401. package/src/tools/executor.ts +2 -8
  402. package/src/tools/host-shell.test.ts +75 -0
  403. package/src/tools/host-shell.ts +24 -0
  404. package/src/tools/host-terminal/host-shell.ts +15 -44
  405. package/src/tools/mcp/mcp-tool-factory.ts +44 -9
  406. package/src/tools/permission-checker.ts +73 -21
  407. package/src/tools/policy-context.ts +1 -0
  408. package/src/tools/shared/filesystem/image-read.ts +1 -1
  409. package/src/tools/skills/find-similar-skills.ts +10 -2
  410. package/src/tools/skills/load.ts +41 -6
  411. package/src/tools/skills/sandbox-runner.ts +8 -16
  412. package/src/tools/skills/skill-tool-factory.ts +1 -0
  413. package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
  414. package/src/tools/terminal/safe-env.ts +39 -9
  415. package/src/tools/terminal/shell.ts +12 -31
  416. package/src/tools/tool-approval-handler.ts +64 -10
  417. package/src/tools/tool-types.ts +10 -0
  418. package/src/tools/types.ts +19 -3
  419. package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
  420. package/src/tools/workflows/manage-workflows.ts +2 -2
  421. package/src/tools/workflows/run-workflow.test.ts +2 -1
  422. package/src/util/__tests__/file-use.test.ts +61 -0
  423. package/src/util/__tests__/process-table.test.ts +66 -0
  424. package/src/util/__tests__/process-tree.test.ts +46 -0
  425. package/src/util/errors.ts +1 -0
  426. package/src/util/file-use.ts +91 -0
  427. package/src/util/host-process.test.ts +17 -0
  428. package/src/util/host-process.ts +75 -0
  429. package/src/util/image-conversion.ts +39 -7
  430. package/src/util/platform.ts +32 -13
  431. package/src/util/process-table.ts +249 -0
  432. package/src/util/process-tree.ts +85 -129
  433. package/src/util/provider-error-patterns.ts +20 -0
  434. package/src/util/spawn.ts +9 -2
  435. package/src/workspace/git-service.ts +4 -14
  436. package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
  437. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
  438. package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
  439. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
  440. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
  441. package/src/workspace/migrations/registry.ts +8 -0
  442. package/src/__tests__/permission-types.test.ts +0 -17
  443. package/src/channels/gateway-discord-admission.ts +0 -32
  444. package/src/persistence/fork-message-copy.ts +0 -239
  445. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
  446. package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
  447. package/src/security/oauth2-device-code.ts +0 -312
@@ -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,20 @@ 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 {
52
+ getManagedUpstream,
53
+ isVellumManagedConnection,
54
+ MANAGED_ROUTABLE_PROVIDERS,
55
+ VELLUM_MANAGED_PROVIDER,
56
+ } from "../vellum-model-routing.js";
39
57
  import { VercelAIGatewayProvider } from "../vercel-ai-gateway/client.js";
40
58
  import type { ResolvedAuth } from "./auth.js";
41
59
  import type { ProviderConnection } from "./auth.js";
@@ -58,6 +76,15 @@ export interface AdapterCreateOpts {
58
76
 
59
77
  type AdapterFactory = (opts: AdapterCreateOpts) => Provider;
60
78
 
79
+ const log = getLogger("adapter-factory");
80
+
81
+ /** The backup route `RetryProvider` escalates to; see {@link makeFallbackRouteResolver}. */
82
+ interface FallbackRoute {
83
+ provider: Provider;
84
+ overrideProfile: string;
85
+ forwardUsageAttributionHeaders: boolean;
86
+ }
87
+
61
88
  /**
62
89
  * Catalog-keyed factory table. Each entry takes a unified
63
90
  * `AdapterCreateOpts` and constructs the underlying provider client. The
@@ -138,6 +165,11 @@ const ADAPTER_FACTORIES: Record<string, AdapterFactory> = {
138
165
  assistantReasoningField: "reasoning_content",
139
166
  ...(baseURL ? { baseURL } : {}),
140
167
  }),
168
+ opencode: ({ apiKey, model, streamTimeoutMs, baseURL }) =>
169
+ new OpenCodeProvider(apiKey, model, {
170
+ streamTimeoutMs,
171
+ ...(baseURL ? { baseURL } : {}),
172
+ }),
141
173
  // Keyless openai-compatible endpoints (e.g. LM Studio) ignore the key; the
142
174
  // placeholder satisfies the OpenAI SDK, which requires a non-empty key.
143
175
  "openai-compatible": ({ apiKey, model, streamTimeoutMs, baseURL }) =>
@@ -275,6 +307,195 @@ export function createAdapterFromConnection(
275
307
  };
276
308
  }
277
309
 
310
+ /**
311
+ * Resolve the backup-profile route for a request whose primary route just
312
+ * failed with an outage-shaped error, so `RetryProvider` can serve it on a
313
+ * different provider instead of surfacing the outage.
314
+ *
315
+ * The winning profile of the FAILED call is re-resolved from the same
316
+ * inputs `normalizeSendMessageOptions` uses (call site plus the request's
317
+ * own override/seed fields), and its `fallbackProfile` pointer names the
318
+ * backup. The upstream comes from the backup's resolved provider; only the
319
+ * `vellum` routing identity carries no upstream of its own, and there the
320
+ * backup's model determines it.
321
+ *
322
+ * Returns a RAW adapter, exactly like `makeCredentialRefresher` above:
323
+ * `RetryProvider` hands the backup route options it has ALREADY normalized
324
+ * (call site consumed, wire params resolved from the backup profile, usage
325
+ * attribution headers stamped). A second `RetryProvider` in between would
326
+ * re-normalize that call-site-less config and strip those headers, leaving
327
+ * degraded traffic unattributed on the platform's billing events.
328
+ *
329
+ * Never throws: a broken backup lookup must surface the original provider
330
+ * error, not a new one.
331
+ */
332
+ function makeFallbackRouteResolver(): (
333
+ failedOptions: SendMessageOptions | undefined,
334
+ ) => Promise<FallbackRoute | null> {
335
+ return async (failedOptions): Promise<FallbackRoute | null> => {
336
+ const failedConfig = failedOptions?.config;
337
+ const callSite = failedConfig?.callSite;
338
+ if (callSite === undefined) {
339
+ return null;
340
+ }
341
+ try {
342
+ const { llm } = getConfig();
343
+ const winner = selectWinningProfile(callSite, llm, {
344
+ overrideProfile: failedConfig?.overrideProfile,
345
+ forceOverrideProfile: failedConfig?.forceOverrideProfile,
346
+ selectionSeed: failedConfig?.selectionSeed,
347
+ isResolvableProvider: dispatchProviderResolvable,
348
+ });
349
+ const primaryProfile =
350
+ winner.profileName ??
351
+ (winner.source === "default" ? "balanced" : undefined);
352
+ if (
353
+ primaryProfile === undefined ||
354
+ !isDefaultProfileKey(primaryProfile)
355
+ ) {
356
+ return null;
357
+ }
358
+ const expectedFallback = FALLBACK_PROFILE_BY_KEY[primaryProfile];
359
+ if (
360
+ winner.entry?.source !== "managed" ||
361
+ winner.entry.fallbackProfile !== expectedFallback
362
+ ) {
363
+ return null;
364
+ }
365
+ const overrideProfile = expectedFallback;
366
+ // Resolved the provider-aware way the runtime resolver resolves every
367
+ // profile name: the backups are companions of the managed column and
368
+ // must not materialize under a BYOK or ChatGPT default provider.
369
+ const backup = resolveDefaultProfileForProvider(
370
+ llm.profiles,
371
+ overrideProfile,
372
+ llm.defaultProvider ?? null,
373
+ );
374
+ if (backup?.model == null) {
375
+ log.warn(
376
+ { connectionName: connection.name, overrideProfile },
377
+ "Backup profile does not resolve on this default provider; keeping the original error",
378
+ );
379
+ return null;
380
+ }
381
+ const { config: resolvedBackup, profileName: resolvedBackupProfile } =
382
+ resolveCallSiteConfigWithProfile(callSite, llm, {
383
+ overrideProfile,
384
+ forceOverrideProfile: true,
385
+ selectionSeed: failedConfig?.selectionSeed,
386
+ isResolvableProvider: dispatchProviderResolvable,
387
+ });
388
+ if (resolvedBackupProfile !== overrideProfile) {
389
+ log.warn(
390
+ {
391
+ connectionName: connection.name,
392
+ overrideProfile,
393
+ selectedProfile: resolvedBackupProfile,
394
+ },
395
+ "Backup profile is not dispatch-resolvable; keeping the original error",
396
+ );
397
+ return null;
398
+ }
399
+ // The model the adapter serves must be the model the fallback send
400
+ // carries, so it comes from the same forced-profile resolution.
401
+ // This callback authenticates the backup with the managed connection
402
+ // it was built for, so it can only serve a backup that routes through
403
+ // the managed column. Refusing any other route keeps managed billing
404
+ // and credentials from being applied to user-owned traffic.
405
+ // Keyed on the connection, not the provider: a call-site fragment that
406
+ // pins a concrete upstream over a managed winner keeps the managed
407
+ // routing and is stamped with this connection's name by the resolver
408
+ // (see `llm-resolver.ts`), so a concrete `provider` here is still
409
+ // managed traffic. Only a profile that names a different connection,
410
+ // or a concrete provider carrying no managed connection at all (a BYOK
411
+ // winner), routes somewhere this callback cannot authenticate.
412
+ const backupConnection = resolvedBackup.provider_connection;
413
+ const routesThroughThisConnection =
414
+ backupConnection === undefined
415
+ ? resolvedBackup.provider === VELLUM_MANAGED_PROVIDER
416
+ : backupConnection === connection.name;
417
+ if (!routesThroughThisConnection) {
418
+ log.warn(
419
+ {
420
+ connectionName: connection.name,
421
+ overrideProfile,
422
+ backupProvider: resolvedBackup.provider,
423
+ backupConnection,
424
+ },
425
+ "Backup profile routes outside the managed connection; keeping the original error",
426
+ );
427
+ return null;
428
+ }
429
+ // The upstream is the resolved provider, the same way normal dispatch
430
+ // picks it for this connection: `connection-resolution.ts` threads the
431
+ // resolved provider through as `providerOverride` for a `vellum`
432
+ // connection and derives the upstream from the model only when the
433
+ // provider is the routing-identity sentinel (`resolveRoutingIdentity`,
434
+ // and the `isVellum && !expectedProvider` branch). Deriving it from the
435
+ // model here regardless would send a provider-pinned call-site fragment
436
+ // to a different upstream than a primary send of the same resolution,
437
+ // and would refuse a managed model the catalog does not list yet even
438
+ // though normal dispatch serves it. A provider the managed proxy cannot
439
+ // front (a non-routable id, or another routing identity such as
440
+ // `chatgpt`) is refused rather than routed.
441
+ const declaredProvider = resolvedBackup.provider;
442
+ const upstream =
443
+ declaredProvider === VELLUM_MANAGED_PROVIDER
444
+ ? getManagedUpstream(resolvedBackup.model)
445
+ : MANAGED_ROUTABLE_PROVIDERS.has(declaredProvider)
446
+ ? declaredProvider
447
+ : null;
448
+ if (upstream === null) {
449
+ log.warn(
450
+ {
451
+ connectionName: connection.name,
452
+ overrideProfile,
453
+ backupProvider: declaredProvider,
454
+ backupModel: resolvedBackup.model,
455
+ },
456
+ "Backup profile does not resolve to a managed upstream; keeping the original error",
457
+ );
458
+ return null;
459
+ }
460
+ // Re-resolve auth for the BACKUP upstream: the managed proxy's base URL
461
+ // is per-upstream, so the primary's resolved auth cannot be reused.
462
+ const backupAuth = await resolveAuth(
463
+ effectiveConnectionAuth(connection),
464
+ upstream,
465
+ { baseUrl: connection.baseUrl },
466
+ );
467
+ if (!backupAuth.ok) {
468
+ return null;
469
+ }
470
+ const backupAdapter = buildConnectionAdapter(
471
+ connection,
472
+ backupAuth.resolved,
473
+ // `useNativeWebSearch` carries over from the primary: it is a
474
+ // property of this request's web-search config, and every managed
475
+ // backup pin either supports native search or ignores the flag.
476
+ { ...opts, model: resolvedBackup.model, provider: upstream },
477
+ );
478
+ if (backupAdapter === null) {
479
+ return null;
480
+ }
481
+ return {
482
+ provider: backupAdapter,
483
+ overrideProfile,
484
+ // Always true: this callback is wired only on the managed proxy, so
485
+ // the backup route runs through it too and its `X-Vellum-*` billing
486
+ // metadata reaches our own backend, never a third party.
487
+ forwardUsageAttributionHeaders: true,
488
+ };
489
+ } catch (err) {
490
+ log.warn(
491
+ { err, connectionName: connection.name, callSite },
492
+ "Failed to resolve a backup profile route; keeping the original error",
493
+ );
494
+ return null;
495
+ }
496
+ };
497
+ }
498
+
278
499
  // Usage-attribution headers (`X-Vellum-*`) are only meaningful when the
279
500
  // request is routed through the Vellum-managed proxy. They carry billing
280
501
  // metadata for our own backend. Forwarding them to a third-party endpoint
@@ -295,8 +516,15 @@ export function createAdapterFromConnection(
295
516
  ? "no-auth"
296
517
  : undefined,
297
518
  connectionName: connection.name,
519
+ // Both escalations are managed-route only. A BYOK, oauth-subscription,
520
+ // or no-auth connection must never fall back: the install may hold no
521
+ // credential for the backup profile's provider, and only the managed
522
+ // column carries `fallbackProfile` pointers in the first place.
298
523
  ...(isManagedProxy
299
- ? { refreshCredentialProvider: makeCredentialRefresher() }
524
+ ? {
525
+ refreshCredentialProvider: makeCredentialRefresher(),
526
+ resolveFallbackRoute: makeFallbackRouteResolver(),
527
+ }
300
528
  : {}),
301
529
  }),
302
530
  );
@@ -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;
@@ -1374,6 +1374,32 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
1374
1374
  // usage never includes cached tokens. `supportsCaching` therefore stays
1375
1375
  // false; the `cacheReadPer1mTokens` rates below only apply if OpenRouter
1376
1376
  // starts reporting cached tokens in usage.
1377
+ {
1378
+ id: "x-ai/grok-4.6",
1379
+ displayName: "Grok 4.6",
1380
+ contextWindowTokens: 500000,
1381
+ // xAI publishes no completion cap; 30K is the tracker-reported
1382
+ // single-response limit used for sibling Grok 4.x catalog rows.
1383
+ maxOutputTokens: 30000,
1384
+ supportsThinking: true,
1385
+ supportsCaching: false,
1386
+ supportsVision: true,
1387
+ supportsToolUse: true,
1388
+ longContextPricingThresholdTokens: 200000,
1389
+ pricing: {
1390
+ inputPer1mTokens: 2,
1391
+ outputPer1mTokens: 6,
1392
+ cacheReadPer1mTokens: 0.5,
1393
+ tiers: [
1394
+ {
1395
+ inputTokenThreshold: 200000,
1396
+ inputPer1mTokens: 4,
1397
+ outputPer1mTokens: 12,
1398
+ cacheReadPer1mTokens: 1,
1399
+ },
1400
+ ],
1401
+ },
1402
+ },
1377
1403
  {
1378
1404
  id: "x-ai/grok-4.5",
1379
1405
  displayName: "Grok 4.5",
@@ -1697,6 +1723,36 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
1697
1723
  pricing: { inputPer1mTokens: 0.2, outputPer1mTokens: 1.1 },
1698
1724
  },
1699
1725
  // Z.ai
1726
+ {
1727
+ id: "z-ai/glm-5.3",
1728
+ displayName: "GLM-5.3",
1729
+ contextWindowTokens: 1048576,
1730
+ maxOutputTokens: 131072,
1731
+ supportsThinking: true,
1732
+ supportsCaching: true,
1733
+ supportsVision: false,
1734
+ supportsToolUse: true,
1735
+ pricing: {
1736
+ inputPer1mTokens: 1.4,
1737
+ outputPer1mTokens: 4.4,
1738
+ cacheReadPer1mTokens: 0.26,
1739
+ },
1740
+ },
1741
+ {
1742
+ id: "z-ai/glm-5.3-flash",
1743
+ displayName: "GLM-5.3 Flash",
1744
+ contextWindowTokens: 1310720,
1745
+ maxOutputTokens: 131072,
1746
+ supportsThinking: true,
1747
+ supportsCaching: true,
1748
+ supportsVision: true,
1749
+ supportsToolUse: true,
1750
+ pricing: {
1751
+ inputPer1mTokens: 0.075,
1752
+ outputPer1mTokens: 0.25,
1753
+ cacheReadPer1mTokens: 0.015,
1754
+ },
1755
+ },
1700
1756
  {
1701
1757
  id: "z-ai/glm-5.2",
1702
1758
  displayName: "GLM-5.2",
@@ -2038,6 +2094,25 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
2038
2094
  models: [],
2039
2095
  defaultModel: "",
2040
2096
  },
2097
+ {
2098
+ id: "opencode",
2099
+ displayName: "OpenCode",
2100
+ subtitle:
2101
+ "OpenCode Zen and OpenCode Go. Models come from the endpoint, not a fixed catalog.",
2102
+ setupMode: "api-key",
2103
+ setupHint:
2104
+ "Enter your OpenCode API key. Leave the base URL empty for Zen, or set it to the Go endpoint.",
2105
+ envVar: "OPENCODE_API_KEY",
2106
+ credentialsGuide: {
2107
+ description:
2108
+ "Create an OpenCode API key, then choose Zen or Go as the endpoint.",
2109
+ url: "https://opencode.ai",
2110
+ linkLabel: "OpenCode",
2111
+ },
2112
+ apiKeyPlaceholder: "sk-...",
2113
+ models: [],
2114
+ defaultModel: "",
2115
+ },
2041
2116
  {
2042
2117
  id: "openai-compatible",
2043
2118
  displayName: "OpenAI-compatible",
@@ -387,6 +387,28 @@ describe("deriveReason", () => {
387
387
  ).toBe("vision_unsupported");
388
388
  });
389
389
 
390
+ test("chat-template failure prose on a 400 → request_shape_unsupported", () => {
391
+ // Together's server-side renderer error for MiniMax M3, verbatim.
392
+ expect(
393
+ deriveReason(
394
+ n({
395
+ message:
396
+ "Failed to apply chat template: invalid operation: object is not callable (in chat:22)",
397
+ }),
398
+ 400,
399
+ ),
400
+ ).toBe("request_shape_unsupported");
401
+ });
402
+
403
+ test("chat-template prose on a 5xx stays server-side, not request_shape_unsupported", () => {
404
+ expect(
405
+ deriveReason(
406
+ n({ message: "Failed to apply chat template: renderer crashed" }),
407
+ 500,
408
+ ),
409
+ ).not.toBe("request_shape_unsupported");
410
+ });
411
+
390
412
  test("402 → insufficient_credits", () => {
391
413
  expect(deriveReason(n(), 402)).toBe("insufficient_credits");
392
414
  });