@namzu/bedrock 1.0.2 → 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.
Files changed (35) hide show
  1. package/CHANGELOG.md +270 -0
  2. package/dist/__tests__/bedrock.test.js +44 -0
  3. package/dist/__tests__/bedrock.test.js.map +1 -1
  4. package/dist/__tests__/error-taxonomy.test.d.ts +17 -0
  5. package/dist/__tests__/error-taxonomy.test.d.ts.map +1 -0
  6. package/dist/__tests__/error-taxonomy.test.js +192 -0
  7. package/dist/__tests__/error-taxonomy.test.js.map +1 -0
  8. package/dist/__tests__/prompt-cache.test.d.ts +2 -0
  9. package/dist/__tests__/prompt-cache.test.d.ts.map +1 -0
  10. package/dist/__tests__/prompt-cache.test.js +68 -0
  11. package/dist/__tests__/prompt-cache.test.js.map +1 -0
  12. package/dist/__tests__/tool-choice-none.test.d.ts +2 -0
  13. package/dist/__tests__/tool-choice-none.test.d.ts.map +1 -0
  14. package/dist/__tests__/tool-choice-none.test.js +76 -0
  15. package/dist/__tests__/tool-choice-none.test.js.map +1 -0
  16. package/dist/__tests__/tool-content-blocks.test.d.ts +2 -0
  17. package/dist/__tests__/tool-content-blocks.test.d.ts.map +1 -0
  18. package/dist/__tests__/tool-content-blocks.test.js +92 -0
  19. package/dist/__tests__/tool-content-blocks.test.js.map +1 -0
  20. package/dist/__tests__/vision.test.d.ts +2 -0
  21. package/dist/__tests__/vision.test.d.ts.map +1 -0
  22. package/dist/__tests__/vision.test.js +77 -0
  23. package/dist/__tests__/vision.test.js.map +1 -0
  24. package/dist/client.d.ts +13 -1
  25. package/dist/client.d.ts.map +1 -1
  26. package/dist/client.js +130 -76
  27. package/dist/client.js.map +1 -1
  28. package/package.json +5 -5
  29. package/src/__tests__/bedrock.test.ts +47 -0
  30. package/src/__tests__/error-taxonomy.test.ts +236 -0
  31. package/src/__tests__/prompt-cache.test.ts +81 -0
  32. package/src/__tests__/tool-choice-none.test.ts +87 -0
  33. package/src/__tests__/tool-content-blocks.test.ts +119 -0
  34. package/src/__tests__/vision.test.ts +98 -0
  35. package/src/client.ts +134 -77
