@vellumai/assistant 0.4.16 → 0.4.18

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 (536) hide show
  1. package/Dockerfile +6 -6
  2. package/README.md +1 -2
  3. package/eslint.config.mjs +2 -2
  4. package/package.json +1 -1
  5. package/src/__tests__/access-request-decision.test.ts +128 -120
  6. package/src/__tests__/account-registry.test.ts +121 -110
  7. package/src/__tests__/active-skill-tools.test.ts +200 -172
  8. package/src/__tests__/actor-token-service.test.ts +341 -274
  9. package/src/__tests__/agent-loop-thinking.test.ts +28 -19
  10. package/src/__tests__/agent-loop.test.ts +798 -378
  11. package/src/__tests__/anthropic-provider.test.ts +405 -247
  12. package/src/__tests__/app-builder-tool-scripts.test.ts +97 -97
  13. package/src/__tests__/app-bundler.test.ts +112 -79
  14. package/src/__tests__/app-executors.test.ts +205 -178
  15. package/src/__tests__/app-git-history.test.ts +90 -73
  16. package/src/__tests__/app-git-service.test.ts +67 -53
  17. package/src/__tests__/app-open-proxy.test.ts +29 -25
  18. package/src/__tests__/approval-conversation-turn.test.ts +100 -81
  19. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +45 -17
  20. package/src/__tests__/approval-message-composer.test.ts +119 -119
  21. package/src/__tests__/approval-primitive.test.ts +264 -233
  22. package/src/__tests__/approval-routes-http.test.ts +4 -3
  23. package/src/__tests__/asset-materialize-tool.test.ts +250 -178
  24. package/src/__tests__/asset-search-tool.test.ts +251 -191
  25. package/src/__tests__/assistant-attachment-directive.test.ts +187 -142
  26. package/src/__tests__/assistant-attachments.test.ts +254 -186
  27. package/src/__tests__/assistant-event-hub.test.ts +105 -63
  28. package/src/__tests__/assistant-event.test.ts +66 -58
  29. package/src/__tests__/assistant-events-sse-hardening.test.ts +113 -73
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +78 -52
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +48 -45
  32. package/src/__tests__/assistant-feature-flags-integration.test.ts +118 -77
  33. package/src/__tests__/assistant-id-boundary-guard.test.ts +158 -104
  34. package/src/__tests__/attachments-store.test.ts +240 -183
  35. package/src/__tests__/attachments.test.ts +70 -62
  36. package/src/__tests__/audit-log-rotation.test.ts +50 -35
  37. package/src/__tests__/browser-fill-credential.test.ts +169 -101
  38. package/src/__tests__/browser-manager.test.ts +97 -75
  39. package/src/__tests__/browser-runtime-check.test.ts +16 -15
  40. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +12 -10
  41. package/src/__tests__/browser-skill-endstate.test.ts +97 -72
  42. package/src/__tests__/bundle-scanner.test.ts +47 -22
  43. package/src/__tests__/bundled-asset.test.ts +74 -47
  44. package/src/__tests__/call-constants.test.ts +19 -19
  45. package/src/__tests__/call-controller.test.ts +1073 -751
  46. package/src/__tests__/call-conversation-messages.test.ts +90 -65
  47. package/src/__tests__/call-domain.test.ts +149 -121
  48. package/src/__tests__/call-pointer-message-composer.test.ts +113 -83
  49. package/src/__tests__/call-pointer-messages.test.ts +213 -154
  50. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +9 -10
  51. package/src/__tests__/call-recovery.test.ts +232 -212
  52. package/src/__tests__/call-routes-http.test.ts +328 -279
  53. package/src/__tests__/call-start-guardian-guard.test.ts +32 -30
  54. package/src/__tests__/call-state-machine.test.ts +62 -51
  55. package/src/__tests__/call-state.test.ts +89 -75
  56. package/src/__tests__/call-store.test.ts +387 -316
  57. package/src/__tests__/callback-handoff-copy.test.ts +84 -82
  58. package/src/__tests__/canonical-guardian-store.test.ts +331 -280
  59. package/src/__tests__/channel-approval-routes.test.ts +1643 -1126
  60. package/src/__tests__/channel-approval.test.ts +139 -137
  61. package/src/__tests__/channel-approvals.test.ts +226 -182
  62. package/src/__tests__/channel-delivery-store.test.ts +232 -194
  63. package/src/__tests__/channel-guardian.test.ts +6 -3
  64. package/src/__tests__/channel-invite-transport.test.ts +107 -92
  65. package/src/__tests__/channel-policy.test.ts +42 -38
  66. package/src/__tests__/channel-readiness-service.test.ts +119 -102
  67. package/src/__tests__/channel-reply-delivery.test.ts +147 -118
  68. package/src/__tests__/channel-retry-sweep.test.ts +153 -110
  69. package/src/__tests__/checker.test.ts +3309 -1850
  70. package/src/__tests__/clarification-resolver.test.ts +91 -79
  71. package/src/__tests__/classifier.test.ts +64 -54
  72. package/src/__tests__/claude-code-skill-regression.test.ts +42 -37
  73. package/src/__tests__/claude-code-tool-profiles.test.ts +31 -29
  74. package/src/__tests__/clawhub.test.ts +92 -82
  75. package/src/__tests__/cli.test.ts +30 -30
  76. package/src/__tests__/clipboard.test.ts +53 -46
  77. package/src/__tests__/commit-guarantee.test.ts +59 -52
  78. package/src/__tests__/commit-message-enrichment-service.test.ts +203 -75
  79. package/src/__tests__/compaction.benchmark.test.ts +33 -31
  80. package/src/__tests__/computer-use-session-compaction.test.ts +60 -50
  81. package/src/__tests__/computer-use-session-lifecycle.test.ts +145 -117
  82. package/src/__tests__/computer-use-session-working-dir.test.ts +62 -48
  83. package/src/__tests__/computer-use-skill-baseline.test.ts +22 -19
  84. package/src/__tests__/computer-use-skill-endstate.test.ts +45 -31
  85. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +121 -88
  86. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +65 -42
  87. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +33 -18
  88. package/src/__tests__/computer-use-tools.test.ts +121 -98
  89. package/src/__tests__/config-schema.test.ts +443 -347
  90. package/src/__tests__/config-watcher.test.ts +96 -81
  91. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +148 -133
  92. package/src/__tests__/conflict-intent-tokenization.test.ts +96 -78
  93. package/src/__tests__/conflict-policy.test.ts +151 -80
  94. package/src/__tests__/conflict-store.test.ts +203 -157
  95. package/src/__tests__/connection-policy.test.ts +89 -59
  96. package/src/__tests__/contacts-tools.test.ts +247 -178
  97. package/src/__tests__/context-memory-e2e.test.ts +306 -214
  98. package/src/__tests__/context-token-estimator.test.ts +114 -74
  99. package/src/__tests__/context-window-manager.test.ts +269 -167
  100. package/src/__tests__/contradiction-checker.test.ts +161 -135
  101. package/src/__tests__/conversation-attention-store.test.ts +350 -290
  102. package/src/__tests__/conversation-attention-telegram.test.ts +156 -114
  103. package/src/__tests__/conversation-pairing.test.ts +220 -113
  104. package/src/__tests__/conversation-routes-guardian-reply.test.ts +164 -104
  105. package/src/__tests__/conversation-routes.test.ts +71 -41
  106. package/src/__tests__/conversation-store.test.ts +390 -235
  107. package/src/__tests__/credential-broker-browser-fill.test.ts +325 -250
  108. package/src/__tests__/credential-broker-server-use.test.ts +283 -243
  109. package/src/__tests__/credential-broker.test.ts +128 -74
  110. package/src/__tests__/credential-host-pattern-match.test.ts +64 -44
  111. package/src/__tests__/credential-metadata-store.test.ts +360 -311
  112. package/src/__tests__/credential-policy-validate.test.ts +81 -65
  113. package/src/__tests__/credential-resolve.test.ts +212 -145
  114. package/src/__tests__/credential-security-e2e.test.ts +144 -103
  115. package/src/__tests__/credential-security-invariants.test.ts +253 -208
  116. package/src/__tests__/credential-selection.test.ts +254 -146
  117. package/src/__tests__/credential-vault-unit.test.ts +531 -341
  118. package/src/__tests__/credential-vault.test.ts +761 -484
  119. package/src/__tests__/daemon-assistant-events.test.ts +91 -66
  120. package/src/__tests__/daemon-lifecycle.test.ts +258 -190
  121. package/src/__tests__/daemon-server-session-init.test.ts +257 -191
  122. package/src/__tests__/date-context.test.ts +314 -249
  123. package/src/__tests__/db-migration-rollback.test.ts +259 -130
  124. package/src/__tests__/db-schedule-syntax-migration.test.ts +78 -41
  125. package/src/__tests__/delete-managed-skill-tool.test.ts +77 -53
  126. package/src/__tests__/deterministic-verification-control-plane.test.ts +183 -135
  127. package/src/__tests__/dictation-mode-detection.test.ts +77 -55
  128. package/src/__tests__/dictation-profile-store.test.ts +70 -56
  129. package/src/__tests__/dictation-text-processing.test.ts +53 -35
  130. package/src/__tests__/diff.test.ts +102 -98
  131. package/src/__tests__/domain-normalize.test.ts +54 -54
  132. package/src/__tests__/domain-policy.test.ts +71 -55
  133. package/src/__tests__/dynamic-page-surface.test.ts +31 -33
  134. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +69 -69
  135. package/src/__tests__/edit-engine.test.ts +56 -56
  136. package/src/__tests__/elevenlabs-client.test.ts +117 -91
  137. package/src/__tests__/elevenlabs-config.test.ts +32 -31
  138. package/src/__tests__/email-classifier.test.ts +15 -12
  139. package/src/__tests__/email-cli.test.ts +121 -108
  140. package/src/__tests__/emit-signal-routing-intent.test.ts +76 -69
  141. package/src/__tests__/encrypted-store.test.ts +180 -154
  142. package/src/__tests__/entity-extractor.test.ts +108 -87
  143. package/src/__tests__/entity-search.test.ts +664 -258
  144. package/src/__tests__/ephemeral-permissions.test.ts +224 -188
  145. package/src/__tests__/event-bus.test.ts +81 -77
  146. package/src/__tests__/extract-email.test.ts +51 -0
  147. package/src/__tests__/file-edit-tool.test.ts +62 -44
  148. package/src/__tests__/file-ops-service.test.ts +131 -114
  149. package/src/__tests__/file-read-tool.test.ts +48 -31
  150. package/src/__tests__/file-write-tool.test.ts +43 -37
  151. package/src/__tests__/filesystem-tools.test.ts +238 -209
  152. package/src/__tests__/followup-tools.test.ts +237 -162
  153. package/src/__tests__/forbidden-legacy-symbols.test.ts +19 -20
  154. package/src/__tests__/frontmatter.test.ts +96 -81
  155. package/src/__tests__/fuzzy-match-property.test.ts +75 -81
  156. package/src/__tests__/fuzzy-match.test.ts +71 -65
  157. package/src/__tests__/gateway-client-managed-outbound.test.ts +76 -57
  158. package/src/__tests__/gateway-only-enforcement.test.ts +467 -369
  159. package/src/__tests__/gateway-only-guard.test.ts +54 -56
  160. package/src/__tests__/gemini-image-service.test.ts +113 -100
  161. package/src/__tests__/gemini-provider.test.ts +297 -220
  162. package/src/__tests__/get-weather.test.ts +188 -114
  163. package/src/__tests__/gmail-integration.test.ts +47 -46
  164. package/src/__tests__/guardian-action-conversation-turn.test.ts +226 -171
  165. package/src/__tests__/guardian-action-copy-generator.test.ts +111 -93
  166. package/src/__tests__/guardian-action-followup-executor.test.ts +215 -151
  167. package/src/__tests__/guardian-action-followup-store.test.ts +199 -167
  168. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +297 -250
  169. package/src/__tests__/guardian-action-late-reply.test.ts +462 -316
  170. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +23 -18
  171. package/src/__tests__/guardian-action-store.test.ts +158 -109
  172. package/src/__tests__/guardian-action-sweep.test.ts +114 -100
  173. package/src/__tests__/guardian-actions-endpoint.test.ts +440 -256
  174. package/src/__tests__/guardian-control-plane-policy.test.ts +497 -331
  175. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +217 -215
  176. package/src/__tests__/guardian-dispatch.test.ts +316 -256
  177. package/src/__tests__/guardian-grant-minting.test.ts +247 -178
  178. package/src/__tests__/guardian-outbound-http.test.ts +337 -209
  179. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +99 -96
  180. package/src/__tests__/guardian-question-copy.test.ts +17 -17
  181. package/src/__tests__/guardian-question-mode.test.ts +134 -100
  182. package/src/__tests__/guardian-routing-invariants.test.ts +679 -613
  183. package/src/__tests__/guardian-routing-state.test.ts +256 -209
  184. package/src/__tests__/guardian-verification-intent-routing.test.ts +94 -88
  185. package/src/__tests__/guardian-verification-voice-binding.test.ts +47 -41
  186. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +0 -1
  187. package/src/__tests__/handle-user-message-secret-resume.test.ts +43 -21
  188. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +92 -76
  189. package/src/__tests__/handlers-cu-observation-blob.test.ts +103 -70
  190. package/src/__tests__/handlers-ipc-blob-probe.test.ts +77 -51
  191. package/src/__tests__/handlers-slack-config.test.ts +63 -54
  192. package/src/__tests__/handlers-task-submit-slash.test.ts +18 -18
  193. package/src/__tests__/handlers-telegram-config.test.ts +662 -329
  194. package/src/__tests__/handlers-twitter-config.test.ts +525 -298
  195. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +270 -195
  196. package/src/__tests__/headless-browser-interactions.test.ts +444 -280
  197. package/src/__tests__/headless-browser-navigate.test.ts +116 -79
  198. package/src/__tests__/headless-browser-read-tools.test.ts +123 -86
  199. package/src/__tests__/headless-browser-snapshot.test.ts +71 -56
  200. package/src/__tests__/heartbeat-service.test.ts +76 -58
  201. package/src/__tests__/history-repair-observability.test.ts +14 -14
  202. package/src/__tests__/history-repair.test.ts +171 -167
  203. package/src/__tests__/home-base-bootstrap.test.ts +30 -27
  204. package/src/__tests__/hooks-blocking.test.ts +86 -37
  205. package/src/__tests__/hooks-cli.test.ts +104 -68
  206. package/src/__tests__/hooks-config.test.ts +81 -43
  207. package/src/__tests__/hooks-discovery.test.ts +106 -96
  208. package/src/__tests__/hooks-integration.test.ts +78 -72
  209. package/src/__tests__/hooks-manager.test.ts +99 -61
  210. package/src/__tests__/hooks-runner.test.ts +94 -71
  211. package/src/__tests__/hooks-settings.test.ts +69 -64
  212. package/src/__tests__/hooks-templates.test.ts +85 -54
  213. package/src/__tests__/hooks-ts-runner.test.ts +82 -45
  214. package/src/__tests__/hooks-watch.test.ts +32 -22
  215. package/src/__tests__/host-file-edit-tool.test.ts +190 -148
  216. package/src/__tests__/host-file-read-tool.test.ts +86 -63
  217. package/src/__tests__/host-file-write-tool.test.ts +98 -64
  218. package/src/__tests__/host-shell-tool.test.ts +342 -233
  219. package/src/__tests__/inbound-invite-redemption.test.ts +194 -152
  220. package/src/__tests__/ingress-member-store.test.ts +163 -159
  221. package/src/__tests__/ingress-reconcile.test.ts +183 -142
  222. package/src/__tests__/ingress-routes-http.test.ts +441 -356
  223. package/src/__tests__/ingress-url-consistency.test.ts +125 -64
  224. package/src/__tests__/integration-status.test.ts +93 -73
  225. package/src/__tests__/intent-routing.test.ts +148 -118
  226. package/src/__tests__/invite-redemption-service.test.ts +163 -121
  227. package/src/__tests__/ipc-blob-store.test.ts +104 -91
  228. package/src/__tests__/ipc-contract-inventory.test.ts +27 -15
  229. package/src/__tests__/ipc-contract.test.ts +24 -23
  230. package/src/__tests__/ipc-protocol.test.ts +52 -46
  231. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +61 -50
  232. package/src/__tests__/ipc-snapshot.test.ts +1135 -1056
  233. package/src/__tests__/ipc-validate.test.ts +240 -179
  234. package/src/__tests__/key-migration.test.ts +123 -90
  235. package/src/__tests__/keychain.test.ts +150 -123
  236. package/src/__tests__/lifecycle-docs-guard.test.ts +65 -64
  237. package/src/__tests__/llm-usage-store.test.ts +112 -87
  238. package/src/__tests__/managed-skill-lifecycle.test.ts +147 -108
  239. package/src/__tests__/managed-store.test.ts +411 -360
  240. package/src/__tests__/mcp-cli.test.ts +189 -123
  241. package/src/__tests__/mcp-health-check.test.ts +26 -21
  242. package/src/__tests__/media-generate-image.test.ts +122 -99
  243. package/src/__tests__/media-reuse-story.e2e.test.ts +282 -214
  244. package/src/__tests__/media-visibility-policy.test.ts +86 -38
  245. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +146 -100
  246. package/src/__tests__/memory-lifecycle-e2e.test.ts +385 -297
  247. package/src/__tests__/memory-query-builder.test.ts +32 -33
  248. package/src/__tests__/memory-recall-quality.test.ts +761 -407
  249. package/src/__tests__/memory-regressions.experimental.test.ts +443 -380
  250. package/src/__tests__/memory-regressions.test.ts +3725 -2642
  251. package/src/__tests__/memory-retrieval-budget.test.ts +7 -8
  252. package/src/__tests__/memory-retrieval.benchmark.test.ts +144 -109
  253. package/src/__tests__/memory-upsert-concurrency.test.ts +292 -201
  254. package/src/__tests__/messaging-send-tool.test.ts +36 -29
  255. package/src/__tests__/migration-cli-flows.test.ts +69 -53
  256. package/src/__tests__/migration-ordering.test.ts +103 -86
  257. package/src/__tests__/mime-builder.test.ts +55 -32
  258. package/src/__tests__/mock-signup-server.test.ts +384 -246
  259. package/src/__tests__/model-intents.test.ts +61 -37
  260. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +9 -12
  261. package/src/__tests__/no-is-trusted-guard.test.ts +24 -21
  262. package/src/__tests__/non-member-access-request.test.ts +294 -249
  263. package/src/__tests__/notification-broadcaster.test.ts +99 -81
  264. package/src/__tests__/notification-decision-fallback.test.ts +223 -178
  265. package/src/__tests__/notification-decision-strategy.test.ts +375 -337
  266. package/src/__tests__/notification-deep-link.test.ts +67 -61
  267. package/src/__tests__/notification-guardian-path.test.ts +248 -206
  268. package/src/__tests__/notification-routing-intent.test.ts +166 -93
  269. package/src/__tests__/notification-telegram-adapter.test.ts +60 -46
  270. package/src/__tests__/notification-thread-candidate-validation.test.ts +78 -75
  271. package/src/__tests__/notification-thread-candidates.test.ts +64 -61
  272. package/src/__tests__/oauth-callback-registry.test.ts +40 -30
  273. package/src/__tests__/oauth-connect-handler.test.ts +109 -89
  274. package/src/__tests__/oauth-scope-policy.test.ts +63 -55
  275. package/src/__tests__/oauth2-gateway-transport.test.ts +252 -174
  276. package/src/__tests__/onboarding-starter-tasks.test.ts +93 -89
  277. package/src/__tests__/onboarding-template-contract.test.ts +93 -94
  278. package/src/__tests__/openai-provider.test.ts +366 -274
  279. package/src/__tests__/pairing-concurrent.test.ts +18 -12
  280. package/src/__tests__/pairing-routes.test.ts +45 -41
  281. package/src/__tests__/parallel-tool.benchmark.test.ts +108 -58
  282. package/src/__tests__/parser.test.ts +316 -226
  283. package/src/__tests__/path-classifier.test.ts +24 -25
  284. package/src/__tests__/path-policy.test.ts +187 -147
  285. package/src/__tests__/phone.test.ts +36 -36
  286. package/src/__tests__/platform-move-helper.test.ts +48 -40
  287. package/src/__tests__/platform-socket-path.test.ts +23 -24
  288. package/src/__tests__/platform-workspace-migration.test.ts +464 -414
  289. package/src/__tests__/platform.test.ts +61 -53
  290. package/src/__tests__/playbook-execution.test.ts +397 -265
  291. package/src/__tests__/playbook-tools.test.ts +267 -196
  292. package/src/__tests__/prebuilt-home-base-seed.test.ts +30 -27
  293. package/src/__tests__/pricing.test.ts +316 -136
  294. package/src/__tests__/profile-compiler.test.ts +206 -188
  295. package/src/__tests__/provider-commit-message-generator.test.ts +114 -106
  296. package/src/__tests__/provider-error-scenarios.test.ts +212 -158
  297. package/src/__tests__/provider-fail-open-selection.test.ts +51 -44
  298. package/src/__tests__/provider-registry-ollama.test.ts +13 -9
  299. package/src/__tests__/provider-streaming.benchmark.test.ts +232 -183
  300. package/src/__tests__/proxy-approval-callback.test.ts +180 -119
  301. package/src/__tests__/public-ingress-urls.test.ts +112 -94
  302. package/src/__tests__/qdrant-manager.test.ts +147 -98
  303. package/src/__tests__/ratelimit.test.ts +152 -82
  304. package/src/__tests__/recording-handler.test.ts +273 -151
  305. package/src/__tests__/recording-intent-fallback.test.ts +94 -75
  306. package/src/__tests__/recording-intent-handler.test.ts +422 -292
  307. package/src/__tests__/recording-intent.test.ts +578 -379
  308. package/src/__tests__/recording-state-machine.test.ts +530 -316
  309. package/src/__tests__/recurrence-engine-rruleset.test.ts +150 -92
  310. package/src/__tests__/recurrence-engine.test.ts +81 -41
  311. package/src/__tests__/recurrence-types.test.ts +63 -44
  312. package/src/__tests__/relay-server.test.ts +2131 -1602
  313. package/src/__tests__/reminder-store.test.ts +158 -80
  314. package/src/__tests__/reminder.test.ts +113 -109
  315. package/src/__tests__/remote-skill-policy.test.ts +96 -72
  316. package/src/__tests__/request-file-tool.test.ts +74 -67
  317. package/src/__tests__/response-tier.test.ts +131 -74
  318. package/src/__tests__/runtime-attachment-metadata.test.ts +107 -70
  319. package/src/__tests__/runtime-events-sse-parity.test.ts +167 -145
  320. package/src/__tests__/runtime-events-sse.test.ts +67 -51
  321. package/src/__tests__/sandbox-diagnostics.test.ts +66 -56
  322. package/src/__tests__/sandbox-host-parity.test.ts +377 -301
  323. package/src/__tests__/scaffold-managed-skill-tool.test.ts +213 -161
  324. package/src/__tests__/schedule-store.test.ts +268 -205
  325. package/src/__tests__/schedule-tools.test.ts +702 -524
  326. package/src/__tests__/scheduler-recurrence.test.ts +240 -130
  327. package/src/__tests__/scoped-approval-grants.test.ts +258 -168
  328. package/src/__tests__/scoped-grant-security-matrix.test.ts +160 -146
  329. package/src/__tests__/script-proxy-certs.test.ts +38 -35
  330. package/src/__tests__/script-proxy-connect-tunnel.test.ts +71 -46
  331. package/src/__tests__/script-proxy-decision-trace.test.ts +161 -84
  332. package/src/__tests__/script-proxy-http-forwarder.test.ts +146 -129
  333. package/src/__tests__/script-proxy-injection-runtime.test.ts +139 -113
  334. package/src/__tests__/script-proxy-mitm-handler.test.ts +226 -142
  335. package/src/__tests__/script-proxy-policy-runtime.test.ts +126 -86
  336. package/src/__tests__/script-proxy-policy.test.ts +308 -153
  337. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +74 -62
  338. package/src/__tests__/script-proxy-router.test.ts +111 -77
  339. package/src/__tests__/script-proxy-session-manager.test.ts +156 -113
  340. package/src/__tests__/script-proxy-session-runtime.test.ts +28 -24
  341. package/src/__tests__/secret-allowlist.test.ts +105 -90
  342. package/src/__tests__/secret-ingress-handler.test.ts +41 -30
  343. package/src/__tests__/secret-onetime-send.test.ts +67 -50
  344. package/src/__tests__/secret-prompt-log-hygiene.test.ts +35 -31
  345. package/src/__tests__/secret-response-routing.test.ts +50 -41
  346. package/src/__tests__/secret-scanner-executor.test.ts +152 -111
  347. package/src/__tests__/secret-scanner.test.ts +495 -413
  348. package/src/__tests__/secure-keys.test.ts +132 -121
  349. package/src/__tests__/send-endpoint-busy.test.ts +313 -232
  350. package/src/__tests__/send-notification-tool.test.ts +43 -42
  351. package/src/__tests__/sensitive-output-placeholders.test.ts +72 -64
  352. package/src/__tests__/sequence-store.test.ts +335 -167
  353. package/src/__tests__/server-history-render.test.ts +341 -202
  354. package/src/__tests__/session-abort-tool-results.test.ts +133 -70
  355. package/src/__tests__/session-approval-overrides.test.ts +93 -91
  356. package/src/__tests__/session-confirmation-signals.test.ts +252 -160
  357. package/src/__tests__/session-conflict-gate.test.ts +775 -585
  358. package/src/__tests__/session-error.test.ts +222 -191
  359. package/src/__tests__/session-evictor.test.ts +79 -62
  360. package/src/__tests__/session-init.benchmark.test.ts +170 -108
  361. package/src/__tests__/session-load-history-repair.test.ts +273 -139
  362. package/src/__tests__/session-messaging-secret-redirect.test.ts +130 -90
  363. package/src/__tests__/session-pre-run-repair.test.ts +106 -59
  364. package/src/__tests__/session-profile-injection.test.ts +198 -130
  365. package/src/__tests__/session-provider-retry-repair.test.ts +223 -141
  366. package/src/__tests__/session-queue.test.ts +624 -321
  367. package/src/__tests__/session-runtime-assembly.test.ts +425 -329
  368. package/src/__tests__/session-runtime-workspace.test.ts +69 -61
  369. package/src/__tests__/session-skill-tools.test.ts +973 -678
  370. package/src/__tests__/session-slash-known.test.ts +185 -133
  371. package/src/__tests__/session-slash-queue.test.ts +147 -81
  372. package/src/__tests__/session-slash-unknown.test.ts +135 -90
  373. package/src/__tests__/session-surfaces-task-progress.test.ts +122 -87
  374. package/src/__tests__/session-tool-setup-app-refresh.test.ts +338 -177
  375. package/src/__tests__/session-tool-setup-memory-scope.test.ts +63 -40
  376. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +60 -37
  377. package/src/__tests__/session-tool-setup-tools-disabled.test.ts +28 -26
  378. package/src/__tests__/session-undo.test.ts +43 -30
  379. package/src/__tests__/session-workspace-cache-state.test.ts +108 -67
  380. package/src/__tests__/session-workspace-injection.test.ts +245 -117
  381. package/src/__tests__/session-workspace-tool-tracking.test.ts +260 -93
  382. package/src/__tests__/shared-filesystem-errors.test.ts +47 -47
  383. package/src/__tests__/shell-credential-ref.test.ts +126 -90
  384. package/src/__tests__/shell-identity.test.ts +134 -111
  385. package/src/__tests__/shell-parser-fuzz.test.ts +263 -179
  386. package/src/__tests__/shell-parser-property.test.ts +435 -288
  387. package/src/__tests__/shell-tool-proxy-mode.test.ts +142 -70
  388. package/src/__tests__/size-guard.test.ts +42 -44
  389. package/src/__tests__/skill-feature-flags-integration.test.ts +79 -52
  390. package/src/__tests__/skill-feature-flags.test.ts +75 -47
  391. package/src/__tests__/skill-include-graph.test.ts +143 -148
  392. package/src/__tests__/skill-load-feature-flag.test.ts +94 -59
  393. package/src/__tests__/skill-load-tool.test.ts +371 -199
  394. package/src/__tests__/skill-projection-feature-flag.test.ts +131 -88
  395. package/src/__tests__/skill-projection.benchmark.test.ts +93 -65
  396. package/src/__tests__/skill-script-runner-host.test.ts +460 -250
  397. package/src/__tests__/skill-script-runner-sandbox.test.ts +168 -108
  398. package/src/__tests__/skill-script-runner.test.ts +115 -74
  399. package/src/__tests__/skill-tool-factory.test.ts +140 -96
  400. package/src/__tests__/skill-tool-manifest.test.ts +306 -210
  401. package/src/__tests__/skill-version-hash.test.ts +70 -56
  402. package/src/__tests__/skills.test.ts +0 -1
  403. package/src/__tests__/slack-channel-config.test.ts +127 -84
  404. package/src/__tests__/slack-skill.test.ts +60 -47
  405. package/src/__tests__/slash-commands-catalog.test.ts +37 -31
  406. package/src/__tests__/slash-commands-parser.test.ts +71 -64
  407. package/src/__tests__/slash-commands-resolver.test.ts +143 -107
  408. package/src/__tests__/slash-commands-rewrite.test.ts +22 -22
  409. package/src/__tests__/sms-messaging-provider.test.ts +74 -47
  410. package/src/__tests__/speaker-identification.test.ts +28 -25
  411. package/src/__tests__/starter-bundle.test.ts +27 -23
  412. package/src/__tests__/starter-task-flow.test.ts +67 -52
  413. package/src/__tests__/subagent-manager-notify.test.ts +154 -108
  414. package/src/__tests__/subagent-tools.test.ts +311 -270
  415. package/src/__tests__/subagent-types.test.ts +40 -40
  416. package/src/__tests__/surface-mutex-cleanup.test.ts +42 -30
  417. package/src/__tests__/swarm-dag-pathological.test.ts +122 -111
  418. package/src/__tests__/swarm-orchestrator.test.ts +135 -101
  419. package/src/__tests__/swarm-plan-validator.test.ts +125 -73
  420. package/src/__tests__/swarm-recursion.test.ts +58 -46
  421. package/src/__tests__/swarm-router-planner.test.ts +99 -74
  422. package/src/__tests__/swarm-session-integration.test.ts +148 -91
  423. package/src/__tests__/swarm-tool.test.ts +65 -45
  424. package/src/__tests__/swarm-worker-backend.test.ts +59 -45
  425. package/src/__tests__/swarm-worker-runner.test.ts +133 -118
  426. package/src/__tests__/system-prompt.test.ts +290 -256
  427. package/src/__tests__/task-compiler.test.ts +176 -120
  428. package/src/__tests__/task-management-tools.test.ts +561 -456
  429. package/src/__tests__/task-memory-cleanup.test.ts +627 -362
  430. package/src/__tests__/task-runner.test.ts +117 -94
  431. package/src/__tests__/task-scheduler.test.ts +113 -84
  432. package/src/__tests__/task-tools.test.ts +349 -264
  433. package/src/__tests__/terminal-sandbox.test.ts +138 -108
  434. package/src/__tests__/terminal-tools.test.ts +350 -305
  435. package/src/__tests__/thread-seed-composer.test.ts +307 -180
  436. package/src/__tests__/tool-approval-handler.test.ts +238 -137
  437. package/src/__tests__/tool-audit-listener.test.ts +69 -69
  438. package/src/__tests__/tool-domain-event-publisher.test.ts +142 -132
  439. package/src/__tests__/tool-execution-abort-cleanup.test.ts +153 -146
  440. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +136 -105
  441. package/src/__tests__/tool-executor-lifecycle-events.test.ts +355 -239
  442. package/src/__tests__/tool-executor-redaction.test.ts +112 -109
  443. package/src/__tests__/tool-executor-shell-integration.test.ts +130 -79
  444. package/src/__tests__/tool-executor.test.ts +1274 -674
  445. package/src/__tests__/tool-grant-request-escalation.test.ts +401 -283
  446. package/src/__tests__/tool-metrics-listener.test.ts +97 -85
  447. package/src/__tests__/tool-notification-listener.test.ts +42 -25
  448. package/src/__tests__/tool-permission-simulate-handler.test.ts +137 -113
  449. package/src/__tests__/tool-policy.test.ts +44 -25
  450. package/src/__tests__/tool-profiling-listener.test.ts +99 -93
  451. package/src/__tests__/tool-result-truncation.test.ts +5 -4
  452. package/src/__tests__/tool-trace-listener.test.ts +131 -111
  453. package/src/__tests__/top-level-renderer.test.ts +62 -58
  454. package/src/__tests__/top-level-scanner.test.ts +68 -64
  455. package/src/__tests__/trace-emitter.test.ts +56 -56
  456. package/src/__tests__/trust-context-guards.test.ts +65 -65
  457. package/src/__tests__/trust-store.test.ts +1239 -806
  458. package/src/__tests__/trusted-contact-approval-notifier.test.ts +339 -275
  459. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +484 -373
  460. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +264 -241
  461. package/src/__tests__/trusted-contact-multichannel.test.ts +182 -142
  462. package/src/__tests__/trusted-contact-verification.test.ts +251 -231
  463. package/src/__tests__/turn-commit.test.ts +259 -200
  464. package/src/__tests__/twilio-config.test.ts +49 -41
  465. package/src/__tests__/twilio-provider.test.ts +140 -126
  466. package/src/__tests__/twilio-rest.test.ts +22 -18
  467. package/src/__tests__/twilio-routes-elevenlabs.test.ts +188 -162
  468. package/src/__tests__/twilio-routes-twiml.test.ts +55 -55
  469. package/src/__tests__/twilio-routes.test.ts +389 -281
  470. package/src/__tests__/twitter-auth-handler.test.ts +184 -139
  471. package/src/__tests__/twitter-cli-error-shaping.test.ts +88 -73
  472. package/src/__tests__/twitter-cli-routing.test.ts +146 -99
  473. package/src/__tests__/twitter-oauth-client.test.ts +82 -65
  474. package/src/__tests__/update-bulletin-format.test.ts +69 -66
  475. package/src/__tests__/update-bulletin-state.test.ts +66 -60
  476. package/src/__tests__/update-bulletin.test.ts +150 -114
  477. package/src/__tests__/update-template-contract.test.ts +15 -10
  478. package/src/__tests__/url-safety.test.ts +288 -265
  479. package/src/__tests__/user-reference.test.ts +32 -32
  480. package/src/__tests__/view-image-tool.test.ts +118 -96
  481. package/src/__tests__/voice-invite-redemption.test.ts +111 -106
  482. package/src/__tests__/voice-quality.test.ts +117 -102
  483. package/src/__tests__/voice-scoped-grant-consumer.test.ts +204 -146
  484. package/src/__tests__/voice-session-bridge.test.ts +351 -216
  485. package/src/__tests__/weather-skill-regression.test.ts +170 -120
  486. package/src/__tests__/web-fetch.test.ts +664 -526
  487. package/src/__tests__/web-search.test.ts +379 -213
  488. package/src/__tests__/work-item-output.test.ts +90 -53
  489. package/src/__tests__/workspace-git-service.test.ts +437 -356
  490. package/src/__tests__/workspace-heartbeat-service.test.ts +125 -91
  491. package/src/__tests__/workspace-lifecycle.test.ts +98 -64
  492. package/src/__tests__/workspace-policy.test.ts +139 -71
  493. package/src/commands/__tests__/cc-command-registry.test.ts +142 -134
  494. package/src/config/__tests__/feature-flag-registry-guard.test.ts +48 -39
  495. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +44 -4
  496. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -1
  497. package/src/config/bundled-skills/messaging/SKILL.md +9 -7
  498. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +15 -5
  499. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -5
  500. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +11 -7
  501. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +34 -32
  502. package/src/config/bundled-tool-registry.ts +2 -0
  503. package/src/config/env.ts +38 -29
  504. package/src/daemon/handlers/skills.ts +18 -10
  505. package/src/daemon/ipc-contract/messages.ts +1 -0
  506. package/src/daemon/ipc-contract/surfaces.ts +7 -1
  507. package/src/daemon/session-agent-loop-handlers.ts +5 -0
  508. package/src/daemon/session-agent-loop.ts +1 -1
  509. package/src/daemon/session-process.ts +1 -1
  510. package/src/daemon/session-surfaces.ts +42 -2
  511. package/src/memory/db-connection.ts +16 -10
  512. package/src/messaging/providers/gmail/adapter.ts +10 -3
  513. package/src/messaging/providers/gmail/client.ts +280 -72
  514. package/src/runtime/auth/__tests__/context.test.ts +75 -65
  515. package/src/runtime/auth/__tests__/credential-service.test.ts +137 -114
  516. package/src/runtime/auth/__tests__/guard-tests.test.ts +84 -90
  517. package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +40 -40
  518. package/src/runtime/auth/__tests__/middleware.test.ts +80 -74
  519. package/src/runtime/auth/__tests__/policy.test.ts +9 -9
  520. package/src/runtime/auth/__tests__/route-policy.test.ts +76 -65
  521. package/src/runtime/auth/__tests__/scopes.test.ts +68 -60
  522. package/src/runtime/auth/__tests__/subject.test.ts +54 -54
  523. package/src/runtime/auth/__tests__/token-service.test.ts +115 -108
  524. package/src/runtime/auth/scopes.ts +3 -0
  525. package/src/runtime/auth/token-service.ts +78 -48
  526. package/src/runtime/auth/types.ts +2 -1
  527. package/src/runtime/http-server.ts +2 -1
  528. package/src/security/secure-keys.ts +103 -53
  529. package/src/sequence/reply-matcher.ts +10 -6
  530. package/src/skills/frontmatter.ts +9 -6
  531. package/src/tools/browser/__tests__/auth-cache.test.ts +69 -63
  532. package/src/tools/browser/__tests__/auth-detector.test.ts +218 -157
  533. package/src/tools/browser/__tests__/jit-auth.test.ts +83 -99
  534. package/src/tools/ui-surface/definitions.ts +2 -1
  535. package/src/util/platform.ts +0 -12
  536. package/docs/architecture/http-token-refresh.md +0 -274
