@tanstack/ai-bedrock 0.0.1
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/README.md +95 -0
- package/dist/esm/adapters/converse-text.d.ts +93 -0
- package/dist/esm/adapters/converse-text.js +430 -0
- package/dist/esm/adapters/converse-text.js.map +1 -0
- package/dist/esm/adapters/responses-text.d.ts +23 -0
- package/dist/esm/adapters/responses-text.js +22 -0
- package/dist/esm/adapters/responses-text.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +30 -0
- package/dist/esm/adapters/text.js +38 -0
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/converse/message-converter.d.ts +14 -0
- package/dist/esm/converse/message-converter.js +181 -0
- package/dist/esm/converse/message-converter.js.map +1 -0
- package/dist/esm/converse/provider-options.d.ts +19 -0
- package/dist/esm/converse/stream-processor.d.ts +46 -0
- package/dist/esm/converse/stream-processor.js +203 -0
- package/dist/esm/converse/stream-processor.js.map +1 -0
- package/dist/esm/converse/structured-output.d.ts +8 -0
- package/dist/esm/converse/structured-output.js +20 -0
- package/dist/esm/converse/structured-output.js.map +1 -0
- package/dist/esm/converse/tool-converter.d.ts +11 -0
- package/dist/esm/converse/tool-converter.js +21 -0
- package/dist/esm/converse/tool-converter.js.map +1 -0
- package/dist/esm/index.d.ts +29 -0
- package/dist/esm/index.js +62 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/message-types.d.ts +25 -0
- package/dist/esm/model-catalog.generated.d.ts +127 -0
- package/dist/esm/model-catalog.generated.js +90 -0
- package/dist/esm/model-catalog.generated.js.map +1 -0
- package/dist/esm/model-meta.d.ts +38 -0
- package/dist/esm/model-meta.js +14 -0
- package/dist/esm/model-meta.js.map +1 -0
- package/dist/esm/text/responses-provider-options.d.ts +29 -0
- package/dist/esm/text/text-provider-options.d.ts +36 -0
- package/dist/esm/utils/auth.d.ts +20 -0
- package/dist/esm/utils/auth.js +49 -0
- package/dist/esm/utils/auth.js.map +1 -0
- package/dist/esm/utils/client.d.ts +19 -0
- package/dist/esm/utils/client.js +35 -0
- package/dist/esm/utils/client.js.map +1 -0
- package/dist/esm/utils/index.d.ts +1 -0
- package/dist/esm/utils/openai-sigv4-fetch.d.ts +10 -0
- package/dist/esm/utils/openai-sigv4-fetch.js +35 -0
- package/dist/esm/utils/openai-sigv4-fetch.js.map +1 -0
- package/package.json +67 -0
- package/src/adapters/converse-text.ts +603 -0
- package/src/adapters/responses-text.ts +73 -0
- package/src/adapters/text.ts +97 -0
- package/src/converse/message-converter.ts +268 -0
- package/src/converse/provider-options.ts +19 -0
- package/src/converse/stream-processor.ts +309 -0
- package/src/converse/structured-output.ts +24 -0
- package/src/converse/tool-converter.ts +49 -0
- package/src/index.ts +190 -0
- package/src/message-types.ts +24 -0
- package/src/model-catalog.generated.ts +88 -0
- package/src/model-meta.ts +69 -0
- package/src/text/responses-provider-options.ts +28 -0
- package/src/text/text-provider-options.ts +33 -0
- package/src/utils/auth.ts +80 -0
- package/src/utils/client.ts +62 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/openai-sigv4-fetch.ts +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# @tanstack/ai-bedrock
|
|
2
|
+
|
|
3
|
+
Amazon Bedrock adapter for TanStack AI — the native Converse API (default) plus the OpenAI-compatible Chat Completions and Responses APIs, with streaming, tool calling, and reasoning.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @tanstack/ai-bedrock
|
|
9
|
+
# or
|
|
10
|
+
npm install @tanstack/ai-bedrock
|
|
11
|
+
# or
|
|
12
|
+
yarn add @tanstack/ai-bedrock
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
Get a Bedrock API key from the [Amazon Bedrock console](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html) and set it as an environment variable:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
BEDROCK_API_KEY=your-bedrock-api-key
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Alternatively, configure AWS credentials for SigV4 auth (see below).
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { bedrockText } from '@tanstack/ai-bedrock'
|
|
29
|
+
import { chat } from '@tanstack/ai'
|
|
30
|
+
|
|
31
|
+
const adapter = bedrockText('us.anthropic.claude-haiku-4-5-20251001-v1:0', {
|
|
32
|
+
region: 'us-east-1',
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
for await (const chunk of chat({
|
|
36
|
+
adapter,
|
|
37
|
+
messages: [{ role: 'user', content: 'Hello from Bedrock!' }],
|
|
38
|
+
})) {
|
|
39
|
+
if (chunk.type === 'TEXT_MESSAGE_CONTENT')
|
|
40
|
+
process.stdout.write(chunk.delta ?? '')
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Responses API
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { bedrockText } from '@tanstack/ai-bedrock'
|
|
48
|
+
|
|
49
|
+
const adapter = bedrockText('openai.gpt-oss-120b-1:0', {
|
|
50
|
+
region: 'us-east-1',
|
|
51
|
+
api: 'responses',
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### With Explicit API Key
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { createBedrockText } from '@tanstack/ai-bedrock'
|
|
59
|
+
|
|
60
|
+
const adapter = createBedrockText(
|
|
61
|
+
'us.amazon.nova-pro-v1:0',
|
|
62
|
+
'your-bedrock-api-key',
|
|
63
|
+
{ region: 'us-west-2' },
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Authentication
|
|
68
|
+
|
|
69
|
+
Auth is resolved in this order:
|
|
70
|
+
|
|
71
|
+
1. Explicit `apiKey` passed to the factory
|
|
72
|
+
2. `BEDROCK_API_KEY` environment variable
|
|
73
|
+
3. `AWS_BEARER_TOKEN_BEDROCK` environment variable
|
|
74
|
+
4. SigV4 via the AWS credential chain
|
|
75
|
+
|
|
76
|
+
SigV4 signing is built in — no additional packages required. On the Converse
|
|
77
|
+
(default) path the `@aws-sdk/client-bedrock-runtime` client signs requests
|
|
78
|
+
itself; the OpenAI-compatible chat/responses paths sign via the bundled
|
|
79
|
+
`@smithy/signature-v4`. Set `auth: 'sigv4'` and provide AWS credentials through
|
|
80
|
+
the standard credential chain (env vars, shared config, instance role):
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const adapter = bedrockText('us.anthropic.claude-haiku-4-5-20251001-v1:0', {
|
|
84
|
+
auth: 'sigv4',
|
|
85
|
+
region: 'us-east-1',
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Documentation
|
|
90
|
+
|
|
91
|
+
Full documentation: [TanStack AI — Amazon Bedrock adapter](https://tanstack.com/ai/latest/docs/adapters/bedrock)
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from '@tanstack/ai/adapters';
|
|
2
|
+
import { ResolvedBedrockAuth } from '../utils/auth.js';
|
|
3
|
+
import { BedrockRuntimeClient, BedrockRuntimeClientConfig, ConverseCommandInput, ConverseCommandOutput, ConverseStreamCommandInput, ConverseStreamOutput } from '@aws-sdk/client-bedrock-runtime';
|
|
4
|
+
import { Modality, StreamChunk, TextOptions } from '@tanstack/ai';
|
|
5
|
+
import { BedrockClientConfig } from '../utils/client.js';
|
|
6
|
+
import { BedrockMessageMetadataByModality } from '../message-types.js';
|
|
7
|
+
import { BedrockConverseModels, ResolveConverseProviderOptions, ResolveInputModalities } from '../model-meta.js';
|
|
8
|
+
import type * as BedrockRuntime from '@aws-sdk/client-bedrock-runtime';
|
|
9
|
+
/** Config for the Converse adapter — same client config as the chat adapter. */
|
|
10
|
+
export interface BedrockConverseConfig extends BedrockClientConfig {
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Bedrock Converse text adapter. Wires the Converse translation modules (message
|
|
14
|
+
* converter, tool converter, stream processor, structured-output forced-tool
|
|
15
|
+
* builder) onto `@tanstack/ai`'s `BaseTextAdapter` and the
|
|
16
|
+
* `@aws-sdk/client-bedrock-runtime` `BedrockRuntimeClient`.
|
|
17
|
+
*
|
|
18
|
+
* The success-path AG-UI lifecycle (`RUN_STARTED`..`RUN_FINISHED`) is owned by
|
|
19
|
+
* `processConverseStream`; this adapter only owns the catch/`RUN_ERROR` path,
|
|
20
|
+
* mirroring openai-base's `chatStream`.
|
|
21
|
+
*
|
|
22
|
+
* The actual SDK calls live behind two protected seams (`sendStream` / `send`)
|
|
23
|
+
* so tests can subclass and inject canned Converse SDK shapes without a real
|
|
24
|
+
* AWS request.
|
|
25
|
+
*/
|
|
26
|
+
export declare class BedrockConverseTextAdapter<TModel extends BedrockConverseModels, TProviderOptions extends Record<string, any> = ResolveConverseProviderOptions<TModel>, TInputModalities extends ReadonlyArray<Modality> = ResolveInputModalities<TModel>> extends BaseTextAdapter<TModel, TProviderOptions, TInputModalities, BedrockMessageMetadataByModality> {
|
|
27
|
+
readonly kind: "text";
|
|
28
|
+
readonly name: "bedrock-converse";
|
|
29
|
+
private clientPromise?;
|
|
30
|
+
private readonly clientConfig;
|
|
31
|
+
constructor(config: BedrockConverseConfig, model: TModel);
|
|
32
|
+
/**
|
|
33
|
+
* Dynamically import `@aws-sdk/client-bedrock-runtime`. The specifier is held
|
|
34
|
+
* in a variable (not a string literal) so bundler dep scanners (e.g. Vite/
|
|
35
|
+
* esbuild optimizeDeps) cannot statically discover the AWS SDK and try to
|
|
36
|
+
* pre-bundle it for the browser — it would fail on the SDK's Node-only
|
|
37
|
+
* `fromTokenFile` export chain. The SDK is Node/server-only and is only
|
|
38
|
+
* reached on a real request. `typeof import(...)` is a type-only reference
|
|
39
|
+
* (erased at emit) so the imported members keep full typing.
|
|
40
|
+
*/
|
|
41
|
+
protected importBedrockRuntime(): Promise<typeof BedrockRuntime>;
|
|
42
|
+
/**
|
|
43
|
+
* Lazily construct the `BedrockRuntimeClient`. The dynamic import keeps
|
|
44
|
+
* `@aws-sdk/client-bedrock-runtime` out of the static/browser graph and
|
|
45
|
+
* defers `resolveBedrockAuth` until a real request is made.
|
|
46
|
+
*/
|
|
47
|
+
protected getClient(): Promise<BedrockRuntimeClient>;
|
|
48
|
+
/**
|
|
49
|
+
* Map resolved auth + endpoint to a `BedrockRuntimeClientConfig`.
|
|
50
|
+
*
|
|
51
|
+
* Recent `@aws-sdk/client-bedrock-runtime` exposes a first-class `token`
|
|
52
|
+
* config field for Bedrock API-key bearer auth. But the client's default
|
|
53
|
+
* auth-scheme order is SigV4 first, then bearer — so passing `token` alone is
|
|
54
|
+
* not enough: the SDK still resolves SigV4 and throws "Could not load
|
|
55
|
+
* credentials from any providers". Pinning `authSchemePreference` to the
|
|
56
|
+
* bearer scheme makes the API key actually get used. SigV4 uses the AWS
|
|
57
|
+
* credential provider chain and the default scheme order.
|
|
58
|
+
*/
|
|
59
|
+
protected buildClientConfig(resolved: ResolvedBedrockAuth, region: string, endpoint: string | undefined): BedrockRuntimeClientConfig;
|
|
60
|
+
protected sendStream(input: ConverseStreamCommandInput): Promise<AsyncIterable<ConverseStreamOutput>>;
|
|
61
|
+
protected send(input: ConverseCommandInput): Promise<ConverseCommandOutput>;
|
|
62
|
+
chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
|
|
63
|
+
/**
|
|
64
|
+
* Structured output via the forced-tool strategy. Converse has no native
|
|
65
|
+
* json_schema response_format, so we force a single tool whose input schema
|
|
66
|
+
* is the requested output schema and read the model's `toolUse.input` back as
|
|
67
|
+
* the structured result.
|
|
68
|
+
*/
|
|
69
|
+
structuredOutput(options: StructuredOutputOptions<TProviderOptions>): Promise<StructuredOutputResult<unknown>>;
|
|
70
|
+
/**
|
|
71
|
+
* Streaming structured output. Same forced-tool strategy as
|
|
72
|
+
* `structuredOutput`, but streamed: the forced tool's `toolUse.input` JSON
|
|
73
|
+
* fragments are accumulated from the Converse stream and a terminal
|
|
74
|
+
* `CUSTOM 'structured-output.complete'` event carries `{ object, raw }`,
|
|
75
|
+
* mirroring openai-base's `structuredOutputStream` contract exactly.
|
|
76
|
+
*/
|
|
77
|
+
structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
|
|
78
|
+
/**
|
|
79
|
+
* Converse sends `tools` and a forced structured-output tool via two separate
|
|
80
|
+
* mechanisms, never together. Declaring `false` makes the engine run the
|
|
81
|
+
* agent loop without `outputSchema` and finalize via `structuredOutput` /
|
|
82
|
+
* `structuredOutputStream`.
|
|
83
|
+
*/
|
|
84
|
+
supportsCombinedToolsAndSchema(): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Translate `TextOptions` into a `ConverseCommandInput`. Shared by chatStream,
|
|
87
|
+
* structuredOutput, and structuredOutputStream (the latter two override
|
|
88
|
+
* `toolConfig` with the forced structured tool afterwards).
|
|
89
|
+
*/
|
|
90
|
+
protected buildInput(options: TextOptions<TProviderOptions>): ConverseCommandInput;
|
|
91
|
+
}
|
|
92
|
+
/** Converse adapter with an explicit API key (low-level; mirrors createBedrockChat). */
|
|
93
|
+
export declare function createBedrockConverse<TModel extends BedrockConverseModels>(model: TModel, apiKey: string, config?: Omit<BedrockConverseConfig, 'apiKey'>): BedrockConverseTextAdapter<TModel>;
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { EventType, convertSchemaToJsonSchema } from "@tanstack/ai";
|
|
2
|
+
import { BaseTextAdapter } from "@tanstack/ai/adapters";
|
|
3
|
+
import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
|
|
4
|
+
import { resolveBedrockAuth } from "../utils/auth.js";
|
|
5
|
+
import { toConverseMessages } from "../converse/message-converter.js";
|
|
6
|
+
import { toToolConfig } from "../converse/tool-converter.js";
|
|
7
|
+
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
|
+
}
|
|
399
|
+
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
|
+
});
|
|
410
|
+
}
|
|
411
|
+
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;
|
|
422
|
+
}
|
|
423
|
+
function createBedrockConverse(model, apiKey, config) {
|
|
424
|
+
return new BedrockConverseTextAdapter({ ...config, apiKey }, model);
|
|
425
|
+
}
|
|
426
|
+
export {
|
|
427
|
+
BedrockConverseTextAdapter,
|
|
428
|
+
createBedrockConverse
|
|
429
|
+
};
|
|
430
|
+
//# sourceMappingURL=converse-text.js.map
|