@namzu/bedrock 1.0.3 → 2.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,245 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 935b8f3: Three controls a caller could set that the runtime then quietly declined to apply.
8
+
9
+ - **`toolChoice: 'none'` permitted tool calls on two drivers.** It means the model must not call a tool. One driver mapped it to the wire's "auto" and the other to `{ type: 'auto' }` — both of which say the model _may_. A caller that had forbidden tool use got a request that allowed it, with nothing in the response to say so. The runtime depends on the guarantee: an advisory consultation passes `'none'` so the advisor answers in prose, into a turn where no executor is waiting for a tool call. Both drivers now answer `'none'` by sending no tools at all, which no wire format can misread.
10
+
11
+ - **`memoryLimitMb` and `maxProcesses` were dropped by the stronger isolation tiers.** They were applied inside the unconfined tier's branch only, so asking for namespace or profile isolation silently removed the blast-radius caps — a control failing in the one direction nobody checks. They are the same shell builtin on every tier; the stronger tiers now apply them one level in, inside the wrapper they already spawn through, and keep doing their own job. The sibling backend in the sandbox package already refuses per-sandbox controls it cannot enforce rather than ignoring them; this is the same rule, satisfied by enforcing.
12
+
13
+ - **`AgentManager.dispose()` cancelled nothing.** It called `cancelAll('' as RunId)`, and `cancelAll` filters by parent run — no task has an empty parent, so it matched nothing, and the next lines cleared the instance map. Every live child was released without its abort controller firing: the work kept running, the budget kept draining, and nothing was left holding a reference to stop it. It now cancels every live child before dropping them. `cancelAll` stays scoped to one parent, which is its actual job.
14
+
15
+ `toBedrockToolConfig` and `buildLimitedSpawn` are exported so the mapping and the spawn shape can be asserted directly rather than through a live process.
16
+
17
+ ### Minor Changes
18
+
19
+ - 935b8f3: Prompt caching is now requested, not just measured.
20
+
21
+ The driver read the cache-hit and cache-write counters off every response
22
+ and never asked for caching, so both were permanently zero: the entire
23
+ static prefix — tool schemas, system instructions, the whole conversation
24
+ so far — was re-sent and re-billed at full rate on every single turn of
25
+ every run. Nothing failed, which is why it went unnoticed; it was purely
26
+ money and latency.
27
+
28
+ A breakpoint on this wire is a content BLOCK rather than an annotation on a
29
+ neighbouring one: everything ahead of it in render order is cached. Render
30
+ order is tools → system → messages, so the driver places one at the tail of
31
+ each section, and each later breakpoint covers everything before it:
32
+
33
+ - after the tool schemas, which render first and are the largest static
34
+ segment — this keeps them cached even when the conversation below changes
35
+ every turn;
36
+ - after the last system block the runtime tagged as static, NOT at the end
37
+ of the system section. The per-run dynamic tail comes after that tag; a
38
+ breakpoint over text that changes every run invalidates the entry each
39
+ turn and bills a cache write for nothing;
40
+ - after the last message, so the next iteration — which only appends —
41
+ reads all the prior history at cache rates.
42
+
43
+ Three breakpoints, one under the wire's limit of four. None are placed
44
+ unless the caller sets `cacheControl`, and none are placed over system
45
+ text with nothing static in it or on a message with no content.
46
+
47
+ - 935b8f3: Every driver can now see.
48
+
49
+ These three dropped `attachments` outright, so a user who attached a
50
+ screenshot got a turn about nothing. `supportsVision: false` said so, which
51
+ made the declaration honest and the driver useless — and it was the last
52
+ place in the estate where a namzu capability existed on one driver and
53
+ silently did not on another.
54
+
55
+ Each wire carries an image differently, so this is one intent and three
56
+ mappings:
57
+
58
+ - **Converse**: raw bytes in an image content block beside the text. The
59
+ tool-result path already did this; the user path never looked at
60
+ `attachments`.
61
+ - **The two-dialect HTTP driver**: a `data:` URI content part on one
62
+ dialect, a base64 source block on the other.
63
+ - **The gateway driver**: a `data:` URI content part.
64
+
65
+ Across all three: a media type the endpoint cannot decode is named in the
66
+ text rather than sent, because a payload it rejects fails the whole request
67
+ and losing the turn is worse than losing sight of one image. A message with
68
+ no attachments keeps its plain-string content, so nothing about an ordinary
69
+ request changes shape. Several attachments on one message are carried in
70
+ order.
71
+
72
+ An image inside a **tool result** still degrades to a text placeholder on
73
+ the HTTP and gateway drivers: a tool message is text-only in those dialects,
74
+ so there is nowhere to put it. Converse carries it, and always did.
75
+
76
+ ### Patch Changes
77
+
78
+ - 935b8f3: The conversation cache anchor advances, so the next request can read it.
79
+
80
+ A single breakpoint at the conversation tail writes a new cache entry every
81
+ turn and reads none of them: by the next request the tail has moved, so the
82
+ marker sits somewhere the previous entry does not cover. The tools and
83
+ system tiers keep hitting through their own breakpoints — which is exactly
84
+ what made this invisible. Only the messages tier silently re-billed as a
85
+ write.
86
+
87
+ Both drivers now place a second anchor one turn back, which is where the
88
+ previous request put its tail marker and therefore the prefix that is
89
+ already cached.
90
+
91
+ It matters most where the history grows fastest. Pending tool results
92
+ collapse into a single message, so a fan-out of ten parallel calls appends
93
+ twenty content blocks in one turn — far enough to push the prior boundary
94
+ out of reach of a backward scan that stops at the first non-empty message.
95
+
96
+ This spends the fourth of the four allowed breakpoints, previously
97
+ documented as deliberately unspent. It is spent on the one tier that was
98
+ never hitting. A conversation with a single message still gets one anchor,
99
+ because there is nowhere behind it to put a second.
100
+
101
+ - 935b8f3: Retry now works on the bedrock driver, and the shared classifier reads a
102
+ status wherever a vendor hides it.
103
+
104
+ An unclassified error is treated as non-retryable, which is the right
105
+ default — but it meant the retry policy was effectively dead on this
106
+ driver, and the one failure most worth backing off from was the one that
107
+ killed the run. The service reports failures as named exception classes,
108
+ and the classifier looked at neither the name nor the status, because the
109
+ status lives in a metadata bag rather than on the error.
110
+
111
+ - `classifyProviderError` now also reads `$metadata.httpStatusCode`. A
112
+ status is a status wherever it hides, and this helps any driver — first
113
+ or third party — whose SDK reports it that way.
114
+ - The bedrock driver maps its own exception vocabulary to provider error
115
+ codes: throttling and quota to `rate_limit`, unavailable and not-ready to
116
+ `overloaded`, internal and stream faults to `server_error`, and the
117
+ non-retryable ones (`ValidationException`, `AccessDeniedException`,
118
+ `ResourceNotFoundException`) to their exact codes so they fail fast
119
+ instead of burning the retry budget.
120
+
121
+ The vocabulary lives in the driver rather than the shared classifier: a
122
+ driver knows its own vendor's error names, and the classifier should stay
123
+ generic. An unrecognised exception passes through untouched — an honest
124
+ unknown beats a confident wrong classification.
125
+
126
+ - 935b8f3: Lift the dependency floor to versions without published advisories
127
+
128
+ Eighty-two open advisories collapsed to a handful of real decisions, because most of them were the same package reached through one path.
129
+
130
+ The telemetry exporters carried a serialization library with twenty-four advisories against it, two of them critical. The exporters move from the 0.57 line to 0.221, and the stable packages beside them from 1.x to 2.x — a major bump for this package, since a consumer pinning the older peers must move with it.
131
+
132
+ The two vendor driver SDKs move to their current releases, closing the advisories that came with them.
133
+
134
+ The test runner accounted for fourteen critical advisories on its own. It is a development dependency and never reaches a published artifact, but it runs in CI against the repository's own contents, so it moves to the first patched release rather than being waved through as out-of-scope.
135
+
136
+ - 935b8f3: Stop dropping tool-failure status on Bedrock, and stop accepting a sandbox
137
+ egress policy this backend cannot enforce.
138
+
139
+ - **Bedrock** flattened every failed tool result into an ordinary success.
140
+ The executor computed `isError`, the SSE and A2A bridges carried it, and
141
+ the driver dropped it — even though Converse has a first-class
142
+ `toolResult.status`. The model's trained tool-failure recovery path keys
143
+ off that field, so namzu was relying on prose formatting to convey "that
144
+ call failed".
145
+
146
+ Scope note: the five OpenAI-shaped drivers are NOT affected, because
147
+ Chat Completions has no error field on a tool message at all. The error
148
+ reaches those models inside the result text, which is the only channel
149
+ the protocol has.
150
+
151
+ - **Docker sandbox** accepted `EgressPolicy` and silently ignored it. A
152
+ host that set `deny-all` believed the container had no network and it had
153
+ whatever `network` was configured. A security control that is accepted
154
+ and ignored is worse than one that does not exist. Now: `deny-all` maps
155
+ to `--network none` (which Docker enforces natively), `allow-all` keeps
156
+ the configured network, and `static` / `resolver` **throw** — this
157
+ backend has no proxy to filter hosts through, and downgrading a
158
+ restrictive policy to "allow everything" is exactly the failure worth
159
+ refusing.
160
+
161
+ - **Docker sandbox** containers now run with `--cap-drop=ALL` and
162
+ `--security-opt=no-new-privileges`, plus an opt-in `runAsUser`.
163
+ `CAP_DAC_OVERRIDE` alone walks past the read-only bind mounts the layout
164
+ sets up, and without `no-new-privileges` a setuid binary in the image
165
+ re-escalates.
166
+
167
+ - 935b8f3: Stop dumping a tool result's base64 payload into the prompt.
168
+
169
+ Four drivers mishandled a tool result carrying content blocks, each in its
170
+ own way: bedrock and the http driver's anthropic dialect `JSON.stringify`d
171
+ the whole array, putting a screenshot's base64 into the prompt as JSON
172
+ text; openrouter and the http driver's openai dialect passed the array
173
+ through raw to an endpoint expecting a string; lmstudio folded it into a
174
+ template literal, producing `[object Object]` per block. The model cannot
175
+ decode any of it, and it costs a fortune in tokens.
176
+
177
+ - The three text-only wires now flatten with the SDK's existing helper,
178
+ which names a non-text block and its size instead of inlining it. The
179
+ openai and ollama drivers already did this — the helper was there and
180
+ four callers were missing.
181
+ - **bedrock sends the image as an image.** That wire carries images
182
+ natively, so a placeholder would be a downgrade the other drivers accept
183
+ only because their format has no room for one. Text and image survive as
184
+ separate blocks in order, and a media type the format does not accept
185
+ still degrades to a named placeholder rather than being smuggled through
186
+ as text.
187
+
188
+ - 935b8f3: Four drivers dumped tool-result payloads into the prompt, and one ignored the strict-schema hint.
189
+
190
+ **A tool result carrying an image was `JSON.stringify`d** on four drivers, so a screenshot reached the model as a wall of quoted base64. The model paid for every character, could read none of them, and — worse — saw a serialized object where a picture should be, with nothing saying anything had been withheld. The SDK's `toolResultToText` exists for exactly this and produces a named placeholder reporting the media type and the size. All four now use it: `@namzu/bedrock`, both dialects of `@namzu/http`, and `@namzu/lmstudio`.
191
+
192
+ **`enforceToolInputSchema` was ignored by `@namzu/openai`.** It names the tools whose model-facing schema should be enforced by constrained generation rather than merely suggested; both sibling drivers consumed it. A caller who had asked for a guaranteed-valid tool input silently got a best-effort one and learned about it from a repair attempt. This is the wire the flag maps onto most directly — it takes the flag on the function itself. The existing test asserting the tools went through untouched read as "the hint is kept out of the request" and actually pinned "the hint does nothing"; it now asserts the hint is consumed and still never appears verbatim.
193
+
194
+ **An extended-thinking request is refused by `@namzu/openai` rather than dropped.** The parameter was accepted and ignored, so a caller who asked for reasoning got an ordinary completion with an empty reasoning list — which reads as "the model did not reason" rather than "nobody asked it to". Turning thinking off stays a no-op, since that is the state the driver is already in.
195
+
196
+ Eleven previously empty test files now cover these drivers, including the capability claims themselves: every driver that declares no vision, documents or tools is pinned against drift in both directions, because the runtime warns or fails on those flags before a request is built, and a flag flipped ahead of its mapping is as wrong as a mapping written without the flag.
197
+
198
+ - 935b8f3: Overflow reaches the rescue that exists for it.
199
+
200
+ Overflow is the one 4xx the runtime can act on: it sheds history and
201
+ retries. Everything else in the 400 family is surfaced. So the rescue is
202
+ gated on the code being **exactly** `context_length_exceeded`, and anything
203
+ that misses that gate dies holding the remedy.
204
+
205
+ Three things missed it. Measured before and after, five of six realistic
206
+ overflow shapes never reached relief; now all six do.
207
+
208
+ - **The structural code was extracted and then discarded.** The cause-chain
209
+ walk returned the first `code` it found and fed it only to the two
210
+ transport-errno sets, so a provider that said `context_length_exceeded` in
211
+ the one field designed to say it was answered with a substring search that
212
+ did not match. A structural code is now consulted **before** the status,
213
+ because it is strictly more specific: a 400 is a category, the code is the
214
+ diagnosis. The gateway `type` discriminator and a nested error envelope
215
+ are read the same way.
216
+ - **The phrase list missed the common wordings.** "too long for", "maximum
217
+ length", "exceeds the maximum", "input is too large" all fell through to a
218
+ plain non-retryable invalid request.
219
+ - **The Converse driver pre-filed `ValidationException` as
220
+ `invalid_request`.** That name covers both a malformed request and a
221
+ prompt past the model's window, and only one of those is recoverable — so
222
+ guessing from the name made the recoverable case unrecoverable by
223
+ construction, because the shared classifier short-circuits on an error
224
+ that already carries a code and never read the body. It now hands that one
225
+ name to the classifier. The result is still a `ProviderError`, so the
226
+ driver's contract is unchanged; it just stops answering a question it
227
+ cannot answer from the name alone.
228
+
229
+ The rate-limit half of the same class is fixed alongside: a provider that
230
+ reports `rate_limit_exceeded` structurally under a 400 is now retryable
231
+ instead of being filed as a bad request.
232
+
233
+ - 935b8f3: A user message can carry a document
234
+
235
+ Documents existed in the type system only in the tool-result direction, and both first-party drivers mapped images only on the input side. So "here is the contract, answer questions about it" — a mainstream workload — was reachable only by having a tool read the file and stringify it. That loses the provider's native document handling (page structure, built-in OCR, citations) and pays the text cost instead.
236
+
237
+ `UserMessage.attachments` is now `MessageAttachment[]`: an image or a document. The discriminant is optional and stays optional — an attachment without one is an image, which is what every attachment was before, so no existing caller changes.
238
+
239
+ `supportsDocuments` sits beside `supportsVision` in the driver capability declaration, and the runtime checks it the same way: a document sent to a driver that declares `false` warns before the request, or throws under `strictCapabilities`, instead of letting the model answer about a file it never saw. The two are counted separately because they are separate wire shapes and a driver can map one without the other.
240
+
241
+ The two first-party drivers map documents natively. The remaining five map images only and now say so; a document reaching them degrades to a named placeholder that says which kind was dropped, rather than one that calls a document an image.
242
+
3
243
  ## 1.0.3
