@tanstack/ai-bedrock 0.1.4 → 0.1.6

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.
@@ -1,430 +1,414 @@
1
- import { EventType, convertSchemaToJsonSchema } from "@tanstack/ai";
2
- import { BaseTextAdapter } from "@tanstack/ai/adapters";
3
- import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
4
1
  import { resolveBedrockAuth } from "../utils/auth.js";
5
2
  import { toConverseMessages } from "../converse/message-converter.js";
6
3
  import { toToolConfig } from "../converse/tool-converter.js";
7
4
  import { processConverseStream, throwIfConverseStreamError } from "../converse/stream-processor.js";
8
- import { buildStructuredToolConfig, STRUCTURED_TOOL_NAME } from "../converse/structured-output.js";
9
- class BedrockConverseTextAdapter extends BaseTextAdapter {
10
- kind = "text";
11
- name = "bedrock-converse";
12
- clientPromise;
13
- clientConfig;
14
- constructor(config, model) {
15
- super({}, model);
16
- this.clientConfig = config;
17
- }
18
- /**
19
- * Dynamically import `@aws-sdk/client-bedrock-runtime`. The specifier is held
20
- * in a variable (not a string literal) so bundler dep scanners (e.g. Vite/
21
- * esbuild optimizeDeps) cannot statically discover the AWS SDK and try to
22
- * pre-bundle it for the browser — it would fail on the SDK's Node-only
23
- * `fromTokenFile` export chain. The SDK is Node/server-only and is only
24
- * reached on a real request. `typeof import(...)` is a type-only reference
25
- * (erased at emit) so the imported members keep full typing.
26
- */
27
- importBedrockRuntime() {
28
- const mod = "@aws-sdk/client-bedrock-runtime";
29
- return import(
30
- /* @vite-ignore */
31
- mod
32
- );
33
- }
34
- /**
35
- * Lazily construct the `BedrockRuntimeClient`. The dynamic import keeps
36
- * `@aws-sdk/client-bedrock-runtime` out of the static/browser graph and
37
- * defers `resolveBedrockAuth` until a real request is made.
38
- */
39
- async getClient() {
40
- if (!this.clientPromise) {
41
- this.clientPromise = (async () => {
42
- const { BedrockRuntimeClient } = await this.importBedrockRuntime();
43
- const region = this.clientConfig.region ?? "us-east-1";
44
- const resolved = resolveBedrockAuth(
45
- {
46
- apiKey: this.clientConfig.apiKey,
47
- region,
48
- auth: this.clientConfig.auth
49
- },
50
- "runtime"
51
- );
52
- return new BedrockRuntimeClient(
53
- this.buildClientConfig(resolved, region, this.clientConfig.baseURL)
54
- );
55
- })().catch((error) => {
56
- this.clientPromise = void 0;
57
- throw error;
58
- });
59
- }
60
- return this.clientPromise;
61
- }
62
- /**
63
- * Map resolved auth + endpoint to a `BedrockRuntimeClientConfig`.
64
- *
65
- * Recent `@aws-sdk/client-bedrock-runtime` exposes a first-class `token`
66
- * config field for Bedrock API-key bearer auth. But the client's default
67
- * auth-scheme order is SigV4 first, then bearer — so passing `token` alone is
68
- * not enough: the SDK still resolves SigV4 and throws "Could not load
69
- * credentials from any providers". Pinning `authSchemePreference` to the
70
- * bearer scheme makes the API key actually get used. SigV4 uses the AWS
71
- * credential provider chain and the default scheme order.
72
- */
73
- buildClientConfig(resolved, region, endpoint) {
74
- if (resolved.kind === "bearer") {
75
- return {
76
- region,
77
- token: { token: resolved.token },
78
- authSchemePreference: ["httpBearerAuth"],
79
- ...endpoint ? { endpoint } : {}
80
- };
81
- }
82
- return {
83
- region: resolved.region,
84
- credentials: resolved.credentials,
85
- ...endpoint ? { endpoint } : {}
86
- };
87
- }
88
- // ---------------------------------------------------------------------------
89
- // SDK seams (overridden in tests so no real AWS call happens)
90
- // ---------------------------------------------------------------------------
91
- async sendStream(input) {
92
- const { ConverseStreamCommand } = await this.importBedrockRuntime();
93
- const client = await this.getClient();
94
- const res = await client.send(new ConverseStreamCommand(input));
95
- if (!res.stream) {
96
- throw new Error("Bedrock Converse: empty stream response");
97
- }
98
- return res.stream;
99
- }
100
- async send(input) {
101
- const { ConverseCommand } = await this.importBedrockRuntime();
102
- const client = await this.getClient();
103
- return client.send(new ConverseCommand(input));
104
- }
105
- // ---------------------------------------------------------------------------
106
- // Public adapter surface
107
- // ---------------------------------------------------------------------------
108
- async *chatStream(options) {
109
- try {
110
- options.logger.request(
111
- `activity=chat provider=${this.name} model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,
112
- { provider: this.name, model: this.model }
113
- );
114
- const input = this.buildInput(options);
115
- const stream = await this.sendStream(input);
116
- yield* processConverseStream(stream, () => this.generateId(), {
117
- threadId: options.threadId,
118
- parentRunId: options.parentRunId,
119
- model: options.model
120
- });
121
- } catch (error) {
122
- const errorPayload = toRunErrorPayload(
123
- error,
124
- `${this.name}.chatStream failed`
125
- );
126
- options.logger.errors(`${this.name}.chatStream fatal`, {
127
- error: errorPayload,
128
- source: `${this.name}.chatStream`
129
- });
130
- yield {
131
- type: EventType.RUN_ERROR,
132
- model: options.model,
133
- timestamp: Date.now(),
134
- message: errorPayload.message,
135
- ...errorPayload.code !== void 0 && { code: errorPayload.code },
136
- error: {
137
- message: errorPayload.message,
138
- ...errorPayload.code !== void 0 && { code: errorPayload.code }
139
- }
140
- };
141
- }
142
- }
143
- /**
144
- * Structured output via the forced-tool strategy. Converse has no native
145
- * json_schema response_format, so we force a single tool whose input schema
146
- * is the requested output schema and read the model's `toolUse.input` back as
147
- * the structured result.
148
- */
149
- async structuredOutput(options) {
150
- const { chatOptions, outputSchema } = options;
151
- try {
152
- chatOptions.logger.request(
153
- `activity=structuredOutput provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,
154
- { provider: this.name, model: this.model }
155
- );
156
- const input = {
157
- ...this.buildInput(chatOptions),
158
- toolConfig: buildStructuredToolConfig(outputSchema)
159
- };
160
- const res = await this.send(input);
161
- const structured = extractStructuredToolInput(res);
162
- if (structured === void 0) {
163
- throw new Error(
164
- `${this.name}.structuredOutput: response contained no forced-tool output`
165
- );
166
- }
167
- return {
168
- data: structured,
169
- rawText: JSON.stringify(structured)
170
- };
171
- } catch (error) {
172
- chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {
173
- error: toRunErrorPayload(error, `${this.name}.structuredOutput failed`),
174
- source: `${this.name}.structuredOutput`
175
- });
176
- throw error;
177
- }
178
- }
179
- /**
180
- * Streaming structured output. Same forced-tool strategy as
181
- * `structuredOutput`, but streamed: the forced tool's `toolUse.input` JSON
182
- * fragments are accumulated from the Converse stream and a terminal
183
- * `CUSTOM 'structured-output.complete'` event carries `{ object, raw }`,
184
- * mirroring openai-base's `structuredOutputStream` contract exactly.
185
- */
186
- async *structuredOutputStream(options) {
187
- const { chatOptions, outputSchema } = options;
188
- const timestamp = Date.now();
189
- const runId = this.generateId();
190
- const threadId = chatOptions.threadId ?? this.generateId();
191
- const messageId = this.generateId();
192
- let hasEmittedRunStarted = false;
193
- let hasEmittedTextMessageStart = false;
194
- let accumulatedRaw = "";
195
- let finishReason = "stop";
196
- try {
197
- chatOptions.logger.request(
198
- `activity=structuredOutputStream provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,
199
- { provider: this.name, model: this.model }
200
- );
201
- const input = {
202
- ...this.buildInput(chatOptions),
203
- toolConfig: buildStructuredToolConfig(outputSchema)
204
- };
205
- const stream = await this.sendStream(input);
206
- for await (const ev of stream) {
207
- if (!hasEmittedRunStarted) {
208
- hasEmittedRunStarted = true;
209
- yield {
210
- type: EventType.RUN_STARTED,
211
- runId,
212
- threadId,
213
- model: chatOptions.model,
214
- timestamp,
215
- parentRunId: chatOptions.parentRunId
216
- };
217
- }
218
- throwIfConverseStreamError(ev);
219
- if ("contentBlockDelta" in ev) {
220
- const delta = ev.contentBlockDelta?.delta;
221
- const fragment = delta && "toolUse" in delta ? delta.toolUse?.input : void 0;
222
- if (fragment !== void 0) {
223
- if (!hasEmittedTextMessageStart) {
224
- hasEmittedTextMessageStart = true;
225
- yield {
226
- type: EventType.TEXT_MESSAGE_START,
227
- messageId,
228
- role: "assistant",
229
- model: chatOptions.model,
230
- timestamp
231
- };
232
- }
233
- accumulatedRaw += fragment;
234
- yield {
235
- type: EventType.TEXT_MESSAGE_CONTENT,
236
- messageId,
237
- delta: fragment,
238
- content: accumulatedRaw,
239
- model: chatOptions.model,
240
- timestamp
241
- };
242
- }
243
- continue;
244
- }
245
- if ("messageStop" in ev) {
246
- const stopReason = ev.messageStop?.stopReason;
247
- finishReason = stopReason === "max_tokens" ? "length" : stopReason === "content_filtered" ? "content_filter" : "stop";
248
- continue;
249
- }
250
- }
251
- if (!hasEmittedRunStarted) {
252
- hasEmittedRunStarted = true;
253
- yield {
254
- type: EventType.RUN_STARTED,
255
- runId,
256
- threadId,
257
- model: chatOptions.model,
258
- timestamp,
259
- parentRunId: chatOptions.parentRunId
260
- };
261
- }
262
- if (hasEmittedTextMessageStart) {
263
- yield {
264
- type: EventType.TEXT_MESSAGE_END,
265
- messageId,
266
- model: chatOptions.model,
267
- timestamp
268
- };
269
- }
270
- if (accumulatedRaw.length === 0) {
271
- yield {
272
- type: EventType.RUN_ERROR,
273
- runId,
274
- model: chatOptions.model,
275
- timestamp,
276
- message: `${this.name}.structuredOutputStream: response contained no content`,
277
- code: "empty-response",
278
- error: {
279
- message: `${this.name}.structuredOutputStream: response contained no content`,
280
- code: "empty-response"
281
- }
282
- };
283
- return;
284
- }
285
- let parsed;
286
- try {
287
- parsed = JSON.parse(accumulatedRaw);
288
- } catch {
289
- yield {
290
- type: EventType.RUN_ERROR,
291
- runId,
292
- model: chatOptions.model,
293
- timestamp,
294
- message: `Failed to parse structured output as JSON. Content: ${accumulatedRaw.slice(0, 200)}${accumulatedRaw.length > 200 ? "..." : ""}`,
295
- code: "parse-error",
296
- error: {
297
- message: "Failed to parse structured output as JSON",
298
- code: "parse-error"
299
- }
300
- };
301
- return;
302
- }
303
- yield {
304
- type: EventType.CUSTOM,
305
- name: "structured-output.complete",
306
- value: {
307
- object: parsed,
308
- raw: accumulatedRaw
309
- },
310
- model: chatOptions.model,
311
- timestamp
312
- };
313
- yield {
314
- type: EventType.RUN_FINISHED,
315
- runId,
316
- threadId,
317
- model: chatOptions.model,
318
- timestamp,
319
- finishReason
320
- };
321
- } catch (error) {
322
- if (!hasEmittedRunStarted) {
323
- hasEmittedRunStarted = true;
324
- yield {
325
- type: EventType.RUN_STARTED,
326
- runId,
327
- threadId,
328
- model: chatOptions.model,
329
- timestamp,
330
- parentRunId: chatOptions.parentRunId
331
- };
332
- }
333
- const errorPayload = toRunErrorPayload(
334
- error,
335
- `${this.name}.structuredOutputStream failed`
336
- );
337
- chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {
338
- error: errorPayload,
339
- source: `${this.name}.structuredOutputStream`
340
- });
341
- yield {
342
- type: EventType.RUN_ERROR,
343
- runId,
344
- model: chatOptions.model,
345
- timestamp,
346
- message: errorPayload.message,
347
- ...errorPayload.code !== void 0 && { code: errorPayload.code },
348
- error: {
349
- message: errorPayload.message,
350
- ...errorPayload.code !== void 0 && { code: errorPayload.code }
351
- }
352
- };
353
- }
354
- }
355
- /**
356
- * Converse sends `tools` and a forced structured-output tool via two separate
357
- * mechanisms, never together. Declaring `false` makes the engine run the
358
- * agent loop without `outputSchema` and finalize via `structuredOutput` /
359
- * `structuredOutputStream`.
360
- */
361
- supportsCombinedToolsAndSchema() {
362
- return false;
363
- }
364
- // ---------------------------------------------------------------------------
365
- // Request construction
366
- // ---------------------------------------------------------------------------
367
- /**
368
- * Translate `TextOptions` into a `ConverseCommandInput`. Shared by chatStream,
369
- * structuredOutput, and structuredOutputStream (the latter two override
370
- * `toolConfig` with the forced structured tool afterwards).
371
- */
372
- buildInput(options) {
373
- const { system, messages } = toConverseMessages(
374
- options.messages,
375
- options.systemPrompts
376
- );
377
- const toolConfig = options.tools ? toToolConfig(convertTools(options.tools)) : void 0;
378
- const modelOptions = options.modelOptions;
379
- const temperature = modelOptions?.temperature;
380
- const topP = modelOptions?.top_p;
381
- const maxTokens = modelOptions?.max_completion_tokens;
382
- const stop = modelOptions?.stop;
383
- const stopSequences = stop == null ? void 0 : Array.isArray(stop) ? stop : [stop];
384
- const inferenceConfig = temperature != null || topP != null || maxTokens != null || stopSequences != null ? {
385
- ...temperature != null && { temperature },
386
- ...topP != null && { topP },
387
- ...maxTokens != null && { maxTokens },
388
- ...stopSequences != null && { stopSequences }
389
- } : void 0;
390
- return {
391
- modelId: this.model,
392
- messages,
393
- ...system.length > 0 && { system },
394
- ...toolConfig && { toolConfig },
395
- ...inferenceConfig && { inferenceConfig }
396
- };
397
- }
398
- }
5
+ import { buildStructuredToolConfig } from "../converse/structured-output.js";
6
+ import { EventType, convertSchemaToJsonSchema } from "@tanstack/ai";
7
+ import { BaseTextAdapter } from "@tanstack/ai/adapters";
8
+ import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
9
+ //#region src/adapters/converse-text.ts
10
+ /**
11
+ * Bedrock Converse text adapter. Wires the Converse translation modules (message
12
+ * converter, tool converter, stream processor, structured-output forced-tool
13
+ * builder) onto `@tanstack/ai`'s `BaseTextAdapter` and the
14
+ * `@aws-sdk/client-bedrock-runtime` `BedrockRuntimeClient`.
15
+ *
16
+ * The success-path AG-UI lifecycle (`RUN_STARTED`..`RUN_FINISHED`) is owned by
17
+ * `processConverseStream`; this adapter only owns the catch/`RUN_ERROR` path,
18
+ * mirroring openai-base's `chatStream`.
19
+ *
20
+ * The actual SDK calls live behind two protected seams (`sendStream` / `send`)
21
+ * so tests can subclass and inject canned Converse SDK shapes without a real
22
+ * AWS request.
23
+ */
24
+ var BedrockConverseTextAdapter = class extends BaseTextAdapter {
25
+ kind = "text";
26
+ name = "bedrock-converse";
27
+ clientPromise;
28
+ clientConfig;
29
+ constructor(config, model) {
30
+ super({}, model);
31
+ this.clientConfig = config;
32
+ }
33
+ /**
34
+ * Dynamically import `@aws-sdk/client-bedrock-runtime`. The specifier is held
35
+ * in a variable (not a string literal) so bundler dep scanners (e.g. Vite/
36
+ * esbuild optimizeDeps) cannot statically discover the AWS SDK and try to
37
+ * pre-bundle it for the browser — it would fail on the SDK's Node-only
38
+ * `fromTokenFile` export chain. The SDK is Node/server-only and is only
39
+ * reached on a real request. `typeof import(...)` is a type-only reference
40
+ * (erased at emit) so the imported members keep full typing.
41
+ */
42
+ importBedrockRuntime() {
43
+ return import(
44
+ /* @vite-ignore */
45
+ "@aws-sdk/client-bedrock-runtime"
46
+ );
47
+ }
48
+ /**
49
+ * Lazily construct the `BedrockRuntimeClient`. The dynamic import keeps
50
+ * `@aws-sdk/client-bedrock-runtime` out of the static/browser graph and
51
+ * defers `resolveBedrockAuth` until a real request is made.
52
+ */
53
+ async getClient() {
54
+ if (!this.clientPromise) this.clientPromise = (async () => {
55
+ const { BedrockRuntimeClient } = await this.importBedrockRuntime();
56
+ const region = this.clientConfig.region ?? "us-east-1";
57
+ const resolved = resolveBedrockAuth({
58
+ apiKey: this.clientConfig.apiKey,
59
+ region,
60
+ auth: this.clientConfig.auth
61
+ }, "runtime");
62
+ return new BedrockRuntimeClient(this.buildClientConfig(resolved, region, this.clientConfig.baseURL));
63
+ })().catch((error) => {
64
+ this.clientPromise = void 0;
65
+ throw error;
66
+ });
67
+ return this.clientPromise;
68
+ }
69
+ /**
70
+ * Map resolved auth + endpoint to a `BedrockRuntimeClientConfig`.
71
+ *
72
+ * Recent `@aws-sdk/client-bedrock-runtime` exposes a first-class `token`
73
+ * config field for Bedrock API-key bearer auth. But the client's default
74
+ * auth-scheme order is SigV4 first, then bearer — so passing `token` alone is
75
+ * not enough: the SDK still resolves SigV4 and throws "Could not load
76
+ * credentials from any providers". Pinning `authSchemePreference` to the
77
+ * bearer scheme makes the API key actually get used. SigV4 uses the AWS
78
+ * credential provider chain and the default scheme order.
79
+ */
80
+ buildClientConfig(resolved, region, endpoint) {
81
+ if (resolved.kind === "bearer") return {
82
+ region,
83
+ token: { token: resolved.token },
84
+ authSchemePreference: ["httpBearerAuth"],
85
+ ...endpoint ? { endpoint } : {}
86
+ };
87
+ return {
88
+ region: resolved.region,
89
+ credentials: resolved.credentials,
90
+ ...endpoint ? { endpoint } : {}
91
+ };
92
+ }
93
+ async sendStream(input) {
94
+ const { ConverseStreamCommand } = await this.importBedrockRuntime();
95
+ const res = await (await this.getClient()).send(new ConverseStreamCommand(input));
96
+ if (!res.stream) throw new Error("Bedrock Converse: empty stream response");
97
+ return res.stream;
98
+ }
99
+ async send(input) {
100
+ const { ConverseCommand } = await this.importBedrockRuntime();
101
+ return (await this.getClient()).send(new ConverseCommand(input));
102
+ }
103
+ async *chatStream(options) {
104
+ try {
105
+ options.logger.request(`activity=chat provider=${this.name} model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`, {
106
+ provider: this.name,
107
+ model: this.model
108
+ });
109
+ const input = this.buildInput(options);
110
+ yield* processConverseStream(await this.sendStream(input), () => this.generateId(), {
111
+ threadId: options.threadId,
112
+ parentRunId: options.parentRunId,
113
+ model: options.model
114
+ });
115
+ } catch (error) {
116
+ const errorPayload = toRunErrorPayload(error, `${this.name}.chatStream failed`);
117
+ options.logger.errors(`${this.name}.chatStream fatal`, {
118
+ error: errorPayload,
119
+ source: `${this.name}.chatStream`
120
+ });
121
+ yield {
122
+ type: EventType.RUN_ERROR,
123
+ model: options.model,
124
+ timestamp: Date.now(),
125
+ message: errorPayload.message,
126
+ ...errorPayload.code !== void 0 && { code: errorPayload.code },
127
+ error: {
128
+ message: errorPayload.message,
129
+ ...errorPayload.code !== void 0 && { code: errorPayload.code }
130
+ }
131
+ };
132
+ }
133
+ }
134
+ /**
135
+ * Structured output via the forced-tool strategy. Converse has no native
136
+ * json_schema response_format, so we force a single tool whose input schema
137
+ * is the requested output schema and read the model's `toolUse.input` back as
138
+ * the structured result.
139
+ */
140
+ async structuredOutput(options) {
141
+ const { chatOptions, outputSchema } = options;
142
+ try {
143
+ chatOptions.logger.request(`activity=structuredOutput provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`, {
144
+ provider: this.name,
145
+ model: this.model
146
+ });
147
+ const input = {
148
+ ...this.buildInput(chatOptions),
149
+ toolConfig: buildStructuredToolConfig(outputSchema)
150
+ };
151
+ const structured = extractStructuredToolInput(await this.send(input));
152
+ if (structured === void 0) throw new Error(`${this.name}.structuredOutput: response contained no forced-tool output`);
153
+ return {
154
+ data: structured,
155
+ rawText: JSON.stringify(structured)
156
+ };
157
+ } catch (error) {
158
+ chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {
159
+ error: toRunErrorPayload(error, `${this.name}.structuredOutput failed`),
160
+ source: `${this.name}.structuredOutput`
161
+ });
162
+ throw error;
163
+ }
164
+ }
165
+ /**
166
+ * Streaming structured output. Same forced-tool strategy as
167
+ * `structuredOutput`, but streamed: the forced tool's `toolUse.input` JSON
168
+ * fragments are accumulated from the Converse stream and a terminal
169
+ * `CUSTOM 'structured-output.complete'` event carries `{ object, raw }`,
170
+ * mirroring openai-base's `structuredOutputStream` contract exactly.
171
+ */
172
+ async *structuredOutputStream(options) {
173
+ const { chatOptions, outputSchema } = options;
174
+ const timestamp = Date.now();
175
+ const runId = this.generateId();
176
+ const threadId = chatOptions.threadId ?? this.generateId();
177
+ const messageId = this.generateId();
178
+ let hasEmittedRunStarted = false;
179
+ let hasEmittedTextMessageStart = false;
180
+ let accumulatedRaw = "";
181
+ let finishReason = "stop";
182
+ try {
183
+ chatOptions.logger.request(`activity=structuredOutputStream provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`, {
184
+ provider: this.name,
185
+ model: this.model
186
+ });
187
+ const input = {
188
+ ...this.buildInput(chatOptions),
189
+ toolConfig: buildStructuredToolConfig(outputSchema)
190
+ };
191
+ const stream = await this.sendStream(input);
192
+ for await (const ev of stream) {
193
+ if (!hasEmittedRunStarted) {
194
+ hasEmittedRunStarted = true;
195
+ yield {
196
+ type: EventType.RUN_STARTED,
197
+ runId,
198
+ threadId,
199
+ model: chatOptions.model,
200
+ timestamp,
201
+ parentRunId: chatOptions.parentRunId
202
+ };
203
+ }
204
+ throwIfConverseStreamError(ev);
205
+ if ("contentBlockDelta" in ev) {
206
+ const delta = ev.contentBlockDelta?.delta;
207
+ const fragment = delta && "toolUse" in delta ? delta.toolUse?.input : void 0;
208
+ if (fragment !== void 0) {
209
+ if (!hasEmittedTextMessageStart) {
210
+ hasEmittedTextMessageStart = true;
211
+ yield {
212
+ type: EventType.TEXT_MESSAGE_START,
213
+ messageId,
214
+ role: "assistant",
215
+ model: chatOptions.model,
216
+ timestamp
217
+ };
218
+ }
219
+ accumulatedRaw += fragment;
220
+ yield {
221
+ type: EventType.TEXT_MESSAGE_CONTENT,
222
+ messageId,
223
+ delta: fragment,
224
+ content: accumulatedRaw,
225
+ model: chatOptions.model,
226
+ timestamp
227
+ };
228
+ }
229
+ continue;
230
+ }
231
+ if ("messageStop" in ev) {
232
+ const stopReason = ev.messageStop?.stopReason;
233
+ finishReason = stopReason === "max_tokens" ? "length" : stopReason === "content_filtered" ? "content_filter" : "stop";
234
+ continue;
235
+ }
236
+ }
237
+ if (!hasEmittedRunStarted) {
238
+ hasEmittedRunStarted = true;
239
+ yield {
240
+ type: EventType.RUN_STARTED,
241
+ runId,
242
+ threadId,
243
+ model: chatOptions.model,
244
+ timestamp,
245
+ parentRunId: chatOptions.parentRunId
246
+ };
247
+ }
248
+ if (hasEmittedTextMessageStart) yield {
249
+ type: EventType.TEXT_MESSAGE_END,
250
+ messageId,
251
+ model: chatOptions.model,
252
+ timestamp
253
+ };
254
+ if (accumulatedRaw.length === 0) {
255
+ yield {
256
+ type: EventType.RUN_ERROR,
257
+ runId,
258
+ model: chatOptions.model,
259
+ timestamp,
260
+ message: `${this.name}.structuredOutputStream: response contained no content`,
261
+ code: "empty-response",
262
+ error: {
263
+ message: `${this.name}.structuredOutputStream: response contained no content`,
264
+ code: "empty-response"
265
+ }
266
+ };
267
+ return;
268
+ }
269
+ let parsed;
270
+ try {
271
+ parsed = JSON.parse(accumulatedRaw);
272
+ } catch {
273
+ yield {
274
+ type: EventType.RUN_ERROR,
275
+ runId,
276
+ model: chatOptions.model,
277
+ timestamp,
278
+ message: `Failed to parse structured output as JSON. Content: ${accumulatedRaw.slice(0, 200)}${accumulatedRaw.length > 200 ? "..." : ""}`,
279
+ code: "parse-error",
280
+ error: {
281
+ message: "Failed to parse structured output as JSON",
282
+ code: "parse-error"
283
+ }
284
+ };
285
+ return;
286
+ }
287
+ yield {
288
+ type: EventType.CUSTOM,
289
+ name: "structured-output.complete",
290
+ value: {
291
+ object: parsed,
292
+ raw: accumulatedRaw
293
+ },
294
+ model: chatOptions.model,
295
+ timestamp
296
+ };
297
+ yield {
298
+ type: EventType.RUN_FINISHED,
299
+ runId,
300
+ threadId,
301
+ model: chatOptions.model,
302
+ timestamp,
303
+ finishReason
304
+ };
305
+ } catch (error) {
306
+ if (!hasEmittedRunStarted) {
307
+ hasEmittedRunStarted = true;
308
+ yield {
309
+ type: EventType.RUN_STARTED,
310
+ runId,
311
+ threadId,
312
+ model: chatOptions.model,
313
+ timestamp,
314
+ parentRunId: chatOptions.parentRunId
315
+ };
316
+ }
317
+ const errorPayload = toRunErrorPayload(error, `${this.name}.structuredOutputStream failed`);
318
+ chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {
319
+ error: errorPayload,
320
+ source: `${this.name}.structuredOutputStream`
321
+ });
322
+ yield {
323
+ type: EventType.RUN_ERROR,
324
+ runId,
325
+ model: chatOptions.model,
326
+ timestamp,
327
+ message: errorPayload.message,
328
+ ...errorPayload.code !== void 0 && { code: errorPayload.code },
329
+ error: {
330
+ message: errorPayload.message,
331
+ ...errorPayload.code !== void 0 && { code: errorPayload.code }
332
+ }
333
+ };
334
+ }
335
+ }
336
+ /**
337
+ * Converse sends `tools` and a forced structured-output tool via two separate
338
+ * mechanisms, never together. Declaring `false` makes the engine run the
339
+ * agent loop without `outputSchema` and finalize via `structuredOutput` /
340
+ * `structuredOutputStream`.
341
+ */
342
+ supportsCombinedToolsAndSchema() {
343
+ return false;
344
+ }
345
+ /**
346
+ * Translate `TextOptions` into a `ConverseCommandInput`. Shared by chatStream,
347
+ * structuredOutput, and structuredOutputStream (the latter two override
348
+ * `toolConfig` with the forced structured tool afterwards).
349
+ */
350
+ buildInput(options) {
351
+ const { system, messages } = toConverseMessages(options.messages, options.systemPrompts);
352
+ const toolConfig = options.tools ? toToolConfig(convertTools(options.tools), "auto") : void 0;
353
+ const modelOptions = options.modelOptions;
354
+ const temperature = modelOptions?.temperature;
355
+ const topP = modelOptions?.top_p;
356
+ const maxTokens = modelOptions?.max_completion_tokens;
357
+ const stop = modelOptions?.stop;
358
+ const stopSequences = stop == null ? void 0 : Array.isArray(stop) ? stop : [stop];
359
+ const inferenceConfig = temperature != null || topP != null || maxTokens != null || stopSequences != null ? {
360
+ ...temperature != null && { temperature },
361
+ ...topP != null && { topP },
362
+ ...maxTokens != null && { maxTokens },
363
+ ...stopSequences != null && { stopSequences }
364
+ } : void 0;
365
+ return {
366
+ modelId: this.model,
367
+ messages,
368
+ ...system.length > 0 && { system },
369
+ ...toolConfig && { toolConfig },
370
+ ...inferenceConfig && { inferenceConfig }
371
+ };
372
+ }
373
+ };
374
+ /**
375
+ * Convert TanStack `Tool[]` to the Converse tool-converter input shape. Reuses
376
+ * the SAME `convertSchemaToJsonSchema` the other adapters use so the Converse
377
+ * tool input schemas match what every other provider sends.
378
+ */
399
379
  function convertTools(tools) {
400
- return tools.map((tool) => {
401
- const inputSchema = convertSchemaToJsonSchema(
402
- tool.inputSchema
403
- ) ?? { type: "object", properties: {}, required: [] };
404
- return {
405
- name: tool.name,
406
- description: tool.description,
407
- inputSchema
408
- };
409
- });
380
+ return tools.map((tool) => {
381
+ const inputSchema = convertSchemaToJsonSchema(tool.inputSchema) ?? {
382
+ type: "object",
383
+ properties: {},
384
+ required: []
385
+ };
386
+ return {
387
+ name: tool.name,
388
+ description: tool.description,
389
+ inputSchema
390
+ };
391
+ });
410
392
  }
393
+ /**
394
+ * Find the forced structured-output tool's `input` in a non-streaming Converse
395
+ * response. SDK-boundary narrowing only — `ConverseOutput` is a tagged union
396
+ * (`{ message }`) and a tool-use block is `{ toolUse: { input } }`.
397
+ */
411
398
  function extractStructuredToolInput(res) {
412
- const message = res.output && "message" in res.output ? res.output.message : void 0;
413
- const content = message?.content ?? [];
414
- for (const block of content) {
415
- if ("toolUse" in block && block.toolUse) {
416
- if (block.toolUse.name === STRUCTURED_TOOL_NAME || block.toolUse.name === void 0) {
417
- return block.toolUse.input;
418
- }
419
- }
420
- }
421
- return void 0;
399
+ const content = (res.output && "message" in res.output ? res.output.message : void 0)?.content ?? [];
400
+ for (const block of content) if ("toolUse" in block && block.toolUse) {
401
+ if (block.toolUse.name === "structured_output" || block.toolUse.name === void 0) return block.toolUse.input;
402
+ }
422
403
  }
404
+ /** Converse adapter with an explicit API key (low-level; mirrors createBedrockChat). */
423
405
  function createBedrockConverse(model, apiKey, config) {
424
- return new BedrockConverseTextAdapter({ ...config, apiKey }, model);
406
+ return new BedrockConverseTextAdapter({
407
+ ...config,
408
+ apiKey
409
+ }, model);
425
410
  }
426
- export {
427
- BedrockConverseTextAdapter,
428
- createBedrockConverse
429
- };
430
- //# sourceMappingURL=converse-text.js.map
411
+ //#endregion
412
+ export { BedrockConverseTextAdapter, createBedrockConverse };
413
+
414
+ //# sourceMappingURL=converse-text.js.map