@stigmer/runner 3.9.0 → 3.11.0

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 (270) hide show
  1. package/README.md +12 -1
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/call-llm.js +9 -10
  4. package/dist/activities/call-llm.js.map +1 -1
  5. package/dist/activities/classify-tool-approvals.d.ts +2 -1
  6. package/dist/activities/classify-tool-approvals.js +28 -2
  7. package/dist/activities/classify-tool-approvals.js.map +1 -1
  8. package/dist/activities/discover-mcp-server.d.ts +32 -0
  9. package/dist/activities/discover-mcp-server.js +162 -27
  10. package/dist/activities/discover-mcp-server.js.map +1 -1
  11. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +8 -0
  12. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +1 -1
  13. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
  14. package/dist/activities/execute-cursor/approval-state.d.ts +28 -2
  15. package/dist/activities/execute-cursor/approval-state.js +7 -1
  16. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  17. package/dist/activities/execute-cursor/attachment-resolver.d.ts +14 -0
  18. package/dist/activities/execute-cursor/attachment-resolver.js +25 -4
  19. package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
  20. package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
  21. package/dist/activities/execute-cursor/blueprint-resolver.js +6 -22
  22. package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
  23. package/dist/activities/execute-cursor/env-resolver.js +3 -1
  24. package/dist/activities/execute-cursor/env-resolver.js.map +1 -1
  25. package/dist/activities/execute-cursor/error-classifier.d.ts +40 -3
  26. package/dist/activities/execute-cursor/error-classifier.js +81 -3
  27. package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
  28. package/dist/activities/execute-cursor/extract-structured-output.d.ts +29 -0
  29. package/dist/activities/execute-cursor/extract-structured-output.js +58 -0
  30. package/dist/activities/execute-cursor/extract-structured-output.js.map +1 -0
  31. package/dist/activities/execute-cursor/hook-script.d.ts +14 -3
  32. package/dist/activities/execute-cursor/hook-script.js +72 -10
  33. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  34. package/dist/activities/execute-cursor/index.d.ts +5 -1
  35. package/dist/activities/execute-cursor/index.js +59 -58
  36. package/dist/activities/execute-cursor/index.js.map +1 -1
  37. package/dist/activities/execute-cursor/mcp-resolver.d.ts +24 -1
  38. package/dist/activities/execute-cursor/mcp-resolver.js +5 -2
  39. package/dist/activities/execute-cursor/mcp-resolver.js.map +1 -1
  40. package/dist/activities/execute-cursor/prompt-builder.d.ts +18 -4
  41. package/dist/activities/execute-cursor/prompt-builder.js +12 -7
  42. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  43. package/dist/activities/execute-cursor/turn-stream.js +4 -1
  44. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  45. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +18 -1
  46. package/dist/activities/execute-deep-agent/attachment-injector.js +68 -23
  47. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  48. package/dist/activities/execute-deep-agent/environment.js +3 -1
  49. package/dist/activities/execute-deep-agent/environment.js.map +1 -1
  50. package/dist/activities/execute-deep-agent/index.js +15 -0
  51. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  52. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -7
  53. package/dist/activities/execute-deep-agent/prompt-builder.js +8 -2
  54. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  55. package/dist/activities/execute-deep-agent/setup.d.ts +10 -0
  56. package/dist/activities/execute-deep-agent/setup.js +65 -26
  57. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  58. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +18 -1
  59. package/dist/activities/execute-deep-agent/subagent-transformer.js +8 -1
  60. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  61. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +11 -4
  62. package/dist/activities/execute-deep-agent/subagent-wiring.js +13 -4
  63. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  64. package/dist/activities/hydrate-workflow-execution.js +3 -1
  65. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  66. package/dist/activities/workflow-event-activities.d.ts +28 -10
  67. package/dist/activities/workflow-event-activities.js +87 -58
  68. package/dist/activities/workflow-event-activities.js.map +1 -1
  69. package/dist/claimcheck/payload-codec.js +21 -1
  70. package/dist/claimcheck/payload-codec.js.map +1 -1
  71. package/dist/client/stigmer-client.d.ts +9 -4
  72. package/dist/client/stigmer-client.js +28 -15
  73. package/dist/client/stigmer-client.js.map +1 -1
  74. package/dist/encryption/config.d.ts +32 -0
  75. package/dist/encryption/config.js +68 -0
  76. package/dist/encryption/config.js.map +1 -0
  77. package/dist/encryption/index.d.ts +3 -0
  78. package/dist/encryption/index.js +3 -0
  79. package/dist/encryption/index.js.map +1 -0
  80. package/dist/encryption/payload-codec.d.ts +41 -0
  81. package/dist/encryption/payload-codec.js +130 -0
  82. package/dist/encryption/payload-codec.js.map +1 -0
  83. package/dist/payload-codecs.d.ts +16 -0
  84. package/dist/payload-codecs.js +38 -0
  85. package/dist/payload-codecs.js.map +1 -0
  86. package/dist/preflight.d.ts +31 -0
  87. package/dist/preflight.js +43 -0
  88. package/dist/preflight.js.map +1 -1
  89. package/dist/runner-manager.js +5 -15
  90. package/dist/runner-manager.js.map +1 -1
  91. package/dist/runner.js +5 -16
  92. package/dist/runner.js.map +1 -1
  93. package/dist/shared/approval-policy.d.ts +9 -3
  94. package/dist/shared/approval-policy.js +15 -6
  95. package/dist/shared/approval-policy.js.map +1 -1
  96. package/dist/shared/attachment-naming.d.ts +53 -0
  97. package/dist/shared/attachment-naming.js +59 -0
  98. package/dist/shared/attachment-naming.js.map +1 -0
  99. package/dist/shared/attachment-vision.d.ts +43 -2
  100. package/dist/shared/attachment-vision.js +72 -6
  101. package/dist/shared/attachment-vision.js.map +1 -1
  102. package/dist/shared/caller-identity.d.ts +23 -2
  103. package/dist/shared/caller-identity.js +36 -5
  104. package/dist/shared/caller-identity.js.map +1 -1
  105. package/dist/shared/channel-attachment.js +1 -0
  106. package/dist/shared/channel-attachment.js.map +1 -1
  107. package/dist/shared/checkpointer/http-saver.d.ts +42 -1
  108. package/dist/shared/checkpointer/http-saver.js +96 -8
  109. package/dist/shared/checkpointer/http-saver.js.map +1 -1
  110. package/dist/shared/conversation-attachment.js +1 -0
  111. package/dist/shared/conversation-attachment.js.map +1 -1
  112. package/dist/shared/datastore-attachment.d.ts +50 -7
  113. package/dist/shared/datastore-attachment.js +93 -11
  114. package/dist/shared/datastore-attachment.js.map +1 -1
  115. package/dist/shared/http-retry.d.ts +43 -0
  116. package/dist/shared/http-retry.js +50 -0
  117. package/dist/shared/http-retry.js.map +1 -0
  118. package/dist/shared/llm-backend.d.ts +275 -0
  119. package/dist/shared/llm-backend.js +425 -0
  120. package/dist/shared/llm-backend.js.map +1 -0
  121. package/dist/shared/llm-proxy.d.ts +8 -0
  122. package/dist/shared/llm-proxy.js +15 -0
  123. package/dist/shared/llm-proxy.js.map +1 -1
  124. package/dist/shared/mcp-enabled-tools.d.ts +57 -0
  125. package/dist/shared/mcp-enabled-tools.js +86 -0
  126. package/dist/shared/mcp-enabled-tools.js.map +1 -0
  127. package/dist/shared/mcp-manager.d.ts +17 -2
  128. package/dist/shared/mcp-manager.js +37 -25
  129. package/dist/shared/mcp-manager.js.map +1 -1
  130. package/dist/shared/mcp-resolver.d.ts +39 -2
  131. package/dist/shared/mcp-resolver.js +38 -2
  132. package/dist/shared/mcp-resolver.js.map +1 -1
  133. package/dist/shared/model-client.d.ts +12 -5
  134. package/dist/shared/model-client.js +138 -18
  135. package/dist/shared/model-client.js.map +1 -1
  136. package/dist/shared/model-error.js +198 -5
  137. package/dist/shared/model-error.js.map +1 -1
  138. package/dist/shared/model-registry.d.ts +20 -2
  139. package/dist/shared/model-registry.js +37 -2
  140. package/dist/shared/model-registry.js.map +1 -1
  141. package/dist/shared/plan-mode-permissions.d.ts +26 -0
  142. package/dist/shared/plan-mode-permissions.js +28 -0
  143. package/dist/shared/plan-mode-permissions.js.map +1 -0
  144. package/dist/worker.d.ts +2 -1
  145. package/dist/worker.js +2 -4
  146. package/dist/worker.js.map +1 -1
  147. package/dist/workflow-engine/types.d.ts +18 -0
  148. package/dist/workflow-engine/types.js.map +1 -1
  149. package/dist/workflows/call-agent-orchestrator.d.ts +9 -0
  150. package/dist/workflows/call-agent-orchestrator.js +1 -0
  151. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  152. package/dist/workflows/connect-mcp-server.js +7 -0
  153. package/dist/workflows/connect-mcp-server.js.map +1 -1
  154. package/dist/workflows/engine-core.js +23 -2
  155. package/dist/workflows/engine-core.js.map +1 -1
  156. package/dist/workflows/execute-from-execution.d.ts +1 -1
  157. package/dist/workflows/execute-from-execution.js +11 -1
  158. package/dist/workflows/execute-from-execution.js.map +1 -1
  159. package/package.json +8 -2
  160. package/src/__tests__/claimcheck-codec.test.ts +36 -0
  161. package/src/__tests__/encryption-codec.test.ts +234 -0
  162. package/src/__tests__/fixtures/encrypted-payload-fixture.json +15 -0
  163. package/src/__tests__/history-encryption-e2e.test.ts +243 -0
  164. package/src/__tests__/preflight.test.ts +50 -2
  165. package/src/activities/__tests__/call-llm.test.ts +75 -0
  166. package/src/activities/__tests__/classify-tool-approvals.test.ts +117 -1
  167. package/src/activities/__tests__/discover-mcp-server.hang.test.ts +103 -0
  168. package/src/activities/__tests__/discover-mcp-server.test.ts +203 -0
  169. package/src/activities/__tests__/workflow-event-activities.test.ts +107 -8
  170. package/src/activities/call-llm.ts +9 -16
  171. package/src/activities/classify-tool-approvals.ts +34 -4
  172. package/src/activities/discover-mcp-server.ts +190 -32
  173. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +9 -0
  174. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +14 -0
  175. package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +92 -0
  176. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +40 -14
  177. package/src/activities/execute-cursor/__tests__/error-classifier-extraction.test.ts +208 -0
  178. package/src/activities/execute-cursor/__tests__/extract-structured-output.test.ts +120 -0
  179. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +93 -0
  180. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +125 -0
  181. package/src/activities/execute-cursor/__tests__/prompt-builder-delegation.test.ts +1 -1
  182. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +13 -0
  183. package/src/activities/execute-cursor/approval-state.ts +30 -1
  184. package/src/activities/execute-cursor/attachment-resolver.ts +38 -3
  185. package/src/activities/execute-cursor/blueprint-resolver.ts +7 -27
  186. package/src/activities/execute-cursor/env-resolver.ts +3 -1
  187. package/src/activities/execute-cursor/error-classifier.ts +91 -4
  188. package/src/activities/execute-cursor/extract-structured-output.ts +72 -0
  189. package/src/activities/execute-cursor/hook-script.ts +74 -10
  190. package/src/activities/execute-cursor/index.ts +63 -72
  191. package/src/activities/execute-cursor/mcp-resolver.ts +36 -2
  192. package/src/activities/execute-cursor/prompt-builder.ts +34 -9
  193. package/src/activities/execute-cursor/turn-stream.ts +5 -2
  194. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +132 -8
  195. package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +104 -0
  196. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +2 -0
  197. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  198. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  199. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +34 -5
  200. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  201. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +173 -0
  202. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +12 -7
  203. package/src/activities/execute-deep-agent/attachment-injector.ts +94 -30
  204. package/src/activities/execute-deep-agent/environment.ts +3 -1
  205. package/src/activities/execute-deep-agent/index.ts +20 -0
  206. package/src/activities/execute-deep-agent/prompt-builder.ts +20 -10
  207. package/src/activities/execute-deep-agent/setup.ts +86 -31
  208. package/src/activities/execute-deep-agent/subagent-transformer.ts +23 -1
  209. package/src/activities/execute-deep-agent/subagent-wiring.ts +14 -4
  210. package/src/activities/hydrate-workflow-execution.ts +3 -1
  211. package/src/activities/workflow-event-activities.ts +96 -69
  212. package/src/claimcheck/payload-codec.ts +33 -1
  213. package/src/client/__tests__/stigmer-client.test.ts +8 -8
  214. package/src/client/stigmer-client.ts +32 -18
  215. package/src/encryption/config.ts +91 -0
  216. package/src/encryption/index.ts +3 -0
  217. package/src/encryption/payload-codec.ts +152 -0
  218. package/src/payload-codecs.ts +56 -0
  219. package/src/preflight.ts +45 -0
  220. package/src/runner-manager.ts +6 -24
  221. package/src/runner.ts +6 -25
  222. package/src/shared/__tests__/approval-policy.test.ts +82 -39
  223. package/src/shared/__tests__/attachment-naming.test.ts +159 -0
  224. package/src/shared/__tests__/attachment-vision.test.ts +97 -0
  225. package/src/shared/__tests__/bedrock-adapter.test.ts +213 -0
  226. package/src/shared/__tests__/bedrock-seam.test.ts +390 -0
  227. package/src/shared/__tests__/caller-identity.test.ts +25 -0
  228. package/src/shared/__tests__/channel-attachment.test.ts +1 -1
  229. package/src/shared/__tests__/connect-backfill.test.ts +1 -0
  230. package/src/shared/__tests__/conversation-attachment.test.ts +1 -1
  231. package/src/shared/__tests__/datastore-attachment.test.ts +129 -1
  232. package/src/shared/__tests__/foundry-adapter.test.ts +276 -0
  233. package/src/shared/__tests__/foundry-seam.test.ts +482 -0
  234. package/src/shared/__tests__/http-retry.test.ts +67 -0
  235. package/src/shared/__tests__/llm-backend.test.ts +616 -0
  236. package/src/shared/__tests__/mcp-enabled-tools.test.ts +86 -0
  237. package/src/shared/__tests__/mcp-manager.test.ts +170 -2
  238. package/src/shared/__tests__/mcp-resolver.test.ts +146 -3
  239. package/src/shared/__tests__/model-client.test.ts +154 -0
  240. package/src/shared/__tests__/model-error.test.ts +289 -1
  241. package/src/shared/__tests__/model-registry.test.ts +71 -0
  242. package/src/shared/__tests__/synthesized-attachment.test.ts +1 -0
  243. package/src/shared/__tests__/vertex-adapter.test.ts +169 -0
  244. package/src/shared/__tests__/vertex-seam.test.ts +295 -0
  245. package/src/shared/approval-policy.ts +14 -7
  246. package/src/shared/attachment-naming.ts +78 -0
  247. package/src/shared/attachment-vision.ts +92 -9
  248. package/src/shared/caller-identity.ts +40 -5
  249. package/src/shared/channel-attachment.ts +1 -0
  250. package/src/shared/checkpointer/__tests__/http-saver.test.ts +196 -1
  251. package/src/shared/checkpointer/http-saver.ts +117 -9
  252. package/src/shared/conversation-attachment.ts +1 -0
  253. package/src/shared/datastore-attachment.ts +106 -11
  254. package/src/shared/http-retry.ts +50 -0
  255. package/src/shared/llm-backend.ts +544 -0
  256. package/src/shared/llm-proxy.ts +15 -0
  257. package/src/shared/mcp-enabled-tools.ts +105 -0
  258. package/src/shared/mcp-manager.ts +43 -26
  259. package/src/shared/mcp-resolver.ts +73 -2
  260. package/src/shared/model-client.ts +161 -19
  261. package/src/shared/model-error.ts +222 -4
  262. package/src/shared/model-registry.ts +50 -2
  263. package/src/shared/plan-mode-permissions.ts +30 -0
  264. package/src/worker.ts +4 -5
  265. package/src/workflow-engine/types.ts +18 -0
  266. package/src/workflows/__tests__/execute-serverless-workflow.test.ts +68 -2
  267. package/src/workflows/call-agent-orchestrator.ts +10 -0
  268. package/src/workflows/connect-mcp-server.ts +7 -0
  269. package/src/workflows/engine-core.ts +23 -2
  270. package/src/workflows/execute-from-execution.ts +12 -2