4
244
 
5
245
  ### Patch Changes
@@ -39,6 +39,7 @@ describe('@namzu/bedrock', () => {
39
39
  supportsStreaming: true,
40
40
  supportsFunctionCalling: true,
41
41
  supportsVision: false,
42
+ supportsDocuments: false,
42
43
  });
43
44
  });
44
45
  it('is exposed on the provider instance for runtime negotiation', () => {
@@ -46,5 +47,48 @@ describe('@namzu/bedrock', () => {
46
47
  expect(provider.capabilities).toEqual(BEDROCK_CAPABILITIES);
47
48
  });
48
49
  });
50
+ it('keeps the Namzu enforcement hint out of the Bedrock command', async () => {
51
+ const provider = new BedrockProvider({ region: 'us-east-1' });
52
+ let commandInput;
53
+ provider.client = {
54
+ send: async (command) => {
55
+ commandInput = command.input;
56
+ return {
57
+ $metadata: { requestId: 'request-test' },
58
+ stream: (async function* () { })(),
59
+ };
60
+ },
61
+ };
62
+ for await (const _chunk of provider.chatStream({
63
+ model: 'anthropic.claude-sonnet-5-v1:0',
64
+ messages: [{ role: 'user', content: 'edit' }],
65
+ tools: [
66
+ {
67
+ type: 'function',
68
+ function: {
69
+ name: 'edit',
70
+ description: 'Edit',
71
+ parameters: { type: 'object' },
72
+ },
73
+ },
74
+ ],
75
+ enforceToolInputSchema: ['edit'],
76
+ })) {
77
+ // Drain the empty test stream.
78
+ }
79
+ expect(commandInput).not.toHaveProperty('enforceToolInputSchema');
80
+ expect(commandInput?.toolConfig).toEqual({
81
+ tools: [
82
+ {
83
+ toolSpec: {
84
+ name: 'edit',
85
+ description: 'Edit',
86
+ inputSchema: { json: { type: 'object' } },
87
+ },
88
+ },
89
+ ],
90
+ toolChoice: { auto: {} },
91
+ });
92
+ });
49
93
  });
