@vellumai/assistant 0.8.2 → 0.8.4

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 (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -0,0 +1,235 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { OpenAIChatCompletionsProvider } from "../chat-completions-provider.js";
4
+
5
+ type ReasoningDetail = {
6
+ type?: string;
7
+ summary?: string | null;
8
+ text?: string | null;
9
+ };
10
+
11
+ type MockChunkDelta = {
12
+ content?: string | null;
13
+ reasoning?: string | null;
14
+ reasoning_content?: string | null;
15
+ reasoning_details?: ReasoningDetail[] | null;
16
+ };
17
+
18
+ type MockChunk = {
19
+ choices: Array<{ delta: MockChunkDelta; finish_reason?: string | null }>;
20
+ model?: string;
21
+ usage?: {
22
+ prompt_tokens: number;
23
+ completion_tokens: number;
24
+ };
25
+ };
26
+
27
+ function makeStream(chunks: MockChunk[]): AsyncIterable<MockChunk> {
28
+ return {
29
+ async *[Symbol.asyncIterator]() {
30
+ for (const c of chunks) yield c;
31
+ },
32
+ };
33
+ }
34
+
35
+ function stubProvider(chunks: MockChunk[]): {
36
+ provider: OpenAIChatCompletionsProvider;
37
+ events: Array<{ type: string; thinking?: string; text?: string }>;
38
+ } {
39
+ const provider = new OpenAIChatCompletionsProvider("test-key", "test-model");
40
+ // Swap the SDK client for a stub whose chat.completions.create returns our
41
+ // canned async iterable.
42
+ (provider as unknown as { client: unknown }).client = {
43
+ chat: {
44
+ completions: {
45
+ create: async () => makeStream(chunks),
46
+ },
47
+ },
48
+ };
49
+ const events: Array<{ type: string; thinking?: string; text?: string }> = [];
50
+ (provider as unknown as { __events: typeof events }).__events = events;
51
+ return { provider, events };
52
+ }
53
+
54
+ async function runStream(
55
+ provider: OpenAIChatCompletionsProvider,
56
+ events: Array<{ type: string; thinking?: string; text?: string }>,
57
+ ): Promise<{
58
+ thinking: string;
59
+ }> {
60
+ const response = await provider.sendMessage(
61
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
62
+ undefined,
63
+ undefined,
64
+ {
65
+ onEvent: (e) => {
66
+ events.push(e as { type: string; thinking?: string; text?: string });
67
+ },
68
+ },
69
+ );
70
+ const thinkingBlock = response.content.find((b) => b.type === "thinking") as
71
+ | { type: "thinking"; thinking: string }
72
+ | undefined;
73
+ return { thinking: thinkingBlock?.thinking ?? "" };
74
+ }
75
+
76
+ describe("OpenAIChatCompletionsProvider reasoning parsing", () => {
77
+ test("emits flat reasoning_content once (Fireworks/DeepSeek/Together/Groq shape)", async () => {
78
+ const { provider, events } = stubProvider([
79
+ { choices: [{ delta: { reasoning_content: "hello " } }] },
80
+ { choices: [{ delta: { reasoning_content: "world" } }] },
81
+ {
82
+ choices: [{ delta: {}, finish_reason: "stop" }],
83
+ usage: { prompt_tokens: 1, completion_tokens: 2 },
84
+ },
85
+ ]);
86
+ const { thinking } = await runStream(provider, events);
87
+ const deltas = events.filter((e) => e.type === "thinking_delta");
88
+ expect(deltas.map((d) => d.thinking)).toEqual(["hello ", "world"]);
89
+ expect(thinking).toBe("hello world");
90
+ });
91
+
92
+ test("emits flat reasoning once (OpenRouter non-Kimi shape)", async () => {
93
+ const { provider, events } = stubProvider([
94
+ { choices: [{ delta: { reasoning: "step " } }] },
95
+ { choices: [{ delta: { reasoning: "two" } }] },
96
+ {
97
+ choices: [{ delta: {}, finish_reason: "stop" }],
98
+ usage: { prompt_tokens: 1, completion_tokens: 2 },
99
+ },
100
+ ]);
101
+ const { thinking } = await runStream(provider, events);
102
+ const deltas = events.filter((e) => e.type === "thinking_delta");
103
+ expect(deltas.map((d) => d.thinking)).toEqual(["step ", "two"]);
104
+ expect(thinking).toBe("step two");
105
+ });
106
+
107
+ test("emits reasoning_details once when only details present", async () => {
108
+ const { provider, events } = stubProvider([
109
+ {
110
+ choices: [
111
+ {
112
+ delta: {
113
+ reasoning_details: [{ type: "reasoning.text", text: "alpha " }],
114
+ },
115
+ },
116
+ ],
117
+ },
118
+ {
119
+ choices: [
120
+ {
121
+ delta: {
122
+ reasoning_details: [
123
+ { type: "reasoning.summary", summary: "beta" },
124
+ ],
125
+ },
126
+ },
127
+ ],
128
+ },
129
+ {
130
+ choices: [{ delta: {}, finish_reason: "stop" }],
131
+ usage: { prompt_tokens: 1, completion_tokens: 2 },
132
+ },
133
+ ]);
134
+ const { thinking } = await runStream(provider, events);
135
+ const deltas = events.filter((e) => e.type === "thinking_delta");
136
+ expect(deltas.map((d) => d.thinking)).toEqual(["alpha ", "beta"]);
137
+ expect(thinking).toBe("alpha beta");
138
+ });
139
+
140
+ test("skips reasoning.encrypted entries entirely", async () => {
141
+ const { provider, events } = stubProvider([
142
+ {
143
+ choices: [
144
+ {
145
+ delta: {
146
+ reasoning_details: [
147
+ { type: "reasoning.encrypted", text: "opaque" },
148
+ ],
149
+ },
150
+ },
151
+ ],
152
+ },
153
+ {
154
+ choices: [{ delta: {}, finish_reason: "stop" }],
155
+ usage: { prompt_tokens: 1, completion_tokens: 2 },
156
+ },
157
+ ]);
158
+ const { thinking } = await runStream(provider, events);
159
+ const deltas = events.filter((e) => e.type === "thinking_delta");
160
+ expect(deltas).toEqual([]);
161
+ expect(thinking).toBe("");
162
+ });
163
+
164
+ test("falls back to flat reasoning when details carry only encrypted entries", async () => {
165
+ const { provider, events } = stubProvider([
166
+ {
167
+ choices: [
168
+ {
169
+ delta: {
170
+ reasoning: "visible ",
171
+ reasoning_details: [
172
+ { type: "reasoning.encrypted", text: "opaque" },
173
+ ],
174
+ },
175
+ },
176
+ ],
177
+ },
178
+ {
179
+ choices: [{ delta: {}, finish_reason: "stop" }],
180
+ usage: { prompt_tokens: 1, completion_tokens: 2 },
181
+ },
182
+ ]);
183
+ const { thinking } = await runStream(provider, events);
184
+ const deltas = events.filter((e) => e.type === "thinking_delta");
185
+ expect(deltas.map((d) => d.thinking)).toEqual(["visible "]);
186
+ expect(thinking).toBe("visible ");
187
+ });
188
+
189
+ test("does NOT double-emit when Kimi K2.6 mirrors text into both fields", async () => {
190
+ // OpenRouter Kimi K2.6 with `reasoning.summary` set sends the same token
191
+ // in both `delta.reasoning` and `delta.reasoning_details[].text`. The
192
+ // structured field is preferred and the flat field is skipped, so each
193
+ // token appears exactly once in the output stream.
194
+ const { provider, events } = stubProvider([
195
+ {
196
+ choices: [
197
+ {
198
+ delta: {
199
+ reasoning: "it ",
200
+ reasoning_details: [{ type: "reasoning.text", text: "it " }],
201
+ },
202
+ },
203
+ ],
204
+ },
205
+ {
206
+ choices: [
207
+ {
208
+ delta: {
209
+ reasoning: "worked",
210
+ reasoning_details: [{ type: "reasoning.text", text: "worked" }],
211
+ },
212
+ },
213
+ ],
214
+ },
215
+ {
216
+ choices: [
217
+ {
218
+ delta: {
219
+ reasoning: "!",
220
+ reasoning_details: [{ type: "reasoning.text", text: "!" }],
221
+ },
222
+ },
223
+ ],
224
+ },
225
+ {
226
+ choices: [{ delta: {}, finish_reason: "stop" }],
227
+ usage: { prompt_tokens: 1, completion_tokens: 3 },
228
+ },
229
+ ]);
230
+ const { thinking } = await runStream(provider, events);
231
+ const deltas = events.filter((e) => e.type === "thinking_delta");
232
+ expect(deltas.map((d) => d.thinking)).toEqual(["it ", "worked", "!"]);
233
+ expect(thinking).toBe("it worked!");
234
+ });
235
+ });
@@ -66,30 +66,56 @@ export interface OpenAIChatCompletionsProviderOptions {
66
66
  extraCreateParams?: Record<string, unknown>;
67
67
  /** Upper bound for `reasoning_effort` sent on the wire. Defaults to "xhigh"
68
68
  * (OpenAI's current ceiling). Compatibility providers whose APIs only
69
- * document `low|medium|high` (e.g. Fireworks) should set this to "high" so
70
- * Vellum's `xhigh`/`max` tiers don't 4xx upstream. */
71
- maxReasoningEffort?: "high" | "xhigh";
69
+ * document `low|medium|high` should set this to "high" so Vellum's
70
+ * `xhigh`/`max` tiers don't 4xx upstream. Set to "max" for providers like
71
+ * Fireworks DeepSeek V4 that accept the full effort range. Subclasses can
72
+ * override {@link OpenAIChatCompletionsProvider.resolveMaxReasoningEffort}
73
+ * for per-model ceilings. */
74
+ maxReasoningEffort?: "high" | "xhigh" | "max";
75
+ /** Parse `<think>...</think>` tags from the content stream into thinking
76
+ * blocks. MiniMax and similar providers embed reasoning inside XML-style
77
+ * tags in the regular content field rather than using `reasoning_content`. */
78
+ parseThinkTags?: boolean;
72
79
  }
73
80
 
74
- /** Map our internal effort values to OpenAI's reasoning_effort parameter.
75
- * OpenAI caps at "xhigh", so our "max" tier collapses to "xhigh". `"none"` is
76
- * passed through explicitly because OpenAI defaults `reasoning_effort` to
77
- * "medium" when the field is omitted the user's opt-out is only honored
78
- * when we send it on the wire. */
79
- const EFFORT_TO_REASONING_EFFORT: Record<
80
- string,
81
- NonNullable<
82
- OpenAI.Chat.Completions.ChatCompletionCreateParams["reasoning_effort"]
83
- >
84
- > = {
81
+ /** Wire-level reasoning_effort values. The OpenAI SDK type doesn't include
82
+ * `"max"`, but Fireworks accepts it for DeepSeek V4; the assignment to
83
+ * `params.reasoning_effort` casts through this union. */
84
+ type ReasoningEffortWire = "none" | "low" | "medium" | "high" | "xhigh" | "max";
85
+
86
+ const REASONING_EFFORT_RANK: Record<ReasoningEffortWire, number> = {
87
+ none: 0,
88
+ low: 1,
89
+ medium: 2,
90
+ high: 3,
91
+ xhigh: 4,
92
+ max: 5,
93
+ };
94
+
95
+ /** Map our internal effort values to a reasoning_effort wire value. `"max"`
96
+ * is emitted raw — providers cap it down to their own ceiling
97
+ * ({@link OpenAIChatCompletionsProviderOptions.maxReasoningEffort}) at send
98
+ * time. `"none"` is passed through explicitly because OpenAI-compatible APIs
99
+ * default `reasoning_effort` to `"medium"` when the field is omitted, so the
100
+ * user's opt-out is only honored when we send it on the wire. */
101
+ export const EFFORT_TO_REASONING_EFFORT: Record<string, ReasoningEffortWire> = {
85
102
  none: "none",
86
103
  low: "low",
87
104
  medium: "medium",
88
105
  high: "high",
89
106
  xhigh: "xhigh",
90
- max: "xhigh",
107
+ max: "max",
91
108
  };
92
109
 
110
+ export function clampReasoningEffort(
111
+ value: ReasoningEffortWire,
112
+ ceiling: "high" | "xhigh" | "max",
113
+ ): ReasoningEffortWire {
114
+ return REASONING_EFFORT_RANK[value] > REASONING_EFFORT_RANK[ceiling]
115
+ ? ceiling
116
+ : value;
117
+ }
118
+
93
119
  const OPENAI_SUPPORTED_IMAGE_TYPES = new Set([
94
120
  "image/jpeg",
95
121
  "image/png",
@@ -97,6 +123,13 @@ const OPENAI_SUPPORTED_IMAGE_TYPES = new Set([
97
123
  "image/webp",
98
124
  ]);
99
125
 
126
+ function partialTagSuffix(text: string, tag: string): number {
127
+ for (let len = Math.min(text.length, tag.length - 1); len > 0; len--) {
128
+ if (text.endsWith(tag.substring(0, len))) return len;
129
+ }
130
+ return 0;
131
+ }
132
+
100
133
  /**
101
134
  * OpenAI-compatible chat-completions transport.
102
135
  *
@@ -111,8 +144,9 @@ export class OpenAIChatCompletionsProvider implements Provider {
111
144
  private model: string;
112
145
  private streamTimeoutMs: number;
113
146
  private extraCreateParams: Record<string, unknown>;
114
- private maxReasoningEffort: "high" | "xhigh";
147
+ private maxReasoningEffort: "high" | "xhigh" | "max";
115
148
  private requestHeaders: Record<string, string>;
149
+ private parseThinkTags: boolean;
116
150
 
117
151
  constructor(
118
152
  apiKey: string,
@@ -130,6 +164,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
130
164
  this.extraCreateParams = options.extraCreateParams ?? {};
131
165
  this.maxReasoningEffort = options.maxReasoningEffort ?? "xhigh";
132
166
  this.requestHeaders = options.requestHeaders ?? {};
167
+ this.parseThinkTags = options.parseThinkTags ?? false;
133
168
  }
134
169
 
135
170
  async sendMessage(
@@ -163,14 +198,24 @@ export class OpenAIChatCompletionsProvider implements Provider {
163
198
  params.max_completion_tokens = maxTokens;
164
199
  }
165
200
 
201
+ // Subclasses (OpenRouter) may already have nested effort under
202
+ // `reasoning.effort` via `buildExtraCreateParams`. Skip the flat
203
+ // `reasoning_effort` assignment in that case to avoid sending both forms,
204
+ // which OpenRouter rejects on reasoning models.
205
+ const nestedReasoningEffort = (
206
+ params as { reasoning?: { effort?: unknown } }
207
+ ).reasoning?.effort;
166
208
  const reasoningEffort = effort
167
209
  ? EFFORT_TO_REASONING_EFFORT[effort]
168
210
  : undefined;
169
- if (reasoningEffort) {
170
- params.reasoning_effort =
171
- reasoningEffort === "xhigh" && this.maxReasoningEffort === "high"
172
- ? "high"
173
- : reasoningEffort;
211
+ if (reasoningEffort && typeof nestedReasoningEffort !== "string") {
212
+ const ceiling = this.resolveMaxReasoningEffort(
213
+ modelOverride ?? this.model,
214
+ );
215
+ params.reasoning_effort = clampReasoningEffort(
216
+ reasoningEffort,
217
+ ceiling,
218
+ ) as OpenAI.Chat.Completions.ChatCompletionCreateParams["reasoning_effort"];
174
219
  }
175
220
 
176
221
  if (tools && tools.length > 0) {
@@ -189,6 +234,68 @@ export class OpenAIChatCompletionsProvider implements Provider {
189
234
 
190
235
  // Accumulate the response from chunks
191
236
  let contentText = "";
237
+ let reasoningText = "";
238
+ let insideThinkBlock = false;
239
+ let pendingContent = "";
240
+
241
+ const flushPendingContent = (final: boolean): void => {
242
+ while (pendingContent.length > 0) {
243
+ if (insideThinkBlock) {
244
+ const closeIdx = pendingContent.indexOf("</think>");
245
+ if (closeIdx >= 0) {
246
+ const thinking = pendingContent.substring(0, closeIdx);
247
+ if (thinking) {
248
+ reasoningText += thinking;
249
+ onEvent?.({ type: "thinking_delta", thinking });
250
+ }
251
+ insideThinkBlock = false;
252
+ pendingContent = pendingContent.substring(
253
+ closeIdx + "</think>".length,
254
+ );
255
+ } else {
256
+ const partial = final
257
+ ? 0
258
+ : partialTagSuffix(pendingContent, "</think>");
259
+ const safeLen = pendingContent.length - partial;
260
+ if (safeLen > 0) {
261
+ const thinking = pendingContent.substring(0, safeLen);
262
+ reasoningText += thinking;
263
+ onEvent?.({ type: "thinking_delta", thinking });
264
+ }
265
+ pendingContent =
266
+ partial > 0 ? pendingContent.substring(safeLen) : "";
267
+ break;
268
+ }
269
+ } else {
270
+ const openIdx = pendingContent.indexOf("<think>");
271
+ if (openIdx >= 0) {
272
+ const text = pendingContent.substring(0, openIdx);
273
+ if (text) {
274
+ contentText += text;
275
+ onEvent?.({ type: "text_delta", text });
276
+ }
277
+ insideThinkBlock = true;
278
+ pendingContent = pendingContent.substring(
279
+ openIdx + "<think>".length,
280
+ );
281
+ } else {
282
+ const partial = final
283
+ ? 0
284
+ : partialTagSuffix(pendingContent, "<think>");
285
+ const safeLen = pendingContent.length - partial;
286
+ if (safeLen > 0) {
287
+ const t = pendingContent.substring(0, safeLen);
288
+ contentText += t;
289
+ onEvent?.({ type: "text_delta", text: t });
290
+ }
291
+ pendingContent =
292
+ partial > 0 ? pendingContent.substring(safeLen) : "";
293
+ break;
294
+ }
295
+ }
296
+ }
297
+ };
298
+
192
299
  const toolCallMap = new Map<
193
300
  number,
194
301
  { id: string; name: string; args: string }
@@ -216,8 +323,62 @@ export class OpenAIChatCompletionsProvider implements Provider {
216
323
  const choice = chunk.choices[0];
217
324
  if (choice) {
218
325
  if (choice.delta.content) {
219
- contentText += choice.delta.content;
220
- onEvent?.({ type: "text_delta", text: choice.delta.content });
326
+ if (this.parseThinkTags) {
327
+ pendingContent += choice.delta.content;
328
+ flushPendingContent(false);
329
+ } else {
330
+ contentText += choice.delta.content;
331
+ onEvent?.({ type: "text_delta", text: choice.delta.content });
332
+ }
333
+ }
334
+
335
+ // Compatibility providers disagree on the field name: Fireworks /
336
+ // DeepSeek / Together / Groq stream `reasoning_content`; OpenRouter
337
+ // (per its ChatAssistantMessage spec) streams `reasoning`, and for
338
+ // reasoning summaries (e.g. Kimi K2.6) also populates
339
+ // `delta.reasoning_details[]` (entries are `reasoning.summary`,
340
+ // `reasoning.text`, or opaque `reasoning.encrypted`).
341
+ //
342
+ // Kimi K2.6 mirrors the same token into BOTH `delta.reasoning` and
343
+ // `delta.reasoning_details[].text` per chunk — prefer details when
344
+ // they carry visible text, otherwise fall through to the flat
345
+ // field. The encrypted-only case must fall through too, so the
346
+ // flat `reasoning` field isn't silently dropped.
347
+ const deltaWithReasoning = choice.delta as {
348
+ reasoning?: string | null;
349
+ reasoning_content?: string | null;
350
+ reasoning_details?: Array<{
351
+ type?: string;
352
+ summary?: string | null;
353
+ text?: string | null;
354
+ }> | null;
355
+ };
356
+
357
+ let sawVisibleDetail = false;
358
+ const reasoningDetails = deltaWithReasoning.reasoning_details;
359
+ if (Array.isArray(reasoningDetails)) {
360
+ for (const entry of reasoningDetails) {
361
+ if (entry.type === "reasoning.encrypted") continue;
362
+ const piece = entry.summary ?? entry.text;
363
+ if (piece) {
364
+ sawVisibleDetail = true;
365
+ reasoningText += piece;
366
+ onEvent?.({ type: "thinking_delta", thinking: piece });
367
+ }
368
+ }
369
+ }
370
+
371
+ if (!sawVisibleDetail) {
372
+ const reasoningContent =
373
+ deltaWithReasoning.reasoning_content ??
374
+ deltaWithReasoning.reasoning;
375
+ if (reasoningContent) {
376
+ reasoningText += reasoningContent;
377
+ onEvent?.({
378
+ type: "thinking_delta",
379
+ thinking: reasoningContent,
380
+ });
381
+ }
221
382
  }
222
383
 
223
384
  if (choice.delta.tool_calls) {
@@ -260,10 +421,27 @@ export class OpenAIChatCompletionsProvider implements Provider {
260
421
  cleanupTimeout();
261
422
  }
262
423
 
424
+ if (this.parseThinkTags && pendingContent) {
425
+ flushPendingContent(true);
426
+ }
427
+
263
428
  // Build content blocks
429
+ const finalReasoning = this.parseThinkTags
430
+ ? reasoningText.trim()
431
+ : reasoningText;
432
+ const finalContent = this.parseThinkTags
433
+ ? contentText.trim()
434
+ : contentText;
264
435
  const content: ContentBlock[] = [];
265
- if (contentText) {
266
- content.push({ type: "text", text: contentText });
436
+ if (finalReasoning) {
437
+ content.push({
438
+ type: "thinking",
439
+ thinking: finalReasoning,
440
+ signature: "",
441
+ });
442
+ }
443
+ if (finalContent) {
444
+ content.push({ type: "text", text: finalContent });
267
445
  }
268
446
  for (const [, tc] of toolCallMap) {
269
447
  let input: Record<string, unknown>;
@@ -393,6 +571,18 @@ export class OpenAIChatCompletionsProvider implements Provider {
393
571
  return this.extraCreateParams;
394
572
  }
395
573
 
574
+ /**
575
+ * Per-request reasoning_effort ceiling. Defaults to the provider-wide
576
+ * `maxReasoningEffort` from constructor options. Subclasses (e.g. Fireworks)
577
+ * override to consult the model catalog so per-model accepted ranges are
578
+ * respected.
579
+ */
580
+ protected resolveMaxReasoningEffort(
581
+ _model: string,
582
+ ): "high" | "xhigh" | "max" {
583
+ return this.maxReasoningEffort;
584
+ }
585
+
396
586
  /** Convert neutral messages + system prompt to OpenAI message format. */
397
587
  private toOpenAIMessages(
398
588
  messages: Message[],
@@ -23,6 +23,9 @@ export interface OpenAIResponsesProviderOptions {
23
23
  providerLabel?: string;
24
24
  streamTimeoutMs?: number;
25
25
  useNativeWebSearch?: boolean;
26
+ /** When true, target the Codex subscription endpoint and strip fields it
27
+ * rejects (`max_output_tokens`, `metadata`). */
28
+ codexSubscription?: boolean;
26
29
  }
27
30
 
28
31
  /** Map our internal effort values to the Responses API reasoning.effort parameter.
@@ -103,6 +106,7 @@ export class OpenAIResponsesProvider implements Provider {
103
106
  private model: string;
104
107
  private streamTimeoutMs: number;
105
108
  private useNativeWebSearch: boolean;
109
+ private codexSubscription: boolean;
106
110
 
107
111
  constructor(
108
112
  apiKey: string,
@@ -111,9 +115,12 @@ export class OpenAIResponsesProvider implements Provider {
111
115
  ) {
112
116
  this.name = options.providerName ?? "openai";
113
117
  this.providerLabel = options.providerLabel ?? "OpenAI";
118
+ this.codexSubscription = options.codexSubscription ?? false;
114
119
  this.client = new OpenAI({
115
120
  apiKey,
116
- baseURL: options.baseURL,
121
+ baseURL: this.codexSubscription
122
+ ? "https://chatgpt.com/backend-api/codex"
123
+ : options.baseURL,
117
124
  });
118
125
  this.model = model;
119
126
  this.streamTimeoutMs = options.streamTimeoutMs ?? 1_800_000;
@@ -142,7 +149,6 @@ export class OpenAIResponsesProvider implements Provider {
142
149
  const params: Record<string, unknown> = {
143
150
  model: modelOverride ?? this.model,
144
151
  input,
145
- store: false,
146
152
  };
147
153
 
148
154
  if (systemPrompt) {
@@ -152,7 +158,7 @@ export class OpenAIResponsesProvider implements Provider {
152
158
  );
153
159
  }
154
160
 
155
- if (maxTokens) {
161
+ if (maxTokens && !this.codexSubscription) {
156
162
  params.max_output_tokens = maxTokens;
157
163
  }
158
164
 
@@ -1,6 +1,10 @@
1
1
  import { ProviderError } from "../../util/errors.js";
2
2
  import { AnthropicProvider } from "../anthropic/client.js";
3
- import { OpenAIChatCompletionsProvider } from "../openai/chat-completions-provider.js";
3
+ import {
4
+ clampReasoningEffort,
5
+ EFFORT_TO_REASONING_EFFORT,
6
+ OpenAIChatCompletionsProvider,
7
+ } from "../openai/chat-completions-provider.js";
4
8
  import { isThinkingConfigEnabled } from "../thinking-config.js";
5
9
  import type {
6
10
  Message,
@@ -53,6 +57,25 @@ export function extractOnlyList(config: unknown): string[] {
53
57
  return only.filter((x): x is string => typeof x === "string" && x.length > 0);
54
58
  }
55
59
 
60
+ // OpenRouter's `reasoning.summary` field controls whether reasoning models emit
61
+ // a human-readable summary alongside (or instead of) encrypted reasoning blocks.
62
+ // Models like Kimi K2.6 return only encrypted `reasoning_details` unless a
63
+ // summary level is requested, so the stream carries no visible thinking content.
64
+ // Default to "detailed" so users see thinking by default; allow per-call
65
+ // override via `config.openrouter.reasoning.summary`. Per OpenRouter's
66
+ // ChatRequestReasoning schema, valid values are "auto" | "concise" | "detailed".
67
+ const VALID_REASONING_SUMMARIES = new Set(["auto", "concise", "detailed"]);
68
+
69
+ function extractReasoningSummaryOverride(config: unknown): string | undefined {
70
+ const cfg = config as
71
+ | { openrouter?: { reasoning?: { summary?: unknown } } }
72
+ | undefined;
73
+ const summary = cfg?.openrouter?.reasoning?.summary;
74
+ return typeof summary === "string" && VALID_REASONING_SUMMARIES.has(summary)
75
+ ? summary
76
+ : undefined;
77
+ }
78
+
56
79
  /**
57
80
  * Rewrite `options.config` for the Anthropic-compat path so OpenRouter's
58
81
  * `provider: { only: [...] }` body field travels through `AnthropicProvider`'s
@@ -160,14 +183,33 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
160
183
  // OpenRouter's unified `reasoning` parameter controls extended thinking on
161
184
  // its OpenAI-compatible endpoint. Anthropic models skip this path entirely and
162
185
  // go through AnthropicProvider, which receives the native `thinking` object.
186
+ //
187
+ // `effort` nests under `reasoning` here (rather than flat `reasoning_effort`)
188
+ // because OpenRouter's documented `ChatRequestReasoning` shape is the union of
189
+ // { effort, summary }. `summary` is required for models like Kimi K2.6 that
190
+ // would otherwise return only encrypted reasoning blocks; we default to
191
+ // "detailed" and let callers override via `config.openrouter.reasoning.summary`.
163
192
  protected override buildExtraCreateParams(
164
193
  options?: SendMessageOptions,
165
194
  ): Record<string, unknown> {
166
195
  const config = options?.config as Record<string, unknown> | undefined;
167
196
  const thinkingEnabled = isThinkingConfigEnabled(config?.thinking);
168
- const extras: Record<string, unknown> = {
169
- reasoning: { enabled: thinkingEnabled },
170
- };
197
+ const effort = config?.effort as string | undefined;
198
+ const mappedEffort = effort
199
+ ? EFFORT_TO_REASONING_EFFORT[effort]
200
+ : undefined;
201
+ 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
+ }
209
+ if (thinkingEnabled) {
210
+ reasoning.summary = summaryOverride ?? "detailed";
211
+ }
212
+ const extras: Record<string, unknown> = { reasoning };
171
213
  const only = extractOnlyList(config);
172
214
  if (only.length > 0) {
173
215
  const existingProvider = (config?.provider ?? {}) as Record<
@@ -43,14 +43,13 @@ export const PLATFORM_PROVIDER_META: Record<string, ManagedProviderMeta> = {
43
43
  },
44
44
  fireworks: {
45
45
  name: "fireworks",
46
- managed: false,
46
+ managed: true,
47
+ proxyPath: "/v1/runtime-proxy/fireworks",
47
48
  },
48
49
  openrouter: {
49
50
  name: "openrouter",
50
51
  managed: false,
51
52
  },
52
53
  ollama: { name: "ollama", managed: false },
53
- zai: { name: "zai", managed: false },
54
- deepseek: { name: "deepseek", managed: false },
55
- minimax: { name: "minimax", managed: false },
54
+ "openai-compatible": { name: "openai-compatible", managed: false },
56
55
  };