@vellumai/assistant 0.11.4 → 0.11.5-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/AGENTS.md +2 -2
  2. package/ARCHITECTURE.md +29 -4
  3. package/README.md +1 -1
  4. package/docs/architecture/memory.md +17 -5
  5. package/docs/architecture/security.md +96 -152
  6. package/docs/guardian-request-flow.md +13 -2
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  16. package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
  17. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
  19. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  21. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  22. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/openapi.yaml +193 -3
  24. package/package.json +1 -1
  25. package/src/__tests__/anthropic-provider.test.ts +10 -3
  26. package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
  27. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  28. package/src/__tests__/channel-approval-routes.test.ts +4 -0
  29. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
  30. package/src/__tests__/channel-reply-delivery.test.ts +26 -13
  31. package/src/__tests__/checker.test.ts +20 -314
  32. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  33. package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
  34. package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
  35. package/src/__tests__/container-cpu-sampler.test.ts +153 -0
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/conversation-attachments.test.ts +0 -1
  38. package/src/__tests__/conversation-error.test.ts +17 -0
  39. package/src/__tests__/conversation-lifecycle.test.ts +20 -26
  40. package/src/__tests__/conversation-pairing.test.ts +186 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
  42. package/src/__tests__/credential-broker-server-use.test.ts +24 -5
  43. package/src/__tests__/credential-routes.test.ts +22 -3
  44. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  45. package/src/__tests__/daemon-credential-client.test.ts +88 -0
  46. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  47. package/src/__tests__/dm-backfill.test.ts +63 -0
  48. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  49. package/src/__tests__/edit-propagation.test.ts +107 -4
  50. package/src/__tests__/events-client-registration.test.ts +28 -0
  51. package/src/__tests__/file-write-tool.test.ts +4 -2
  52. package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
  53. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
  54. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  55. package/src/__tests__/host-proxy-interface.test.ts +11 -1
  56. package/src/__tests__/host-shell-tool.test.ts +23 -4
  57. package/src/__tests__/image-conversion.test.ts +143 -1
  58. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  59. package/src/__tests__/input-repairs.test.ts +207 -0
  60. package/src/__tests__/live-workspace-guard.test.ts +75 -0
  61. package/src/__tests__/mcp-abort-signal.test.ts +1 -1
  62. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  63. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
  64. package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
  65. package/src/__tests__/mtime-cache.test.ts +2 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
  67. package/src/__tests__/openai-provider.test.ts +22 -0
  68. package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
  69. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  70. package/src/__tests__/platform.test.ts +16 -1
  71. package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
  72. package/src/__tests__/plugin-api-shim.test.ts +5 -0
  73. package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
  74. package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
  75. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  76. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  77. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  78. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  79. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
  80. package/src/__tests__/reaction-persistence.test.ts +200 -7
  81. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  82. package/src/__tests__/resource-pressure-guard.test.ts +428 -0
  83. package/src/__tests__/resource-pressure-routes.test.ts +113 -0
  84. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  85. package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
  86. package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
  87. package/src/__tests__/skill-tool-factory.test.ts +161 -0
  88. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  89. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  90. package/src/__tests__/tool-executor.test.ts +63 -38
  91. package/src/__tests__/tool-policy.test.ts +97 -1
  92. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
  93. package/src/__tests__/user-plugin-loader.test.ts +2 -0
  94. package/src/__tests__/validate-input.test.ts +243 -7
  95. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
  97. package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
  98. package/src/acp/acp-claude-oauth.ts +38 -21
  99. package/src/acp/prepare-agent-env.ts +146 -55
  100. package/src/agent/loop-tool-dedup.test.ts +161 -0
  101. package/src/agent/loop.ts +68 -22
  102. package/src/api/constants/app-tools.ts +34 -0
  103. package/src/api/events/resource-pressure-status-changed.ts +53 -0
  104. package/src/api/index.ts +19 -0
  105. package/src/api/responses/resource-pressure-status.ts +25 -0
  106. package/src/approvals/guardian-channel-delivery.ts +70 -6
  107. package/src/approvals/guardian-request-resolvers.ts +32 -66
  108. package/src/channels/__tests__/message-audience.test.ts +49 -0
  109. package/src/channels/__tests__/types.test.ts +17 -3
  110. package/src/channels/message-audience.ts +40 -0
  111. package/src/channels/types.ts +16 -12
  112. package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
  113. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
  114. package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
  115. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  116. package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
  117. package/src/cli/commands/__tests__/keys.test.ts +89 -8
  118. package/src/cli/commands/__tests__/plugins.test.ts +57 -1
  119. package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
  120. package/src/cli/commands/channel-verification-sessions.ts +11 -21
  121. package/src/cli/commands/conversations.help.ts +34 -0
  122. package/src/cli/commands/conversations.ts +125 -0
  123. package/src/cli/commands/inference-providers.ts +9 -4
  124. package/src/cli/commands/inference.help.ts +9 -4
  125. package/src/cli/commands/keys.help.ts +13 -1
  126. package/src/cli/commands/keys.ts +26 -15
  127. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  128. package/src/cli/commands/memory/index.help.ts +41 -27
  129. package/src/cli/commands/memory/index.ts +2 -0
  130. package/src/cli/commands/memory/memory-v2.ts +58 -54
  131. package/src/cli/commands/memory/memory-validate.ts +18 -0
  132. package/src/cli/commands/monitoring.ts +1 -0
  133. package/src/cli/commands/plugins.help.ts +28 -31
  134. package/src/cli/commands/plugins.ts +10 -0
  135. package/src/cli/commands/skills.help.ts +13 -16
  136. package/src/cli/commands/trust.ts +3 -14
  137. package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
  138. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
  139. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
  140. package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
  141. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
  142. package/src/cli/lib/bundled-marketplace.json +13 -0
  143. package/src/cli/lib/daemon-credential-client.ts +25 -3
  144. package/src/cli/lib/install-from-github.ts +35 -24
  145. package/src/cli/lib/merge-plugin-tree.ts +22 -4
  146. package/src/cli/lib/plugin-surfaces.ts +27 -0
  147. package/src/config/feature-flag-registry.json +35 -19
  148. package/src/config/webhook-routing.ts +8 -0
  149. package/src/context/compactor.ts +31 -2
  150. package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
  151. package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
  152. package/src/daemon/conversation-error.ts +13 -0
  153. package/src/daemon/conversation-messaging.ts +22 -2
  154. package/src/daemon/conversation-process.ts +11 -0
  155. package/src/daemon/conversation-store.ts +10 -0
  156. package/src/daemon/conversation-surfaces.ts +21 -8
  157. package/src/daemon/conversation.ts +12 -10
  158. package/src/daemon/lifecycle.ts +6 -2
  159. package/src/daemon/message-types/conversations.ts +5 -7
  160. package/src/daemon/provider-rejection-log-fields.ts +124 -0
  161. package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
  162. package/src/daemon/resource-pressure-guard.ts +387 -0
  163. package/src/daemon/shutdown-handlers.ts +2 -0
  164. package/src/daemon/startup-error.ts +16 -0
  165. package/src/daemon/trust-context.ts +14 -10
  166. package/src/daemon/unsendable-image-notice.ts +76 -0
  167. package/src/hooks/registry.ts +65 -51
  168. package/src/ipc/__tests__/socket-path.test.ts +15 -7
  169. package/src/ipc/gateway-client.test.ts +1 -1
  170. package/src/ipc/gateway-client.ts +14 -21
  171. package/src/ipc/socket-cleanup.ts +2 -11
  172. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
  173. package/src/messaging/provider-message-metadata.ts +128 -0
  174. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
  175. package/src/messaging/providers/channel-transport.ts +90 -13
  176. package/src/messaging/providers/discord/send.ts +16 -0
  177. package/src/messaging/providers/discord/transport.ts +11 -1
  178. package/src/messaging/providers/index.ts +109 -16
  179. package/src/messaging/providers/slack/message-metadata.ts +45 -0
  180. package/src/messaging/providers/slack/render-transcript.ts +1 -4
  181. package/src/messaging/providers/slack/send.test.ts +6 -9
  182. package/src/messaging/providers/slack/send.ts +40 -28
  183. package/src/messaging/providers/slack/transport.ts +22 -26
  184. package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
  185. package/src/messaging/providers/telegram-bot/transport.ts +3 -6
  186. package/src/messaging/read-provider-metadata.test.ts +157 -0
  187. package/src/messaging/read-provider-metadata.ts +51 -0
  188. package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
  189. package/src/notifications/__tests__/edit-notification.test.ts +174 -2
  190. package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
  191. package/src/notifications/adapters/macos.ts +55 -0
  192. package/src/notifications/adapters/slack.ts +10 -5
  193. package/src/notifications/assistant-reply-producer.ts +37 -4
  194. package/src/notifications/conversation-pairing.ts +111 -21
  195. package/src/notifications/edit-notification.ts +38 -8
  196. package/src/notifications/emit-signal.ts +12 -14
  197. package/src/notifications/home-feed-side-effect.ts +218 -15
  198. package/src/notifications/types.ts +5 -0
  199. package/src/permissions/AGENTS.md +16 -0
  200. package/src/permissions/checker.test.ts +75 -122
  201. package/src/permissions/checker.ts +44 -560
  202. package/src/permissions/confirmation-guardian-request.test.ts +28 -3
  203. package/src/permissions/confirmation-guardian-request.ts +5 -1
  204. package/src/persistence/conversation-crud.ts +23 -20
  205. package/src/persistence/conversation-queries.ts +14 -1
  206. package/src/persistence/conversation-types.ts +22 -0
  207. package/src/persistence/delivery-crud.ts +122 -15
  208. package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
  209. package/src/plugin-api/conversation-turn.ts +27 -0
  210. package/src/plugin-api/credential-scope.test.ts +15 -0
  211. package/src/plugin-api/credential-scope.ts +14 -0
  212. package/src/plugin-api/index.ts +19 -1
  213. package/src/plugin-api/resolve-credential.ts +15 -10
  214. package/src/plugin-api/store-credential.ts +148 -0
  215. package/src/plugin-api/system-card.ts +36 -0
  216. package/src/plugin-api/vision-support.test.ts +82 -0
  217. package/src/plugin-api/vision-support.ts +14 -2
  218. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
  219. package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
  220. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
  221. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
  222. package/src/plugins/defaults/image-recovery/detect.ts +24 -1
  223. package/src/plugins/defaults/main.ts +15 -0
  224. package/src/plugins/defaults/memory/AGENTS.md +11 -3
  225. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  226. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
  227. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  228. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  229. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
  230. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  231. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  232. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  233. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  234. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  235. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  236. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
  237. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
  238. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  239. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  240. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  241. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  242. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  243. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  244. package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
  245. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
  246. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  247. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  248. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  249. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  250. package/src/plugins/mtime-cache.ts +1 -1
  251. package/src/plugins/pipeline.ts +14 -7
  252. package/src/plugins/plugin-execution-context.ts +35 -11
  253. package/src/plugins/plugin-tree-walk.ts +63 -4
  254. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  255. package/src/providers/anthropic/client.ts +332 -241
  256. package/src/providers/connection-resolution.ts +2 -1
  257. package/src/providers/content-blocks.ts +22 -0
  258. package/src/providers/gemini/client.ts +5 -2
  259. package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
  260. package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
  261. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
  262. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
  263. package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
  264. package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
  265. package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
  266. package/src/providers/inference/adapter-factory.ts +33 -2
  267. package/src/providers/inference/auth.ts +13 -2
  268. package/src/providers/inference/credential-usage.ts +37 -0
  269. package/src/providers/inference/missing-credential-guard.ts +110 -0
  270. package/src/providers/inference/resolve-auth.ts +7 -7
  271. package/src/providers/media-resolve.ts +176 -15
  272. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
  273. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  274. package/src/providers/openai/chat-completions-provider.ts +322 -42
  275. package/src/routes/route-host-protocol.ts +7 -0
  276. package/src/routes/worker.ts +31 -10
  277. package/src/runtime/AGENTS.md +35 -0
  278. package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
  279. package/src/runtime/__tests__/web-presence.test.ts +234 -0
  280. package/src/runtime/assistant-event-hub.ts +38 -0
  281. package/src/runtime/channel-reply-delivery.ts +54 -31
  282. package/src/runtime/channel-retry-sweep.ts +6 -4
  283. package/src/runtime/effective-capabilities.test.ts +19 -0
  284. package/src/runtime/effective-capabilities.ts +25 -0
  285. package/src/runtime/guardian-reply-router.ts +6 -2
  286. package/src/runtime/http-errors.ts +1 -0
  287. package/src/runtime/http-server.ts +20 -5
  288. package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
  289. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
  290. package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
  291. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
  292. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
  293. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
  294. package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
  295. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  296. package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
  297. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  298. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
  299. package/src/runtime/routes/approval-routes.ts +37 -4
  300. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
  301. package/src/runtime/routes/canned-message-complete.ts +68 -29
  302. package/src/runtime/routes/client-routes.ts +113 -0
  303. package/src/runtime/routes/conversation-list-routes.ts +22 -7
  304. package/src/runtime/routes/conversation-management-routes.ts +15 -9
  305. package/src/runtime/routes/conversation-routes.ts +36 -22
  306. package/src/runtime/routes/credential-in-use.ts +85 -0
  307. package/src/runtime/routes/credential-routes.ts +55 -85
  308. package/src/runtime/routes/guardian-approval-interception.ts +22 -22
  309. package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
  310. package/src/runtime/routes/identity-routes.ts +5 -121
  311. package/src/runtime/routes/inbound-message-handler.ts +71 -27
  312. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
  313. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
  314. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
  315. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
  316. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
  317. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
  318. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
  319. package/src/runtime/routes/index.ts +2 -0
  320. package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
  321. package/src/runtime/routes/monitoring-routes.ts +9 -1
  322. package/src/runtime/routes/plugins-routes.ts +17 -5
  323. package/src/runtime/routes/resource-pressure-routes.ts +22 -0
  324. package/src/runtime/routes/secret-routes.ts +39 -8
  325. package/src/runtime/routes/settings-routes.ts +6 -6
  326. package/src/runtime/routes/surface-action-routes.ts +20 -19
  327. package/src/runtime/routes/user-route-dispatcher.ts +31 -2
  328. package/src/runtime/routes/user-route-resolution.ts +18 -1
  329. package/src/runtime/slack-reply-session.test.ts +23 -13
  330. package/src/runtime/slack-reply-session.ts +38 -55
  331. package/src/runtime/web-presence.ts +90 -0
  332. package/src/skills/validate-input.ts +177 -40
  333. package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
  334. package/src/subagent/consult-context.ts +7 -23
  335. package/src/tools/credentials/broker.ts +24 -79
  336. package/src/tools/credentials/ref-parse.ts +35 -0
  337. package/src/tools/credentials/resolve.ts +4 -10
  338. package/src/tools/credentials/store.ts +168 -0
  339. package/src/tools/credentials/tool-policy.ts +67 -0
  340. package/src/tools/executor.ts +53 -15
  341. package/src/tools/network/__tests__/web-search.test.ts +41 -1
  342. package/src/tools/network/url-safety.ts +9 -16
  343. package/src/tools/network/web-search.ts +21 -3
  344. package/src/tools/permission-checker.ts +43 -52
  345. package/src/tools/schema-transforms.ts +40 -5
  346. package/src/tools/shared/input-repairs.ts +160 -0
  347. package/src/tools/skills/skill-tool-factory.ts +18 -4
  348. package/src/tools/subagent/spawn.ts +0 -1
  349. package/src/tools/tool-approval-handler.ts +16 -10
  350. package/src/tools/tool-types.ts +26 -13
  351. package/src/tools/types.ts +6 -6
  352. package/src/util/__tests__/cgroup-memory.test.ts +3 -0
  353. package/src/util/cgroup-memory.ts +3 -0
  354. package/src/util/container-cpu-sampler.ts +250 -0
  355. package/src/util/image-conversion.ts +178 -14
  356. package/src/util/platform.ts +93 -10
  357. package/src/permissions/ipc-risk-types.ts +0 -143
  358. package/src/permissions/risk-types.ts +0 -76
