@vellumai/assistant 0.4.17 → 0.4.19

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 (528) hide show
  1. package/docs/runbook-trusted-contacts.md +5 -3
  2. package/eslint.config.mjs +2 -2
  3. package/package.json +1 -1
  4. package/src/__tests__/access-request-decision.test.ts +128 -120
  5. package/src/__tests__/account-registry.test.ts +121 -110
  6. package/src/__tests__/active-skill-tools.test.ts +200 -172
  7. package/src/__tests__/actor-token-service.test.ts +341 -274
  8. package/src/__tests__/agent-loop-thinking.test.ts +28 -19
  9. package/src/__tests__/agent-loop.test.ts +798 -378
  10. package/src/__tests__/anthropic-provider.test.ts +405 -247
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +97 -97
  12. package/src/__tests__/app-bundler.test.ts +112 -79
  13. package/src/__tests__/app-executors.test.ts +205 -178
  14. package/src/__tests__/app-git-history.test.ts +90 -73
  15. package/src/__tests__/app-git-service.test.ts +67 -53
  16. package/src/__tests__/app-open-proxy.test.ts +29 -25
  17. package/src/__tests__/approval-conversation-turn.test.ts +100 -81
  18. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +45 -17
  19. package/src/__tests__/approval-message-composer.test.ts +119 -119
  20. package/src/__tests__/approval-primitive.test.ts +264 -233
  21. package/src/__tests__/approval-routes-http.test.ts +4 -3
  22. package/src/__tests__/asset-materialize-tool.test.ts +250 -178
  23. package/src/__tests__/asset-search-tool.test.ts +251 -191
  24. package/src/__tests__/assistant-attachment-directive.test.ts +187 -142
  25. package/src/__tests__/assistant-attachments.test.ts +254 -186
  26. package/src/__tests__/assistant-event-hub.test.ts +105 -63
  27. package/src/__tests__/assistant-event.test.ts +66 -58
  28. package/src/__tests__/assistant-events-sse-hardening.test.ts +113 -73
  29. package/src/__tests__/assistant-feature-flag-guard.test.ts +78 -52
  30. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +48 -45
  31. package/src/__tests__/assistant-feature-flags-integration.test.ts +118 -77
  32. package/src/__tests__/assistant-id-boundary-guard.test.ts +158 -104
  33. package/src/__tests__/attachments-store.test.ts +240 -183
  34. package/src/__tests__/attachments.test.ts +70 -62
  35. package/src/__tests__/audit-log-rotation.test.ts +50 -35
  36. package/src/__tests__/browser-fill-credential.test.ts +169 -101
  37. package/src/__tests__/browser-manager.test.ts +97 -75
  38. package/src/__tests__/browser-runtime-check.test.ts +16 -15
  39. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +12 -10
  40. package/src/__tests__/browser-skill-endstate.test.ts +97 -72
  41. package/src/__tests__/bundle-scanner.test.ts +47 -22
  42. package/src/__tests__/bundled-asset.test.ts +74 -47
  43. package/src/__tests__/call-constants.test.ts +19 -19
  44. package/src/__tests__/call-controller.test.ts +0 -1
  45. package/src/__tests__/call-conversation-messages.test.ts +90 -65
  46. package/src/__tests__/call-domain.test.ts +149 -121
  47. package/src/__tests__/call-pointer-message-composer.test.ts +113 -83
  48. package/src/__tests__/call-pointer-messages.test.ts +213 -154
  49. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +9 -10
  50. package/src/__tests__/call-recovery.test.ts +232 -212
  51. package/src/__tests__/call-routes-http.test.ts +0 -1
  52. package/src/__tests__/call-start-guardian-guard.test.ts +32 -30
  53. package/src/__tests__/call-state-machine.test.ts +62 -51
  54. package/src/__tests__/call-state.test.ts +89 -75
  55. package/src/__tests__/call-store.test.ts +387 -316
  56. package/src/__tests__/callback-handoff-copy.test.ts +84 -82
  57. package/src/__tests__/canonical-guardian-store.test.ts +331 -280
  58. package/src/__tests__/channel-approval-routes.test.ts +1643 -1115
  59. package/src/__tests__/channel-approval.test.ts +139 -137
  60. package/src/__tests__/channel-approvals.test.ts +7 -2
  61. package/src/__tests__/channel-delivery-store.test.ts +232 -194
  62. package/src/__tests__/channel-guardian.test.ts +5 -3
  63. package/src/__tests__/channel-invite-transport.test.ts +107 -92
  64. package/src/__tests__/channel-policy.test.ts +42 -38
  65. package/src/__tests__/channel-readiness-service.test.ts +119 -102
  66. package/src/__tests__/channel-reply-delivery.test.ts +147 -118
  67. package/src/__tests__/channel-retry-sweep.test.ts +153 -110
  68. package/src/__tests__/checker.test.ts +3309 -1850
  69. package/src/__tests__/clarification-resolver.test.ts +91 -79
  70. package/src/__tests__/classifier.test.ts +64 -54
  71. package/src/__tests__/claude-code-skill-regression.test.ts +42 -37
  72. package/src/__tests__/claude-code-tool-profiles.test.ts +31 -29
  73. package/src/__tests__/clawhub.test.ts +92 -82
  74. package/src/__tests__/cli.test.ts +30 -30
  75. package/src/__tests__/clipboard.test.ts +53 -46
  76. package/src/__tests__/commit-guarantee.test.ts +59 -52
  77. package/src/__tests__/commit-message-enrichment-service.test.ts +203 -75
  78. package/src/__tests__/compaction.benchmark.test.ts +33 -31
  79. package/src/__tests__/computer-use-session-compaction.test.ts +60 -50
  80. package/src/__tests__/computer-use-session-lifecycle.test.ts +145 -117
  81. package/src/__tests__/computer-use-session-working-dir.test.ts +62 -48
  82. package/src/__tests__/computer-use-skill-baseline.test.ts +22 -19
  83. package/src/__tests__/computer-use-skill-endstate.test.ts +45 -31
  84. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +121 -88
  85. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +65 -42
  86. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +33 -18
  87. package/src/__tests__/computer-use-tools.test.ts +121 -98
  88. package/src/__tests__/config-schema.test.ts +443 -347
  89. package/src/__tests__/config-watcher.test.ts +96 -81
  90. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +148 -133
  91. package/src/__tests__/conflict-intent-tokenization.test.ts +96 -78
  92. package/src/__tests__/conflict-policy.test.ts +151 -80
  93. package/src/__tests__/conflict-store.test.ts +203 -157
  94. package/src/__tests__/connection-policy.test.ts +89 -59
  95. package/src/__tests__/contacts-tools.test.ts +247 -178
  96. package/src/__tests__/context-memory-e2e.test.ts +306 -214
  97. package/src/__tests__/context-token-estimator.test.ts +114 -74
  98. package/src/__tests__/context-window-manager.test.ts +269 -167
  99. package/src/__tests__/contradiction-checker.test.ts +161 -135
  100. package/src/__tests__/conversation-attention-store.test.ts +350 -290
  101. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  102. package/src/__tests__/conversation-pairing.test.ts +220 -113
  103. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  104. package/src/__tests__/conversation-store.test.ts +390 -235
  105. package/src/__tests__/credential-broker-browser-fill.test.ts +325 -250
  106. package/src/__tests__/credential-broker-server-use.test.ts +283 -243
  107. package/src/__tests__/credential-broker.test.ts +128 -74
  108. package/src/__tests__/credential-host-pattern-match.test.ts +64 -44
  109. package/src/__tests__/credential-metadata-store.test.ts +360 -311
  110. package/src/__tests__/credential-policy-validate.test.ts +81 -65
  111. package/src/__tests__/credential-resolve.test.ts +212 -145
  112. package/src/__tests__/credential-security-e2e.test.ts +144 -103
  113. package/src/__tests__/credential-security-invariants.test.ts +253 -208
  114. package/src/__tests__/credential-selection.test.ts +254 -146
  115. package/src/__tests__/credential-vault-unit.test.ts +531 -341
  116. package/src/__tests__/credential-vault.test.ts +761 -484
  117. package/src/__tests__/daemon-assistant-events.test.ts +91 -66
  118. package/src/__tests__/daemon-lifecycle.test.ts +258 -190
  119. package/src/__tests__/daemon-server-session-init.test.ts +2 -1
  120. package/src/__tests__/date-context.test.ts +314 -249
  121. package/src/__tests__/db-migration-rollback.test.ts +259 -130
  122. package/src/__tests__/db-schedule-syntax-migration.test.ts +78 -41
  123. package/src/__tests__/delete-managed-skill-tool.test.ts +77 -53
  124. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
  125. package/src/__tests__/dictation-mode-detection.test.ts +77 -55
  126. package/src/__tests__/dictation-profile-store.test.ts +70 -56
  127. package/src/__tests__/dictation-text-processing.test.ts +53 -35
  128. package/src/__tests__/diff.test.ts +102 -98
  129. package/src/__tests__/domain-normalize.test.ts +54 -54
  130. package/src/__tests__/domain-policy.test.ts +71 -55
  131. package/src/__tests__/dynamic-page-surface.test.ts +31 -33
  132. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +69 -69
  133. package/src/__tests__/edit-engine.test.ts +56 -56
  134. package/src/__tests__/elevenlabs-client.test.ts +117 -91
  135. package/src/__tests__/elevenlabs-config.test.ts +32 -31
  136. package/src/__tests__/email-classifier.test.ts +15 -12
  137. package/src/__tests__/email-cli.test.ts +121 -108
  138. package/src/__tests__/emit-signal-routing-intent.test.ts +76 -69
  139. package/src/__tests__/encrypted-store.test.ts +180 -154
  140. package/src/__tests__/entity-extractor.test.ts +108 -87
  141. package/src/__tests__/entity-search.test.ts +664 -258
  142. package/src/__tests__/ephemeral-permissions.test.ts +224 -188
  143. package/src/__tests__/event-bus.test.ts +81 -77
  144. package/src/__tests__/extract-email.test.ts +29 -20
  145. package/src/__tests__/file-edit-tool.test.ts +62 -44
  146. package/src/__tests__/file-ops-service.test.ts +131 -114
  147. package/src/__tests__/file-read-tool.test.ts +48 -31
  148. package/src/__tests__/file-write-tool.test.ts +43 -37
  149. package/src/__tests__/filesystem-tools.test.ts +238 -209
  150. package/src/__tests__/followup-tools.test.ts +237 -162
  151. package/src/__tests__/forbidden-legacy-symbols.test.ts +19 -20
  152. package/src/__tests__/frontmatter.test.ts +96 -81
  153. package/src/__tests__/fuzzy-match-property.test.ts +75 -81
  154. package/src/__tests__/fuzzy-match.test.ts +71 -65
  155. package/src/__tests__/gateway-client-managed-outbound.test.ts +76 -57
  156. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  157. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  158. package/src/__tests__/gemini-image-service.test.ts +113 -100
  159. package/src/__tests__/gemini-provider.test.ts +297 -220
  160. package/src/__tests__/get-weather.test.ts +188 -114
  161. package/src/__tests__/gmail-integration.test.ts +13 -5
  162. package/src/__tests__/guardian-action-conversation-turn.test.ts +226 -171
  163. package/src/__tests__/guardian-action-copy-generator.test.ts +111 -93
  164. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -1
  165. package/src/__tests__/guardian-action-followup-store.test.ts +199 -167
  166. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +297 -250
  167. package/src/__tests__/guardian-action-late-reply.test.ts +462 -316
  168. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +23 -18
  169. package/src/__tests__/guardian-action-store.test.ts +158 -109
  170. package/src/__tests__/guardian-action-sweep.test.ts +114 -100
  171. package/src/__tests__/guardian-actions-endpoint.test.ts +440 -256
  172. package/src/__tests__/guardian-control-plane-policy.test.ts +497 -331
  173. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +217 -215
  174. package/src/__tests__/guardian-dispatch.test.ts +316 -256
  175. package/src/__tests__/guardian-grant-minting.test.ts +247 -178
  176. package/src/__tests__/guardian-outbound-http.test.ts +5 -3
  177. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +99 -96
  178. package/src/__tests__/guardian-question-copy.test.ts +17 -17
  179. package/src/__tests__/guardian-question-mode.test.ts +134 -100
  180. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  181. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  182. package/src/__tests__/guardian-verification-intent-routing.test.ts +94 -88
  183. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  184. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +0 -1
  185. package/src/__tests__/handle-user-message-secret-resume.test.ts +7 -2
  186. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +92 -76
  187. package/src/__tests__/handlers-cu-observation-blob.test.ts +103 -70
  188. package/src/__tests__/handlers-ipc-blob-probe.test.ts +77 -51
  189. package/src/__tests__/handlers-slack-config.test.ts +63 -54
  190. package/src/__tests__/handlers-task-submit-slash.test.ts +18 -18
  191. package/src/__tests__/handlers-telegram-config.test.ts +662 -329
  192. package/src/__tests__/handlers-twitter-config.test.ts +525 -298
  193. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +5 -2
  194. package/src/__tests__/headless-browser-interactions.test.ts +444 -280
  195. package/src/__tests__/headless-browser-navigate.test.ts +116 -79
  196. package/src/__tests__/headless-browser-read-tools.test.ts +123 -86
  197. package/src/__tests__/headless-browser-snapshot.test.ts +71 -56
  198. package/src/__tests__/heartbeat-service.test.ts +76 -58
  199. package/src/__tests__/history-repair-observability.test.ts +14 -14
  200. package/src/__tests__/history-repair.test.ts +171 -167
  201. package/src/__tests__/home-base-bootstrap.test.ts +30 -27
  202. package/src/__tests__/hooks-blocking.test.ts +86 -37
  203. package/src/__tests__/hooks-cli.test.ts +104 -68
  204. package/src/__tests__/hooks-config.test.ts +81 -43
  205. package/src/__tests__/hooks-discovery.test.ts +106 -96
  206. package/src/__tests__/hooks-integration.test.ts +78 -72
  207. package/src/__tests__/hooks-manager.test.ts +99 -61
  208. package/src/__tests__/hooks-runner.test.ts +94 -71
  209. package/src/__tests__/hooks-settings.test.ts +69 -64
  210. package/src/__tests__/hooks-templates.test.ts +85 -54
  211. package/src/__tests__/hooks-ts-runner.test.ts +82 -45
  212. package/src/__tests__/hooks-watch.test.ts +32 -22
  213. package/src/__tests__/host-file-edit-tool.test.ts +190 -148
  214. package/src/__tests__/host-file-read-tool.test.ts +86 -63
  215. package/src/__tests__/host-file-write-tool.test.ts +98 -64
  216. package/src/__tests__/host-shell-tool.test.ts +342 -233
  217. package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
  218. package/src/__tests__/ingress-member-store.test.ts +163 -159
  219. package/src/__tests__/ingress-reconcile.test.ts +13 -6
  220. package/src/__tests__/ingress-routes-http.test.ts +441 -356
  221. package/src/__tests__/ingress-url-consistency.test.ts +125 -64
  222. package/src/__tests__/integration-status.test.ts +93 -73
  223. package/src/__tests__/intent-routing.test.ts +148 -118
  224. package/src/__tests__/invite-redemption-service.test.ts +163 -121
  225. package/src/__tests__/ipc-blob-store.test.ts +104 -91
  226. package/src/__tests__/ipc-contract-inventory.test.ts +27 -15
  227. package/src/__tests__/ipc-contract.test.ts +24 -23
  228. package/src/__tests__/ipc-protocol.test.ts +52 -46
  229. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +61 -50
  230. package/src/__tests__/ipc-snapshot.test.ts +1135 -1056
  231. package/src/__tests__/ipc-validate.test.ts +240 -179
  232. package/src/__tests__/key-migration.test.ts +123 -90
  233. package/src/__tests__/keychain.test.ts +150 -123
  234. package/src/__tests__/lifecycle-docs-guard.test.ts +65 -64
  235. package/src/__tests__/llm-usage-store.test.ts +112 -87
  236. package/src/__tests__/managed-skill-lifecycle.test.ts +147 -108
  237. package/src/__tests__/managed-store.test.ts +411 -360
  238. package/src/__tests__/mcp-cli.test.ts +190 -124
  239. package/src/__tests__/mcp-health-check.test.ts +26 -21
  240. package/src/__tests__/media-generate-image.test.ts +122 -99
  241. package/src/__tests__/media-reuse-story.e2e.test.ts +282 -214
  242. package/src/__tests__/media-visibility-policy.test.ts +86 -38
  243. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +146 -100
  244. package/src/__tests__/memory-lifecycle-e2e.test.ts +385 -297
  245. package/src/__tests__/memory-query-builder.test.ts +32 -33
  246. package/src/__tests__/memory-recall-quality.test.ts +761 -407
  247. package/src/__tests__/memory-regressions.experimental.test.ts +443 -380
  248. package/src/__tests__/memory-regressions.test.ts +3725 -2642
  249. package/src/__tests__/memory-retrieval-budget.test.ts +7 -8
  250. package/src/__tests__/memory-retrieval.benchmark.test.ts +144 -109
  251. package/src/__tests__/memory-upsert-concurrency.test.ts +292 -201
  252. package/src/__tests__/messaging-send-tool.test.ts +36 -29
  253. package/src/__tests__/migration-cli-flows.test.ts +69 -53
  254. package/src/__tests__/migration-ordering.test.ts +103 -86
  255. package/src/__tests__/mime-builder.test.ts +55 -32
  256. package/src/__tests__/mock-signup-server.test.ts +384 -246
  257. package/src/__tests__/model-intents.test.ts +61 -37
  258. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +9 -12
  259. package/src/__tests__/no-is-trusted-guard.test.ts +24 -21
  260. package/src/__tests__/non-member-access-request.test.ts +3 -2
  261. package/src/__tests__/notification-broadcaster.test.ts +99 -81
  262. package/src/__tests__/notification-decision-fallback.test.ts +223 -178
  263. package/src/__tests__/notification-decision-strategy.test.ts +375 -337
  264. package/src/__tests__/notification-deep-link.test.ts +67 -61
  265. package/src/__tests__/notification-guardian-path.test.ts +248 -206
  266. package/src/__tests__/notification-routing-intent.test.ts +166 -93
  267. package/src/__tests__/notification-thread-candidate-validation.test.ts +78 -75
  268. package/src/__tests__/notification-thread-candidates.test.ts +64 -61
  269. package/src/__tests__/oauth-callback-registry.test.ts +40 -30
  270. package/src/__tests__/oauth-connect-handler.test.ts +109 -89
  271. package/src/__tests__/oauth-scope-policy.test.ts +63 -55
  272. package/src/__tests__/oauth2-gateway-transport.test.ts +252 -174
  273. package/src/__tests__/onboarding-starter-tasks.test.ts +93 -89
  274. package/src/__tests__/onboarding-template-contract.test.ts +93 -94
  275. package/src/__tests__/openai-provider.test.ts +366 -274
  276. package/src/__tests__/pairing-concurrent.test.ts +18 -12
  277. package/src/__tests__/pairing-routes.test.ts +45 -41
  278. package/src/__tests__/parallel-tool.benchmark.test.ts +108 -58
  279. package/src/__tests__/parser.test.ts +316 -226
  280. package/src/__tests__/path-classifier.test.ts +24 -25
  281. package/src/__tests__/path-policy.test.ts +187 -147
  282. package/src/__tests__/phone.test.ts +36 -36
  283. package/src/__tests__/platform-move-helper.test.ts +48 -40
  284. package/src/__tests__/platform-socket-path.test.ts +23 -24
  285. package/src/__tests__/platform-workspace-migration.test.ts +464 -414
  286. package/src/__tests__/platform.test.ts +61 -53
  287. package/src/__tests__/playbook-execution.test.ts +397 -265
  288. package/src/__tests__/playbook-tools.test.ts +267 -196
  289. package/src/__tests__/prebuilt-home-base-seed.test.ts +30 -27
  290. package/src/__tests__/pricing.test.ts +316 -136
  291. package/src/__tests__/profile-compiler.test.ts +206 -188
  292. package/src/__tests__/provider-commit-message-generator.test.ts +114 -106
  293. package/src/__tests__/provider-error-scenarios.test.ts +212 -158
  294. package/src/__tests__/provider-fail-open-selection.test.ts +51 -44
  295. package/src/__tests__/provider-registry-ollama.test.ts +13 -9
  296. package/src/__tests__/provider-streaming.benchmark.test.ts +232 -183
  297. package/src/__tests__/proxy-approval-callback.test.ts +180 -119
  298. package/src/__tests__/public-ingress-urls.test.ts +112 -94
  299. package/src/__tests__/qdrant-manager.test.ts +147 -98
  300. package/src/__tests__/ratelimit.test.ts +152 -82
  301. package/src/__tests__/recording-handler.test.ts +273 -151
  302. package/src/__tests__/recording-intent-fallback.test.ts +94 -75
  303. package/src/__tests__/recording-intent-handler.test.ts +9 -2
  304. package/src/__tests__/recording-intent.test.ts +578 -379
  305. package/src/__tests__/recording-state-machine.test.ts +530 -316
  306. package/src/__tests__/recurrence-engine-rruleset.test.ts +150 -92
  307. package/src/__tests__/recurrence-engine.test.ts +81 -41
  308. package/src/__tests__/recurrence-types.test.ts +63 -44
  309. package/src/__tests__/relay-server.test.ts +2131 -1602
  310. package/src/__tests__/reminder-store.test.ts +158 -80
  311. package/src/__tests__/reminder.test.ts +113 -109
  312. package/src/__tests__/remote-skill-policy.test.ts +96 -72
  313. package/src/__tests__/request-file-tool.test.ts +74 -67
  314. package/src/__tests__/response-tier.test.ts +131 -74
  315. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  316. package/src/__tests__/runtime-events-sse-parity.test.ts +167 -145
  317. package/src/__tests__/runtime-events-sse.test.ts +0 -1
  318. package/src/__tests__/sandbox-diagnostics.test.ts +66 -56
  319. package/src/__tests__/sandbox-host-parity.test.ts +377 -301
  320. package/src/__tests__/scaffold-managed-skill-tool.test.ts +213 -161
  321. package/src/__tests__/schedule-store.test.ts +268 -205
  322. package/src/__tests__/schedule-tools.test.ts +702 -524
  323. package/src/__tests__/scheduler-recurrence.test.ts +240 -130
  324. package/src/__tests__/scoped-approval-grants.test.ts +258 -168
  325. package/src/__tests__/scoped-grant-security-matrix.test.ts +160 -146
  326. package/src/__tests__/script-proxy-certs.test.ts +38 -35
  327. package/src/__tests__/script-proxy-connect-tunnel.test.ts +71 -46
  328. package/src/__tests__/script-proxy-decision-trace.test.ts +161 -84
  329. package/src/__tests__/script-proxy-http-forwarder.test.ts +146 -129
  330. package/src/__tests__/script-proxy-injection-runtime.test.ts +139 -113
  331. package/src/__tests__/script-proxy-mitm-handler.test.ts +226 -142
  332. package/src/__tests__/script-proxy-policy-runtime.test.ts +126 -86
  333. package/src/__tests__/script-proxy-policy.test.ts +308 -153
  334. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +74 -62
  335. package/src/__tests__/script-proxy-router.test.ts +111 -77
  336. package/src/__tests__/script-proxy-session-manager.test.ts +156 -113
  337. package/src/__tests__/script-proxy-session-runtime.test.ts +28 -24
  338. package/src/__tests__/secret-allowlist.test.ts +105 -90
  339. package/src/__tests__/secret-ingress-handler.test.ts +41 -30
  340. package/src/__tests__/secret-onetime-send.test.ts +67 -50
  341. package/src/__tests__/secret-prompt-log-hygiene.test.ts +35 -31
  342. package/src/__tests__/secret-response-routing.test.ts +50 -41
  343. package/src/__tests__/secret-scanner-executor.test.ts +152 -111
  344. package/src/__tests__/secret-scanner.test.ts +495 -413
  345. package/src/__tests__/secure-keys.test.ts +132 -121
  346. package/src/__tests__/send-endpoint-busy.test.ts +8 -3
  347. package/src/__tests__/send-notification-tool.test.ts +43 -42
  348. package/src/__tests__/sensitive-output-placeholders.test.ts +72 -64
  349. package/src/__tests__/sequence-store.test.ts +335 -167
  350. package/src/__tests__/server-history-render.test.ts +341 -202
  351. package/src/__tests__/session-abort-tool-results.test.ts +133 -70
  352. package/src/__tests__/session-confirmation-signals.test.ts +252 -160
  353. package/src/__tests__/session-conflict-gate.test.ts +775 -585
  354. package/src/__tests__/session-error.test.ts +222 -191
  355. package/src/__tests__/session-evictor.test.ts +79 -62
  356. package/src/__tests__/session-init.benchmark.test.ts +170 -108
  357. package/src/__tests__/session-load-history-repair.test.ts +273 -139
  358. package/src/__tests__/session-messaging-secret-redirect.test.ts +130 -90
  359. package/src/__tests__/session-pre-run-repair.test.ts +106 -59
  360. package/src/__tests__/session-profile-injection.test.ts +198 -130
  361. package/src/__tests__/session-provider-retry-repair.test.ts +223 -141
  362. package/src/__tests__/session-queue.test.ts +624 -321
  363. package/src/__tests__/session-runtime-assembly.test.ts +425 -329
  364. package/src/__tests__/session-runtime-workspace.test.ts +69 -61
  365. package/src/__tests__/session-skill-tools.test.ts +973 -678
  366. package/src/__tests__/session-slash-known.test.ts +185 -133
  367. package/src/__tests__/session-slash-queue.test.ts +147 -81
  368. package/src/__tests__/session-slash-unknown.test.ts +135 -90
  369. package/src/__tests__/session-surfaces-task-progress.test.ts +122 -87
  370. package/src/__tests__/session-tool-setup-app-refresh.test.ts +338 -177
  371. package/src/__tests__/session-tool-setup-memory-scope.test.ts +63 -40
  372. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +60 -37
  373. package/src/__tests__/session-tool-setup-tools-disabled.test.ts +28 -26
  374. package/src/__tests__/session-undo.test.ts +43 -30
  375. package/src/__tests__/session-workspace-cache-state.test.ts +108 -67
  376. package/src/__tests__/session-workspace-injection.test.ts +245 -117
  377. package/src/__tests__/session-workspace-tool-tracking.test.ts +260 -93
  378. package/src/__tests__/shared-filesystem-errors.test.ts +47 -47
  379. package/src/__tests__/shell-credential-ref.test.ts +126 -90
  380. package/src/__tests__/shell-identity.test.ts +134 -111
  381. package/src/__tests__/shell-parser-fuzz.test.ts +263 -179
  382. package/src/__tests__/shell-parser-property.test.ts +435 -288
  383. package/src/__tests__/shell-tool-proxy-mode.test.ts +142 -70
  384. package/src/__tests__/size-guard.test.ts +42 -44
  385. package/src/__tests__/skill-feature-flags-integration.test.ts +79 -52
  386. package/src/__tests__/skill-feature-flags.test.ts +75 -47
  387. package/src/__tests__/skill-include-graph.test.ts +143 -148
  388. package/src/__tests__/skill-load-feature-flag.test.ts +94 -59
  389. package/src/__tests__/skill-load-tool.test.ts +371 -199
  390. package/src/__tests__/skill-projection-feature-flag.test.ts +131 -88
  391. package/src/__tests__/skill-projection.benchmark.test.ts +93 -65
  392. package/src/__tests__/skill-script-runner-host.test.ts +460 -250
  393. package/src/__tests__/skill-script-runner-sandbox.test.ts +168 -108
  394. package/src/__tests__/skill-script-runner.test.ts +115 -74
  395. package/src/__tests__/skill-tool-factory.test.ts +140 -96
  396. package/src/__tests__/skill-tool-manifest.test.ts +306 -210
  397. package/src/__tests__/skill-version-hash.test.ts +70 -56
  398. package/src/__tests__/skills.test.ts +0 -1
  399. package/src/__tests__/slack-channel-config.test.ts +127 -84
  400. package/src/__tests__/slack-skill.test.ts +60 -47
  401. package/src/__tests__/slash-commands-catalog.test.ts +37 -31
  402. package/src/__tests__/slash-commands-parser.test.ts +71 -64
  403. package/src/__tests__/slash-commands-resolver.test.ts +143 -107
  404. package/src/__tests__/slash-commands-rewrite.test.ts +22 -22
  405. package/src/__tests__/sms-messaging-provider.test.ts +4 -0
  406. package/src/__tests__/speaker-identification.test.ts +28 -25
  407. package/src/__tests__/starter-bundle.test.ts +27 -23
  408. package/src/__tests__/starter-task-flow.test.ts +67 -52
  409. package/src/__tests__/subagent-manager-notify.test.ts +154 -108
  410. package/src/__tests__/subagent-tools.test.ts +311 -270
  411. package/src/__tests__/subagent-types.test.ts +40 -40
  412. package/src/__tests__/surface-mutex-cleanup.test.ts +42 -30
  413. package/src/__tests__/swarm-dag-pathological.test.ts +122 -111
  414. package/src/__tests__/swarm-orchestrator.test.ts +135 -101
  415. package/src/__tests__/swarm-plan-validator.test.ts +125 -73
  416. package/src/__tests__/swarm-recursion.test.ts +58 -46
  417. package/src/__tests__/swarm-router-planner.test.ts +99 -74
  418. package/src/__tests__/swarm-session-integration.test.ts +148 -91
  419. package/src/__tests__/swarm-tool.test.ts +65 -45
  420. package/src/__tests__/swarm-worker-backend.test.ts +59 -45
  421. package/src/__tests__/swarm-worker-runner.test.ts +133 -118
  422. package/src/__tests__/system-prompt.test.ts +311 -256
  423. package/src/__tests__/task-compiler.test.ts +176 -120
  424. package/src/__tests__/task-management-tools.test.ts +561 -456
  425. package/src/__tests__/task-memory-cleanup.test.ts +627 -362
  426. package/src/__tests__/task-runner.test.ts +117 -94
  427. package/src/__tests__/task-scheduler.test.ts +113 -84
  428. package/src/__tests__/task-tools.test.ts +349 -264
  429. package/src/__tests__/terminal-sandbox.test.ts +138 -108
  430. package/src/__tests__/terminal-tools.test.ts +350 -305
  431. package/src/__tests__/thread-seed-composer.test.ts +307 -180
  432. package/src/__tests__/tool-approval-handler.test.ts +238 -137
  433. package/src/__tests__/tool-audit-listener.test.ts +69 -69
  434. package/src/__tests__/tool-domain-event-publisher.test.ts +142 -132
  435. package/src/__tests__/tool-execution-abort-cleanup.test.ts +155 -146
  436. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +136 -105
  437. package/src/__tests__/tool-executor-lifecycle-events.test.ts +355 -239
  438. package/src/__tests__/tool-executor-redaction.test.ts +112 -109
  439. package/src/__tests__/tool-executor-shell-integration.test.ts +130 -79
  440. package/src/__tests__/tool-executor.test.ts +1274 -674
  441. package/src/__tests__/tool-grant-request-escalation.test.ts +401 -283
  442. package/src/__tests__/tool-metrics-listener.test.ts +97 -85
  443. package/src/__tests__/tool-notification-listener.test.ts +42 -25
  444. package/src/__tests__/tool-permission-simulate-handler.test.ts +137 -113
  445. package/src/__tests__/tool-policy.test.ts +44 -25
  446. package/src/__tests__/tool-profiling-listener.test.ts +99 -93
  447. package/src/__tests__/tool-result-truncation.test.ts +5 -4
  448. package/src/__tests__/tool-trace-listener.test.ts +131 -111
  449. package/src/__tests__/top-level-renderer.test.ts +62 -58
  450. package/src/__tests__/top-level-scanner.test.ts +68 -64
  451. package/src/__tests__/trace-emitter.test.ts +56 -56
  452. package/src/__tests__/trust-context-guards.test.ts +65 -65
  453. package/src/__tests__/trust-store.test.ts +1239 -806
  454. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  455. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  456. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +3 -2
  457. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -2
  458. package/src/__tests__/trusted-contact-verification.test.ts +251 -231
  459. package/src/__tests__/turn-commit.test.ts +259 -200
  460. package/src/__tests__/twilio-provider.test.ts +140 -126
  461. package/src/__tests__/twilio-rest.test.ts +22 -18
  462. package/src/__tests__/twilio-routes-elevenlabs.test.ts +0 -1
  463. package/src/__tests__/twilio-routes-twiml.test.ts +55 -55
  464. package/src/__tests__/twilio-routes.test.ts +0 -1
  465. package/src/__tests__/twitter-auth-handler.test.ts +184 -139
  466. package/src/__tests__/twitter-cli-error-shaping.test.ts +88 -73
  467. package/src/__tests__/twitter-cli-routing.test.ts +146 -99
  468. package/src/__tests__/twitter-oauth-client.test.ts +82 -65
  469. package/src/__tests__/update-bulletin-format.test.ts +69 -66
  470. package/src/__tests__/update-bulletin-state.test.ts +66 -60
  471. package/src/__tests__/update-bulletin.test.ts +150 -114
  472. package/src/__tests__/update-template-contract.test.ts +15 -10
  473. package/src/__tests__/url-safety.test.ts +288 -265
  474. package/src/__tests__/user-reference.test.ts +32 -32
  475. package/src/__tests__/view-image-tool.test.ts +118 -96
  476. package/src/__tests__/voice-invite-redemption.test.ts +111 -106
  477. package/src/__tests__/voice-quality.test.ts +117 -102
  478. package/src/__tests__/voice-scoped-grant-consumer.test.ts +204 -146
  479. package/src/__tests__/voice-session-bridge.test.ts +351 -216
  480. package/src/__tests__/weather-skill-regression.test.ts +170 -120
  481. package/src/__tests__/web-fetch.test.ts +664 -526
  482. package/src/__tests__/web-search.test.ts +379 -213
  483. package/src/__tests__/work-item-output.test.ts +90 -53
  484. package/src/__tests__/workspace-git-service.test.ts +437 -356
  485. package/src/__tests__/workspace-heartbeat-service.test.ts +125 -91
  486. package/src/__tests__/workspace-lifecycle.test.ts +98 -64
  487. package/src/__tests__/workspace-policy.test.ts +139 -71
  488. package/src/cli/mcp.ts +81 -28
  489. package/src/commands/__tests__/cc-command-registry.test.ts +142 -134
  490. package/src/config/__tests__/feature-flag-registry-guard.test.ts +48 -39
  491. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +25 -10
  492. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -1
  493. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +6 -11
  494. package/src/config/bundled-skills/messaging/SKILL.md +4 -3
  495. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +15 -5
  496. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -5
  497. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -2
  498. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +34 -32
  499. package/src/config/bundled-skills/sms-setup/SKILL.md +8 -16
  500. package/src/config/bundled-skills/telegram-setup/SKILL.md +3 -3
  501. package/src/config/bundled-skills/trusted-contacts/SKILL.md +13 -25
  502. package/src/config/bundled-skills/twilio-setup/SKILL.md +13 -23
  503. package/src/config/bundled-tool-registry.ts +2 -0
  504. package/src/config/env.ts +3 -4
  505. package/src/config/system-prompt.ts +32 -0
  506. package/src/mcp/client.ts +2 -7
  507. package/src/memory/db-connection.ts +16 -10
  508. package/src/messaging/providers/gmail/adapter.ts +10 -3
  509. package/src/messaging/providers/gmail/client.ts +280 -72
  510. package/src/runtime/auth/__tests__/context.test.ts +75 -65
  511. package/src/runtime/auth/__tests__/credential-service.test.ts +137 -114
  512. package/src/runtime/auth/__tests__/guard-tests.test.ts +84 -90
  513. package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +40 -40
  514. package/src/runtime/auth/__tests__/middleware.test.ts +80 -74
  515. package/src/runtime/auth/__tests__/policy.test.ts +9 -9
  516. package/src/runtime/auth/__tests__/route-policy.test.ts +76 -65
  517. package/src/runtime/auth/__tests__/scopes.test.ts +68 -60
  518. package/src/runtime/auth/__tests__/subject.test.ts +54 -54
  519. package/src/runtime/auth/__tests__/token-service.test.ts +115 -108
  520. package/src/runtime/auth/scopes.ts +3 -0
  521. package/src/runtime/auth/token-service.ts +4 -1
  522. package/src/runtime/auth/types.ts +2 -1
  523. package/src/runtime/http-server.ts +2 -1
  524. package/src/security/secure-keys.ts +120 -54
  525. package/src/tools/browser/__tests__/auth-cache.test.ts +69 -63
  526. package/src/tools/browser/__tests__/auth-detector.test.ts +218 -157
  527. package/src/tools/browser/__tests__/jit-auth.test.ts +83 -99
  528. package/src/tools/terminal/safe-env.ts +7 -0
@@ -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
  });