@@ -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":""}
@@ -0,0 +1,77 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { BEDROCK_CAPABILITIES, toBedrockMessages } from '../client.js';
3
+ /**
4
+ * This driver does not map image attachments, and says so.
5
+ *
6
+ * That is a legitimate state — a driver may ship without a wire shape —
7
+ * but only while the claim and the code agree. The failure this pins is
8
+ * drift in either direction: flipping the flag without writing the mapping
9
+ * makes the runtime stop warning about images that still vanish, and
10
+ * writing the mapping without flipping the flag makes it keep warning
11
+ * about images that now arrive.
12
+ *
13
+ * The runtime reads the flag and warns (or fails under `strictCapabilities`)
14
+ * before the request is built, so the flag is the contract.
15
+ */
16
+ const PNG = 'iVBORw0KGgo=';
17
+ function contentFor(attachments) {
18
+ const messages = toBedrockMessages([
19
+ { role: 'user', content: 'what is this', attachments },
20
+ ]);
21
+ return messages[0]?.content;
22
+ }
23
+ describe('the capability claim and the mapper agree', () => {
24
+ it('declares that images are not mapped', () => {
25
+ expect(BEDROCK_CAPABILITIES.supportsVision).toBe(false);
26
+ });
27
+ it('declares that documents are not mapped', () => {
28
+ expect(BEDROCK_CAPABILITIES.supportsDocuments).toBe(false);
29
+ });
30
+ it('sends no image block, matching the claim', () => {
31
+ const content = contentFor([{ type: 'image', data: PNG, mediaType: 'image/png' }]);
32
+ const blocks = content;
33
+ expect(blocks.some((b) => 'image' in b)).toBe(false);
34
+ });
35
+ it('does not smuggle the payload into the text either', () => {
36
+ const content = contentFor([{ type: 'image', data: 'A'.repeat(2048), mediaType: 'image/png' }]);
37
+ // Dropping is the declared behaviour. Stringifying it into the
38
+ // prompt would be worse than dropping: unreadable, and billed.
39
+ expect(JSON.stringify(content)).not.toContain('A'.repeat(64));
40
+ });
41
+ it('still sends the message text', () => {
42
+ const content = contentFor([{ type: 'image', data: PNG, mediaType: 'image/png' }]);
43
+ const blocks = content;
44
+ expect(blocks.some((b) => b.text === 'what is this')).toBe(true);
45
+ });
46
+ it('leaves a plain message untouched', () => {
47
+ const messages = toBedrockMessages([
48
+ { role: 'user', content: 'no attachments' },
49
+ ]);
50
+ expect(messages[0]?.content).toEqual([{ text: 'no attachments' }]);
51
+ });
52
+ });
53
+ describe('what the driver does map is unaffected', () => {
54
+ it('maps tool calls, which is what this driver is for', () => {
55
+ const messages = toBedrockMessages([
56
+ { role: 'user', content: 'read it' },
57
+ {
58
+ role: 'assistant',
59
+ content: null,
60
+ toolCalls: [
61
+ { id: 'call_1', type: 'function', function: { name: 'read', arguments: '{"p":1}' } },
62
+ ],
63
+ },
64
+ ]);
65
+ const toolUse = (messages[1]?.content ?? []).find((b) => b.toolUse);
66
+ expect(toolUse.toolUse.name).toBe('read');
67
+ expect(toolUse.toolUse.input).toEqual({ p: 1 });
68
+ });
69
+ it('drops system messages from the turn list', () => {
70
+ const messages = toBedrockMessages([
71
+ { role: 'system', content: 'be terse' },
72
+ { role: 'user', content: 'hi' },
73
+ ]);
74
+ expect(messages.map((m) => m.role)).toEqual(['user']);
75
+ });
76
+ });
77
+ //# sourceMappingURL=vision.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision.test.js","sourceRoot":"","sources":["../../src/__tests__/vision.test.ts"],"names":[],"mappings":"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"}
package/dist/client.d.ts CHANGED
@@ -1,5 +1,16 @@
1
- import type { ChatCompletionParams, LLMProvider, ModelInfo, ProviderCapabilities, StreamChunk } from '@namzu/sdk';
1
+ import type { Message as BedrockMessage, ToolConfiguration } from '@aws-sdk/client-bedrock-runtime';
2
+ import type { ChatCompletionParams, LLMProvider, ModelInfo, ProviderCapabilities, StreamChunk, TokenUsage } from '@namzu/sdk';
2
3
  import type { BedrockConfig } from './types.js';
