@vellumai/assistant 0.8.5 → 0.8.6

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 (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -1,6 +1,9 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import { OpenAIChatCompletionsProvider } from "../chat-completions-provider.js";
3
+ import {
4
+ OpenAIChatCompletionsProvider,
5
+ type OpenAIChatCompletionsProviderOptions,
6
+ } from "../chat-completions-provider.js";
4
7
 
5
8
  type ReasoningDetail = {
6
9
  type?: string;
@@ -32,23 +35,35 @@ function makeStream(chunks: MockChunk[]): AsyncIterable<MockChunk> {
32
35
  };
33
36
  }
34
37
 
35
- function stubProvider(chunks: MockChunk[]): {
38
+ function stubProvider(
39
+ chunks: MockChunk[],
40
+ options?: OpenAIChatCompletionsProviderOptions,
41
+ ): {
36
42
  provider: OpenAIChatCompletionsProvider;
37
43
  events: Array<{ type: string; thinking?: string; text?: string }>;
44
+ requests: unknown[];
38
45
  } {
39
- const provider = new OpenAIChatCompletionsProvider("test-key", "test-model");
46
+ const provider = new OpenAIChatCompletionsProvider(
47
+ "test-key",
48
+ "test-model",
49
+ options,
50
+ );
51
+ const requests: unknown[] = [];
40
52
  // Swap the SDK client for a stub whose chat.completions.create returns our
41
53
  // canned async iterable.
42
54
  (provider as unknown as { client: unknown }).client = {
43
55
  chat: {
44
56
  completions: {
45
- create: async () => makeStream(chunks),
57
+ create: async (params: unknown) => {
58
+ requests.push(params);
59
+ return makeStream(chunks);
60
+ },
46
61
  },
47
62
  },
48
63
  };
49
64
  const events: Array<{ type: string; thinking?: string; text?: string }> = [];
50
65
  (provider as unknown as { __events: typeof events }).__events = events;
51
- return { provider, events };
66
+ return { provider, events, requests };
52
67
  }
53
68
 
54
69
  async function runStream(
@@ -232,4 +247,141 @@ describe("OpenAIChatCompletionsProvider reasoning parsing", () => {
232
247
  expect(deltas.map((d) => d.thinking)).toEqual(["it ", "worked", "!"]);
233
248
  expect(thinking).toBe("it worked!");
234
249
  });
250
+
251
+ test("round-trips prior assistant thinking as reasoning_content when field is set", async () => {
252
+ const { provider, requests } = stubProvider(
253
+ [
254
+ {
255
+ choices: [{ delta: { content: "continued" }, finish_reason: "stop" }],
256
+ usage: { prompt_tokens: 4, completion_tokens: 2 },
257
+ },
258
+ ],
259
+ { assistantReasoningField: "reasoning_content" },
260
+ );
261
+
262
+ await provider.sendMessage([
263
+ { role: "user", content: [{ type: "text", text: "first question" }] },
264
+ {
265
+ role: "assistant",
266
+ content: [
267
+ { type: "thinking", thinking: "hidden chain state", signature: "" },
268
+ { type: "text", text: "first answer" },
269
+ ],
270
+ },
271
+ { role: "user", content: [{ type: "text", text: "follow up" }] },
272
+ ]);
273
+
274
+ const params = requests[0] as {
275
+ messages: Array<{
276
+ role: string;
277
+ content: string | null;
278
+ reasoning_content?: string;
279
+ }>;
280
+ };
281
+ const assistantMsg = params.messages.find((m) => m.role === "assistant");
282
+ expect(assistantMsg).toEqual({
283
+ role: "assistant",
284
+ content: "first answer",
285
+ reasoning_content: "hidden chain state",
286
+ });
287
+ });
288
+
289
+ test("uses reasoning field for OpenRouter-style round-trip", async () => {
290
+ const { provider, requests } = stubProvider(
291
+ [
292
+ {
293
+ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
294
+ usage: { prompt_tokens: 2, completion_tokens: 1 },
295
+ },
296
+ ],
297
+ { assistantReasoningField: "reasoning" },
298
+ );
299
+
300
+ await provider.sendMessage([
301
+ {
302
+ role: "assistant",
303
+ content: [
304
+ { type: "thinking", thinking: "visible summary", signature: "" },
305
+ { type: "text", text: "answer" },
306
+ ],
307
+ },
308
+ ]);
309
+
310
+ const params = requests[0] as {
311
+ messages: Array<{
312
+ role: string;
313
+ reasoning?: string;
314
+ reasoning_content?: string;
315
+ }>;
316
+ };
317
+ expect(params.messages[0].reasoning).toBe("visible summary");
318
+ expect(params.messages[0].reasoning_content).toBeUndefined();
319
+ });
320
+
321
+ test("drops thinking blocks when assistantReasoningField is unset", async () => {
322
+ const { provider, requests } = stubProvider([
323
+ {
324
+ choices: [{ delta: { content: "reply" }, finish_reason: "stop" }],
325
+ usage: { prompt_tokens: 2, completion_tokens: 1 },
326
+ },
327
+ ]);
328
+
329
+ await provider.sendMessage([
330
+ {
331
+ role: "assistant",
332
+ content: [
333
+ { type: "thinking", thinking: "should be dropped", signature: "" },
334
+ { type: "text", text: "visible" },
335
+ ],
336
+ },
337
+ ]);
338
+
339
+ const params = requests[0] as {
340
+ messages: Array<{
341
+ role: string;
342
+ content: string | null;
343
+ reasoning?: string;
344
+ reasoning_content?: string;
345
+ }>;
346
+ };
347
+ const assistantMsg = params.messages[0];
348
+ expect(assistantMsg.content).toBe("visible");
349
+ expect(assistantMsg.reasoning).toBeUndefined();
350
+ expect(assistantMsg.reasoning_content).toBeUndefined();
351
+ });
352
+
353
+ test("skips Anthropic-originated thinking blocks (with signatures)", async () => {
354
+ const { provider, requests } = stubProvider(
355
+ [
356
+ {
357
+ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
358
+ usage: { prompt_tokens: 2, completion_tokens: 1 },
359
+ },
360
+ ],
361
+ { assistantReasoningField: "reasoning_content" },
362
+ );
363
+
364
+ await provider.sendMessage([
365
+ {
366
+ role: "assistant",
367
+ content: [
368
+ {
369
+ type: "thinking",
370
+ thinking: "anthropic thinking",
371
+ signature: "sig-abc",
372
+ },
373
+ { type: "thinking", thinking: "deepseek thinking", signature: "" },
374
+ { type: "text", text: "answer" },
375
+ ],
376
+ },
377
+ ]);
378
+
379
+ const params = requests[0] as {
380
+ messages: Array<{
381
+ role: string;
382
+ reasoning_content?: string;
383
+ }>;
384
+ };
385
+ expect(params.messages[0].reasoning_content).toBe("deepseek thinking");
386
+ });
235
387
  });
@@ -54,6 +54,81 @@ export function detectOpenAICompatibleContextOverflow(
54
54
  return extractOverflowTokensFromMessage(message);
55
55
  }
56
56
 
57
+ /**
58
+ * Build the human-readable error string surfaced from an
59
+ * `OpenAI.APIError`. The SDK's `error.message` is typically a one-line
60
+ * summary like `"400 Provider returned error"` — useless when the
61
+ * upstream is OpenRouter (which wraps the real downstream error on
62
+ * `error.error.metadata.raw`) or any provider that returns nested
63
+ * structured details.
64
+ *
65
+ * Returns `{ detail, requestId }` where `detail` includes the JSON body
66
+ * (truncated) and `requestId` is the upstream correlation header when
67
+ * present. Callers fold these into the thrown `ProviderError` so log
68
+ * lines actually identify the failure without re-running the request.
69
+ *
70
+ * Exported for tests.
71
+ */
72
+ export function extractApiErrorDetail(
73
+ error: InstanceType<typeof OpenAI.APIError>,
74
+ ): { detail: string; requestId: string | undefined } {
75
+ const body = (error as { error?: unknown }).error;
76
+ let detail = "";
77
+ if (body !== undefined && body !== null) {
78
+ try {
79
+ const serialized = typeof body === "string" ? body : JSON.stringify(body);
80
+ if (serialized && serialized !== "{}") {
81
+ detail =
82
+ serialized.length > MAX_API_ERROR_DETAIL_CHARS
83
+ ? `${serialized.slice(0, MAX_API_ERROR_DETAIL_CHARS)}…`
84
+ : serialized;
85
+ }
86
+ } catch {
87
+ // Body had a cycle or threw on toJSON — fall through with empty detail.
88
+ }
89
+ }
90
+ const requestId = readHeader(error.headers, [
91
+ "x-request-id",
92
+ "x-openrouter-request-id",
93
+ "openai-request-id",
94
+ "x-amzn-requestid",
95
+ ]);
96
+ return { detail, requestId };
97
+ }
98
+
99
+ /** Cap on the inline-rendered body to keep log lines readable while still
100
+ * surfacing the meaningful upstream payload. Tuned to comfortably hold
101
+ * OpenRouter's `error.metadata.raw` strings, which are typically <1KB. */
102
+ const MAX_API_ERROR_DETAIL_CHARS = 2000;
103
+
104
+ /**
105
+ * Read the first matching header from an SDK error's headers object,
106
+ * tolerating both Map-like (`Headers.get()`) and plain-object shapes.
107
+ * Mirrors the shape-tolerance already in `extractRetryAfterMs`.
108
+ */
109
+ function readHeader(
110
+ headers: unknown,
111
+ names: readonly string[],
112
+ ): string | undefined {
113
+ if (!headers) return undefined;
114
+ const getter =
115
+ typeof (headers as { get?: unknown }).get === "function"
116
+ ? (headers as { get(name: string): string | null }).get.bind(headers)
117
+ : undefined;
118
+ for (const name of names) {
119
+ let value: string | null | undefined;
120
+ if (getter) {
121
+ value = getter(name);
122
+ } else if (typeof headers === "object") {
123
+ const record = headers as Record<string, unknown>;
124
+ const raw = record[name] ?? record[name.toLowerCase()];
125
+ value = typeof raw === "string" ? raw : undefined;
126
+ }
127
+ if (typeof value === "string" && value.length > 0) return value;
128
+ }
129
+ return undefined;
130
+ }
131
+
57
132
  export interface OpenAIChatCompletionsProviderOptions {
58
133
  baseURL?: string;
59
134
  providerName?: string;
@@ -75,6 +150,10 @@ export interface OpenAIChatCompletionsProviderOptions {
75
150
  * blocks. MiniMax and similar providers embed reasoning inside XML-style
76
151
  * tags in the regular content field rather than using `reasoning_content`. */
77
152
  parseThinkTags?: boolean;
153
+ /** Wire field used to replay prior assistant thinking on multi-turn requests.
154
+ * DeepSeek/Fireworks use `"reasoning_content"`; OpenRouter uses `"reasoning"`.
155
+ * When unset, thinking blocks are dropped from outbound assistant messages. */
156
+ assistantReasoningField?: "reasoning" | "reasoning_content";
78
157
  }
79
158
 
80
159
  /** Wire-level reasoning_effort values. The OpenAI SDK type doesn't include
@@ -146,6 +225,10 @@ export class OpenAIChatCompletionsProvider implements Provider {
146
225
  private maxReasoningEffort: "high" | "xhigh" | "max";
147
226
  private requestHeaders: Record<string, string>;
148
227
  private parseThinkTags: boolean;
228
+ private assistantReasoningField:
229
+ | "reasoning"
230
+ | "reasoning_content"
231
+ | undefined;
149
232
 
150
233
  constructor(
151
234
  apiKey: string,
@@ -168,6 +251,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
168
251
  this.maxReasoningEffort = options.maxReasoningEffort ?? "xhigh";
169
252
  this.requestHeaders = options.requestHeaders ?? {};
170
253
  this.parseThinkTags = options.parseThinkTags ?? false;
254
+ this.assistantReasoningField = options.assistantReasoningField;
171
255
  }
172
256
 
173
257
  async sendMessage(
@@ -524,18 +608,18 @@ export class OpenAIChatCompletionsProvider implements Provider {
524
608
  ? signal.reason
525
609
  : undefined;
526
610
  if (error instanceof OpenAI.APIError) {
611
+ const { detail, requestId } = extractApiErrorDetail(error);
612
+ const detailSuffix = detail ? ` ${detail}` : "";
613
+ const requestIdSuffix = requestId ? ` [request_id=${requestId}]` : "";
614
+ const formattedMessage = `${this.providerLabel} API error (${error.status}): ${error.message}${detailSuffix}${requestIdSuffix}`;
527
615
  const overflow = detectOpenAICompatibleContextOverflow(error);
528
616
  if (overflow) {
529
- throw new ContextOverflowError(
530
- `${this.providerLabel} API error (${error.status}): ${error.message}`,
531
- this.name,
532
- {
533
- actualTokens: overflow.actualTokens,
534
- maxTokens: overflow.maxTokens,
535
- statusCode: error.status,
536
- cause: error,
537
- },
538
- );
617
+ throw new ContextOverflowError(formattedMessage, this.name, {
618
+ actualTokens: overflow.actualTokens,
619
+ maxTokens: overflow.maxTokens,
620
+ statusCode: error.status,
621
+ cause: error,
622
+ });
539
623
  }
540
624
  const retryAfterMs = extractRetryAfterMs(error.headers);
541
625
  const errorOptions: {
@@ -546,7 +630,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
546
630
  errorOptions.retryAfterMs = retryAfterMs;
547
631
  if (abortReason) errorOptions.abortReason = abortReason;
548
632
  throw new ProviderError(
549
- `${this.providerLabel} API error (${error.status}): ${error.message}`,
633
+ formattedMessage,
550
634
  this.name,
551
635
  error.status,
552
636
  Object.keys(errorOptions).length > 0 ? errorOptions : undefined,
@@ -661,6 +745,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
661
745
  msg: Message,
662
746
  ): OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam {
663
747
  const textParts: string[] = [];
748
+ const reasoningParts: string[] = [];
664
749
  const toolCalls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[] =
665
750
  [];
666
751
 
@@ -669,6 +754,10 @@ export class OpenAIChatCompletionsProvider implements Provider {
669
754
  case "text":
670
755
  textParts.push(block.text);
671
756
  break;
757
+ case "thinking":
758
+ // Anthropic thinking blocks carry signatures — skip those.
759
+ if (!block.signature) reasoningParts.push(block.thinking);
760
+ break;
672
761
  case "tool_use":
673
762
  toolCalls.push({
674
763
  id: block.id,
@@ -685,7 +774,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
685
774
  case "web_search_tool_result":
686
775
  textParts.push("[Web search results]");
687
776
  break;
688
- // thinking, redacted_thinking, image — not applicable for OpenAI assistant messages
777
+ // redacted_thinking, image — not applicable for OpenAI assistant messages
689
778
  }
690
779
  }
691
780
 
@@ -695,6 +784,15 @@ export class OpenAIChatCompletionsProvider implements Provider {
695
784
  content: textParts.length > 0 ? textParts.join("") : null,
696
785
  };
697
786
 
787
+ if (reasoningParts.length > 0 && this.assistantReasoningField) {
788
+ (
789
+ result as OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam & {
790
+ reasoning?: string;
791
+ reasoning_content?: string;
792
+ }
793
+ )[this.assistantReasoningField] = reasoningParts.join("");
794
+ }
795
+
698
796
  if (toolCalls.length > 0) {
699
797
  result.tool_calls = toolCalls;
700
798
  }
@@ -8,7 +8,9 @@
8
8
  * resolution of an "Any active OpenAI connection" profile.
9
9
  */
10
10
  export const CODEX_SUBSCRIPTION_MODEL_IDS: ReadonlySet<string> = new Set([
11
+ "gpt-5.5",
11
12
  "gpt-5.4",
13
+ "gpt-5.4-mini",
12
14
  "gpt-5.3-codex",
13
15
  ]);
14
16
 
@@ -26,7 +26,7 @@ export interface OpenAIResponsesProviderOptions {
26
26
  streamTimeoutMs?: number;
27
27
  useNativeWebSearch?: boolean;
28
28
  /** When true, target the Codex subscription endpoint and strip fields it
29
- * rejects (`max_output_tokens`, `reasoning`, `text`, `tools`). */
29
+ * rejects (`max_output_tokens`). */
30
30
  codexSubscription?: boolean;
31
31
  }
32
32
 
@@ -74,6 +74,9 @@ interface ResponsesStreamEvent {
74
74
  response?: {
75
75
  model?: string;
76
76
  status?: string;
77
+ incomplete_details?: {
78
+ reason?: "max_output_tokens" | "content_filter";
79
+ } | null;
77
80
  /** Full output items array — preserved as part of rawResponse for the
78
81
  * LLM context normalizer, which uses its presence to detect Responses
79
82
  * API payloads in stored diagnostics. */
@@ -188,24 +191,22 @@ export class OpenAIResponsesProvider implements Provider {
188
191
  params.max_output_tokens = maxTokens;
189
192
  }
190
193
 
191
- if (!this.codexSubscription) {
192
- const reasoningEffort = effort
193
- ? EFFORT_TO_REASONING_EFFORT[effort]
194
- : undefined;
195
- if (reasoningEffort) {
196
- params.reasoning = { effort: reasoningEffort };
197
- }
194
+ const reasoningEffort = effort
195
+ ? EFFORT_TO_REASONING_EFFORT[effort]
196
+ : undefined;
197
+ if (reasoningEffort) {
198
+ params.reasoning = { effort: reasoningEffort };
199
+ }
198
200
 
199
- if (
200
- verbosity &&
201
- VALID_VERBOSITIES.has(verbosity) &&
202
- modelSupportsVerbosity(modelOverride ?? this.model)
203
- ) {
204
- params.text = { verbosity };
205
- }
201
+ if (
202
+ verbosity &&
203
+ VALID_VERBOSITIES.has(verbosity) &&
204
+ modelSupportsVerbosity(modelOverride ?? this.model)
205
+ ) {
206
+ params.text = { verbosity };
206
207
  }
207
208
 
208
- if (tools && tools.length > 0 && !this.codexSubscription) {
209
+ if (tools && tools.length > 0) {
209
210
  if (
210
211
  this.useNativeWebSearch &&
211
212
  tools.some((t) => t.name === "web_search")
@@ -218,9 +219,9 @@ export class OpenAIResponsesProvider implements Provider {
218
219
  parameters: t.input_schema,
219
220
  strict: null,
220
221
  }));
221
- const webSearchTool = {
222
- type: "web_search_preview" as const,
223
- };
222
+ const webSearchTool = this.codexSubscription
223
+ ? { type: "web_search" as const, external_web_access: false }
224
+ : { type: "web_search_preview" as const };
224
225
  params.tools = [...mappedOther, webSearchTool];
225
226
  } else {
226
227
  params.tools = tools.map((t) => ({
@@ -256,20 +257,26 @@ export class OpenAIResponsesProvider implements Provider {
256
257
  let rawFinalResponse: unknown = undefined;
257
258
 
258
259
  try {
259
- // The SDK exposes `client.responses.stream()` cast through
260
- // `unknown` to avoid `any` while the SDK's exported types stabilise.
260
+ // Use `create()` with `stream: true` instead of the higher-level
261
+ // `stream()` helper. The `stream()` helper wraps the response in a
262
+ // `ResponseStream` that runs `maybeParseResponse()` after iteration,
263
+ // which crashes when the Codex subscription endpoint omits `output`
264
+ // from the `response.completed` event payload.
261
265
  const responsesApi = this.client.responses as unknown as {
262
- stream(
266
+ create(
263
267
  p: Record<string, unknown>,
264
- o: { signal: AbortSignal; headers?: Record<string, string> },
265
- ): AsyncIterable<ResponsesStreamEvent>;
268
+ o?: { signal?: AbortSignal; headers?: Record<string, string> },
269
+ ): Promise<AsyncIterable<ResponsesStreamEvent>>;
266
270
  };
267
- const stream = responsesApi.stream(params, {
268
- signal: timeoutSignal,
269
- ...(usageAttributionHeaders
270
- ? { headers: usageAttributionHeaders }
271
- : {}),
272
- });
271
+ const stream = await responsesApi.create(
272
+ { ...params, stream: true },
273
+ {
274
+ signal: timeoutSignal,
275
+ ...(usageAttributionHeaders
276
+ ? { headers: usageAttributionHeaders }
277
+ : {}),
278
+ },
279
+ );
273
280
 
274
281
  for await (const event of stream) {
275
282
  switch (event.type) {
@@ -341,7 +348,8 @@ export class OpenAIResponsesProvider implements Provider {
341
348
  break;
342
349
  }
343
350
 
344
- case "response.completed": {
351
+ case "response.completed":
352
+ case "response.incomplete": {
345
353
  const response = event.response;
346
354
  if (response) {
347
355
  rawFinalResponse = response;
@@ -356,15 +364,22 @@ export class OpenAIResponsesProvider implements Provider {
356
364
  cachedInputTokens =
357
365
  response.usage.input_tokens_details?.cached_tokens ?? 0;
358
366
  }
359
- finishReason = response.status ?? "completed";
367
+ finishReason =
368
+ response.incomplete_details?.reason ??
369
+ response.status ??
370
+ (event.type === "response.incomplete"
371
+ ? "incomplete"
372
+ : "completed");
360
373
  }
361
374
  // Emit server_tool_complete for any web search calls that were started.
362
- for (const toolUseId of webSearchCallIds) {
363
- onEvent?.({
364
- type: "server_tool_complete",
365
- toolUseId,
366
- isError: false,
367
- });
375
+ if (event.type === "response.completed") {
376
+ for (const toolUseId of webSearchCallIds) {
377
+ onEvent?.({
378
+ type: "server_tool_complete",
379
+ toolUseId,
380
+ isError: false,
381
+ });
382
+ }
368
383
  }
369
384
  break;
370
385
  }
@@ -438,21 +453,6 @@ export class OpenAIResponsesProvider implements Provider {
438
453
  ? signal.reason
439
454
  : undefined;
440
455
  if (error instanceof OpenAI.APIError) {
441
- // Temporary diagnostic: log the raw error shape for Codex 400 debugging
442
- if (this.codexSubscription) {
443
- log.warn(
444
- {
445
- status: error.status,
446
- message: error.message,
447
- code: error.code,
448
- type: error.type,
449
- param: error.param,
450
- errorBody: error.error,
451
- headers: Object.fromEntries(error.headers?.entries?.() ?? []),
452
- },
453
- "Codex subscription API error — raw details",
454
- );
455
- }
456
456
  const overflow = detectOpenAICompatibleContextOverflow(error);
457
457
  if (overflow) {
458
458
  throw new ContextOverflowError(
@@ -122,6 +122,7 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
122
122
  providerLabel: "OpenRouter",
123
123
  streamTimeoutMs: options.streamTimeoutMs,
124
124
  requestHeaders: OPENROUTER_APP_ATTRIBUTION_HEADERS,
125
+ assistantReasoningField: "reasoning",
125
126
  });
126
127
  this.openRouterApiKey = apiKey;
127
128
  this.defaultModel = model;
@@ -199,17 +200,21 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
199
200
  ? EFFORT_TO_REASONING_EFFORT[effort]
200
201
  : undefined;
201
202
  const summaryOverride = extractReasoningSummaryOverride(config);
202
- const reasoning: Record<string, unknown> = { enabled: thinkingEnabled };
203
- if (mappedEffort) {
204
- reasoning.effort = clampReasoningEffort(
205
- mappedEffort,
206
- this.resolveMaxReasoningEffort(this.resolveEffectiveModel(options)),
207
- );
208
- }
203
+ // Only send `reasoning` when explicitly enabling thinking. Omitting the
204
+ // field lets OpenRouter use the model's natural default, which avoids 400s
205
+ // from reasoning-only models (e.g. DeepSeek R1) that reject `enabled: false`.
206
+ const extras: Record<string, unknown> = {};
209
207
  if (thinkingEnabled) {
208
+ const reasoning: Record<string, unknown> = { enabled: true };
209
+ if (mappedEffort) {
210
+ reasoning.effort = clampReasoningEffort(
211
+ mappedEffort,
212
+ this.resolveMaxReasoningEffort(this.resolveEffectiveModel(options)),
213
+ );
214
+ }
210
215
  reasoning.summary = summaryOverride ?? "detailed";
216
+ extras.reasoning = reasoning;
211
217
  }
212
- const extras: Record<string, unknown> = { reasoning };
213
218
  const only = extractOnlyList(config);
214
219
  if (only.length > 0) {
215
220
  const existingProvider = (config?.provider ?? {}) as Record<
@@ -9,7 +9,10 @@ import { getConfig } from "../config/loader.js";
9
9
  import type { LLMCallSite } from "../config/schemas/llm.js";
10
10
  import { getDb } from "../memory/db-connection.js";
11
11
  import { getLogger } from "../util/logger.js";
12
- import { isConnectionCompatibleWithModel } from "./connection-model-compat.js";
12
+ import {
13
+ describeSubscriptionModelIncompatibility,
14
+ isConnectionCompatibleWithModel,
15
+ } from "./connection-model-compat.js";
13
16
  import { tryResolveProviderForConnectionName } from "./connection-resolution.js";
14
17
  import { listConnections } from "./inference/connections.js";
15
18
  import { initializeProviders, listProviders } from "./registry.js";
@@ -117,23 +120,29 @@ export async function resolveConfiguredProvider(
117
120
 
118
121
  // Connection-aware path: every dispatch goes through `provider_connection`.
119
122
  // The boot-time backfill ensures every profile has one in production.
120
- // When unset (profile set provider with "Any active" connection, test envs
121
- // that skip backfill, freshly-installed configs not yet backfilled, or
122
- // users who manually cleared the field), try to auto-resolve from the
123
- // provider before falling back to null.
123
+ // When unset (profile set provider without a connection, test envs that
124
+ // skip backfill, freshly-installed configs not yet backfilled, or users
125
+ // who manually cleared the field), try to auto-resolve from the provider
126
+ // before falling back to null.
124
127
  if (!connectionName) {
125
128
  if (inferenceProvider) {
126
129
  try {
127
130
  const candidates = listConnections(getDb(), {
128
131
  provider: inferenceProvider,
129
132
  });
130
- const active = candidates.find(
131
- (c) =>
132
- c.status === "active" &&
133
- isConnectionCompatibleWithModel(c, resolved.model),
133
+ const active = candidates.find((c) =>
134
+ isConnectionCompatibleWithModel(c, resolved.model),
134
135
  );
135
136
  if (active) {
136
137
  connectionName = active.name;
138
+ } else {
139
+ const incompatMsg = describeSubscriptionModelIncompatibility(
140
+ candidates,
141
+ resolved.model,
142
+ );
143
+ if (incompatMsg) {
144
+ log.warn({ callSite, inferenceProvider, model: resolved.model }, incompatMsg);
145
+ }
137
146
  }
138
147
  } catch {
139
148
  // DB not available — fall through to the existing null-return path.