@@ -143,6 +143,71 @@ describe("VisionBudget.offer — eligibility", () => {
143
143
  });
144
144
  });
145
145
 
146
+ describe("VisionBudget — model vision capability gate", () => {
147
+ const blind = () => new VisionBudget(DEEP_AGENT_VISION_PROFILE, { modelVision: false });
148
+
149
+ it("degrades every recognizable image with model_no_vision when the model is flagged blind", () => {
150
+ expect(blind().offer("photo.png", "image/png", imageBytes("png"))).toEqual({
151
+ kind: "degraded",
152
+ reason: "model_no_vision",
153
+ });
154
+ // Sniff-authoritative acceptance is gated too: real image bytes with a
155
+ // non-image declared type are still image-shaped.
156
+ expect(blind().offer("blob.bin", "application/octet-stream", imageBytes("jpeg"))).toEqual({
157
+ kind: "degraded",
158
+ reason: "model_no_vision",
159
+ });
160
+ });
161
+
162
+ it("reports model_no_vision (not type_mismatch) for declared-but-unsniffable images", () => {
163
+ // A HEIC renamed .jpg on a blind model: "unreadable format" would invite
164
+ // a re-encode that cannot help — blindness is the whole story.
165
+ expect(blind().offer("photo.jpg", "image/jpeg", Buffer.from("not an image"))).toEqual({
166
+ kind: "degraded",
167
+ reason: "model_no_vision",
168
+ });
169
+ });
170
+
171
+ it("gates before format and size rules so their resend advice never leaks", () => {
172
+ const cursorBlind = new VisionBudget(CURSOR_VISION_PROFILE, {
173
+ maxImageBytes: 100,
174
+ modelVision: false,
175
+ });
176
+ // WebP on the Cursor profile would be unsupported_format; oversized would
177
+ // be too_large. Blindness pre-empts both.
178
+ expect(cursorBlind.offer("sticker.webp", "image/webp", imageBytes("webp"))).toEqual({
179
+ kind: "degraded",
180
+ reason: "model_no_vision",
181
+ });
182
+ expect(cursorBlind.offer("big.png", "image/png", imageBytes("png", 101))).toEqual({
183
+ kind: "degraded",
184
+ reason: "model_no_vision",
185
+ });
186
+ });
187
+
188
+ it("still skips non-image input silently on a blind model", () => {
189
+ expect(blind().offer("doc.pdf", "application/pdf", Buffer.from("%PDF-1.7"))).toEqual({
190
+ kind: "skipped",
191
+ });
192
+ });
193
+
194
+ it("treats explicit true and unknown identically: fail-open", () => {
195
+ const sighted = new VisionBudget(DEEP_AGENT_VISION_PROFILE, { modelVision: true });
196
+ const unknown = new VisionBudget(DEEP_AGENT_VISION_PROFILE, { modelVision: undefined });
197
+ expect(sighted.offer("a.png", "image/png", imageBytes("png")).kind).toBe("accepted");
198
+ expect(unknown.offer("a.png", "image/png", imageBytes("png")).kind).toBe("accepted");
199
+ });
200
+
201
+ it("modelCannotSee + offerBlind settle a candidate without reading bytes", () => {
202
+ const budget = blind();
203
+ expect(budget.modelCannotSee()).toBe(true);
204
+ expect(budget.offerBlind()).toEqual({ kind: "degraded", reason: "model_no_vision" });
205
+
206
+ const sighted = new VisionBudget(DEEP_AGENT_VISION_PROFILE);
207
+ expect(sighted.modelCannotSee()).toBe(false);
208
+ });
209
+ });
210
+
146
211
  describe("VisionBudget — size and count budgets", () => {
147
212
  it("accepts exactly at the per-image cap and degrades one byte over it", () => {
148
213
  const budget = new VisionBudget(DEEP_AGENT_VISION_PROFILE, {
@@ -317,6 +382,38 @@ describe("visionDisclosureLines", () => {
317
382
  expect(lines).toHaveLength(2);
318
383
  });
319
384
 
385
+ it("gives blind-model entries honest advice instead of the resend suggestion", () => {
386
+ const lines = visionDisclosureLines(
387
+ [],
388
+ [
389
+ { path: ".stigmer/inputs/a.png", reason: "model_no_vision" },
390
+ { path: ".stigmer/inputs/b.jpg", reason: "model_no_vision" },
391
+ ],
392
+ );
393
+ expect(lines[0]).toBe(
394
+ "NOT VIEWABLE INLINE: `.stigmer/inputs/a.png` (model cannot view images), " +
395
+ "`.stigmer/inputs/b.jpg` (model cannot view images).",
396
+ );
397
+ expect(lines[1]).toContain("does not support image input");
398
+ expect(lines[1]).toContain("no resend will help");
399
+ // The resend-smaller suggestion must never appear for a blind model.
400
+ expect(lines.join("\n")).not.toContain("resend it as a smaller PNG or JPEG");
401
+ expect(lines).toHaveLength(2);
402
+ });
403
+
404
+ it("keeps both advice lines, each scoped to its reasons, when reasons mix", () => {
405
+ const lines = visionDisclosureLines(
406
+ [],
407
+ [
408
+ { path: ".stigmer/inputs/big.png", reason: "too_large" },
409
+ { path: ".stigmer/inputs/a.png", reason: "model_no_vision" },
410
+ ],
411
+ );
412
+ expect(lines[1]).toContain("ask the user to resend");
413
+ expect(lines[2]).toContain("no resend will help");
414
+ expect(lines).toHaveLength(3);
415
+ });
416
+
320
417
  it("returns nothing when there is nothing to disclose", () => {
321
418
  expect(visionDisclosureLines([], [])).toEqual([]);
322
419
  });
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Adapter test for the bedrock backend branch of buildChatModel — the
3
+ * complement of bedrock-seam.test.ts, mirroring vertex-adapter.test.ts.
4
+ *
5
+ * The seam test pins the REAL AnthropicBedrock client's wire behavior under
6
+ * a hand-built ChatAnthropic. This file pins OUR production wiring: a real
7
+ * `buildChatModel` and a real `ChatAnthropic` drive a mocked
8
+ * `@anthropic-ai/bedrock-sdk`, so LangChain's lazy `createClient` invocation
9
+ * path (client constructed on first request, once per cached client) is
10
+ * exercised exactly as production does it. Together the two files cover the
11
+ * whole chain without any test-only injection seam in production code.
12
+ *
13
+ * Pinned here:
14
+ * - the factory forwards LangChain's `maxRetries: 0` to the client (a
15
+ * factory that drops it nests SDK retries inside LangChain's own loop),
16
+ * - the factory forwards the request timeout (clientOptions.timeout ->
17
+ * factory options -> SDK constructor; how STIGMER_LLM_REQUEST_TIMEOUT_MS
18
+ * bounds this backend),
19
+ * - construction and invocation succeed with NO ANTHROPIC_API_KEY (Bedrock
20
+ * auth is AWS's, and ChatAnthropic waives the key when createClient is
21
+ * provided),
22
+ * - the translated `anthropic.…-v1:0` id (with the deployment's inference
23
+ * prefix and map overrides applied) crosses the wire while the returned
24
+ * apiModelId stays canonical (the canonical-id invariant),
25
+ * - the maxTokens landmine: Bedrock ids miss LangChain's per-model default
26
+ * table, so the adapter must inject the CANONICAL id's default,
27
+ * - usage_metadata survives the adapter (what billing reads).
28
+ */
29
+
30
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
31
+ import { HumanMessage, AIMessage } from "@langchain/core/messages";
32
+
33
+ const { bedrockCtorArgs, createRequests } = vi.hoisted(() => ({
34
+ bedrockCtorArgs: [] as Array<{ maxRetries?: number; timeout?: number }>,
35
+ createRequests: [] as Array<Record<string, unknown>>,
36
+ }));
37
+
38
+ vi.mock("@anthropic-ai/bedrock-sdk", () => ({
39
+ AnthropicBedrock: class {
40
+ readonly maxRetries: number | undefined;
41
+ readonly messages = {
42
+ create: async (request: Record<string, unknown>) => {
43
+ createRequests.push(request);
44
+ return {
45
+ id: "msg_bedrock_adapter_01",
46
+ type: "message",
47
+ role: "assistant",
48
+ model: request.model,
49
+ content: [{ type: "text", text: "Namaste from Bedrock." }],
50
+ stop_reason: "end_turn",
51
+ stop_sequence: null,
52
+ usage: { input_tokens: 7, output_tokens: 5 },
53
+ };
54
+ },
55
+ };
56
+
57
+ constructor(opts: { maxRetries?: number; timeout?: number }) {
58
+ bedrockCtorArgs.push(opts);
59
+ this.maxRetries = opts.maxRetries;
60
+ }
61
+ },
62
+ }));
63
+
64
+ import { ChatAnthropic } from "@langchain/anthropic";
65
+
66
+ import { buildChatModel } from "../model-client.js";
67
+ import { _resetRegistryCache } from "../model-registry.js";
68
+
69
+ describe("buildChatModel bedrock adapter", () => {
70
+ const savedEnv = { ...process.env };
71
+
72
+ beforeEach(() => {
73
+ _resetRegistryCache();
74
+ bedrockCtorArgs.length = 0;
75
+ createRequests.length = 0;
76
+ delete process.env.ANTHROPIC_API_KEY;
77
+ delete process.env.STIGMER_BEDROCK_MODEL_MAP;
78
+ delete process.env.STIGMER_BEDROCK_INFERENCE_PREFIX;
79
+ process.env.STIGMER_ANTHROPIC_BACKEND = "bedrock";
80
+ process.env.AWS_REGION = "ap-south-1";
81
+ // Registry offline → resolveToApiModelId passes the id through, keeping
82
+ // this test free of registry fixtures (that path has its own tests).
83
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("registry offline"));
84
+ });
85
+
86
+ afterEach(() => {
87
+ _resetRegistryCache();
88
+ vi.restoreAllMocks();
89
+ process.env = { ...savedEnv };
90
+ });
91
+
92
+ it("drives the mocked client through the real createClient path with the invariants intact", async () => {
93
+ const { model, apiModelId } = await buildChatModel({
94
+ modelName: "claude-sonnet-4-5-20250929",
95
+ maxTokens: 1024,
96
+ });
97
+
98
+ // Canonical id returned; the client is not constructed yet (lazy factory).
99
+ expect(apiModelId).toBe("claude-sonnet-4-5-20250929");
100
+ expect(bedrockCtorArgs).toHaveLength(0);
101
+
102
+ const result = await model.invoke([new HumanMessage("Weather in Chennai?")]);
103
+
104
+ // First request constructed exactly one (batch) client, with LangChain's
105
+ // maxRetries: 0 honored — LangChain owns retrying.
106
+ expect(bedrockCtorArgs).toHaveLength(1);
107
+ expect(bedrockCtorArgs[0].maxRetries).toBe(0);
108
+
109
+ // The wire carries the Bedrock-translated id; billing already got the
110
+ // canonical one above.
111
+ expect(createRequests).toHaveLength(1);
112
+ expect(createRequests[0].model).toBe("anthropic.claude-sonnet-4-5-20250929-v1:0");
113
+ expect(createRequests[0].max_tokens).toBe(1024);
114
+
115
+ // Response and usage flow back through LangChain untouched.
116
+ expect(result).toBeInstanceOf(AIMessage);
117
+ expect(result.text).toBe("Namaste from Bedrock.");
118
+ expect((result as AIMessage).usage_metadata).toMatchObject({
119
+ input_tokens: 7,
120
+ output_tokens: 5,
121
+ total_tokens: 12,
122
+ });
123
+ });
124
+
125
+ it("applies the deployment's inference prefix to the wire id, canonical id untouched", async () => {
126
+ process.env.STIGMER_BEDROCK_INFERENCE_PREFIX = "us";
127
+
128
+ const { model, apiModelId } = await buildChatModel({
129
+ modelName: "claude-sonnet-4-6",
130
+ maxTokens: 256,
131
+ });
132
+ await model.invoke([new HumanMessage("hi")]);
133
+
134
+ expect(apiModelId).toBe("claude-sonnet-4-6");
135
+ expect(createRequests.at(-1)?.model).toBe("us.anthropic.claude-sonnet-4-6-v1:0");
136
+ });
137
+
138
+ it("honors a model-map override over prefix and rule", async () => {
139
+ process.env.STIGMER_BEDROCK_MODEL_MAP =
140
+ "claude-sonnet-4-6=eu.anthropic.claude-sonnet-4-6-custom-v2:0";
141
+ process.env.STIGMER_BEDROCK_INFERENCE_PREFIX = "us";
142
+
143
+ const { model } = await buildChatModel({
144
+ modelName: "claude-sonnet-4-6",
145
+ maxTokens: 256,
146
+ });
147
+ await model.invoke([new HumanMessage("hi")]);
148
+
149
+ expect(createRequests.at(-1)?.model).toBe("eu.anthropic.claude-sonnet-4-6-custom-v2:0");
150
+ });
151
+
152
+ it("injects the CANONICAL id's default maxTokens when the caller omits it (the 4096 landmine)", async () => {
153
+ // LangChain's per-model default table prefix-matches the model string.
154
+ // `anthropic.…` ids match nothing and silently fall back to 4096 —
155
+ // setup.ts omits maxTokens, so every deep-agent call would be capped.
156
+ // The adapter must inject what the canonical id would have received on
157
+ // the public API or vertex, whatever that is at the installed LangChain
158
+ // version (16384 for 4.5/4.6-generation ids at 1.4.0).
159
+ const canonicalDefault = new ChatAnthropic({
160
+ model: "claude-sonnet-4-5-20250929",
161
+ apiKey: "probe",
162
+ }).maxTokens;
163
+ const bedrockFallback = new ChatAnthropic({
164
+ model: "anthropic.claude-sonnet-4-5-20250929-v1:0",
165
+ apiKey: "probe",
166
+ }).maxTokens;
167
+ // The landmine this guards against: if these ever agree, the injection
168
+ // is redundant (but still correct) — the pin below is what matters.
169
+ expect(bedrockFallback).not.toBe(canonicalDefault);
170
+
171
+ const { model } = await buildChatModel({ modelName: "claude-sonnet-4-5-20250929" });
172
+ await model.invoke([new HumanMessage("hi")]);
173
+
174
+ expect(createRequests.at(-1)?.max_tokens).toBe(canonicalDefault);
175
+ });
176
+
177
+ it("forwards the request timeout to the SDK client (STIGMER_LLM_REQUEST_TIMEOUT_MS path)", async () => {
178
+ // The timeout rides clientOptions -> factory options -> SDK constructor.
179
+ // A factory that drops it silently unbounds the operator's timeout on
180
+ // this backend (the regression T06 repaired for the public path).
181
+ const { model } = await buildChatModel({
182
+ modelName: "claude-sonnet-4-6",
183
+ maxTokens: 256,
184
+ timeoutMs: 5000,
185
+ });
186
+ await model.invoke([new HumanMessage("hi")]);
187
+
188
+ expect(bedrockCtorArgs).toHaveLength(1);
189
+ expect(bedrockCtorArgs[0].timeout).toBe(5000);
190
+ expect(bedrockCtorArgs[0].maxRetries).toBe(0);
191
+ });
192
+
193
+ it("constructs and invokes with no ANTHROPIC_API_KEY anywhere in the environment", async () => {
194
+ expect(process.env.ANTHROPIC_API_KEY).toBeUndefined();
195
+
196
+ const { model } = await buildChatModel({
197
+ modelName: "claude-sonnet-4-6",
198
+ maxTokens: 256,
199
+ });
200
+ const result = await model.invoke([new HumanMessage("hi")]);
201
+
202
+ expect(result).toBeInstanceOf(AIMessage);
203
+ expect(createRequests.at(-1)?.model).toBe("anthropic.claude-sonnet-4-6-v1:0");
204
+ });
205
+
206
+ it("fails at dispatch with the catalog message when AWS_REGION is missing", async () => {
207
+ delete process.env.AWS_REGION;
208
+
209
+ await expect(
210
+ buildChatModel({ modelName: "claude-sonnet-4-6", maxTokens: 256 }),
211
+ ).rejects.toThrow(/AWS_REGION/);
212
+ });
213
+ });