@kindgi/adapter-model-bedrock 0.0.0-bootstrap.0 → 0.1.7-rc.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/LICENSE +201 -0
- package/README.md +87 -2
- package/dist/config.d.ts +43 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +159 -0
- package/dist/config.js.map +1 -0
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +210 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/nova-thinking.d.ts +15 -0
- package/dist/nova-thinking.d.ts.map +1 -0
- package/dist/nova-thinking.js +28 -0
- package/dist/nova-thinking.js.map +1 -0
- package/package.json +51 -4
- package/src/config.ts +201 -0
- package/src/entry.ts +274 -0
- package/src/index.ts +20 -0
- package/src/nova-thinking.ts +41 -0
package/dist/entry.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
/**
|
|
4
|
+
* The Amazon Bedrock adapter: a registration's models (model or inference-profile ids, such as
|
|
5
|
+
* `us.amazon.nova-pro-v1:0`) on Bedrock's Converse API, through `@kindgi/adapter-model-shared`
|
|
6
|
+
* (retries, typed errors, the reasoning state across a pause, usage and cost).
|
|
7
|
+
*
|
|
8
|
+
* Pinned, each with a test:
|
|
9
|
+
* - It signs in before each attempt (`beforeAttempt`), with the call's abort signal, and the
|
|
10
|
+
* attempt's request reads what it got (`attemptPrepared()`): calls at once never share one,
|
|
11
|
+
* and a failure is typed there (the library would report a credential failure as a plain
|
|
12
|
+
* error, and retry it).
|
|
13
|
+
* - `auth: aws-identity` (the default): requests signed with SigV4, with credentials from the
|
|
14
|
+
* runtime's AWS identity (`AdapterFactoryInput.identities.aws`), asked for every attempt (it
|
|
15
|
+
* refreshes them itself), within 10 seconds. `apiKey: ''` keeps a stray
|
|
16
|
+
* `AWS_BEARER_TOKEN_BEDROCK` from switching the provider to a bearer key. Only Bedrock's
|
|
17
|
+
* runtime in the region gets them (`isBedrockRuntimeHost`): any other host is refused.
|
|
18
|
+
* - `auth: api-key`: the key `secret_ref` names, read for every attempt and sent as the
|
|
19
|
+
* bearer token, so a rotated key takes effect on the next call. No SigV4, so no AWS
|
|
20
|
+
* credentials are looked for.
|
|
21
|
+
* - A failed sign-in (no credentials, no key, a blank one) is an `auth` error, never retried.
|
|
22
|
+
* - No request follows a redirect: the credential goes to the endpoint, nowhere else.
|
|
23
|
+
* - Prompt caching: cache points after the system prompt and, when the call will be sent
|
|
24
|
+
* again, the last message, for Claude and Nova models whose registration prices cache reads
|
|
25
|
+
* (`cachesPrompts`); none for any other model.
|
|
26
|
+
* - The region is the registration's (`metadata.region`), never `AWS_REGION`, and the endpoint
|
|
27
|
+
* is always given (the region's own, or `adapter_config.baseURL`), so
|
|
28
|
+
* `AWS_ENDPOINT_URL_BEDROCK_RUNTIME` and `AWS_ENDPOINT_URL` are never read.
|
|
29
|
+
*/
|
|
30
|
+
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
|
|
31
|
+
import { ModelProviderError, tokenCostUsd } from '@kindgi/adapter-model-shared';
|
|
32
|
+
import { attemptPrepared, createAiSdkModelProvider } from '@kindgi/adapter-model-shared/ai-sdk';
|
|
33
|
+
import { adapterConfigError, identitiesPresent, } from '@kindgi/capabilities';
|
|
34
|
+
import { bearerValueProblem } from '@kindgi/types';
|
|
35
|
+
import { BEDROCK_ADAPTER_ID, readBedrockConfig } from './config.js';
|
|
36
|
+
import { isNovaModel, withoutLeadingThinking } from './nova-thinking.js';
|
|
37
|
+
/**
|
|
38
|
+
* What the provider is built with in `api-key` mode, so it sends a bearer token: never sent,
|
|
39
|
+
* since the current key replaces it on every request.
|
|
40
|
+
*/
|
|
41
|
+
const KEY_SET_PER_REQUEST = 'set-per-request';
|
|
42
|
+
/** How long the runtime's AWS identity may take before the call fails as `auth`. */
|
|
43
|
+
export const IDENTITY_TIMEOUT_MS = 10_000;
|
|
44
|
+
/** What's wrong with a registration, for `POST /v1/providers` and `GET …/check`. */
|
|
45
|
+
export function bedrockCheckConfig(input) {
|
|
46
|
+
const read = readBedrockConfig(input);
|
|
47
|
+
return read.kind === 'ok' ? [] : read.problems;
|
|
48
|
+
}
|
|
49
|
+
export const bedrockAdapterFactory = (input) => {
|
|
50
|
+
const { metadata } = input;
|
|
51
|
+
const read = readBedrockConfig({
|
|
52
|
+
metadata,
|
|
53
|
+
...(input.config !== undefined && { config: input.config }),
|
|
54
|
+
hasSecretRef: input.resolveApiKey !== undefined,
|
|
55
|
+
identities: identitiesPresent(input.identities),
|
|
56
|
+
});
|
|
57
|
+
if (read.kind === 'err') {
|
|
58
|
+
throw adapterConfigError(BEDROCK_ADAPTER_ID, metadata.id, read.problems[0]);
|
|
59
|
+
}
|
|
60
|
+
const { region, baseURL, auth } = read.config;
|
|
61
|
+
const identity = input.identities?.aws;
|
|
62
|
+
const resolveApiKey = input.resolveApiKey;
|
|
63
|
+
const provider = createAiSdkModelProvider({
|
|
64
|
+
metadata,
|
|
65
|
+
...(input.fetch !== undefined && { fetch: input.fetch }),
|
|
66
|
+
beforeAttempt: (signal) => auth === 'aws-identity'
|
|
67
|
+
? awsCredentials(identity, signal).then((credentials) => ({
|
|
68
|
+
credentials,
|
|
69
|
+
}))
|
|
70
|
+
: bedrockKey(resolveApiKey).then((key) => ({ key })),
|
|
71
|
+
languageModel: (name, fetch) => {
|
|
72
|
+
const send = withoutRedirects(fetch);
|
|
73
|
+
return createAmazonBedrock({
|
|
74
|
+
region,
|
|
75
|
+
baseURL,
|
|
76
|
+
...(auth === 'aws-identity'
|
|
77
|
+
? {
|
|
78
|
+
apiKey: '',
|
|
79
|
+
// This attempt's, asked for in `beforeAttempt`: the library's own ask never fails.
|
|
80
|
+
credentialProvider: async () => signedIn().credentials,
|
|
81
|
+
fetch: send,
|
|
82
|
+
}
|
|
83
|
+
: { apiKey: KEY_SET_PER_REQUEST, fetch: withBearerKey(send) }),
|
|
84
|
+
})(name);
|
|
85
|
+
},
|
|
86
|
+
cost: (model, usage) => tokenCostUsd(model, usage),
|
|
87
|
+
cacheMark: (model) => (cachesPrompts(model) ? BEDROCK_CACHE_POINT : undefined),
|
|
88
|
+
explain: (error) => (error.status === 403 ? accessHint(auth, region) : undefined),
|
|
89
|
+
});
|
|
90
|
+
// Nova's chain of thought, written into its answer, is taken out (`nova-thinking.ts`).
|
|
91
|
+
return {
|
|
92
|
+
...provider,
|
|
93
|
+
invoke: async (call) => {
|
|
94
|
+
const result = await provider.invoke(call);
|
|
95
|
+
return isNovaModel(call.model) ? withoutLeadingThinking(result) : result;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Bedrock caches only the prompt prefixes a request marks with a cache point (5-minute TTL).
|
|
101
|
+
* Where the marks go: `withCacheMarks` in `@kindgi/adapter-model-shared`.
|
|
102
|
+
*/
|
|
103
|
+
const BEDROCK_CACHE_POINT = { bedrock: { cachePoint: { type: 'default' } } };
|
|
104
|
+
/** Anthropic's Claude and Amazon Nova, by model or inference-profile id (`us.`, `global.`, …). */
|
|
105
|
+
const CACHING_FAMILY = /^(?:[a-z-]+\.)?(?:anthropic\.claude|amazon\.nova)-/;
|
|
106
|
+
/**
|
|
107
|
+
* Whether the adapter marks a model's prompts for Bedrock's cache: the registration prices
|
|
108
|
+
* cache reads (a positive `cachedPromptMultiplier` or `promptCacheReadMultiplier`), and the
|
|
109
|
+
* model is Claude or Nova, the families whose caching was seen live on Bedrock (2026-10-10).
|
|
110
|
+
* Others, OpenAI's models on Bedrock or an application inference profile's ARN among them, stay
|
|
111
|
+
* unmarked until they are.
|
|
112
|
+
*/
|
|
113
|
+
export function cachesPrompts(model) {
|
|
114
|
+
const cost = model.cost;
|
|
115
|
+
const read = cost.cachedPromptMultiplier ?? cost.promptCacheReadMultiplier;
|
|
116
|
+
return typeof read === 'number' && read > 0 && CACHING_FAMILY.test(model.name);
|
|
117
|
+
}
|
|
118
|
+
/** The entry a runtime registers: the factory, and its static check. */
|
|
119
|
+
export const bedrockAdapterEntry = {
|
|
120
|
+
adapterId: BEDROCK_ADAPTER_ID,
|
|
121
|
+
capabilityKind: 'llm-inference',
|
|
122
|
+
factory: bedrockAdapterFactory,
|
|
123
|
+
checkConfig: bedrockCheckConfig,
|
|
124
|
+
};
|
|
125
|
+
/** What a 403 from Bedrock can mean, beyond its own words, for how the adapter signed in. */
|
|
126
|
+
function accessHint(auth, region) {
|
|
127
|
+
const noAccess = `when the account has no access to the model in ${region} (model access, or a Marketplace agreement still being made: retry in 15 minutes)`;
|
|
128
|
+
return auth === 'aws-identity'
|
|
129
|
+
? `Bedrock refuses when the runtime's identity's policy lacks bedrock:InvokeModel on the model (for an inference profile, on the profile and on the foundation model in each of its Regions), ${noAccess}, or when the credentials have expired.`
|
|
130
|
+
: `Bedrock refuses when the IAM user the API key belongs to lacks bedrock:InvokeModel on the model (for an inference profile, on the profile and on the foundation model in each of its Regions), ${noAccess}, or when the key has expired or been revoked.`;
|
|
131
|
+
}
|
|
132
|
+
/** This attempt's sign-in, read by its request. */
|
|
133
|
+
function signedIn() {
|
|
134
|
+
const signed = attemptPrepared();
|
|
135
|
+
if (signed === undefined) {
|
|
136
|
+
throw new ModelProviderError('auth', undefined, 'The request has no sign-in of its own.');
|
|
137
|
+
}
|
|
138
|
+
return signed;
|
|
139
|
+
}
|
|
140
|
+
/** The runtime's AWS credentials for an attempt: bounded, stopped with the call, `auth` when none. */
|
|
141
|
+
async function awsCredentials(identity, signal) {
|
|
142
|
+
let credentials;
|
|
143
|
+
try {
|
|
144
|
+
credentials = await bounded(identity(), IDENTITY_TIMEOUT_MS, signal);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
// (A call stopped meanwhile ends with its own reason: the retries check the signal first.)
|
|
148
|
+
throw new ModelProviderError('auth', undefined, `The runtime's AWS identity gave no credentials: ${messageOf(error)}. KINDGI_AWS_IDENTITY says where they come from.`, { cause: error });
|
|
149
|
+
}
|
|
150
|
+
if (credentials?.accessKeyId === undefined || credentials.accessKeyId === '') {
|
|
151
|
+
throw new ModelProviderError('auth', undefined, "The runtime's AWS identity returned no credentials. KINDGI_AWS_IDENTITY says where they come from.");
|
|
152
|
+
}
|
|
153
|
+
return credentials;
|
|
154
|
+
}
|
|
155
|
+
/** The current key `secret_ref` names, for an attempt; `auth` when there's none. */
|
|
156
|
+
async function bedrockKey(resolveApiKey) {
|
|
157
|
+
let key;
|
|
158
|
+
try {
|
|
159
|
+
key = await resolveApiKey();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
throw new ModelProviderError('auth', undefined, `The Bedrock API key secret_ref names couldn't be read: ${messageOf(error)}.`, { cause: error });
|
|
163
|
+
}
|
|
164
|
+
// Whitespace around a key (a stored file's newline) is never the key's.
|
|
165
|
+
const trimmed = key.trim();
|
|
166
|
+
if (trimmed === '') {
|
|
167
|
+
throw new ModelProviderError('auth', undefined, 'The Bedrock API key secret_ref names is empty.');
|
|
168
|
+
}
|
|
169
|
+
// Sent as `Bearer <key>`, so checked as sent, before the request: fetch's own error would
|
|
170
|
+
// quote it.
|
|
171
|
+
const problem = bearerValueProblem(trimmed);
|
|
172
|
+
if (problem !== undefined) {
|
|
173
|
+
throw new ModelProviderError('auth', undefined, `The Bedrock API key secret_ref names has ${problem}, which a header can't carry: store the secret without it.`);
|
|
174
|
+
}
|
|
175
|
+
return trimmed;
|
|
176
|
+
}
|
|
177
|
+
/** The bearer token on the request, from this attempt's key. */
|
|
178
|
+
function withBearerKey(fetch) {
|
|
179
|
+
return async (url, init) => {
|
|
180
|
+
const headers = new Headers(init?.headers);
|
|
181
|
+
headers.set('authorization', `Bearer ${signedIn().key}`);
|
|
182
|
+
return fetch(url, { ...init, headers });
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** No request follows a redirect, so its credential reaches the endpoint and nothing else. */
|
|
186
|
+
function withoutRedirects(fetch) {
|
|
187
|
+
return (url, init) => fetch(url, { ...init, redirect: 'error' });
|
|
188
|
+
}
|
|
189
|
+
/** The promise, or a failure after `ms`, or the signal's reason when the call stops first. */
|
|
190
|
+
function bounded(promise, ms, signal) {
|
|
191
|
+
// An abort that has already happened fires no event.
|
|
192
|
+
if (signal?.aborted === true)
|
|
193
|
+
return Promise.reject(signal.reason);
|
|
194
|
+
let timer;
|
|
195
|
+
let onAbort;
|
|
196
|
+
const stop = new Promise((_, reject) => {
|
|
197
|
+
timer = setTimeout(() => reject(new Error(`no answer in ${ms / 1000} s`)), ms);
|
|
198
|
+
if (signal !== undefined) {
|
|
199
|
+
onAbort = () => reject(signal.reason);
|
|
200
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return Promise.race([Promise.resolve(promise), stop]).finally(() => {
|
|
204
|
+
clearTimeout(timer);
|
|
205
|
+
if (onAbort !== undefined)
|
|
206
|
+
signal?.removeEventListener('abort', onAbort);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
210
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { BEDROCK_ADAPTER_ID, BEDROCK_AUTHS, type BedrockAuth, type BedrockConfig, bedrockRuntimeEndpoint, isBedrockRuntimeHost, partitionDnsSuffix, readBedrockConfig, } from './config.js';
|
|
2
|
+
export { IDENTITY_TIMEOUT_MS, bedrockAdapterEntry, bedrockAdapterFactory, bedrockCheckConfig, } from './entry.js';
|
|
3
|
+
export { NOVA_THINKING_REMOVED, isNovaModel, withoutLeadingThinking } from './nova-thinking.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
export { BEDROCK_ADAPTER_ID, BEDROCK_AUTHS, bedrockRuntimeEndpoint, isBedrockRuntimeHost, partitionDnsSuffix, readBedrockConfig, } from './config.js';
|
|
4
|
+
export { IDENTITY_TIMEOUT_MS, bedrockAdapterEntry, bedrockAdapterFactory, bedrockCheckConfig, } from './entry.js';
|
|
5
|
+
export { NOVA_THINKING_REMOVED, isNovaModel, withoutLeadingThinking } from './nova-thinking.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EACL,kBAAkB,EAClB,aAAa,EAGb,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amazon Nova writes its chain of thought into the answer text when tools are in play:
|
|
3
|
+
* `<thinking>…</thinking>` before the reply. Other vendors keep reasoning apart, and a
|
|
4
|
+
* `ModelCallResult` has no slot for reasoning text, so for a Nova model one leading block is
|
|
5
|
+
* removed from the answer, with a warning saying so (the model-call record keeps the warning).
|
|
6
|
+
* Only an exact leading pair is touched: a block elsewhere in the text, or one that isn't
|
|
7
|
+
* closed, is left as it came.
|
|
8
|
+
*/
|
|
9
|
+
import type { ModelCallResult } from '@kindgi/capabilities';
|
|
10
|
+
/** The warning code a removed `<thinking>` block leaves on the result. */
|
|
11
|
+
export declare const NOVA_THINKING_REMOVED = "reasoning-text-removed";
|
|
12
|
+
export declare const isNovaModel: (name: string) => boolean;
|
|
13
|
+
/** The result with one leading `<thinking>…</thinking>` block taken out of its answer. */
|
|
14
|
+
export declare function withoutLeadingThinking(result: ModelCallResult): ModelCallResult;
|
|
15
|
+
//# sourceMappingURL=nova-thinking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nova-thinking.d.ts","sourceRoot":"","sources":["../src/nova-thinking.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,0EAA0E;AAC1E,eAAO,MAAM,qBAAqB,2BAA2B,CAAC;AAO9D,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,OAA0B,CAAC;AAEtE,0FAA0F;AAC1F,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAe/E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
/** The warning code a removed `<thinking>` block leaves on the result. */
|
|
4
|
+
export const NOVA_THINKING_REMOVED = 'reasoning-text-removed';
|
|
5
|
+
/** A Nova model id or inference profile (`us.amazon.nova-pro-v1:0`, or an ARN naming one). */
|
|
6
|
+
const NOVA = /(^|[.:/])amazon\.nova-/;
|
|
7
|
+
/** One block at the very start of the text (whitespace aside), closed. */
|
|
8
|
+
const LEADING_THINKING = /^\s*<thinking>[\s\S]*?<\/thinking>\s*/;
|
|
9
|
+
export const isNovaModel = (name) => NOVA.test(name);
|
|
10
|
+
/** The result with one leading `<thinking>…</thinking>` block taken out of its answer. */
|
|
11
|
+
export function withoutLeadingThinking(result) {
|
|
12
|
+
const content = result.message.content;
|
|
13
|
+
const found = LEADING_THINKING.exec(content);
|
|
14
|
+
if (found === null)
|
|
15
|
+
return result;
|
|
16
|
+
return {
|
|
17
|
+
...result,
|
|
18
|
+
message: { ...result.message, content: content.slice(found[0].length) },
|
|
19
|
+
warnings: [
|
|
20
|
+
...(result.warnings ?? []),
|
|
21
|
+
{
|
|
22
|
+
code: NOVA_THINKING_REMOVED,
|
|
23
|
+
message: `${result.provider.model} wrote its reasoning into the answer: the leading <thinking> block (${found[0].trim().length} characters) was removed.`,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=nova-thinking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nova-thinking.js","sourceRoot":"","sources":["../src/nova-thinking.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAajC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,qBAAqB,GAAG,wBAAwB,CAAC;AAE9D,8FAA8F;AAC9F,MAAM,IAAI,GAAG,wBAAwB,CAAC;AACtC,0EAA0E;AAC1E,MAAM,gBAAgB,GAAG,uCAAuC,CAAC;AAEjE,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEtE,0FAA0F;AAC1F,MAAM,UAAU,sBAAsB,CAAC,MAAuB;IAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;IACvC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,OAAO;QACL,GAAG,MAAM;QACT,OAAO,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE;QACvE,QAAQ,EAAE;YACR,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;YAC1B;gBACE,IAAI,EAAE,qBAAqB;gBAC3B,OAAO,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,uEAAuE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,2BAA2B;aAC1J;SACF;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kindgi/adapter-model-bedrock",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.7-rc.0",
|
|
4
|
+
"description": "Amazon Bedrock ModelProvider for @kindgi/capabilities: Claude, Nova, Llama, Mistral and OpenAI's models on Bedrock's Converse API, by the runtime's AWS identity or a Bedrock API key.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"repository": {
|
|
7
|
-
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/kindgi/kindgi-sdk.git",
|
|
9
|
+
"directory": "packages/adapters/model-bedrock"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@ai-sdk/amazon-bedrock": "5.0.111",
|
|
26
|
+
"@kindgi/adapter-model-shared": "0.1.7-rc.0",
|
|
27
|
+
"@kindgi/capabilities": "0.1.7-rc.0",
|
|
28
|
+
"@kindgi/types": "0.1.7-rc.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.10.5",
|
|
32
|
+
"typescript": "^5.7.3",
|
|
33
|
+
"vitest": "^2.1.8"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.12.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"provenance": true
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/kindgi/kindgi-sdk/tree/main/packages/adapters/model-bedrock#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/kindgi/kindgi-sdk/issues"
|
|
45
|
+
},
|
|
46
|
+
"main": "./dist/index.js",
|
|
47
|
+
"types": "./dist/index.d.ts",
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc -p tsconfig.build.json",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An Amazon Bedrock registration, read and checked: the region (`metadata.region`), how to sign
|
|
6
|
+
* in, and the endpoint. Static: no network, no secret read (`checkConfig` runs it at
|
|
7
|
+
* registration, the factory again when it builds).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { AdapterConfigCheckInput, AdapterConfigProblem } from '@kindgi/capabilities';
|
|
11
|
+
|
|
12
|
+
export const BEDROCK_ADAPTER_ID = '@kindgi/adapter-model-bedrock';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The runtime's AWS identity, requests signed with SigV4 (the default), or a Bedrock API key
|
|
16
|
+
* (`secret_ref`, sent as a bearer token), which AWS recommends for exploration only.
|
|
17
|
+
*/
|
|
18
|
+
export const BEDROCK_AUTHS = ['aws-identity', 'api-key'] as const;
|
|
19
|
+
export type BedrockAuth = (typeof BEDROCK_AUTHS)[number];
|
|
20
|
+
|
|
21
|
+
export interface BedrockConfig {
|
|
22
|
+
/** The AWS region the requests go to and are signed for. */
|
|
23
|
+
readonly region: string;
|
|
24
|
+
/** The `bedrock-runtime` endpoint: the region's own, or the registration's `baseURL`. */
|
|
25
|
+
readonly baseURL: string;
|
|
26
|
+
readonly auth: BedrockAuth;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const KEYS = ['auth', 'baseURL'] as const;
|
|
30
|
+
/** An AWS region name: `us-east-2`, `us-gov-west-1`, `eusc-de-east-1`. */
|
|
31
|
+
const REGION = /^[a-z]{2,4}(?:-[a-z]+)+-\d{1,2}$/;
|
|
32
|
+
/**
|
|
33
|
+
* The DNS suffix of each AWS partition other than `aws` (`amazonaws.com`), by region prefix:
|
|
34
|
+
* the same table `@ai-sdk/amazon-bedrock` builds its default endpoint from. The runtime's AWS
|
|
35
|
+
* identity keeps a copy for its STS endpoint, until both read one shared module.
|
|
36
|
+
*/
|
|
37
|
+
const PARTITION_DNS_SUFFIXES: readonly (readonly [prefix: string, suffix: string])[] = [
|
|
38
|
+
['cn-', 'amazonaws.com.cn'],
|
|
39
|
+
['us-iso-', 'c2s.ic.gov'],
|
|
40
|
+
['us-isob-', 'sc2s.sgov.gov'],
|
|
41
|
+
['eu-isoe-', 'cloud.adc-e.uk'],
|
|
42
|
+
['us-isof-', 'csp.hci.ic.gov'],
|
|
43
|
+
['eusc-', 'amazonaws.eu'],
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const at = (key: string) => `/adapter_config/${key}`;
|
|
47
|
+
|
|
48
|
+
/** The DNS suffix of a region's partition: `amazonaws.com`, `amazonaws.com.cn`, … */
|
|
49
|
+
export function partitionDnsSuffix(region: string): string {
|
|
50
|
+
return (
|
|
51
|
+
PARTITION_DNS_SUFFIXES.find(([prefix]) => region.startsWith(prefix))?.[1] ?? 'amazonaws.com'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The region's own `bedrock-runtime` endpoint. */
|
|
56
|
+
export function bedrockRuntimeEndpoint(region: string): string {
|
|
57
|
+
return `https://bedrock-runtime.${region}.${partitionDnsSuffix(region)}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether a host is Bedrock's runtime in this region: its own endpoint, its FIPS endpoint, or
|
|
62
|
+
* an interface VPC endpoint's own name. Only these get requests signed with the runtime's AWS
|
|
63
|
+
* credentials: a registration naming another host would receive the session token and a
|
|
64
|
+
* signature. Sources (read 2026-10-09): https://docs.aws.amazon.com/general/latest/gr/bedrock.html
|
|
65
|
+
* (the endpoints and FIPS endpoints) and
|
|
66
|
+
* https://docs.aws.amazon.com/bedrock/latest/userguide/vpc-interface-endpoints.html
|
|
67
|
+
* (`vpce-id.bedrock-runtime.region.vpce.amazonaws.com`).
|
|
68
|
+
*/
|
|
69
|
+
export function isBedrockRuntimeHost(hostname: string, region: string): boolean {
|
|
70
|
+
const host = hostname.toLowerCase();
|
|
71
|
+
const tail = `${region}.${partitionDnsSuffix(region)}`;
|
|
72
|
+
if (host === `bedrock-runtime.${tail}` || host === `bedrock-runtime-fips.${tail}`) return true;
|
|
73
|
+
const vpce = `.bedrock-runtime.${region}.vpce.${partitionDnsSuffix(region)}`;
|
|
74
|
+
return host.endsWith(vpce) && /^vpce-[a-z0-9-]+$/.test(host.slice(0, -vpce.length));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The registration read, or every problem with it. */
|
|
78
|
+
export function readBedrockConfig(
|
|
79
|
+
input: AdapterConfigCheckInput,
|
|
80
|
+
):
|
|
81
|
+
| { readonly kind: 'ok'; readonly config: BedrockConfig }
|
|
82
|
+
| { readonly kind: 'err'; readonly problems: readonly AdapterConfigProblem[] } {
|
|
83
|
+
const config = input.config ?? {};
|
|
84
|
+
const problems: AdapterConfigProblem[] = [];
|
|
85
|
+
|
|
86
|
+
for (const key of Object.keys(config)) {
|
|
87
|
+
if (!(KEYS as readonly string[]).includes(key)) {
|
|
88
|
+
problems.push({
|
|
89
|
+
path: at(key),
|
|
90
|
+
message: `adapter_config.${key} isn't an Amazon Bedrock setting: it takes auth and baseURL (the region is metadata.region).`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const region = input.metadata.region;
|
|
96
|
+
const regionOk = typeof region === 'string' && REGION.test(region);
|
|
97
|
+
if (!regionOk) {
|
|
98
|
+
problems.push({
|
|
99
|
+
path: '/metadata/region',
|
|
100
|
+
message: `metadata.region must be the AWS region Bedrock runs in (e.g. us-east-2): the requests go to it and are signed for it. Got "${String(region)}".`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const auth = config.auth ?? 'aws-identity';
|
|
105
|
+
const baseURL = config.baseURL;
|
|
106
|
+
if (baseURL !== undefined) {
|
|
107
|
+
readBaseURL(baseURL, auth, regionOk ? (region as string) : undefined, problems);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!(BEDROCK_AUTHS as readonly unknown[]).includes(auth)) {
|
|
111
|
+
problems.push({
|
|
112
|
+
path: at('auth'),
|
|
113
|
+
message: `adapter_config.auth must be one of ${BEDROCK_AUTHS.join(', ')}.`,
|
|
114
|
+
});
|
|
115
|
+
} else if (auth === 'api-key' && !input.hasSecretRef) {
|
|
116
|
+
problems.push({
|
|
117
|
+
path: '/secret_ref',
|
|
118
|
+
message:
|
|
119
|
+
"adapter_config.auth = api-key needs secret_ref: the Bedrock API key (or leave auth unset, to sign in as the runtime's AWS identity).",
|
|
120
|
+
});
|
|
121
|
+
} else if (auth === 'aws-identity' && input.hasSecretRef) {
|
|
122
|
+
problems.push({
|
|
123
|
+
path: '/secret_ref',
|
|
124
|
+
message:
|
|
125
|
+
"adapter_config.auth = aws-identity (the default) signs in as the runtime's AWS identity: remove secret_ref, or set auth = api-key for a Bedrock API key.",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (auth === 'aws-identity' && input.identities?.aws === false) {
|
|
129
|
+
problems.push({
|
|
130
|
+
path: at('auth'),
|
|
131
|
+
message:
|
|
132
|
+
"adapter_config.auth = aws-identity (the default) needs the runtime's AWS identity (KINDGI_AWS_IDENTITY says where it comes from), and this runtime has none. Or set auth = api-key for a Bedrock API key.",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (problems.length > 0) return { kind: 'err', problems };
|
|
137
|
+
return {
|
|
138
|
+
kind: 'ok',
|
|
139
|
+
config: {
|
|
140
|
+
region,
|
|
141
|
+
baseURL: (baseURL as string | undefined) ?? bedrockRuntimeEndpoint(region),
|
|
142
|
+
auth: auth as BedrockAuth,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* A custom endpoint: https, with no credentials, query or fragment (they'd sit in the stored
|
|
149
|
+
* settings); Bedrock's own host at its root; with `auth: aws-identity`, Bedrock's runtime in
|
|
150
|
+
* the region only. A gateway (with `auth: api-key`) is any host and path.
|
|
151
|
+
*/
|
|
152
|
+
function readBaseURL(
|
|
153
|
+
value: unknown,
|
|
154
|
+
auth: unknown,
|
|
155
|
+
region: string | undefined,
|
|
156
|
+
problems: AdapterConfigProblem[],
|
|
157
|
+
): void {
|
|
158
|
+
const path = at('baseURL');
|
|
159
|
+
const url = typeof value === 'string' ? parseUrl(value) : undefined;
|
|
160
|
+
if (url === undefined || url.protocol !== 'https:') {
|
|
161
|
+
problems.push({
|
|
162
|
+
path,
|
|
163
|
+
message:
|
|
164
|
+
'adapter_config.baseURL must be an https URL: a bedrock-runtime endpoint of your own, such as a VPC endpoint or a FIPS endpoint.',
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const raw = value as string;
|
|
169
|
+
if (url.username !== '' || url.password !== '') {
|
|
170
|
+
problems.push({
|
|
171
|
+
path,
|
|
172
|
+
message:
|
|
173
|
+
"adapter_config.baseURL can't hold credentials: a key goes in secret_ref, never in the URL.",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (url.search !== '' || raw.includes('?') || url.hash !== '' || raw.includes('#')) {
|
|
177
|
+
problems.push({ path, message: "adapter_config.baseURL can't hold a query or a fragment." });
|
|
178
|
+
}
|
|
179
|
+
const bedrockHost = region !== undefined && isBedrockRuntimeHost(url.hostname, region);
|
|
180
|
+
if (bedrockHost && url.pathname !== '/' && url.pathname !== '') {
|
|
181
|
+
problems.push({
|
|
182
|
+
path,
|
|
183
|
+
message: `adapter_config.baseURL is an endpoint's root (https://${url.host}), with no path: the adapter adds the API's.`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (auth === 'aws-identity' && region !== undefined && !bedrockHost) {
|
|
187
|
+
const suffix = partitionDnsSuffix(region);
|
|
188
|
+
problems.push({
|
|
189
|
+
path,
|
|
190
|
+
message: `adapter_config.auth = aws-identity signs requests with the runtime's AWS credentials, which go only to Bedrock's runtime in ${region} (bedrock-runtime.${region}.${suffix}, bedrock-runtime-fips.${region}.${suffix}, or a VPC endpoint's vpce-….bedrock-runtime.${region}.vpce.${suffix}); ${url.hostname} isn't one. A VPC endpoint with private DNS needs no baseURL; another endpoint takes auth = api-key.`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function parseUrl(value: string): URL | undefined {
|
|
196
|
+
try {
|
|
197
|
+
return new URL(value);
|
|
198
|
+
} catch {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|