4
+ export declare function toBedrockMessages(messages: ChatCompletionParams['messages']): BedrockMessage[];
5
+ export declare function toBedrockToolConfig(params: ChatCompletionParams): ToolConfiguration | undefined;
6
+ interface RawBedrockUsage {
7
+ inputTokens?: number;
8
+ outputTokens?: number;
9
+ totalTokens?: number;
10
+ cacheReadInputTokenCount?: number;
11
+ cacheWriteInputTokenCount?: number;
12
+ }
13
+ export declare function parseBedrockUsage(raw?: RawBedrockUsage): TokenUsage;
3
14
  /**
4
15
  * What this DRIVER does, not what Bedrock could do: tools are mapped
5
16
  * to the Converse toolConfig, but user-message image `attachments`
@@ -18,4 +29,5 @@ export declare class BedrockProvider implements LLMProvider {
18
29
  listModels(): Promise<ModelInfo[]>;
19
30
  healthCheck(): Promise<boolean>;
20
31
  }
32
+ export {};
21
33
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACX,oBAAoB,EAEpB,WAAW,EACX,SAAS,EACT,oBAAoB,EACpB,WAAW,EAGX,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAqM/C;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,EAAE,oBAKlC,CAAA;AAED,qBAAa,eAAgB,YAAW,WAAW;IAClD,QAAQ,CAAC,EAAE,aAAY;IACvB,QAAQ,CAAC,IAAI,iBAAgB;IAC7B,QAAQ,CAAC,YAAY,uBAAuB;IAE5C,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,aAAa;IAoB1B,UAAU,CAAC,MAAM,EAAE,oBAAoB,GAAG,aAAa,CAAC,WAAW,CAAC;IA6HrE,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAmClC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAerC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACX,OAAO,IAAI,cAAc,EAOzB,iBAAiB,EAEjB,MAAM,iCAAiC,CAAA;AACxC,OAAO,KAAK,EACX,oBAAoB,EAEpB,WAAW,EACX,SAAS,EACT,oBAAoB,EACpB,WAAW,EACX,UAAU,EAEV,MAAM,YAAY,CAAA;AASnB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAY/C,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,oBAAoB,CAAC,UAAU,CAAC,GAAG,cAAc,EAAE,CAgE9F;AA6BD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,iBAAiB,GAAG,SAAS,CA0C/F;AAYD,UAAU,eAAe;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,yBAAyB,CAAC,EAAE,MAAM,CAAA;CAClC;AAED,wBAAgB,iBAAiB,CAAC,GAAG,CAAC,EAAE,eAAe,GAAG,UAAU,CAmBnE;AAoBD;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,EAAE,oBAOlC,CAAA;AAED,qBAAa,eAAgB,YAAW,WAAW;IAClD,QAAQ,CAAC,EAAE,aAAY;IACvB,QAAQ,CAAC,IAAI,iBAAgB;IAC7B,QAAQ,CAAC,YAAY,uBAAuB;IAE5C,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,aAAa;IAoB1B,UAAU,CAAC,MAAM,EAAE,oBAAoB,GAAG,aAAa,CAAC,WAAW,CAAC;IAkKrE,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAmClC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAerC"}
package/dist/client.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { BedrockRuntimeClient, ConverseCommand, ConverseStreamCommand, } from '@aws-sdk/client-bedrock-runtime';
2
+ import { toolResultToText } from '@namzu/sdk';
3
+ import { ProviderRequestError, isCallerAbortError, isProviderRequestError, providerVendorError, } from '@namzu/sdk';
2
4
  function extractSystemBlocks(messages) {
3
5
  return messages
4
6
  .filter((m) => m.role === 'system')
@@ -7,7 +9,7 @@ function extractSystemBlocks(messages) {
7
9
  function toBedrockRole(role) {
8
10
  return role === 'assistant' ? 'assistant' : 'user';
9
11
  }
10
- function toBedrockMessages(messages) {
12
+ export function toBedrockMessages(messages) {
11
13
  const out = [];
12
14
  let pendingToolResults = [];
13
15
  const flushToolResults = () => {
@@ -21,8 +23,13 @@ function toBedrockMessages(messages) {
21
23
  continue;
22
24
  if (msg.role === 'tool') {
23
25
  const toolMsg = msg;
26
+ // `JSON.stringify` used to run here, so a tool result carrying an
27
+ // image sent the model a wall of quoted base64: unreadable, and
28
+ // paid for by the character. This driver does not map image
29
+ // content, so the honest form is the SDK's named placeholder —
30
+ // which says what was there and how big it was.
24
31
  const resultBlock = {
25
- text: typeof toolMsg.content === 'string' ? toolMsg.content : JSON.stringify(toolMsg.content),
32
+ text: toolResultToText(toolMsg.content ?? ''),
26
33
  };
27
34
  pendingToolResults.push({
28
35
  toolResult: {
@@ -83,7 +90,12 @@ function extractToolNamesFromHistory(messages) {
83
90
  }
84
91
  return Array.from(names);
85
92
  }
86
- function toBedrockToolConfig(params) {
93
+ export function toBedrockToolConfig(params) {
94
+ // 'none' means the model must not call a tool. This used to map to the
95
+ // wire's 'auto', which means it MAY — the opposite, and silent. No wire
96
+ // format lets a model call a tool it was never given, so send none.
97
+ if (params.toolChoice === 'none')
98
+ return undefined;
87
99
  if (params.tools && params.tools.length > 0) {
88
100
  const tools = params.tools.map((t) => ({
89
101
  toolSpec: {
@@ -112,11 +124,10 @@ function toBedrockToolConfig(params) {
112
124
  }
113
125
  return undefined;
114
126
  }
127
+ /** 'none' never reaches here — it is answered by omitting the tools. */
115
128
  function formatToolChoice(tc) {
116
129
  if (!tc || tc === 'auto')
117
130
  return { auto: {} };
118
- if (tc === 'none')
119
- return { auto: {} };
120
131
  if (tc === 'required')
121
132
  return { any: {} };
122
133
  if (typeof tc === 'object' && tc.type === 'function') {
@@ -124,7 +135,7 @@ function formatToolChoice(tc) {
124
135
  }
125
136
  return { auto: {} };
126
137
  }
127
- function parseUsage(raw) {
138
+ export function parseBedrockUsage(raw) {
128
139
  if (!raw) {
129
140
  return {
130
141
  promptTokens: 0,
@@ -170,6 +181,8 @@ export const BEDROCK_CAPABILITIES = {
170
181
  supportsStreaming: true,
171
182
  supportsFunctionCalling: true,
172
183
  supportsVision: false,
184
+ // Images only. A document degrades to a named placeholder.
185
+ supportsDocuments: false,
173
186
  };
174
187
  export class BedrockProvider {
175
188
  id = 'bedrock';
@@ -212,101 +225,142 @@ export class BedrockProvider {
212
225
  toolConfig,
213
226
  inferenceConfig,
214
227
  });
215
- const response = await this.client.send(command, {
216
- requestTimeout: this.config.timeout ?? 120_000,
217
- // Per-request abort: a Stop tears the in-flight Converse stream down.
218
- abortSignal: params.signal,
219
- });
228
+ // AWS models failures as distinct exception CLASSES rather than status
229
+ // codes (ThrottlingException, ValidationException, AccessDeniedException),
230
+ // and lets them escape verbatim. Classify from the class name, then drop
231
+ // the AWS error — no re-throw, no `cause`.
232
+ let response;
233
+ try {
234
+ response = await this.client.send(command, {
235
+ requestTimeout: this.config.timeout ?? 120_000,
236
+ // Per-request abort: a Stop tears the in-flight Converse stream down.
237
+ abortSignal: params.signal,
238
+ });
239
+ }
240
+ catch (err) {
241
+ if (isCallerAbortError(err, params.signal))
242
+ throw params.signal?.reason ?? err;
243
+ if (isProviderRequestError(err))
244
+ throw err;
245
+ throw providerVendorError({ providerId: 'bedrock', error: err });
246
+ }
220
247
  if (!response.stream) {
221
- throw new Error('Bedrock returned no stream body');
248
+ throw new ProviderRequestError({
249
+ kind: 'server',
250
+ providerId: 'bedrock',
251
+ detail: 'the response contained no stream body',
252
+ });
222
253
  }
223
254
  const requestId = response.$metadata.requestId ?? `bedrock-${Date.now()}`;
224
255
  const activeToolCalls = new Map();
225
256
  let toolCallIndex = 0;
226
- for await (const event of response.stream) {
227
- // Stop pulling promptly on abort; `for await` calls the stream's
228
- // `.return()` on this throw, releasing the connection.
229
- params.signal?.throwIfAborted();
230
- try {
231
- if ('contentBlockDelta' in event && event.contentBlockDelta?.delta) {
232
- const delta = event.contentBlockDelta.delta;
233
- if ('text' in delta && delta.text) {
234
- yield {
235
- id: requestId,
236
- delta: { content: delta.text },
237
- };
257
+ try {
258
+ for await (const event of response.stream) {
259
+ // Stop pulling promptly on abort; `for await` calls the stream's
260
+ // `.return()` on this throw, releasing the connection.
261
+ params.signal?.throwIfAborted();
262
+ // Bedrock reports several post-handshake failures as UNION
263
+ // EVENTS, not thrown exceptions. Ignoring these members makes a
264
+ // throttled or failed stream look like a clean EOF.
265
+ const streamFailure = ('internalServerException' in event ? event.internalServerException : undefined) ??
266
+ ('modelStreamErrorException' in event ? event.modelStreamErrorException : undefined) ??
267
+ ('validationException' in event ? event.validationException : undefined) ??
268
+ ('throttlingException' in event ? event.throttlingException : undefined) ??
269
+ ('serviceUnavailableException' in event ? event.serviceUnavailableException : undefined);
270
+ if (streamFailure) {
271
+ throw providerVendorError({
272
+ providerId: 'bedrock',
273
+ error: streamFailure,
274
+ });
275
+ }
276
+ try {
277
+ if ('contentBlockDelta' in event && event.contentBlockDelta?.delta) {
278
+ const delta = event.contentBlockDelta.delta;
279
+ if ('text' in delta && delta.text) {
280
+ yield {
281
+ id: requestId,
282
+ delta: { content: delta.text },
283
+ };
284
+ }
285
+ if ('toolUse' in delta && delta.toolUse) {
286
+ const idx = event.contentBlockDelta.contentBlockIndex ?? toolCallIndex;
287
+ const active = activeToolCalls.get(idx);
288
+ if (active) {
289
+ active.args += delta.toolUse.input ?? '';
290
+ yield {
291
+ id: requestId,
292
+ delta: {
293
+ toolCalls: [
294
+ {
295
+ index: idx,
296
+ function: { arguments: delta.toolUse.input ?? '' },
297
+ },
298
+ ],
299
+ },
300
+ };
301
+ }
302
+ }
238
303
  }
239
- if ('toolUse' in delta && delta.toolUse) {
240
- const idx = event.contentBlockDelta.contentBlockIndex ?? toolCallIndex;
241
- const active = activeToolCalls.get(idx);
242
- if (active) {
243
- active.args += delta.toolUse.input ?? '';
304
+ if ('contentBlockStart' in event && event.contentBlockStart?.start) {
305
+ const start = event.contentBlockStart.start;
306
+ if ('toolUse' in start && start.toolUse) {
307
+ const idx = event.contentBlockStart.contentBlockIndex ?? toolCallIndex;
308
+ activeToolCalls.set(idx, {
309
+ id: start.toolUse.toolUseId ?? `tool-${Date.now()}`,
310
+ name: start.toolUse.name ?? '',
311
+ args: '',
312
+ });
244
313
  yield {
245
314
  id: requestId,
246
315
  delta: {
247
316
  toolCalls: [
248
317
  {
249
318
  index: idx,
250
- function: { arguments: delta.toolUse.input ?? '' },
319
+ id: start.toolUse.toolUseId,
320
+ type: 'function',
321
+ function: { name: start.toolUse.name ?? '' },
251
322
  },
252
323
  ],
253
324
  },
254
325
  };
326
+ toolCallIndex = idx + 1;
255
327
  }
256
328
  }
257
- }
258
- if ('contentBlockStart' in event && event.contentBlockStart?.start) {
259
- const start = event.contentBlockStart.start;
260
- if ('toolUse' in start && start.toolUse) {
261
- const idx = event.contentBlockStart.contentBlockIndex ?? toolCallIndex;
262
- activeToolCalls.set(idx, {
263
- id: start.toolUse.toolUseId ?? `tool-${Date.now()}`,
264
- name: start.toolUse.name ?? '',
265
- args: '',
266
- });
329
+ if ('contentBlockStop' in event) {
330
+ }
331
+ if ('messageStop' in event && event.messageStop) {
267
332
  yield {
268
333
  id: requestId,
269
- delta: {
270
- toolCalls: [
271
- {
272
- index: idx,
273
- id: start.toolUse.toolUseId,
274
- type: 'function',
275
- function: { name: start.toolUse.name ?? '' },
276
- },
277
- ],
278
- },
334
+ delta: {},
335
+ finishReason: mapStopReason(event.messageStop.stopReason),
336
+ };
337
+ }
338
+ if ('metadata' in event && event.metadata?.usage) {
339
+ const usage = parseBedrockUsage(event.metadata.usage);
340
+ yield {
341
+ id: requestId,
342
+ delta: {},
343
+ usage,
279
344
  };
280
- toolCallIndex = idx + 1;
281
345
  }
282
346
  }
283
- if ('contentBlockStop' in event) {
284
- }
285
- if ('messageStop' in event && event.messageStop) {
286
- yield {
287
- id: requestId,
288
- delta: {},
289
- finishReason: mapStopReason(event.messageStop.stopReason),
290
- };
291
- }
292
- if ('metadata' in event && event.metadata?.usage) {
293
- const usage = parseUsage(event.metadata.usage);
294
- yield {
295
- id: requestId,
296
- delta: {},
297
- usage,
298
- };
347
+ catch (parseErr) {
348
+ if (isProviderRequestError(parseErr))
349
+ throw parseErr;
350
+ throw new ProviderRequestError({
351
+ kind: 'server',
352
+ providerId: 'bedrock',
353
+ detail: 'the provider stream returned malformed data',
354
+ });
299
355
  }
300
356
  }
301
- catch (parseErr) {
302
- yield {
303
- id: requestId,
304
- delta: { content: undefined },
305
- finishReason: undefined,
306
- usage: undefined,
307
- error: `Stream parse error: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`,
308
- };
309
- }
357
+ }
358
+ catch (err) {
359
+ if (isCallerAbortError(err, params.signal))
360
+ throw params.signal?.reason ?? err;
361
+ if (isProviderRequestError(err))
362
+ throw err;
363
+ throw providerVendorError({ providerId: 'bedrock', error: err });
310
364
  }
311
365
  }
312
366
  async listModels() {