@@ -1,6 +1,6 @@
1
- import { beforeEach, describe, expect, mock,test } from 'bun:test';
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- import type { Message, ToolDefinition } from '../providers/types.js';
3
+ import type { Message, ToolDefinition } from "../providers/types.js";
4
4
 
5
5
  // ---------------------------------------------------------------------------
6
6
  // Mock Anthropic SDK — must be before importing the provider
@@ -10,15 +10,15 @@ let lastStreamParams: Record<string, unknown> | null = null;
10
10
  let _lastStreamOptions: Record<string, unknown> | null = null;
11
11
 
12
12
  const fakeResponse = {
13
- content: [{ type: 'text', text: 'Hello' }],
14
- model: 'claude-sonnet-4-6',
13
+ content: [{ type: "text", text: "Hello" }],
14
+ model: "claude-sonnet-4-6",
15
15
  usage: {
16
16
  input_tokens: 100,
17
17
  output_tokens: 20,
18
18
  cache_creation_input_tokens: 50,
19
19
  cache_read_input_tokens: 30,
20
20
  },
21
- stop_reason: 'end_turn',
21
+ stop_reason: "end_turn",
22
22
  };
23
23
 
24
24
  class FakeAPIError extends Error {
@@ -26,16 +26,19 @@ class FakeAPIError extends Error {
26
26
  constructor(status: number, message: string) {
27
27
  super(message);
28
28
  this.status = status;
29
- this.name = 'APIError';
29
+ this.name = "APIError";
30
30
  }
31
31
  }
32
32
 
33
- mock.module('@anthropic-ai/sdk', () => ({
33
+ mock.module("@anthropic-ai/sdk", () => ({
34
34
  default: class MockAnthropic {
35
35
  static APIError = FakeAPIError;
36
36
  constructor() {}
37
37
  messages = {
38
- stream: (params: Record<string, unknown>, options?: Record<string, unknown>) => {
38
+ stream: (
39
+ params: Record<string, unknown>,
40
+ options?: Record<string, unknown>,
41
+ ) => {
39
42
  lastStreamParams = JSON.parse(JSON.stringify(params));
40
43
  _lastStreamOptions = options ?? null;
41
44
  const handlers: Record<string, ((...args: unknown[]) => void)[]> = {};
@@ -46,7 +49,7 @@ mock.module('@anthropic-ai/sdk', () => ({
46
49
  },
47
50
  async finalMessage() {
48
51
  // Fire text events
49
- for (const cb of handlers['text'] ?? []) cb('Hello');
52
+ for (const cb of handlers["text"] ?? []) cb("Hello");
50
53
  return fakeResponse;
51
54
  },
52
55
  };
@@ -56,66 +59,94 @@ mock.module('@anthropic-ai/sdk', () => ({
56
59
  }));
57
60
 
58
61
  // Import after mocking
59
- import { AnthropicProvider, PLACEHOLDER_BLOCKS_OMITTED,PLACEHOLDER_EMPTY_TURN } from '../providers/anthropic/client.js';
62
+ import {
63
+ AnthropicProvider,
64
+ PLACEHOLDER_BLOCKS_OMITTED,
65
+ PLACEHOLDER_EMPTY_TURN,
66
+ } from "../providers/anthropic/client.js";
60
67
 
61
68
  // ---------------------------------------------------------------------------
62
69
  // Helpers
63
70
  // ---------------------------------------------------------------------------
64
71
 
65
72
  function userMsg(text: string): Message {
66
- return { role: 'user', content: [{ type: 'text', text }] };
73
+ return { role: "user", content: [{ type: "text", text }] };
67
74
  }
68
75
 
69
76
  function assistantMsg(text: string): Message {
70
- return { role: 'assistant', content: [{ type: 'text', text }] };
77
+ return { role: "assistant", content: [{ type: "text", text }] };
71
78
  }
72
79
 
73
80
  function toolUseMsg(id: string, name: string): Message {
74
81
  return {
75
- role: 'assistant',
76
- content: [{ type: 'tool_use', id, name, input: {} }],
82
+ role: "assistant",
83
+ content: [{ type: "tool_use", id, name, input: {} }],
77
84
  };
78
85
  }
79
86
 
80
87
  function toolResultMsg(toolUseId: string, content: string): Message {
81
88
  return {
82
- role: 'user',
83
- content: [{ type: 'tool_result', tool_use_id: toolUseId, content, is_error: false }],
89
+ role: "user",
90
+ content: [
91
+ { type: "tool_result", tool_use_id: toolUseId, content, is_error: false },
92
+ ],
84
93
  };
85
94
  }
86
95
 
87
96
  const sampleTools: ToolDefinition[] = [
88
- { name: 'file_read', description: 'Read a file', input_schema: { type: 'object', properties: { path: { type: 'string' } } } },
89
- { name: 'file_write', description: 'Write a file', input_schema: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } } } },
90
- { name: 'bash', description: 'Run shell commands', input_schema: { type: 'object', properties: { command: { type: 'string' } } } },
97
+ {
98
+ name: "file_read",
99
+ description: "Read a file",
100
+ input_schema: { type: "object", properties: { path: { type: "string" } } },
101
+ },
102
+ {
103
+ name: "file_write",
104
+ description: "Write a file",
105
+ input_schema: {
106
+ type: "object",
107
+ properties: { path: { type: "string" }, content: { type: "string" } },
108
+ },
109
+ },
110
+ {
111
+ name: "bash",
112
+ description: "Run shell commands",
113
+ input_schema: {
114
+ type: "object",
115
+ properties: { command: { type: "string" } },
116
+ },
117
+ },
91
118
  ];
92
119
 
93
120
  // ---------------------------------------------------------------------------
94
121
  // Tests — Cache-Control Characterization
95
122
  // ---------------------------------------------------------------------------
96
123
 
97
- describe('AnthropicProvider — Cache-Control Characterization', () => {
124
+ describe("AnthropicProvider — Cache-Control Characterization", () => {
98
125
  let provider: AnthropicProvider;
99
126
 
100
127
  beforeEach(() => {
101
128
  lastStreamParams = null;
102
129
  _lastStreamOptions = null;
103
- provider = new AnthropicProvider('sk-ant-test', 'claude-sonnet-4-6');
130
+ provider = new AnthropicProvider("sk-ant-test", "claude-sonnet-4-6");
104
131
  });
105
132
 
106
133
  // -----------------------------------------------------------------------
107
134
  // System prompt cache control
108
135
  // -----------------------------------------------------------------------
109
- test('system prompt has cache_control ephemeral', async () => {
110
- await provider.sendMessage([userMsg('Hi')], undefined, 'You are helpful.');
136
+ test("system prompt has cache_control ephemeral", async () => {
137
+ await provider.sendMessage([userMsg("Hi")], undefined, "You are helpful.");
111
138
 
112
- const system = lastStreamParams!.system as Array<{ type: string; text: string; cache_control?: { type: string } }>;
139
+ const system = lastStreamParams!.system as Array<{
140
+ type: string;
141
+ text: string;
142
+ cache_control?: { type: string };
143
+ }>;
113
144
  expect(system).toHaveLength(1);
114
- expect(system[0].cache_control).toEqual({ type: 'ephemeral' });
145
+ expect(system[0].cache_control).toEqual({ type: "ephemeral" });
115
146
  });
116
147
 
117
- test('no system param when system prompt is omitted', async () => {
118
- await provider.sendMessage([userMsg('Hi')]);
148
+ test("no system param when system prompt is omitted", async () => {
149
+ await provider.sendMessage([userMsg("Hi")]);
119
150
 
120
151
  expect(lastStreamParams!.system).toBeUndefined();
121
152
  });
@@ -123,10 +154,13 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
123
154
  // -----------------------------------------------------------------------
124
155
  // Tool cache control
125
156
  // -----------------------------------------------------------------------
126
- test('only last tool definition includes cache_control', async () => {
127
- await provider.sendMessage([userMsg('Hi')], sampleTools);
157
+ test("only last tool definition includes cache_control", async () => {
158
+ await provider.sendMessage([userMsg("Hi")], sampleTools);
128
159
 
129
- const tools = lastStreamParams!.tools as Array<{ name: string; cache_control?: { type: string } }>;
160
+ const tools = lastStreamParams!.tools as Array<{
161
+ name: string;
162
+ cache_control?: { type: string };
163
+ }>;
130
164
  expect(tools).toHaveLength(3);
131
165
 
132
166
  // First two tools: no cache_control
@@ -134,19 +168,22 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
134
168
  expect(tools[1].cache_control).toBeUndefined();
135
169
 
136
170
  // Last tool: cache_control ephemeral
137
- expect(tools[2].cache_control).toEqual({ type: 'ephemeral' });
171
+ expect(tools[2].cache_control).toEqual({ type: "ephemeral" });
138
172
  });
139
173
 
140
- test('single tool gets cache_control', async () => {
141
- await provider.sendMessage([userMsg('Hi')], [sampleTools[0]]);
174
+ test("single tool gets cache_control", async () => {
175
+ await provider.sendMessage([userMsg("Hi")], [sampleTools[0]]);
142
176
 
143
- const tools = lastStreamParams!.tools as Array<{ name: string; cache_control?: { type: string } }>;
177
+ const tools = lastStreamParams!.tools as Array<{
178
+ name: string;
179
+ cache_control?: { type: string };
180
+ }>;
144
181
  expect(tools).toHaveLength(1);
145
- expect(tools[0].cache_control).toEqual({ type: 'ephemeral' });
182
+ expect(tools[0].cache_control).toEqual({ type: "ephemeral" });
146
183
  });
147
184
 
148
- test('no tools param when tools are omitted', async () => {
149
- await provider.sendMessage([userMsg('Hi')]);
185
+ test("no tools param when tools are omitted", async () => {
186
+ await provider.sendMessage([userMsg("Hi")]);
150
187
 
151
188
  expect(lastStreamParams!.tools).toBeUndefined();
152
189
  });
@@ -154,71 +191,89 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
154
191
  // -----------------------------------------------------------------------
155
192
  // User turn cache breakpoints — last two user turns
156
193
  // -----------------------------------------------------------------------
157
- test('last user turn gets cache_control on trailing content block', async () => {
158
- await provider.sendMessage([userMsg('Hello')]);
194
+ test("last user turn gets cache_control on trailing content block", async () => {
195
+ await provider.sendMessage([userMsg("Hello")]);
159
196
 
160
197
  const messages = lastStreamParams!.messages as Array<{
161
198
  role: string;
162
- content: Array<{ type: string; text: string; cache_control?: { type: string } }>;
199
+ content: Array<{
200
+ type: string;
201
+ text: string;
202
+ cache_control?: { type: string };
203
+ }>;
163
204
  }>;
164
205
  const lastUser = messages[messages.length - 1];
165
- expect(lastUser.role).toBe('user');
166
- expect(lastUser.content[lastUser.content.length - 1].cache_control).toEqual({ type: 'ephemeral' });
206
+ expect(lastUser.role).toBe("user");
207
+ expect(lastUser.content[lastUser.content.length - 1].cache_control).toEqual(
208
+ { type: "ephemeral" },
209
+ );
167
210
  });
168
211
 
169
- test('last two user turns get cache_control, earlier turns do not', async () => {
212
+ test("last two user turns get cache_control, earlier turns do not", async () => {
170
213
  const messages: Message[] = [
171
- userMsg('Turn 1'), // user turn 0 — no cache
172
- assistantMsg('Response 1'),
173
- userMsg('Turn 2'), // user turn 1 — cache (second-to-last)
174
- assistantMsg('Response 2'),
175
- userMsg('Turn 3'), // user turn 2 — cache (last)
214
+ userMsg("Turn 1"), // user turn 0 — no cache
215
+ assistantMsg("Response 1"),
216
+ userMsg("Turn 2"), // user turn 1 — cache (second-to-last)
217
+ assistantMsg("Response 2"),
218
+ userMsg("Turn 3"), // user turn 2 — cache (last)
176
219
  ];
177
220
  await provider.sendMessage(messages);
178
221
 
179
222
  const sent = lastStreamParams!.messages as Array<{
180
223
  role: string;
181
- content: Array<{ type: string; text: string; cache_control?: { type: string } }>;
224
+ content: Array<{
225
+ type: string;
226
+ text: string;
227
+ cache_control?: { type: string };
228
+ }>;
182
229
  }>;
183
230
 
184
231
  // Find user messages in order
185
- const userMessages = sent.filter(m => m.role === 'user');
232
+ const userMessages = sent.filter((m) => m.role === "user");
186
233
  expect(userMessages).toHaveLength(3);
187
234
 
188
235
  // First user turn: no cache_control
189
- const firstUserLastBlock = userMessages[0].content[userMessages[0].content.length - 1];
236
+ const firstUserLastBlock =
237
+ userMessages[0].content[userMessages[0].content.length - 1];
190
238
  expect(firstUserLastBlock.cache_control).toBeUndefined();
191
239
 
192
240
  // Second user turn: cache_control ephemeral
193
- const secondUserLastBlock = userMessages[1].content[userMessages[1].content.length - 1];
194
- expect(secondUserLastBlock.cache_control).toEqual({ type: 'ephemeral' });
241
+ const secondUserLastBlock =
242
+ userMessages[1].content[userMessages[1].content.length - 1];
243
+ expect(secondUserLastBlock.cache_control).toEqual({ type: "ephemeral" });
195
244
 
196
245
  // Third user turn: cache_control ephemeral
197
- const thirdUserLastBlock = userMessages[2].content[userMessages[2].content.length - 1];
198
- expect(thirdUserLastBlock.cache_control).toEqual({ type: 'ephemeral' });
246
+ const thirdUserLastBlock =
247
+ userMessages[2].content[userMessages[2].content.length - 1];
248
+ expect(thirdUserLastBlock.cache_control).toEqual({ type: "ephemeral" });
199
249
  });
200
250
 
201
- test('single user turn gets cache_control (only one user = last one)', async () => {
202
- await provider.sendMessage([userMsg('Only turn')]);
251
+ test("single user turn gets cache_control (only one user = last one)", async () => {
252
+ await provider.sendMessage([userMsg("Only turn")]);
203
253
 
204
254
  const sent = lastStreamParams!.messages as Array<{
205
255
  role: string;
206
- content: Array<{ type: string; text: string; cache_control?: { type: string } }>;
256
+ content: Array<{
257
+ type: string;
258
+ text: string;
259
+ cache_control?: { type: string };
260
+ }>;
207
261
  }>;
208
- const userMessages = sent.filter(m => m.role === 'user');
262
+ const userMessages = sent.filter((m) => m.role === "user");
209
263
  expect(userMessages).toHaveLength(1);
210
- expect(userMessages[0].content[userMessages[0].content.length - 1].cache_control)
211
- .toEqual({ type: 'ephemeral' });
264
+ expect(
265
+ userMessages[0].content[userMessages[0].content.length - 1].cache_control,
266
+ ).toEqual({ type: "ephemeral" });
212
267
  });
213
268
 
214
269
  // -----------------------------------------------------------------------
215
270
  // User turn with tool_result — cache breakpoint on trailing block
216
271
  // -----------------------------------------------------------------------
217
- test('user turn containing tool_result gets cache_control on last block', async () => {
272
+ test("user turn containing tool_result gets cache_control on last block", async () => {
218
273
  const messages: Message[] = [
219
- userMsg('Read file'),
220
- toolUseMsg('tu_1', 'file_read'),
221
- toolResultMsg('tu_1', 'file contents here'),
274
+ userMsg("Read file"),
275
+ toolUseMsg("tu_1", "file_read"),
276
+ toolResultMsg("tu_1", "file contents here"),
222
277
  ];
223
278
  await provider.sendMessage(messages);
224
279
 
@@ -226,30 +281,34 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
226
281
  role: string;
227
282
  content: Array<{ type: string; cache_control?: { type: string } }>;
228
283
  }>;
229
- const userMsgs = sent.filter(m => m.role === 'user');
284
+ const userMsgs = sent.filter((m) => m.role === "user");
230
285
  // Both user turns (first user msg + tool_result msg) should get cache
231
286
  for (const u of userMsgs) {
232
287
  const last = u.content[u.content.length - 1];
233
- expect(last.cache_control).toEqual({ type: 'ephemeral' });
288
+ expect(last.cache_control).toEqual({ type: "ephemeral" });
234
289
  }
235
290
  });
236
291
 
237
292
  // -----------------------------------------------------------------------
238
293
  // Negative: assistant messages never get cache_control
239
294
  // -----------------------------------------------------------------------
240
- test('assistant messages do not get cache_control', async () => {
295
+ test("assistant messages do not get cache_control", async () => {
241
296
  const messages: Message[] = [
242
- userMsg('Hi'),
243
- assistantMsg('Hello!'),
244
- userMsg('How are you?'),
297
+ userMsg("Hi"),
298
+ assistantMsg("Hello!"),
299
+ userMsg("How are you?"),
245
300
  ];
246
301
  await provider.sendMessage(messages);
247
302
 
248
303
  const sent = lastStreamParams!.messages as Array<{
249
304
  role: string;
250
- content: Array<{ type: string; text: string; cache_control?: { type: string } }>;
305
+ content: Array<{
306
+ type: string;
307
+ text: string;
308
+ cache_control?: { type: string };
309
+ }>;
251
310
  }>;
252
- const assistantMsgs = sent.filter(m => m.role === 'assistant');
311
+ const assistantMsgs = sent.filter((m) => m.role === "assistant");
253
312
  for (const a of assistantMsgs) {
254
313
  if (Array.isArray(a.content)) {
255
314
  for (const block of a.content) {
@@ -262,30 +321,34 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
262
321
  // -----------------------------------------------------------------------
263
322
  // Multi-block user message: cache lands on LAST block
264
323
  // -----------------------------------------------------------------------
265
- test('multi-block user message caches only the last block', async () => {
324
+ test("multi-block user message caches only the last block", async () => {
266
325
  const multiBlockUser: Message = {
267
- role: 'user',
326
+ role: "user",
268
327
  content: [
269
- { type: 'text', text: 'First block' },
270
- { type: 'text', text: 'Second block' },
328
+ { type: "text", text: "First block" },
329
+ { type: "text", text: "Second block" },
271
330
  ],
272
331
  };
273
332
  await provider.sendMessage([multiBlockUser]);
274
333
 
275
334
  const sent = lastStreamParams!.messages as Array<{
276
335
  role: string;
277
- content: Array<{ type: string; text: string; cache_control?: { type: string } }>;
336
+ content: Array<{
337
+ type: string;
338
+ text: string;
339
+ cache_control?: { type: string };
340
+ }>;
278
341
  }>;
279
342
  const user = sent[0];
280
343
  expect(user.content[0].cache_control).toBeUndefined();
281
- expect(user.content[1].cache_control).toEqual({ type: 'ephemeral' });
344
+ expect(user.content[1].cache_control).toEqual({ type: "ephemeral" });
282
345
  });
283
346
 
284
347
  // -----------------------------------------------------------------------
285
348
  // Usage: cache tokens are aggregated into inputTokens
286
349
  // -----------------------------------------------------------------------
287
- test('usage aggregates cache tokens into inputTokens', async () => {
288
- const result = await provider.sendMessage([userMsg('Hi')]);
350
+ test("usage aggregates cache tokens into inputTokens", async () => {
351
+ const result = await provider.sendMessage([userMsg("Hi")]);
289
352
 
290
353
  expect(result.usage.inputTokens).toBe(100 + 50 + 30); // input + creation + read
291
354
  expect(result.usage.cacheCreationInputTokens).toBe(50);
@@ -295,44 +358,61 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
295
358
  // -----------------------------------------------------------------------
296
359
  // Cache compatibility with workspace context injection
297
360
  // -----------------------------------------------------------------------
298
- test('workspace-prepended user message caches trailing block, not workspace block', async () => {
361
+ test("workspace-prepended user message caches trailing block, not workspace block", async () => {
299
362
  // Simulates what applyRuntimeInjections does: prepend workspace block, keep user text as trailing
300
363
  const workspaceInjectedUser: Message = {
301
- role: 'user',
364
+ role: "user",
302
365
  content: [
303
- { type: 'text', text: '<workspace_top_level>\nRoot: /sandbox\nDirectories: src, tests\n</workspace_top_level>' },
304
- { type: 'text', text: 'What files are in src?' },
366
+ {
367
+ type: "text",
368
+ text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src, tests\n</workspace_top_level>",
369
+ },
370
+ { type: "text", text: "What files are in src?" },
305
371
  ],
306
372
  };
307
373
  await provider.sendMessage([workspaceInjectedUser]);
308
374
 
309
375
  const sent = lastStreamParams!.messages as Array<{
310
376
  role: string;
311
- content: Array<{ type: string; text: string; cache_control?: { type: string } }>;
377
+ content: Array<{
378
+ type: string;
379
+ text: string;
380
+ cache_control?: { type: string };
381
+ }>;
312
382
  }>;
313
383
  const user = sent[0];
314
384
  expect(user.content).toHaveLength(2);
315
385
  // Workspace block (first): no cache_control
316
386
  expect(user.content[0].cache_control).toBeUndefined();
317
387
  // User text (last): gets cache_control
318
- expect(user.content[1].cache_control).toEqual({ type: 'ephemeral' });
388
+ expect(user.content[1].cache_control).toEqual({ type: "ephemeral" });
319
389
  });
320
390
 
321
- test('workspace + dynamic profile: cache still lands on trailing block', async () => {
391
+ test("workspace + dynamic profile: cache still lands on trailing block", async () => {
322
392
  // Simulates workspace prepended + dynamic profile appended
323
393
  const injectedUser: Message = {
324
- role: 'user',
394
+ role: "user",
325
395
  content: [
326
- { type: 'text', text: '<workspace_top_level>\nRoot: /sandbox\nDirectories: src, tests\n</workspace_top_level>' },
327
- { type: 'text', text: 'Help me debug this' },
328
- { type: 'text', text: '<dynamic_profile>\nUser prefers TypeScript.\n</dynamic_profile>' },
396
+ {
397
+ type: "text",
398
+ text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src, tests\n</workspace_top_level>",
399
+ },
400
+ { type: "text", text: "Help me debug this" },
401
+ {
402
+ type: "text",
403
+ text: "<dynamic_profile>\nUser prefers TypeScript.\n</dynamic_profile>",
404
+ },
329
405
  ],
330
406
  };
331
407
  await provider.sendMessage([injectedUser]);
332
408
 
333
409
  const sent = lastStreamParams!.messages as Array<{
334
410
  role: string;
335
- content: Array<{ type: string; text: string; cache_control?: { type: string } }>;
411
+ content: Array<{
412
+ type: string;
413
+ text: string;
414
+ cache_control?: { type: string };
415
+ }>;
336
416
  }>;
337
417
  const user = sent[0];
338
418
  expect(user.content).toHaveLength(3);
@@ -341,41 +421,47 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
341
421
  // User text (middle): no cache
342
422
  expect(user.content[1].cache_control).toBeUndefined();
343
423
  // Dynamic profile (last): gets cache_control
344
- expect(user.content[2].cache_control).toEqual({ type: 'ephemeral' });
424
+ expect(user.content[2].cache_control).toEqual({ type: "ephemeral" });
345
425
  });
346
426
 
347
427
  // -----------------------------------------------------------------------
348
428
  // ensureToolPairing — tool_use / tool_result pairing repair
349
429
  // -----------------------------------------------------------------------
350
430
 
351
- test('tool_use with missing tool_result gets synthetic result injected', async () => {
431
+ test("tool_use with missing tool_result gets synthetic result injected", async () => {
352
432
  const messages: Message[] = [
353
- userMsg('Do something'),
354
- toolUseMsg('tu_1', 'file_read'),
355
- userMsg('Thanks'), // user text but no tool_result for tu_1
433
+ userMsg("Do something"),
434
+ toolUseMsg("tu_1", "file_read"),
435
+ userMsg("Thanks"), // user text but no tool_result for tu_1
356
436
  ];
357
437
  await provider.sendMessage(messages);
358
438
 
359
439
  const sent = lastStreamParams!.messages as Array<{
360
440
  role: string;
361
- content: Array<{ type: string; tool_use_id?: string; is_error?: boolean }>;
441
+ content: Array<{
442
+ type: string;
443
+ tool_use_id?: string;
444
+ is_error?: boolean;
445
+ }>;
362
446
  }>;
363
447
 
364
448
  // The second user message (after assistant) should now contain a synthetic tool_result
365
449
  const userAfterAssistant = sent[2];
366
- expect(userAfterAssistant.role).toBe('user');
450
+ expect(userAfterAssistant.role).toBe("user");
367
451
  // Anthropic expects tool_result blocks to start the immediate next user message.
368
- expect(userAfterAssistant.content[0].type).toBe('tool_result');
369
- const toolResults = userAfterAssistant.content.filter((b) => b.type === 'tool_result');
452
+ expect(userAfterAssistant.content[0].type).toBe("tool_result");
453
+ const toolResults = userAfterAssistant.content.filter(
454
+ (b) => b.type === "tool_result",
455
+ );
370
456
  expect(toolResults).toHaveLength(1);
371
- expect(toolResults[0].tool_use_id).toBe('tu_1');
457
+ expect(toolResults[0].tool_use_id).toBe("tu_1");
372
458
  expect(toolResults[0].is_error).toBe(true);
373
459
  });
374
460
 
375
- test('tool_use at end of messages gets synthetic user message appended', async () => {
461
+ test("tool_use at end of messages gets synthetic user message appended", async () => {
376
462
  const messages: Message[] = [
377
- userMsg('Read file'),
378
- toolUseMsg('tu_end', 'file_read'),
463
+ userMsg("Read file"),
464
+ toolUseMsg("tu_end", "file_read"),
379
465
  ];
380
466
  await provider.sendMessage(messages);
381
467
 
@@ -386,17 +472,17 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
386
472
 
387
473
  // A synthetic user message should have been appended
388
474
  expect(sent).toHaveLength(3);
389
- expect(sent[2].role).toBe('user');
390
- const toolResults = sent[2].content.filter((b) => b.type === 'tool_result');
475
+ expect(sent[2].role).toBe("user");
476
+ const toolResults = sent[2].content.filter((b) => b.type === "tool_result");
391
477
  expect(toolResults).toHaveLength(1);
392
- expect(toolResults[0].tool_use_id).toBe('tu_end');
478
+ expect(toolResults[0].tool_use_id).toBe("tu_end");
393
479
  });
394
480
 
395
- test('tool_use with matching tool_result passes through unchanged', async () => {
481
+ test("tool_use with matching tool_result passes through unchanged", async () => {
396
482
  const messages: Message[] = [
397
- userMsg('Read file'),
398
- toolUseMsg('tu_ok', 'file_read'),
399
- toolResultMsg('tu_ok', 'file contents'),
483
+ userMsg("Read file"),
484
+ toolUseMsg("tu_ok", "file_read"),
485
+ toolResultMsg("tu_ok", "file contents"),
400
486
  ];
401
487
  await provider.sendMessage(messages);
402
488
 
@@ -407,30 +493,43 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
407
493
 
408
494
  // No synthetic messages or blocks added
409
495
  expect(sent).toHaveLength(3);
410
- const toolResults = sent[2].content.filter((b) => b.type === 'tool_result');
496
+ const toolResults = sent[2].content.filter((b) => b.type === "tool_result");
411
497
  expect(toolResults).toHaveLength(1);
412
- expect(toolResults[0].tool_use_id).toBe('tu_ok');
498
+ expect(toolResults[0].tool_use_id).toBe("tu_ok");
413
499
  });
414
500
 
415
- test('reconstructs collapsed assistant/tool_result/user timeline before sending', async () => {
501
+ test("reconstructs collapsed assistant/tool_result/user timeline before sending", async () => {
416
502
  const messages: Message[] = [
417
- userMsg('Read files'),
503
+ userMsg("Read files"),
418
504
  {
419
- role: 'assistant',
505
+ role: "assistant",
420
506
  content: [
421
- { type: 'text', text: 'Working on it.' },
422
- { type: 'tool_use', id: 'tu_a', name: 'file_read', input: {} },
423
- { type: 'tool_use', id: 'tu_b', name: 'bash', input: {} },
424
- { type: 'text', text: 'One moment.' },
507
+ { type: "text", text: "Working on it." },
508
+ { type: "tool_use", id: "tu_a", name: "file_read", input: {} },
509
+ { type: "tool_use", id: "tu_b", name: "bash", input: {} },
510
+ { type: "text", text: "One moment." },
425
511
  ],
426
512
  },
427
513
  {
428
- role: 'user',
514
+ role: "user",
429
515
  content: [
430
- { type: 'text', text: '<workspace_top_level>\nRoot: /sandbox\n</workspace_top_level>' },
431
- { type: 'tool_result', tool_use_id: 'tu_b', content: 'result B', is_error: false },
432
- { type: 'text', text: 'continue please' },
433
- { type: 'tool_result', tool_use_id: 'tu_a', content: 'result A', is_error: false },
516
+ {
517
+ type: "text",
518
+ text: "<workspace_top_level>\nRoot: /sandbox\n</workspace_top_level>",
519
+ },
520
+ {
521
+ type: "tool_result",
522
+ tool_use_id: "tu_b",
523
+ content: "result B",
524
+ is_error: false,
525
+ },
526
+ { type: "text", text: "continue please" },
527
+ {
528
+ type: "tool_result",
529
+ tool_use_id: "tu_a",
530
+ content: "result A",
531
+ is_error: false,
532
+ },
434
533
  ],
435
534
  },
436
535
  ];
@@ -445,66 +544,97 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
445
544
  // user, assistant(tool_use...), user(tool_results), assistant(carryover text), user(remaining text)
446
545
  expect(sent).toHaveLength(5);
447
546
 
448
- expect(sent[1].role).toBe('assistant');
449
- expect(sent[1].content.map((b) => b.type)).toEqual(['text', 'tool_use', 'tool_use']);
450
-
451
- expect(sent[2].role).toBe('user');
452
- expect(sent[2].content[0]).toMatchObject({ type: 'tool_result', tool_use_id: 'tu_a' });
453
- expect(sent[2].content[1]).toMatchObject({ type: 'tool_result', tool_use_id: 'tu_b' });
547
+ expect(sent[1].role).toBe("assistant");
548
+ expect(sent[1].content.map((b) => b.type)).toEqual([
549
+ "text",
550
+ "tool_use",
551
+ "tool_use",
552
+ ]);
553
+
554
+ expect(sent[2].role).toBe("user");
555
+ expect(sent[2].content[0]).toMatchObject({
556
+ type: "tool_result",
557
+ tool_use_id: "tu_a",
558
+ });
559
+ expect(sent[2].content[1]).toMatchObject({
560
+ type: "tool_result",
561
+ tool_use_id: "tu_b",
562
+ });
454
563
  expect(sent[2].content).toHaveLength(2);
455
564
 
456
- expect(sent[3].role).toBe('assistant');
457
- expect(sent[3].content.map((b) => b.type)).toEqual(['text']);
565
+ expect(sent[3].role).toBe("assistant");
566
+ expect(sent[3].content.map((b) => b.type)).toEqual(["text"]);
458
567
 
459
- expect(sent[4].role).toBe('user');
460
- expect(sent[4].content.map((b) => b.type)).toEqual(['text', 'text']);
568
+ expect(sent[4].role).toBe("user");
569
+ expect(sent[4].content.map((b) => b.type)).toEqual(["text", "text"]);
461
570
  });
462
571
 
463
- test('multiple tool_use with partial results gets missing ones filled', async () => {
572
+ test("multiple tool_use with partial results gets missing ones filled", async () => {
464
573
  const messages: Message[] = [
465
- userMsg('Do things'),
574
+ userMsg("Do things"),
466
575
  {
467
- role: 'assistant',
576
+ role: "assistant",
468
577
  content: [
469
- { type: 'tool_use', id: 'tu_a', name: 'file_read', input: {} },
470
- { type: 'tool_use', id: 'tu_b', name: 'file_write', input: {} },
471
- { type: 'tool_use', id: 'tu_c', name: 'bash', input: {} },
578
+ { type: "tool_use", id: "tu_a", name: "file_read", input: {} },
579
+ { type: "tool_use", id: "tu_b", name: "file_write", input: {} },
580
+ { type: "tool_use", id: "tu_c", name: "bash", input: {} },
472
581
  ],
473
582
  },
474
583
  // Only tu_a has a result
475
- toolResultMsg('tu_a', 'result A'),
584
+ toolResultMsg("tu_a", "result A"),
476
585
  ];
477
586
  await provider.sendMessage(messages);
478
587
 
479
588
  const sent = lastStreamParams!.messages as Array<{
480
589
  role: string;
481
- content: Array<{ type: string; tool_use_id?: string; is_error?: boolean }>;
590
+ content: Array<{
591
+ type: string;
592
+ tool_use_id?: string;
593
+ is_error?: boolean;
594
+ }>;
482
595
  }>;
483
596
 
484
597
  const userAfterAssistant = sent[2];
485
- const toolResults = userAfterAssistant.content.filter((b) => b.type === 'tool_result');
598
+ const toolResults = userAfterAssistant.content.filter(
599
+ (b) => b.type === "tool_result",
600
+ );
486
601
  expect(toolResults).toHaveLength(3);
487
- expect(userAfterAssistant.content[0]).toMatchObject({ type: 'tool_result', tool_use_id: 'tu_a' });
488
- expect(userAfterAssistant.content[1]).toMatchObject({ type: 'tool_result', tool_use_id: 'tu_b' });
489
- expect(userAfterAssistant.content[2]).toMatchObject({ type: 'tool_result', tool_use_id: 'tu_c' });
602
+ expect(userAfterAssistant.content[0]).toMatchObject({
603
+ type: "tool_result",
604
+ tool_use_id: "tu_a",
605
+ });
606
+ expect(userAfterAssistant.content[1]).toMatchObject({
607
+ type: "tool_result",
608
+ tool_use_id: "tu_b",
609
+ });
610
+ expect(userAfterAssistant.content[2]).toMatchObject({
611
+ type: "tool_result",
612
+ tool_use_id: "tu_c",
613
+ });
490
614
 
491
615
  // tu_a: original result
492
- expect(toolResults.find((r) => r.tool_use_id === 'tu_a')!.is_error).toBeFalsy();
616
+ expect(
617
+ toolResults.find((r) => r.tool_use_id === "tu_a")!.is_error,
618
+ ).toBeFalsy();
493
619
  // tu_b and tu_c: synthetic
494
- expect(toolResults.find((r) => r.tool_use_id === 'tu_b')!.is_error).toBe(true);
495
- expect(toolResults.find((r) => r.tool_use_id === 'tu_c')!.is_error).toBe(true);
620
+ expect(toolResults.find((r) => r.tool_use_id === "tu_b")!.is_error).toBe(
621
+ true,
622
+ );
623
+ expect(toolResults.find((r) => r.tool_use_id === "tu_c")!.is_error).toBe(
624
+ true,
625
+ );
496
626
  });
497
627
 
498
- test('consecutive assistant messages with tool_use each get synthetic results', async () => {
628
+ test("consecutive assistant messages with tool_use each get synthetic results", async () => {
499
629
  const messages: Message[] = [
500
- userMsg('Start'),
501
- toolUseMsg('tu_1', 'file_read'),
630
+ userMsg("Start"),
631
+ toolUseMsg("tu_1", "file_read"),
502
632
  // missing tool_result for tu_1, then another assistant
503
633
  {
504
- role: 'assistant',
505
- content: [{ type: 'tool_use', id: 'tu_2', name: 'bash', input: {} }],
634
+ role: "assistant",
635
+ content: [{ type: "tool_use", id: "tu_2", name: "bash", input: {} }],
506
636
  },
507
- userMsg('Done'),
637
+ userMsg("Done"),
508
638
  ];
509
639
  await provider.sendMessage(messages);
510
640
 
@@ -515,24 +645,34 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
515
645
 
516
646
  // Should be: user, assistant(tu_1), synthetic_user(tu_1), assistant(tu_2), user_with_synthetic(tu_2)
517
647
  expect(sent).toHaveLength(5);
518
- expect(sent[0].role).toBe('user');
519
- expect(sent[1].role).toBe('assistant');
520
- expect(sent[2].role).toBe('user');
521
- expect(sent[2].content.some((b) => b.type === 'tool_result' && b.tool_use_id === 'tu_1')).toBe(true);
522
- expect(sent[3].role).toBe('assistant');
523
- expect(sent[4].role).toBe('user');
524
- expect(sent[4].content.some((b) => b.type === 'tool_result' && b.tool_use_id === 'tu_2')).toBe(true);
648
+ expect(sent[0].role).toBe("user");
649
+ expect(sent[1].role).toBe("assistant");
650
+ expect(sent[2].role).toBe("user");
651
+ expect(
652
+ sent[2].content.some(
653
+ (b) => b.type === "tool_result" && b.tool_use_id === "tu_1",
654
+ ),
655
+ ).toBe(true);
656
+ expect(sent[3].role).toBe("assistant");
657
+ expect(sent[4].role).toBe("user");
658
+ expect(
659
+ sent[4].content.some(
660
+ (b) => b.type === "tool_result" && b.tool_use_id === "tu_2",
661
+ ),
662
+ ).toBe(true);
525
663
  });
526
664
 
527
- test('assistant message with only unknown blocks gets placeholder text', async () => {
665
+ test("assistant message with only unknown blocks gets placeholder text", async () => {
528
666
  const messages: Message[] = [
529
- userMsg('Start'),
667
+ userMsg("Start"),
530
668
  // Assistant message with only ui_surface (unknown type) — will be filtered
531
669
  {
532
- role: 'assistant',
533
- content: [{ type: 'ui_surface' as 'text', text: 'this will be filtered' }],
670
+ role: "assistant",
671
+ content: [
672
+ { type: "ui_surface" as "text", text: "this will be filtered" },
673
+ ],
534
674
  },
535
- userMsg('Continue'),
675
+ userMsg("Continue"),
536
676
  ];
537
677
  await provider.sendMessage(messages);
538
678
 
@@ -543,26 +683,26 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
543
683
 
544
684
  // Should preserve alternation: user, assistant (with placeholder), user
545
685
  expect(sent).toHaveLength(3);
546
- expect(sent[0].role).toBe('user');
547
- expect(sent[1].role).toBe('assistant');
686
+ expect(sent[0].role).toBe("user");
687
+ expect(sent[1].role).toBe("assistant");
548
688
  expect(sent[1].content).toHaveLength(1);
549
- expect(sent[1].content[0].type).toBe('text');
689
+ expect(sent[1].content[0].type).toBe("text");
550
690
  expect(sent[1].content[0].text).toBe(PLACEHOLDER_BLOCKS_OMITTED);
551
- expect(sent[2].role).toBe('user');
691
+ expect(sent[2].role).toBe("user");
552
692
  });
553
693
 
554
- test('assistant message with mix of known and unknown blocks keeps known blocks', async () => {
694
+ test("assistant message with mix of known and unknown blocks keeps known blocks", async () => {
555
695
  const messages: Message[] = [
556
- userMsg('Start'),
696
+ userMsg("Start"),
557
697
  {
558
- role: 'assistant',
698
+ role: "assistant",
559
699
  content: [
560
- { type: 'text', text: 'Valid text' },
561
- { type: 'ui_surface' as 'text', text: 'this will be filtered' },
562
- { type: 'text', text: 'More valid text' },
700
+ { type: "text", text: "Valid text" },
701
+ { type: "ui_surface" as "text", text: "this will be filtered" },
702
+ { type: "text", text: "More valid text" },
563
703
  ],
564
704
  },
565
- userMsg('Continue'),
705
+ userMsg("Continue"),
566
706
  ];
567
707
  await provider.sendMessage(messages);
568
708
 
@@ -572,23 +712,23 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
572
712
  }>;
573
713
 
574
714
  expect(sent).toHaveLength(3);
575
- expect(sent[1].role).toBe('assistant');
715
+ expect(sent[1].role).toBe("assistant");
576
716
  expect(sent[1].content).toHaveLength(2);
577
- expect(sent[1].content[0].text).toBe('Valid text');
578
- expect(sent[1].content[1].text).toBe('More valid text');
717
+ expect(sent[1].content[0].text).toBe("Valid text");
718
+ expect(sent[1].content[1].text).toBe("More valid text");
579
719
  });
580
720
 
581
- test('assistant message with only whitespace text gets placeholder to preserve alternation', async () => {
721
+ test("assistant message with only whitespace text gets placeholder to preserve alternation", async () => {
582
722
  const messages: Message[] = [
583
- userMsg('Start'),
723
+ userMsg("Start"),
584
724
  {
585
- role: 'assistant',
725
+ role: "assistant",
586
726
  content: [
587
- { type: 'text', text: ' ' },
588
- { type: 'text', text: '\n\t' },
727
+ { type: "text", text: " " },
728
+ { type: "text", text: "\n\t" },
589
729
  ],
590
730
  },
591
- userMsg('Continue'),
731
+ userMsg("Continue"),
592
732
  ];
593
733
  await provider.sendMessage(messages);
594
734
 
@@ -600,32 +740,32 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
600
740
  // Whitespace-only assistant messages between user messages must be preserved
601
741
  // with a placeholder to maintain Anthropic's strict role alternation
602
742
  expect(sent).toHaveLength(3);
603
- expect(sent[0].role).toBe('user');
604
- expect(sent[0].content[0].text).toBe('Start');
605
- expect(sent[1].role).toBe('assistant');
743
+ expect(sent[0].role).toBe("user");
744
+ expect(sent[0].content[0].text).toBe("Start");
745
+ expect(sent[1].role).toBe("assistant");
606
746
  expect(sent[1].content).toHaveLength(1);
607
- expect(sent[1].content[0].type).toBe('text');
747
+ expect(sent[1].content[0].type).toBe("text");
608
748
  expect(sent[1].content[0].text).toBe(PLACEHOLDER_EMPTY_TURN);
609
- expect(sent[2].role).toBe('user');
610
- expect(sent[2].content[0].text).toBe('Continue');
749
+ expect(sent[2].role).toBe("user");
750
+ expect(sent[2].content[0].text).toBe("Continue");
611
751
  });
612
752
 
613
- test('unknown-blocks-only assistant followed by empty user does not produce consecutive same-role messages', async () => {
753
+ test("unknown-blocks-only assistant followed by empty user does not produce consecutive same-role messages", async () => {
614
754
  // Same edge case as the empty-assistant test below, but triggered by an
615
755
  // assistant turn whose blocks are all unknown (e.g. ui_surface). The turn
616
756
  // becomes a [internal blocks omitted] placeholder which must also be
617
757
  // removed when adjacent to a real assistant message.
618
758
  const messages: Message[] = [
619
- userMsg('Start'),
759
+ userMsg("Start"),
620
760
  {
621
- role: 'assistant',
622
- content: [{ type: 'ui_surface' as 'text', text: 'invisible' }], // unknown → placeholder
761
+ role: "assistant",
762
+ content: [{ type: "ui_surface" as "text", text: "invisible" }], // unknown → placeholder
623
763
  },
624
764
  {
625
- role: 'user',
626
- content: [{ type: 'text', text: ' \n ' }], // whitespace-only → empty after filtering
765
+ role: "user",
766
+ content: [{ type: "text", text: " \n " }], // whitespace-only → empty after filtering
627
767
  },
628
- assistantMsg('Real response'),
768
+ assistantMsg("Real response"),
629
769
  ];
630
770
  await provider.sendMessage(messages);
631
771
 
@@ -640,22 +780,22 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
640
780
  }
641
781
  });
642
782
 
643
- test('empty assistant followed by empty user does not produce consecutive same-role messages', async () => {
783
+ test("empty assistant followed by empty user does not produce consecutive same-role messages", async () => {
644
784
  // Edge case: an empty assistant turn gets a placeholder injected, but if
645
785
  // the following user turn also filters to empty (e.g. whitespace-only),
646
786
  // the user turn is dropped and the placeholder ends up adjacent to the
647
787
  // next real assistant turn — producing consecutive assistant roles.
648
788
  const messages: Message[] = [
649
- userMsg('Start'),
789
+ userMsg("Start"),
650
790
  {
651
- role: 'assistant',
652
- content: [{ type: 'text', text: ' ' }], // whitespace-only → empty after filtering
791
+ role: "assistant",
792
+ content: [{ type: "text", text: " " }], // whitespace-only → empty after filtering
653
793
  },
654
794
  {
655
- role: 'user',
656
- content: [{ type: 'text', text: ' \n ' }], // whitespace-only → empty after filtering
795
+ role: "user",
796
+ content: [{ type: "text", text: " \n " }], // whitespace-only → empty after filtering
657
797
  },
658
- assistantMsg('Real response'),
798
+ assistantMsg("Real response"),
659
799
  ];
660
800
  await provider.sendMessage(messages);
661
801
 
@@ -674,27 +814,32 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
674
814
  // Workspace context injection + cache control
675
815
  // -----------------------------------------------------------------------
676
816
 
677
- test('carryover with tool_result-only user turn emits synthetic user message', async () => {
817
+ test("carryover with tool_result-only user turn emits synthetic user message", async () => {
678
818
  // This tests the fix for consecutive assistant messages when:
679
819
  // - assistant has both tool_use blocks and trailing non-tool blocks (carryover)
680
820
  // - following user message contains ONLY tool_result blocks (no other content)
681
821
  const messages: Message[] = [
682
- userMsg('Read file'),
822
+ userMsg("Read file"),
683
823
  {
684
- role: 'assistant',
824
+ role: "assistant",
685
825
  content: [
686
- { type: 'tool_use', id: 'tu_1', name: 'file_read', input: {} },
687
- { type: 'text', text: 'Checking the file now.' }, // carryover content
826
+ { type: "tool_use", id: "tu_1", name: "file_read", input: {} },
827
+ { type: "text", text: "Checking the file now." }, // carryover content
688
828
  ],
689
829
  },
690
830
  {
691
- role: 'user',
831
+ role: "user",
692
832
  content: [
693
833
  // ONLY tool_result, no other content
694
- { type: 'tool_result', tool_use_id: 'tu_1', content: 'file contents', is_error: false },
834
+ {
835
+ type: "tool_result",
836
+ tool_use_id: "tu_1",
837
+ content: "file contents",
838
+ is_error: false,
839
+ },
695
840
  ],
696
841
  },
697
- assistantMsg('Next response'),
842
+ assistantMsg("Next response"),
698
843
  ];
699
844
  await provider.sendMessage(messages);
700
845
 
@@ -711,47 +856,56 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
711
856
  // 5. user((continue)) <-- synthetic user message to maintain alternation
712
857
  // 6. assistant(Next response)
713
858
  expect(sent).toHaveLength(6);
714
- expect(sent[0].role).toBe('user');
715
- expect(sent[1].role).toBe('assistant');
716
- expect(sent[1].content[0].type).toBe('tool_use');
717
- expect(sent[2].role).toBe('user');
718
- expect(sent[2].content[0].type).toBe('tool_result');
719
- expect(sent[3].role).toBe('assistant');
720
- expect(sent[3].content[0].type).toBe('text');
721
- expect(sent[3].content[0].text).toBe('Checking the file now.');
722
- expect(sent[4].role).toBe('user');
723
- expect(sent[4].content[0].type).toBe('text');
724
- expect(sent[4].content[0].text).toBe('(continue)');
725
- expect(sent[5].role).toBe('assistant');
726
- expect(sent[5].content[0].text).toBe('Next response');
859
+ expect(sent[0].role).toBe("user");
860
+ expect(sent[1].role).toBe("assistant");
861
+ expect(sent[1].content[0].type).toBe("tool_use");
862
+ expect(sent[2].role).toBe("user");
863
+ expect(sent[2].content[0].type).toBe("tool_result");
864
+ expect(sent[3].role).toBe("assistant");
865
+ expect(sent[3].content[0].type).toBe("text");
866
+ expect(sent[3].content[0].text).toBe("Checking the file now.");
867
+ expect(sent[4].role).toBe("user");
868
+ expect(sent[4].content[0].type).toBe("text");
869
+ expect(sent[4].content[0].text).toBe("(continue)");
870
+ expect(sent[5].role).toBe("assistant");
871
+ expect(sent[5].content[0].text).toBe("Next response");
727
872
  });
728
873
 
729
- test('multi-turn with workspace injection: cache on last two user turns only', async () => {
874
+ test("multi-turn with workspace injection: cache on last two user turns only", async () => {
730
875
  const messages: Message[] = [
731
876
  // Turn 1: workspace + user text (should NOT get cache - it's the 3rd-to-last user turn)
732
877
  {
733
- role: 'user',
878
+ role: "user",
734
879
  content: [
735
- { type: 'text', text: '<workspace_top_level>\nRoot: /sandbox\nDirectories: src\n</workspace_top_level>' },
736
- { type: 'text', text: 'Turn 1' },
880
+ {
881
+ type: "text",
882
+ text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src\n</workspace_top_level>",
883
+ },
884
+ { type: "text", text: "Turn 1" },
737
885
  ],
738
886
  },
739
- assistantMsg('Response 1'),
887
+ assistantMsg("Response 1"),
740
888
  // Turn 2: workspace + user text (should get cache - second-to-last)
741
889
  {
742
- role: 'user',
890
+ role: "user",
743
891
  content: [
744
- { type: 'text', text: '<workspace_top_level>\nRoot: /sandbox\nDirectories: src, lib\n</workspace_top_level>' },
745
- { type: 'text', text: 'Turn 2' },
892
+ {
893
+ type: "text",
894
+ text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src, lib\n</workspace_top_level>",
895
+ },
896
+ { type: "text", text: "Turn 2" },
746
897
  ],
747
898
  },
748
- assistantMsg('Response 2'),
899
+ assistantMsg("Response 2"),
749
900
  // Turn 3: workspace + user text (should get cache - last)
750
901
  {
751
- role: 'user',
902
+ role: "user",
752
903
  content: [
753
- { type: 'text', text: '<workspace_top_level>\nRoot: /sandbox\nDirectories: src, lib, docs\n</workspace_top_level>' },
754
- { type: 'text', text: 'Turn 3' },
904
+ {
905
+ type: "text",
906
+ text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src, lib, docs\n</workspace_top_level>",
907
+ },
908
+ { type: "text", text: "Turn 3" },
755
909
  ],
756
910
  },
757
911
  ];
@@ -759,9 +913,13 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
759
913
 
760
914
  const sent = lastStreamParams!.messages as Array<{
761
915
  role: string;
762
- content: Array<{ type: string; text: string; cache_control?: { type: string } }>;
916
+ content: Array<{
917
+ type: string;
918
+ text: string;
919
+ cache_control?: { type: string };
920
+ }>;
763
921
  }>;
764
- const userMsgs = sent.filter(m => m.role === 'user');
922
+ const userMsgs = sent.filter((m) => m.role === "user");
765
923
  expect(userMsgs).toHaveLength(3);
766
924
 
767
925
  // Turn 1: no cache on any block
@@ -770,10 +928,10 @@ describe('AnthropicProvider — Cache-Control Characterization', () => {
770
928
 
771
929
  // Turn 2: cache on last block only
772
930
  expect(userMsgs[1].content[0].cache_control).toBeUndefined();
773
- expect(userMsgs[1].content[1].cache_control).toEqual({ type: 'ephemeral' });
931
+ expect(userMsgs[1].content[1].cache_control).toEqual({ type: "ephemeral" });
774
932
 
775
933
  // Turn 3: cache on last block only
776
934
  expect(userMsgs[2].content[0].cache_control).toBeUndefined();
777
- expect(userMsgs[2].content[1].cache_control).toEqual({ type: 'ephemeral' });
935
+ expect(userMsgs[2].content[1].cache_control).toEqual({ type: "ephemeral" });
778
936
  });
779
937
  });