@@ -47,6 +47,18 @@ const log = getLogger("anthropic-client");
47
47
  /** Validation-specific timeout (10s) so a stalled network doesn't block key submission. */
48
48
  const VALIDATION_TIMEOUT_MS = 10_000;
49
49
 
50
+ /**
51
+ * Cap on `pause_turn` continuations within one `sendMessage` call. The API
52
+ * pauses a long-running server-tool turn (e.g. web search) with
53
+ * `stop_reason: "pause_turn"`; the client resumes it by re-sending the paused
54
+ * assistant content as-is. The cap bounds pathological pause loops; web
55
+ * search allows `max_uses: 5` per turn, so 6 leaves headroom for one pause
56
+ * per search. On cap-out the response is returned as-is and the deferred-tail
57
+ * preservation in `analyzeServerToolPairing` keeps any unresolved
58
+ * `server_tool_use` blocks intact for a later request.
59
+ */
60
+ export const MAX_PAUSE_TURN_CONTINUATIONS = 6;
61
+
50
62
  /**
51
63
  * Detect Anthropic's `prompt_too_long` context-overflow signal.
52
64
  *
@@ -1357,6 +1369,10 @@ export class AnthropicProvider implements Provider {
1357
1369
  }
1358
1370
 
1359
1371
  let response: Anthropic.Message;
1372
+ // Paused segments accumulated by the `pause_turn` continuation loop,
1373
+ // in stream order, excluding the final response. Empty for the normal
1374
+ // single-request case.
1375
+ const pausedSegments: Anthropic.Message[] = [];
1360
1376
  try {
1361
1377
  recordProviderRequestDiagnostics({ model_id: params.model });
1362
1378
  const requestHeaders = {
@@ -1369,284 +1385,359 @@ export class AnthropicProvider implements Provider {
1369
1385
  ? { headers: requestHeaders }
1370
1386
  : {}),
1371
1387
  };
1372
- const stream: UnifiedStream = useFastMode
1373
- ? (this.client.beta.messages.stream(
1374
- {
1375
- ...(params as Record<string, unknown>),
1376
- speed: "fast" as const,
1377
- betas,
1378
- } as Anthropic.Beta.Messages.MessageCreateParamsNonStreaming &
1379
- Anthropic.Beta.Messages.MessageCreateParamsStreaming,
1380
- requestOptions,
1381
- ) as unknown as UnifiedStream)
1382
- : betas.length > 0
1388
+ // Issue one streaming request and return its final message. A local
1389
+ // helper so `pause_turn` continuations get fresh listener state per
1390
+ // request (content shadow, text sentinel buffer, tool-input
1391
+ // accumulators) instead of inheriting a prior segment's buffers.
1392
+ const streamOnce = async (
1393
+ streamParams: Anthropic.MessageStreamParams,
1394
+ ): Promise<Anthropic.Message> => {
1395
+ const stream: UnifiedStream = useFastMode
1383
1396
  ? (this.client.beta.messages.stream(
1384
1397
  {
1385
- ...(params as Record<string, unknown>),
1398
+ ...(streamParams as Record<string, unknown>),
1399
+ speed: "fast" as const,
1386
1400
  betas,
1387
1401
  } as Anthropic.Beta.Messages.MessageCreateParamsNonStreaming &
1388
1402
  Anthropic.Beta.Messages.MessageCreateParamsStreaming,
1389
1403
  requestOptions,
1390
1404
  ) as unknown as UnifiedStream)
1391
- : (this.client.messages.stream(
1392
- params,
1393
- requestOptions,
1394
- ) as unknown as UnifiedStream);
1395
-
1396
- // Shadow the streamed content blocks so a mid-stream SDK accumulator
1397
- // failure on malformed tool-argument JSON can be salvaged instead of
1398
- // discarding the whole response (see StreamContentShadow).
1399
- const contentShadow = new StreamContentShadow();
1400
-
1401
- // Buffer streaming text until it's clear the accumulated text isn't
1402
- // going to form a placeholder sentinel. Sentinels are injected into
1403
- // outbound requests for role alternation and are sometimes echoed by
1404
- // the model — including an echo whose `\x00` guard arrived as a leading
1405
- // space — so the prefix and completion checks normalize edge whitespace
1406
- // and control bytes (the same normalization cleanAssistantContent and
1407
- // the display serializer use). Holding back partial prefixes keeps them
1408
- // off the live UI before they are stripped at completion. The buffer
1409
- // resets on every content_block_start.
1410
- let textBuffer = "";
1411
-
1412
- stream.on("text", (text) => {
1413
- textBuffer += text;
1414
- if (couldBePlaceholderSentinelPrefix(textBuffer)) {
1415
- return;
1416
- }
1417
- onEvent?.({ type: "text_delta", text: textBuffer });
1418
- textBuffer = "";
1419
- });
1405
+ : betas.length > 0
1406
+ ? (this.client.beta.messages.stream(
1407
+ {
1408
+ ...(streamParams as Record<string, unknown>),
1409
+ betas,
1410
+ } as Anthropic.Beta.Messages.MessageCreateParamsNonStreaming &
1411
+ Anthropic.Beta.Messages.MessageCreateParamsStreaming,
1412
+ requestOptions,
1413
+ ) as unknown as UnifiedStream)
1414
+ : (this.client.messages.stream(
1415
+ streamParams,
1416
+ requestOptions,
1417
+ ) as unknown as UnifiedStream);
1418
+
1419
+ // Shadow the streamed content blocks so a mid-stream SDK accumulator
1420
+ // failure on malformed tool-argument JSON can be salvaged instead of
1421
+ // discarding the whole response (see StreamContentShadow).
1422
+ const contentShadow = new StreamContentShadow();
1423
+
1424
+ // Buffer streaming text until it's clear the accumulated text isn't
1425
+ // going to form a placeholder sentinel. Sentinels are injected into
1426
+ // outbound requests for role alternation and are sometimes echoed by
1427
+ // the model — including an echo whose `\x00` guard arrived as a leading
1428
+ // space — so the prefix and completion checks normalize edge whitespace
1429
+ // and control bytes (the same normalization cleanAssistantContent and
1430
+ // the display serializer use). Holding back partial prefixes keeps them
1431
+ // off the live UI before they are stripped at completion. The buffer
1432
+ // resets on every content_block_start.
1433
+ let textBuffer = "";
1434
+
1435
+ stream.on("text", (text) => {
1436
+ textBuffer += text;
1437
+ if (couldBePlaceholderSentinelPrefix(textBuffer)) {
1438
+ return;
1439
+ }
1440
+ onEvent?.({ type: "text_delta", text: textBuffer });
1441
+ textBuffer = "";
1442
+ });
1420
1443
 
1421
- stream.on("thinking", (thinking) => {
1422
- onEvent?.({ type: "thinking_delta", thinking });
1423
- });
1444
+ stream.on("thinking", (thinking) => {
1445
+ onEvent?.({ type: "thinking_delta", thinking });
1446
+ });
1424
1447
 
1425
- // Track which tool is currently streaming so we can attribute inputJson deltas.
1426
- let currentStreamingToolName: string | undefined;
1427
- let currentStreamingToolUseId: string | undefined;
1428
- let accumulatedInputJson = "";
1429
- let lastInputJsonEmitMs = 0;
1430
- let pendingInputJsonFlush: ReturnType<typeof setTimeout> | undefined;
1431
-
1432
- // Anthropic streams `server_tool_use` block input via `input_json_delta`
1433
- // events (the block's own `input` field is `{}` at content_block_start).
1434
- // We accumulate the JSON separately from regular `tool_use` blocks so
1435
- // the daemon can read the resolved query when the paired
1436
- // `web_search_tool_result` arrives — without this, downstream activity
1437
- // metadata sees an empty query.
1438
- let currentServerToolUseId: string | undefined;
1439
- let accumulatedServerToolInputJson = "";
1440
- const resolvedServerToolInputs = new Map<
1441
- string,
1442
- Record<string, unknown>
1443
- >();
1444
-
1445
- stream.on("streamEvent", (event) => {
1446
- contentShadow.handleEvent(event as ShadowStreamEvent);
1447
- // Reset the text sentinel buffer at each content-block boundary.
1448
- // A new block starts fresh; at the end of a block, flush any
1449
- // buffered text that is NOT a complete sentinel, and drop it if
1450
- // it is one.
1451
- if (event.type === "content_block_start") {
1452
- textBuffer = "";
1453
- }
1454
- if (
1455
- event.type === "content_block_start" &&
1456
- event.content_block.type === "tool_use"
1457
- ) {
1458
- currentStreamingToolName = event.content_block.name;
1459
- currentStreamingToolUseId = event.content_block.id;
1460
- accumulatedInputJson = "";
1461
- lastInputJsonEmitMs = 0;
1462
- onEvent?.({
1463
- type: "tool_use_preview_start",
1464
- toolUseId: event.content_block.id,
1465
- toolName: event.content_block.name,
1466
- });
1467
- }
1468
- if (
1469
- event.type === "content_block_start" &&
1470
- event.content_block.type === "server_tool_use"
1471
- ) {
1472
- currentServerToolUseId = event.content_block.id;
1473
- accumulatedServerToolInputJson = "";
1474
- onEvent?.({
1475
- type: "server_tool_start",
1476
- name: event.content_block.name,
1477
- toolUseId: event.content_block.id,
1478
- input:
1479
- (event.content_block as { input?: Record<string, unknown> })
1480
- .input ?? {},
1481
- });
1482
- }
1483
- if (
1484
- event.type === "content_block_start" &&
1485
- event.content_block.type === "web_search_tool_result"
1486
- ) {
1487
- const block = event.content_block as {
1488
- tool_use_id: string;
1489
- content?:
1490
- | { type: "web_search_tool_result_error"; error_code?: string }
1491
- | unknown[];
1492
- };
1493
- const isError =
1494
- !Array.isArray(block.content) &&
1495
- block.content?.type === "web_search_tool_result_error";
1496
- const errorCode =
1497
- isError && !Array.isArray(block.content)
1498
- ? block.content?.error_code
1499
- : undefined;
1500
- const resolvedInput = resolvedServerToolInputs.get(
1501
- block.tool_use_id,
1502
- );
1503
- resolvedServerToolInputs.delete(block.tool_use_id);
1504
- onEvent?.({
1505
- type: "server_tool_complete",
1506
- toolUseId: block.tool_use_id,
1507
- isError: !!isError,
1508
- ...(Array.isArray(block.content)
1509
- ? { content: block.content }
1510
- : {}),
1511
- ...(resolvedInput ? { resolvedInput } : {}),
1512
- ...(errorCode ? { errorCode } : {}),
1513
- });
1514
- }
1515
- if (event.type === "content_block_stop") {
1516
- if (pendingInputJsonFlush) {
1517
- clearTimeout(pendingInputJsonFlush);
1518
- pendingInputJsonFlush = undefined;
1448
+ // Track which tool is currently streaming so we can attribute inputJson deltas.
1449
+ let currentStreamingToolName: string | undefined;
1450
+ let currentStreamingToolUseId: string | undefined;
1451
+ let accumulatedInputJson = "";
1452
+ let lastInputJsonEmitMs = 0;
1453
+ let pendingInputJsonFlush: ReturnType<typeof setTimeout> | undefined;
1454
+
1455
+ // Anthropic streams `server_tool_use` block input via `input_json_delta`
1456
+ // events (the block's own `input` field is `{}` at content_block_start).
1457
+ // We accumulate the JSON separately from regular `tool_use` blocks so
1458
+ // the daemon can read the resolved query when the paired
1459
+ // `web_search_tool_result` arrives — without this, downstream activity
1460
+ // metadata sees an empty query.
1461
+ let currentServerToolUseId: string | undefined;
1462
+ let accumulatedServerToolInputJson = "";
1463
+ const resolvedServerToolInputs = new Map<
1464
+ string,
1465
+ Record<string, unknown>
1466
+ >();
1467
+
1468
+ stream.on("streamEvent", (event) => {
1469
+ contentShadow.handleEvent(event as ShadowStreamEvent);
1470
+ // Reset the text sentinel buffer at each content-block boundary.
1471
+ // A new block starts fresh; at the end of a block, flush any
1472
+ // buffered text that is NOT a complete sentinel, and drop it if
1473
+ // it is one.
1474
+ if (event.type === "content_block_start") {
1475
+ textBuffer = "";
1519
1476
  }
1520
- if (currentStreamingToolName && accumulatedInputJson) {
1477
+ if (
1478
+ event.type === "content_block_start" &&
1479
+ event.content_block.type === "tool_use"
1480
+ ) {
1481
+ currentStreamingToolName = event.content_block.name;
1482
+ currentStreamingToolUseId = event.content_block.id;
1483
+ accumulatedInputJson = "";
1484
+ lastInputJsonEmitMs = 0;
1521
1485
  onEvent?.({
1522
- type: "input_json_delta",
1523
- toolName: currentStreamingToolName,
1524
- toolUseId: currentStreamingToolUseId!,
1525
- accumulatedJson: accumulatedInputJson,
1486
+ type: "tool_use_preview_start",
1487
+ toolUseId: event.content_block.id,
1488
+ toolName: event.content_block.name,
1526
1489
  });
1527
1490
  }
1528
- currentStreamingToolName = undefined;
1529
- currentStreamingToolUseId = undefined;
1530
- accumulatedInputJson = "";
1531
- // Finalize the resolved input for a `server_tool_use` block (e.g.
1532
- // the actual web-search query) so the paired `web_search_tool_result`
1533
- // emits `server_tool_complete` with `resolvedInput` populated.
1534
- if (currentServerToolUseId && accumulatedServerToolInputJson) {
1535
- try {
1536
- const parsed = JSON.parse(accumulatedServerToolInputJson);
1537
- if (parsed && typeof parsed === "object") {
1538
- resolvedServerToolInputs.set(
1539
- currentServerToolUseId,
1540
- parsed as Record<string, unknown>,
1541
- );
1542
- }
1543
- } catch {
1544
- // Malformed partial JSON — drop silently; downstream falls
1545
- // back to whatever was captured at server_tool_start.
1546
- }
1547
- }
1548
- currentServerToolUseId = undefined;
1549
- accumulatedServerToolInputJson = "";
1550
- // Flush residual text buffer unless it is a sentinel.
1551
1491
  if (
1552
- textBuffer.length > 0 &&
1553
- !isPlaceholderSentinelText(textBuffer)
1492
+ event.type === "content_block_start" &&
1493
+ event.content_block.type === "server_tool_use"
1554
1494
  ) {
1555
- onEvent?.({ type: "text_delta", text: textBuffer });
1495
+ currentServerToolUseId = event.content_block.id;
1496
+ accumulatedServerToolInputJson = "";
1497
+ onEvent?.({
1498
+ type: "server_tool_start",
1499
+ name: event.content_block.name,
1500
+ toolUseId: event.content_block.id,
1501
+ input:
1502
+ (event.content_block as { input?: Record<string, unknown> })
1503
+ .input ?? {},
1504
+ });
1556
1505
  }
1557
- textBuffer = "";
1558
- }
1559
- });
1560
-
1561
- stream.on("inputJson", (partialJson) => {
1562
- if (currentServerToolUseId) {
1563
- // Server-tool input (e.g. `web_search` query) — accumulate without
1564
- // emitting `input_json_delta`; the daemon only consumes the
1565
- // finalized value from `server_tool_complete.resolvedInput`.
1566
- accumulatedServerToolInputJson += partialJson;
1567
- return;
1568
- }
1569
- if (!currentStreamingToolName) {
1570
- return;
1571
- }
1572
- accumulatedInputJson += partialJson;
1573
- const now = Date.now();
1574
- if (now - lastInputJsonEmitMs >= 150) {
1575
- lastInputJsonEmitMs = now;
1576
- if (pendingInputJsonFlush) {
1577
- clearTimeout(pendingInputJsonFlush);
1578
- pendingInputJsonFlush = undefined;
1506
+ if (
1507
+ event.type === "content_block_start" &&
1508
+ event.content_block.type === "web_search_tool_result"
1509
+ ) {
1510
+ const block = event.content_block as {
1511
+ tool_use_id: string;
1512
+ content?:
1513
+ | {
1514
+ type: "web_search_tool_result_error";
1515
+ error_code?: string;
1516
+ }
1517
+ | unknown[];
1518
+ };
1519
+ const isError =
1520
+ !Array.isArray(block.content) &&
1521
+ block.content?.type === "web_search_tool_result_error";
1522
+ const errorCode =
1523
+ isError && !Array.isArray(block.content)
1524
+ ? block.content?.error_code
1525
+ : undefined;
1526
+ const resolvedInput = resolvedServerToolInputs.get(
1527
+ block.tool_use_id,
1528
+ );
1529
+ resolvedServerToolInputs.delete(block.tool_use_id);
1530
+ onEvent?.({
1531
+ type: "server_tool_complete",
1532
+ toolUseId: block.tool_use_id,
1533
+ isError: !!isError,
1534
+ ...(Array.isArray(block.content)
1535
+ ? { content: block.content }
1536
+ : {}),
1537
+ ...(resolvedInput ? { resolvedInput } : {}),
1538
+ ...(errorCode ? { errorCode } : {}),
1539
+ });
1579
1540
  }
1580
- onEvent?.({
1581
- type: "input_json_delta",
1582
- toolName: currentStreamingToolName,
1583
- toolUseId: currentStreamingToolUseId!,
1584
- accumulatedJson: accumulatedInputJson,
1585
- });
1586
- } else if (!pendingInputJsonFlush) {
1587
- const toolName = currentStreamingToolName;
1588
- const toolUseId = currentStreamingToolUseId!;
1589
- pendingInputJsonFlush = setTimeout(() => {
1590
- pendingInputJsonFlush = undefined;
1591
- lastInputJsonEmitMs = Date.now();
1592
- if (currentStreamingToolName === toolName) {
1541
+ if (event.type === "content_block_stop") {
1542
+ if (pendingInputJsonFlush) {
1543
+ clearTimeout(pendingInputJsonFlush);
1544
+ pendingInputJsonFlush = undefined;
1545
+ }
1546
+ if (currentStreamingToolName && accumulatedInputJson) {
1593
1547
  onEvent?.({
1594
1548
  type: "input_json_delta",
1595
- toolName,
1596
- toolUseId,
1549
+ toolName: currentStreamingToolName,
1550
+ toolUseId: currentStreamingToolUseId!,
1597
1551
  accumulatedJson: accumulatedInputJson,
1598
1552
  });
1599
1553
  }
1600
- }, 150);
1601
- }
1602
- });
1554
+ currentStreamingToolName = undefined;
1555
+ currentStreamingToolUseId = undefined;
1556
+ accumulatedInputJson = "";
1557
+ // Finalize the resolved input for a `server_tool_use` block (e.g.
1558
+ // the actual web-search query) so the paired `web_search_tool_result`
1559
+ // emits `server_tool_complete` with `resolvedInput` populated.
1560
+ if (currentServerToolUseId && accumulatedServerToolInputJson) {
1561
+ try {
1562
+ const parsed = JSON.parse(accumulatedServerToolInputJson);
1563
+ if (parsed && typeof parsed === "object") {
1564
+ resolvedServerToolInputs.set(
1565
+ currentServerToolUseId,
1566
+ parsed as Record<string, unknown>,
1567
+ );
1568
+ }
1569
+ } catch {
1570
+ // Malformed partial JSON — drop silently; downstream falls
1571
+ // back to whatever was captured at server_tool_start.
1572
+ }
1573
+ }
1574
+ currentServerToolUseId = undefined;
1575
+ accumulatedServerToolInputJson = "";
1576
+ // Flush residual text buffer unless it is a sentinel.
1577
+ if (
1578
+ textBuffer.length > 0 &&
1579
+ !isPlaceholderSentinelText(textBuffer)
1580
+ ) {
1581
+ onEvent?.({ type: "text_delta", text: textBuffer });
1582
+ }
1583
+ textBuffer = "";
1584
+ }
1585
+ });
1603
1586
 
1604
- try {
1605
- response = await stream.finalMessage();
1606
- } catch (error) {
1607
- // The SDK rejects finalMessage() the moment a tool_use block's
1608
- // argument JSON stops parsing, discarding everything it streamed.
1609
- // Salvage the observed prefix instead: completed blocks plus the
1610
- // malformed call wrapped under `_raw`, which the tool layer bounces
1611
- // back to the model as an error tool_result so it can self-correct
1612
- // in the next iteration. Any other rejection rethrows untouched.
1613
- const salvaged = contentShadow.salvage(error);
1614
- if (salvaged === undefined) {
1615
- throw error;
1587
+ stream.on("inputJson", (partialJson) => {
1588
+ if (currentServerToolUseId) {
1589
+ // Server-tool input (e.g. `web_search` query) — accumulate without
1590
+ // emitting `input_json_delta`; the daemon only consumes the
1591
+ // finalized value from `server_tool_complete.resolvedInput`.
1592
+ accumulatedServerToolInputJson += partialJson;
1593
+ return;
1594
+ }
1595
+ if (!currentStreamingToolName) {
1596
+ return;
1597
+ }
1598
+ accumulatedInputJson += partialJson;
1599
+ const now = Date.now();
1600
+ if (now - lastInputJsonEmitMs >= 150) {
1601
+ lastInputJsonEmitMs = now;
1602
+ if (pendingInputJsonFlush) {
1603
+ clearTimeout(pendingInputJsonFlush);
1604
+ pendingInputJsonFlush = undefined;
1605
+ }
1606
+ onEvent?.({
1607
+ type: "input_json_delta",
1608
+ toolName: currentStreamingToolName,
1609
+ toolUseId: currentStreamingToolUseId!,
1610
+ accumulatedJson: accumulatedInputJson,
1611
+ });
1612
+ } else if (!pendingInputJsonFlush) {
1613
+ const toolName = currentStreamingToolName;
1614
+ const toolUseId = currentStreamingToolUseId!;
1615
+ pendingInputJsonFlush = setTimeout(() => {
1616
+ pendingInputJsonFlush = undefined;
1617
+ lastInputJsonEmitMs = Date.now();
1618
+ if (currentStreamingToolName === toolName) {
1619
+ onEvent?.({
1620
+ type: "input_json_delta",
1621
+ toolName,
1622
+ toolUseId,
1623
+ accumulatedJson: accumulatedInputJson,
1624
+ });
1625
+ }
1626
+ }, 150);
1627
+ }
1628
+ });
1629
+
1630
+ try {
1631
+ return await stream.finalMessage();
1632
+ } catch (error) {
1633
+ // The SDK rejects finalMessage() the moment a tool_use block's
1634
+ // argument JSON stops parsing, discarding everything it streamed.
1635
+ // Salvage the observed prefix instead: completed blocks plus the
1636
+ // malformed call wrapped under `_raw`, which the tool layer bounces
1637
+ // back to the model as an error tool_result so it can self-correct
1638
+ // in the next iteration. Any other rejection rethrows untouched.
1639
+ const salvaged = contentShadow.salvage(error);
1640
+ if (salvaged === undefined) {
1641
+ throw error;
1642
+ }
1643
+ log.warn(
1644
+ {
1645
+ model: streamParams.model,
1646
+ toolName: salvaged.toolName,
1647
+ rawArgsLength: salvaged.rawArgsLength,
1648
+ },
1649
+ "Salvaged stream with unparseable tool-call arguments; returning _raw-wrapped tool call",
1650
+ );
1651
+ return {
1652
+ ...salvaged.message,
1653
+ model: streamParams.model,
1654
+ } as unknown as Anthropic.Message;
1616
1655
  }
1617
- log.warn(
1656
+ };
1657
+
1658
+ response = await streamOnce(params);
1659
+
1660
+ // `pause_turn`: the API paused a long-running server-tool turn (e.g.
1661
+ // web search) mid-execution. The documented resume flow is to re-send
1662
+ // the paused assistant content as-is in a follow-up request; the
1663
+ // model then continues the same turn. Each paused segment is kept so
1664
+ // the returned response carries the full turn's content and summed
1665
+ // usage. On cap-out the paused response returns as-is — downstream
1666
+ // deferred-tail preservation keeps its unresolved `server_tool_use`
1667
+ // blocks intact.
1668
+ while (
1669
+ response.stop_reason === "pause_turn" &&
1670
+ pausedSegments.length < MAX_PAUSE_TURN_CONTINUATIONS
1671
+ ) {
1672
+ pausedSegments.push(response);
1673
+ log.info(
1618
1674
  {
1619
1675
  model: params.model,
1620
- toolName: salvaged.toolName,
1621
- rawArgsLength: salvaged.rawArgsLength,
1676
+ continuation: pausedSegments.length,
1677
+ contentBlocks: response.content.length,
1622
1678
  },
1623
- "Salvaged stream with unparseable tool-call arguments; returning _raw-wrapped tool call",
1679
+ "Anthropic pause_turn — re-sending paused assistant content to resume the turn",
1680
+ );
1681
+ params = {
1682
+ ...params,
1683
+ messages: [
1684
+ ...params.messages,
1685
+ { role: "assistant" as const, content: response.content },
1686
+ ],
1687
+ };
1688
+ // Keep the 400-diagnostics dump in the catch block accurate for
1689
+ // continuation requests.
1690
+ sentMessages = params.messages;
1691
+ response = await streamOnce(params);
1692
+ }
1693
+ if (response.stop_reason === "pause_turn") {
1694
+ log.warn(
1695
+ { model: params.model, continuations: pausedSegments.length },
1696
+ "Anthropic pause_turn continuation cap reached; returning paused response as-is",
1624
1697
  );
1625
- response = {
1626
- ...salvaged.message,
1627
- model: params.model,
1628
- } as unknown as Anthropic.Message;
1629
1698
  }
1630
1699
  } finally {
1631
1700
  cleanupTimeout();
1632
1701
  }
1633
1702
 
1703
+ // A paused turn spans several billed requests: the assistant content is
1704
+ // every paused segment's content followed by the final response's, and
1705
+ // usage sums across all of them. With no paused segments this reduces
1706
+ // to the single response.
1707
+ const segments = [...pausedSegments, response];
1708
+ const sumOptional = (
1709
+ values: Array<number | null | undefined>,
1710
+ ): number | undefined =>
1711
+ values.some((v) => typeof v === "number")
1712
+ ? values.reduce<number>((acc, v) => acc + (v ?? 0), 0)
1713
+ : undefined;
1634
1714
  return {
1635
- content: response.content.map((block) =>
1636
- this.fromAnthropicBlock(block),
1637
- ),
1715
+ content: segments
1716
+ .flatMap((segment) => segment.content)
1717
+ .map((block) => this.fromAnthropicBlock(block)),
1638
1718
  model: response.model,
1639
1719
  resolvedEndpoint: this.client.baseURL,
1640
1720
  usage: {
1641
- inputTokens:
1642
- response.usage.input_tokens +
1643
- (response.usage.cache_creation_input_tokens ?? 0) +
1644
- (response.usage.cache_read_input_tokens ?? 0),
1645
- outputTokens: response.usage.output_tokens,
1646
- cacheCreationInputTokens:
1647
- response.usage.cache_creation_input_tokens ?? undefined,
1648
- cacheReadInputTokens:
1649
- response.usage.cache_read_input_tokens ?? undefined,
1721
+ inputTokens: segments.reduce(
1722
+ (acc, segment) =>
1723
+ acc +
1724
+ segment.usage.input_tokens +
1725
+ (segment.usage.cache_creation_input_tokens ?? 0) +
1726
+ (segment.usage.cache_read_input_tokens ?? 0),
1727
+ 0,
1728
+ ),
1729
+ outputTokens: segments.reduce(
1730
+ (acc, segment) => acc + segment.usage.output_tokens,
1731
+ 0,
1732
+ ),
1733
+ cacheCreationInputTokens: sumOptional(
1734
+ segments.map(
1735
+ (segment) => segment.usage.cache_creation_input_tokens,
1736
+ ),
1737
+ ),
1738
+ cacheReadInputTokens: sumOptional(
1739
+ segments.map((segment) => segment.usage.cache_read_input_tokens),
1740
+ ),
1650
1741
  },
1651
1742
  stopReason: response.stop_reason ?? "unknown",
1652
1743
  rawRequest: params,
@@ -706,7 +706,8 @@ export async function preflightResolvedConfig(
706
706
 
707
707
  switch (connection.auth.type) {
708
708
  case "api_key":
709
- case "oauth_subscription": {
709
+ case "oauth_subscription":
710
+ case "service_account": {
710
711
  const presence = await checkCredentialPresence(
711
712
  connection.auth.credential,
712
713
  );