50
94
  //# sourceMappingURL=bedrock.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bedrock.test.js","sourceRoot":"","sources":["../../src/__tests__/bedrock.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEpF,6EAA6E;AAC7E,yEAAyE;AACzE,8CAA8C;AAC9C,UAAU,CAAC,GAAG,EAAE;IACf,IAAI,gBAAgB,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7C,gBAAgB,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;IACvC,CAAC;AACF,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC/B,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,gBAAgB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAC3D,eAAe,EAAE,CAAA;YACjB,MAAM,CAAC,gBAAgB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAC1D,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QAC1D,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;YAC1E,eAAe,EAAE,CAAA;YACjB,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAA;QACrE,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;YAClE,eAAe,EAAE,CAAA;YACjB,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAA;YAC9D,MAAM,CAAC,gBAAgB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3D,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;YACvE,eAAe,EAAE,CAAA;YACjB,MAAM,IAAI,GAAG,gBAAgB,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;YACxD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAA;QAC3C,CAAC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,oBAAoB,CAAC,CAAC,OAAO,CAAC;gBACpC,aAAa,EAAE,IAAI;gBACnB,iBAAiB,EAAE,IAAI;gBACvB,uBAAuB,EAAE,IAAI;gBAC7B,cAAc,EAAE,KAAK;aACrB,CAAC,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;YACtE,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;YAC7D,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAA;QAC5D,CAAC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"bedrock.test.js","sourceRoot":"","sources":["../../src/__tests__/bedrock.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEpF,6EAA6E;AAC7E,yEAAyE;AACzE,8CAA8C;AAC9C,UAAU,CAAC,GAAG,EAAE;IACf,IAAI,gBAAgB,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7C,gBAAgB,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;IACvC,CAAC;AACF,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC/B,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,gBAAgB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAC3D,eAAe,EAAE,CAAA;YACjB,MAAM,CAAC,gBAAgB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAC1D,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QAC1D,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;YAC1E,eAAe,EAAE,CAAA;YACjB,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAA;QACrE,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;YAClE,eAAe,EAAE,CAAA;YACjB,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAA;YAC9D,MAAM,CAAC,gBAAgB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3D,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;YACvE,eAAe,EAAE,CAAA;YACjB,MAAM,IAAI,GAAG,gBAAgB,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;YACxD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAA;QAC3C,CAAC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,oBAAoB,CAAC,CAAC,OAAO,CAAC;gBACpC,aAAa,EAAE,IAAI;gBACnB,iBAAiB,EAAE,IAAI;gBACvB,uBAAuB,EAAE,IAAI;gBAC7B,cAAc,EAAE,KAAK;gBACrB,iBAAiB,EAAE,KAAK;aACxB,CAAC,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;YACtE,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;YAC7D,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAA;QAC5D,CAAC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;QAC7D,IAAI,YAAiD,CACpD;QAAC,QAA2C,CAAC,MAAM,GAAG;YACtD,IAAI,EAAE,KAAK,EAAE,OAA2C,EAAE,EAAE;gBAC3D,YAAY,GAAG,OAAO,CAAC,KAAK,CAAA;gBAC5B,OAAO;oBACN,SAAS,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE;oBACxC,MAAM,EAAE,CAAC,KAAK,SAAS,CAAC,MAAK,CAAC,CAAC,EAAE;iBACjC,CAAA;YACF,CAAC;SACD,CAAA;QAED,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,QAAQ,CAAC,UAAU,CAAC;YAC9C,KAAK,EAAE,gCAAgC;YACvC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;YAC7C,KAAK,EAAE;gBACN;oBACC,IAAI,EAAE,UAAU;oBAChB,QAAQ,EAAE;wBACT,IAAI,EAAE,MAAM;wBACZ,WAAW,EAAE,MAAM;wBACnB,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;qBAC9B;iBACD;aACD;YACD,sBAAsB,EAAE,CAAC,MAAM,CAAC;SAChC,CAAC,EAAE,CAAC;YACJ,+BAA+B;QAChC,CAAC;QAED,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,wBAAwB,CAAC,CAAA;QACjE,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,OAAO,CAAC;YACxC,KAAK,EAAE;gBACN;oBACC,QAAQ,EAAE;wBACT,IAAI,EAAE,MAAM;wBACZ,WAAW,EAAE,MAAM;wBACnB,WAAW,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;qBACzC;iBACD;aACD;YACD,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;SACxB,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prompt-cache.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-cache.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/prompt-cache.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,68 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseBedrockUsage } from '../client.js';
