@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 +240 -0
- package/dist/__tests__/bedrock.test.js +44 -0
- package/dist/__tests__/bedrock.test.js.map +1 -1
- package/dist/__tests__/prompt-cache.test.d.ts +2 -0
- package/dist/__tests__/prompt-cache.test.d.ts.map +1 -0
- package/dist/__tests__/prompt-cache.test.js +68 -0
- package/dist/__tests__/prompt-cache.test.js.map +1 -0
- package/dist/__tests__/tool-choice-none.test.d.ts +2 -0
- package/dist/__tests__/tool-choice-none.test.d.ts.map +1 -0
- package/dist/__tests__/tool-choice-none.test.js +76 -0
- package/dist/__tests__/tool-choice-none.test.js.map +1 -0
- package/dist/__tests__/tool-content-blocks.test.d.ts +2 -0
- package/dist/__tests__/tool-content-blocks.test.d.ts.map +1 -0
- package/dist/__tests__/tool-content-blocks.test.js +92 -0
- package/dist/__tests__/tool-content-blocks.test.js.map +1 -0
- package/dist/__tests__/vision.test.d.ts +2 -0
- package/dist/__tests__/vision.test.d.ts.map +1 -0
- package/dist/__tests__/vision.test.js +77 -0
- package/dist/__tests__/vision.test.js.map +1 -0
- package/dist/client.d.ts +13 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +19 -7
- package/dist/client.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/bedrock.test.ts +47 -0
- package/src/__tests__/prompt-cache.test.ts +81 -0
- package/src/__tests__/tool-choice-none.test.ts +87 -0
- package/src/__tests__/tool-content-blocks.test.ts +119 -0
- package/src/__tests__/vision.test.ts +98 -0
- package/src/client.ts +21 -8
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;
|
|
1
|
+
{"version":3,"file":"bedrock.test.js","sourceRoot":"","sources":["../../src/__tests__/bedrock.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"vision.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/vision.test.ts"],"names":[],"mappings":""}
|