3
+ /**
4
+ * Cache reads and cache writes are priced differently from ordinary input,
5
+ * so folding either into the prompt count misreports what a run cost — and
6
+ * misreports it silently, because the total still looks plausible.
7
+ *
8
+ * The counters are also the only evidence that the breakpoints are working
9
+ * at all: a run whose cached count stays at zero is paying full price for
10
+ * a prefix it believes is cached, and nothing else says so.
11
+ */
12
+ describe('cache counters are reported on their own axis', () => {
13
+ it('keeps reads out of the prompt count', () => {
14
+ const usage = parseBedrockUsage({
15
+ inputTokens: 10,
16
+ outputTokens: 2,
17
+ cacheReadInputTokenCount: 900,
18
+ });
19
+ expect(usage.promptTokens).toBe(10);
20
+ expect(usage.cachedTokens).toBe(900);
21
+ });
22
+ it('keeps writes on their own axis too', () => {
23
+ const usage = parseBedrockUsage({
24
+ inputTokens: 10,
25
+ outputTokens: 2,
26
+ cacheWriteInputTokenCount: 40,
27
+ });
28
+ expect(usage.cacheWriteTokens).toBe(40);
29
+ expect(usage.promptTokens).toBe(10);
30
+ });
31
+ it('reports both when both happen in one turn', () => {
32
+ const usage = parseBedrockUsage({
33
+ inputTokens: 10,
34
+ outputTokens: 2,
35
+ cacheReadInputTokenCount: 900,
36
+ cacheWriteInputTokenCount: 40,
37
+ });
38
+ expect(usage).toMatchObject({ cachedTokens: 900, cacheWriteTokens: 40 });
39
+ });
40
+ it('reads an absent counter as zero, not undefined', () => {
41
+ const usage = parseBedrockUsage({ inputTokens: 5, outputTokens: 1 });
42
+ // A consumer summing these must not have to guard every field; an
43
+ // undefined here becomes NaN one addition later.
44
+ expect(usage.cachedTokens).toBe(0);
45
+ expect(usage.cacheWriteTokens).toBe(0);
46
+ });
47
+ it('totals input and output, and nothing else', () => {
48
+ const usage = parseBedrockUsage({
49
+ inputTokens: 10,
50
+ outputTokens: 2,
51
+ cacheReadInputTokenCount: 900,
52
+ });
53
+ // Cached reads are already counted upstream; adding them here would
54
+ // double-count the same tokens in the run's own ledger.
55
+ expect(usage.totalTokens).toBe(12);
56
+ });
57
+ it('reads a missing usage payload as all zeros', () => {
58
+ const usage = parseBedrockUsage(undefined);
59
+ expect(usage).toEqual({
60
+ promptTokens: 0,
61
+ completionTokens: 0,
62
+ totalTokens: 0,
63
+ cachedTokens: 0,
64
+ cacheWriteTokens: 0,
65
+ });
66
+ });
67
+ });
68
+ //# sourceMappingURL=prompt-cache.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-cache.test.js","sourceRoot":"","sources":["../../src/__tests__/prompt-cache.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAEhD;;;;;;;;GAQG;AAEH,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC9D,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC9C,MAAM,KAAK,GAAG,iBAAiB,CAAC;YAC/B,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,CAAC;YACf,wBAAwB,EAAE,GAAG;SAC7B,CAAC,CAAA;QAEF,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACnC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC7C,MAAM,KAAK,GAAG,iBAAiB,CAAC;YAC/B,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,CAAC;YACf,yBAAyB,EAAE,EAAE;SAC7B,CAAC,CAAA;QAEF,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACvC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACpD,MAAM,KAAK,GAAG,iBAAiB,CAAC;YAC/B,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,CAAC;YACf,wBAAwB,EAAE,GAAG;YAC7B,yBAAyB,EAAE,EAAE;SAC7B,CAAC,CAAA;QAEF,MAAM,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC,CAAA;IACzE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACzD,MAAM,KAAK,GAAG,iBAAiB,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAA;QAEpE,kEAAkE;QAClE,iDAAiD;QACjD,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAClC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACpD,MAAM,KAAK,GAAG,iBAAiB,CAAC;YAC/B,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,CAAC;YACf,wBAAwB,EAAE,GAAG;SAC7B,CAAC,CAAA;QAEF,oEAAoE;QACpE,wDAAwD;QACxD,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACrD,MAAM,KAAK,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAA;QAE1C,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;YACrB,YAAY,EAAE,CAAC;YACf,gBAAgB,EAAE,CAAC;YACnB,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,CAAC;YACf,gBAAgB,EAAE,CAAC;SACnB,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tool-choice-none.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-choice-none.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tool-choice-none.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { toBedrockToolConfig } from '../client.js';
3
+ /**
4
+ * `toolChoice: 'none'` means the model must not call a tool. This driver
5
+ * mapped it to the wire's "auto", which means the model MAY — the exact
6
+ * opposite of what the caller asked for, and silent.
7
+ *
8
+ * The runtime relies on it: an advisory consultation passes `'none'` so the
9
+ * advisor answers in prose. Under this driver the advisor could emit a tool
10
+ * call instead, into a turn with no executor waiting for one.
11
+ *
12
+ * The fix is not a better mapping. Every version of every wire format
13
+ * agrees a model cannot call a tool it was never given, so `'none'` sends
14
+ * no tools at all — a guarantee rather than a request.
15
+ */
16
+ const TOOLS = [
17
+ {
18
+ type: 'function',
19
+ function: { name: 'read', description: 'read a file', parameters: { type: 'object' } },
20
+ },
21
+ ];
22
+ describe("a caller that said 'none' gets no tools on the wire", () => {
23
+ it('omits the tool config entirely', () => {
24
+ expect(toBedrockToolConfig({ model: 'm', messages: [], tools: TOOLS, toolChoice: 'none' })).toBe(undefined);
25
+ });
26
+ it('omits it even when the history contains tool blocks', () => {
27
+ const config = toBedrockToolConfig({
28
+ model: 'm',
29
+ messages: [
30
+ {
31
+ role: 'assistant',
32
+ content: null,
33
+ toolCalls: [{ id: 'c1', type: 'function', function: { name: 'read', arguments: '{}' } }],
34
+ },
35
+ { role: 'tool', content: 'ok', toolCallId: 'c1' },
36
+ ],
37
+ toolChoice: 'none',
38
+ });
39
+ expect(config).toBe(undefined);
40
+ });
41
+ });
42
+ describe('the other choices still reach the wire unchanged', () => {
43
+ it('maps auto', () => {
44
+ const config = toBedrockToolConfig({
45
+ model: 'm',
46
+ messages: [],
47
+ tools: TOOLS,
48
+ toolChoice: 'auto',
49
+ });
50
+ expect(config?.toolChoice).toEqual({ auto: {} });
51
+ expect(config?.tools).toHaveLength(1);
52
+ });
53
+ it('maps required', () => {
54
+ const config = toBedrockToolConfig({
55
+ model: 'm',
56
+ messages: [],
57
+ tools: TOOLS,
58
+ toolChoice: 'required',
59
+ });
60
+ expect(config?.toolChoice).toEqual({ any: {} });
61
+ });
62
+ it('maps a named tool', () => {
63
+ const config = toBedrockToolConfig({
64
+ model: 'm',
65
+ messages: [],
66
+ tools: TOOLS,
67
+ toolChoice: { type: 'function', function: { name: 'read' } },
68
+ });
69
+ expect(config?.toolChoice).toEqual({ tool: { name: 'read' } });
70
+ });
71
+ it('defaults to auto when the caller said nothing', () => {
72
+ const config = toBedrockToolConfig({ model: 'm', messages: [], tools: TOOLS });
73
+ expect(config?.toolChoice).toEqual({ auto: {} });
74
+ });
75
+ });
76
+ //# sourceMappingURL=tool-choice-none.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-choice-none.test.js","sourceRoot":"","sources":["../../src/__tests__/tool-choice-none.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tool-content-blocks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-content-blocks.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tool-content-blocks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { toBedrockMessages } from '../client.js';
3
+ /**
4
+ * A tool result carrying content blocks was `JSON.stringify`d into the
5
+ * text field, so a screenshot reached the model as a wall of quoted base64:
6
+ * unreadable, and paid for by the character.
7
+ *
8
+ * This driver does not map image content — `supportsVision` is false and
9
+ * says so — which makes the honest form the SDK's named placeholder. It
10
+ * reports what was there and how big it was, so the model knows something
11
+ * was withheld rather than believing it saw a JSON object.
12
+ */
13
+ const PNG = 'iVBORw0KGgo=';
14
+ function toolResultFor(content) {
15
+ const messages = toBedrockMessages([
16
+ { role: 'user', content: 'take a shot' },
17
+ {
18
+ role: 'assistant',
19
+ content: null,
20
+ toolCalls: [
21
+ { id: 'call_1', type: 'function', function: { name: 'screenshot', arguments: '{}' } },
22
+ ],
23
+ },
24
+ { role: 'tool', toolCallId: 'call_1', content },
25
+ ]);
26
+ const block = messages
27
+ .flatMap((m) => m.content ?? [])
28
+ .find((b) => b.toolResult);
29
+ return block.toolResult;
30
+ }
31
+ function textOf(result) {
32
+ const content = result.content;
33
+ return content.map((c) => c.text ?? '').join('\n');
34
+ }
35
+ describe('a tool result the wire cannot carry is described, not dumped', () => {
36
+ it('names the media type and size instead of the payload', () => {
37
+ const text = textOf(toolResultFor([{ type: 'image', mediaType: 'image/png', data: PNG }]));
38
+ expect(text).toContain('image/png');
39
+ expect(text).toMatch(/\d+(\.\d+)? (B|KB|MB)/);
40
+ });
41
+ it('does not put the base64 payload in the text', () => {
42
+ const text = textOf(toolResultFor([{ type: 'image', mediaType: 'image/png', data: 'A'.repeat(2048) }]));
43
+ // The bug in one assertion: the payload used to be JSON-quoted into
44
+ // the text field, so the model paid for 2048 unreadable characters.
45
+ expect(text).not.toContain('A'.repeat(64));
46
+ });
47
+ it('keeps the text blocks that sit alongside', () => {
48
+ const text = textOf(toolResultFor([
49
+ { type: 'text', text: 'captured the window' },
50
+ { type: 'image', mediaType: 'image/png', data: PNG },
51
+ ]));
52
+ expect(text).toContain('captured the window');
53
+ expect(text).toContain('image/png');
54
+ });
55
+ it('names a document by its filename', () => {
56
+ const text = textOf(toolResultFor([
57
+ { type: 'document', mediaType: 'application/pdf', data: 'JVBER', name: 'lease.pdf' },
58
+ ]));
59
+ expect(text).toContain('lease.pdf');
60
+ expect(text).toContain('application/pdf');
61
+ });
62
+ });
63
+ describe('the plain cases are unchanged', () => {
64
+ it('passes a string result straight through', () => {
65
+ expect(textOf(toolResultFor('just text'))).toBe('just text');
66
+ });
67
+ it('passes text-only blocks through as their text', () => {
68
+ expect(textOf(toolResultFor([{ type: 'text', text: 'only words' }]))).toBe('only words');
69
+ });
70
+ it('answers the tool call it was given', () => {
71
+ expect(toolResultFor('x').toolUseId).toBe('call_1');
72
+ });
73
+ it('groups results answering one turn into a single message', () => {
74
+ const messages = toBedrockMessages([
75
+ { role: 'user', content: 'read both' },
76
+ {
77
+ role: 'assistant',
78
+ content: null,
79
+ toolCalls: [
80
+ { id: 'call_1', type: 'function', function: { name: 'read', arguments: '{}' } },
81
+ { id: 'call_2', type: 'function', function: { name: 'read', arguments: '{}' } },
82
+ ],
83
+ },
84
+ { role: 'tool', toolCallId: 'call_1', content: 'one' },
85
+ { role: 'tool', toolCallId: 'call_2', content: 'two' },
86
+ ]);
87
+ const resultTurns = messages.filter((m) => (m.content ?? []).every((b) => b.toolResult));
88
+ expect(resultTurns).toHaveLength(1);
89
+ expect(resultTurns[0]?.content).toHaveLength(2);
90
+ });
91
+ });
92
+ //# sourceMappingURL=tool-content-blocks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-content-blocks.test.js","sourceRoot":"","sources":["../../src/__tests__/tool-content-blocks.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=vision.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/vision.test.ts"],"names":[],"mappings":""}