@openclaw/amazon-bedrock-provider 2026.5.12-beta.7
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/dist/api.js +3 -0
- package/dist/aws-credential-refresh.js +25 -0
- package/dist/config-compat.js +94 -0
- package/dist/discovery-shared.js +16 -0
- package/dist/discovery.js +379 -0
- package/dist/embedding-provider.js +270 -0
- package/dist/index.js +13 -0
- package/dist/memory-embedding-adapter.js +34 -0
- package/dist/provider-policy-api.js +9 -0
- package/dist/register.sync.runtime.js +357 -0
- package/dist/setup-api.js +20 -0
- package/dist/thinking-policy.js +30 -0
- package/openclaw.plugin.json +80 -0
- package/package.json +55 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { mergeImplicitBedrockProvider, resolveBedrockConfigApiKey } from "./discovery-shared.js";
|
|
2
|
+
import { discoverBedrockModels, resetBedrockDiscoveryCacheForTest, resolveImplicitBedrockProvider } from "./discovery.js";
|
|
3
|
+
export { discoverBedrockModels, mergeImplicitBedrockProvider, resetBedrockDiscoveryCacheForTest, resolveBedrockConfigApiKey, resolveImplicitBedrockProvider };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region extensions/amazon-bedrock/aws-credential-refresh.ts
|
|
2
|
+
let sharedIniFileLoaderForTest;
|
|
3
|
+
function hasStaticAwsCredentialEnv(env) {
|
|
4
|
+
return Boolean(env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY);
|
|
5
|
+
}
|
|
6
|
+
function shouldRefreshAwsSharedConfigCacheForBedrock(env) {
|
|
7
|
+
if (env.AWS_BEDROCK_SKIP_AUTH === "1" || env.AWS_BEARER_TOKEN_BEDROCK) return false;
|
|
8
|
+
return !hasStaticAwsCredentialEnv(env);
|
|
9
|
+
}
|
|
10
|
+
async function loadSharedIniFileLoader() {
|
|
11
|
+
if (sharedIniFileLoaderForTest !== void 0) {
|
|
12
|
+
if (!sharedIniFileLoaderForTest) throw new Error("AWS shared INI file loader unavailable");
|
|
13
|
+
return sharedIniFileLoaderForTest;
|
|
14
|
+
}
|
|
15
|
+
return await import("@smithy/shared-ini-file-loader");
|
|
16
|
+
}
|
|
17
|
+
async function refreshAwsSharedConfigCacheForBedrock(env = process.env) {
|
|
18
|
+
if (!shouldRefreshAwsSharedConfigCacheForBedrock(env)) return;
|
|
19
|
+
await (await loadSharedIniFileLoader()).loadSharedConfigFiles({ ignoreCache: true });
|
|
20
|
+
}
|
|
21
|
+
function setAwsSharedIniFileLoaderForTest(loader) {
|
|
22
|
+
sharedIniFileLoaderForTest = loader;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { refreshAwsSharedConfigCacheForBedrock, setAwsSharedIniFileLoaderForTest, shouldRefreshAwsSharedConfigCacheForBedrock };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
2
|
+
//#region extensions/amazon-bedrock/config-compat.ts
|
|
3
|
+
const LEGACY_PATH = "models.bedrockDiscovery";
|
|
4
|
+
const TARGET_PATH = "plugins.entries.amazon-bedrock.config.discovery";
|
|
5
|
+
const BLOCKED_OBJECT_KEYS = new Set([
|
|
6
|
+
"__proto__",
|
|
7
|
+
"prototype",
|
|
8
|
+
"constructor"
|
|
9
|
+
]);
|
|
10
|
+
function isBlockedObjectKey(key) {
|
|
11
|
+
return BLOCKED_OBJECT_KEYS.has(key);
|
|
12
|
+
}
|
|
13
|
+
function getRecord(value) {
|
|
14
|
+
return isRecord(value) ? value : null;
|
|
15
|
+
}
|
|
16
|
+
function ensureRecord(root, key) {
|
|
17
|
+
const existing = root[key];
|
|
18
|
+
if (isRecord(existing)) return existing;
|
|
19
|
+
const next = {};
|
|
20
|
+
root[key] = next;
|
|
21
|
+
return next;
|
|
22
|
+
}
|
|
23
|
+
function mergeMissing(target, source) {
|
|
24
|
+
for (const [key, value] of Object.entries(source)) {
|
|
25
|
+
if (value === void 0 || isBlockedObjectKey(key)) continue;
|
|
26
|
+
const existing = target[key];
|
|
27
|
+
if (existing === void 0) {
|
|
28
|
+
target[key] = value;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (isRecord(existing) && isRecord(value)) mergeMissing(existing, value);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function cloneRecord(value) {
|
|
35
|
+
return { ...value };
|
|
36
|
+
}
|
|
37
|
+
function resolveLegacyBedrockDiscoveryConfig(raw) {
|
|
38
|
+
if (!isRecord(raw)) return;
|
|
39
|
+
return getRecord(getRecord(raw.models)?.bedrockDiscovery) ?? void 0;
|
|
40
|
+
}
|
|
41
|
+
function pruneEmptyModelsRoot(root) {
|
|
42
|
+
const models = getRecord(root.models);
|
|
43
|
+
if (models && Object.keys(models).length === 0) delete root.models;
|
|
44
|
+
}
|
|
45
|
+
function migrateAmazonBedrockLegacyConfig(raw) {
|
|
46
|
+
if (!isRecord(raw)) return {
|
|
47
|
+
config: raw,
|
|
48
|
+
changes: []
|
|
49
|
+
};
|
|
50
|
+
const legacy = resolveLegacyBedrockDiscoveryConfig(raw);
|
|
51
|
+
if (!legacy) return {
|
|
52
|
+
config: raw,
|
|
53
|
+
changes: []
|
|
54
|
+
};
|
|
55
|
+
const nextRoot = structuredClone(raw);
|
|
56
|
+
const models = ensureRecord(nextRoot, "models");
|
|
57
|
+
delete models.bedrockDiscovery;
|
|
58
|
+
pruneEmptyModelsRoot(nextRoot);
|
|
59
|
+
const changes = [];
|
|
60
|
+
if (Object.keys(legacy).length === 0) {
|
|
61
|
+
changes.push(`Removed empty ${LEGACY_PATH}.`);
|
|
62
|
+
return {
|
|
63
|
+
config: nextRoot,
|
|
64
|
+
changes
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const config = ensureRecord(ensureRecord(ensureRecord(ensureRecord(nextRoot, "plugins"), "entries"), "amazon-bedrock"), "config");
|
|
68
|
+
const existing = getRecord(config.discovery) ?? void 0;
|
|
69
|
+
if (!existing) {
|
|
70
|
+
config.discovery = cloneRecord(legacy);
|
|
71
|
+
changes.push(`Moved ${LEGACY_PATH} → ${TARGET_PATH}.`);
|
|
72
|
+
return {
|
|
73
|
+
config: nextRoot,
|
|
74
|
+
changes
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const merged = cloneRecord(existing);
|
|
78
|
+
mergeMissing(merged, legacy);
|
|
79
|
+
config.discovery = merged;
|
|
80
|
+
if (JSON.stringify(merged) !== JSON.stringify(existing)) {
|
|
81
|
+
changes.push(`Merged ${LEGACY_PATH} → ${TARGET_PATH} (filled missing fields from legacy; kept explicit plugin config values).`);
|
|
82
|
+
return {
|
|
83
|
+
config: nextRoot,
|
|
84
|
+
changes
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
changes.push(`Removed ${LEGACY_PATH} (${TARGET_PATH} already set).`);
|
|
88
|
+
return {
|
|
89
|
+
config: nextRoot,
|
|
90
|
+
changes
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { migrateAmazonBedrockLegacyConfig };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { resolveAwsSdkEnvVarName } from "openclaw/plugin-sdk/provider-auth-runtime";
|
|
2
|
+
//#region extensions/amazon-bedrock/discovery-shared.ts
|
|
3
|
+
function resolveBedrockConfigApiKey(env = process.env) {
|
|
4
|
+
return resolveAwsSdkEnvVarName(env);
|
|
5
|
+
}
|
|
6
|
+
function mergeImplicitBedrockProvider(params) {
|
|
7
|
+
const { existing, implicit } = params;
|
|
8
|
+
if (!existing) return implicit;
|
|
9
|
+
return {
|
|
10
|
+
...implicit,
|
|
11
|
+
...existing,
|
|
12
|
+
models: Array.isArray(existing.models) && existing.models.length > 0 ? existing.models : implicit.models
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { mergeImplicitBedrockProvider, resolveBedrockConfigApiKey };
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { refreshAwsSharedConfigCacheForBedrock } from "./aws-credential-refresh.js";
|
|
2
|
+
import { resolveBedrockConfigApiKey } from "./discovery-shared.js";
|
|
3
|
+
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
|
+
import { createSubsystemLogger } from "openclaw/plugin-sdk/core";
|
|
5
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
6
|
+
//#region extensions/amazon-bedrock/discovery.ts
|
|
7
|
+
const log = createSubsystemLogger("bedrock-discovery");
|
|
8
|
+
const DEFAULT_REFRESH_INTERVAL_SECONDS = 3600;
|
|
9
|
+
const DEFAULT_CONTEXT_WINDOW = 32e3;
|
|
10
|
+
const DEFAULT_MAX_TOKENS = 4096;
|
|
11
|
+
/**
|
|
12
|
+
* Bedrock's ListFoundationModels and GetFoundationModel APIs return no token
|
|
13
|
+
* limit information — only model ID, name, modalities, and lifecycle status.
|
|
14
|
+
* There is currently no Bedrock API to discover context windows or max output
|
|
15
|
+
* tokens programmatically.
|
|
16
|
+
*
|
|
17
|
+
* This map provides correct context window values for known models so that
|
|
18
|
+
* session management, compaction thresholds, and context overflow detection
|
|
19
|
+
* work correctly. If AWS adds token metadata to the API in the future, this
|
|
20
|
+
* table should become a fallback rather than the primary source.
|
|
21
|
+
*
|
|
22
|
+
* Inference profile prefixes (us., eu., ap., global.) are stripped before lookup.
|
|
23
|
+
*
|
|
24
|
+
* Sources: https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html
|
|
25
|
+
* https://platform.claude.com/docs/en/about-claude/models
|
|
26
|
+
*/
|
|
27
|
+
const KNOWN_CONTEXT_WINDOWS = {
|
|
28
|
+
"anthropic.claude-3-7-sonnet-20250219-v1:0": 2e5,
|
|
29
|
+
"anthropic.claude-opus-4-7": 1e6,
|
|
30
|
+
"anthropic.claude-opus-4-6-v1": 1e6,
|
|
31
|
+
"anthropic.claude-opus-4-6-v1:0": 1e6,
|
|
32
|
+
"anthropic.claude-sonnet-4-6": 1e6,
|
|
33
|
+
"anthropic.claude-sonnet-4-6-v1:0": 1e6,
|
|
34
|
+
"anthropic.claude-sonnet-4-5-20250929-v1:0": 2e5,
|
|
35
|
+
"anthropic.claude-sonnet-4-20250514-v1:0": 2e5,
|
|
36
|
+
"anthropic.claude-opus-4-5-20251101-v1:0": 2e5,
|
|
37
|
+
"anthropic.claude-opus-4-1-20250805-v1:0": 2e5,
|
|
38
|
+
"anthropic.claude-haiku-4-5-20251001-v1:0": 2e5,
|
|
39
|
+
"anthropic.claude-3-5-haiku-20241022-v1:0": 2e5,
|
|
40
|
+
"anthropic.claude-3-haiku-20240307-v1:0": 2e5,
|
|
41
|
+
"amazon.nova-premier-v1:0": 1e6,
|
|
42
|
+
"amazon.nova-pro-v1:0": 3e5,
|
|
43
|
+
"amazon.nova-lite-v1:0": 3e5,
|
|
44
|
+
"amazon.nova-micro-v1:0": 128e3,
|
|
45
|
+
"amazon.nova-2-lite-v1:0": 3e5,
|
|
46
|
+
"minimax.minimax-m2.5": 1e6,
|
|
47
|
+
"minimax.minimax-m2.1": 1e6,
|
|
48
|
+
"minimax.minimax-m2": 1e6,
|
|
49
|
+
"meta.llama4-maverick-17b-instruct-v1:0": 1e6,
|
|
50
|
+
"meta.llama4-scout-17b-instruct-v1:0": 512e3,
|
|
51
|
+
"meta.llama3-3-70b-instruct-v1:0": 128e3,
|
|
52
|
+
"meta.llama3-2-90b-instruct-v1:0": 128e3,
|
|
53
|
+
"meta.llama3-2-11b-instruct-v1:0": 128e3,
|
|
54
|
+
"meta.llama3-2-3b-instruct-v1:0": 128e3,
|
|
55
|
+
"meta.llama3-2-1b-instruct-v1:0": 128e3,
|
|
56
|
+
"meta.llama3-1-405b-instruct-v1:0": 128e3,
|
|
57
|
+
"meta.llama3-1-70b-instruct-v1:0": 128e3,
|
|
58
|
+
"meta.llama3-1-8b-instruct-v1:0": 128e3,
|
|
59
|
+
"nvidia.nemotron-super-3-120b": 256e3,
|
|
60
|
+
"nvidia.nemotron-nano-3-30b": 128e3,
|
|
61
|
+
"nvidia.nemotron-nano-12b-v2": 128e3,
|
|
62
|
+
"nvidia.nemotron-nano-9b-v2": 128e3,
|
|
63
|
+
"mistral.mistral-large-3-675b-instruct": 128e3,
|
|
64
|
+
"mistral.mistral-large-2407-v1:0": 128e3,
|
|
65
|
+
"mistral.mistral-small-2402-v1:0": 32e3,
|
|
66
|
+
"deepseek.r1-v1:0": 128e3,
|
|
67
|
+
"deepseek.v3.2": 128e3,
|
|
68
|
+
"cohere.command-r-plus-v1:0": 128e3,
|
|
69
|
+
"cohere.command-r-v1:0": 128e3,
|
|
70
|
+
"ai21.jamba-1-5-large-v1:0": 256e3,
|
|
71
|
+
"ai21.jamba-1-5-mini-v1:0": 256e3,
|
|
72
|
+
"google.gemma-3-27b-it": 128e3,
|
|
73
|
+
"google.gemma-3-12b-it": 128e3,
|
|
74
|
+
"google.gemma-3-4b-it": 128e3,
|
|
75
|
+
"zai.glm-5": 128e3,
|
|
76
|
+
"zai.glm-4.7": 128e3,
|
|
77
|
+
"zai.glm-4.7-flash": 128e3,
|
|
78
|
+
"qwen.qwen3-coder-next": 256e3,
|
|
79
|
+
"qwen.qwen3-coder-30b-a3b-v1:0": 256e3,
|
|
80
|
+
"qwen.qwen3-32b-v1:0": 128e3,
|
|
81
|
+
"qwen.qwen3-vl-235b-a22b": 128e3
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the real context window for a Bedrock model ID.
|
|
85
|
+
* Strips inference profile prefixes (us., eu., ap., global.) before lookup.
|
|
86
|
+
*/
|
|
87
|
+
function resolveKnownContextWindow(modelId) {
|
|
88
|
+
const candidates = [modelId, modelId.replace(/^(?:us|eu|ap|apac|au|jp|global)\./, "")];
|
|
89
|
+
for (const candidate of candidates) {
|
|
90
|
+
if (KNOWN_CONTEXT_WINDOWS[candidate] !== void 0) return KNOWN_CONTEXT_WINDOWS[candidate];
|
|
91
|
+
const withoutVersionSuffix = candidate.replace(/:0$/, "");
|
|
92
|
+
if (withoutVersionSuffix !== candidate && KNOWN_CONTEXT_WINDOWS[withoutVersionSuffix] !== void 0) return KNOWN_CONTEXT_WINDOWS[withoutVersionSuffix];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const DEFAULT_COST = {
|
|
96
|
+
input: 0,
|
|
97
|
+
output: 0,
|
|
98
|
+
cacheRead: 0,
|
|
99
|
+
cacheWrite: 0
|
|
100
|
+
};
|
|
101
|
+
async function loadBedrockDiscoverySdk() {
|
|
102
|
+
const { BedrockClient, ListFoundationModelsCommand, ListInferenceProfilesCommand } = await import("@aws-sdk/client-bedrock");
|
|
103
|
+
return {
|
|
104
|
+
createClient: (region) => new BedrockClient({ region }),
|
|
105
|
+
createListFoundationModelsCommand: () => new ListFoundationModelsCommand({}),
|
|
106
|
+
createListInferenceProfilesCommand: (input) => new ListInferenceProfilesCommand(input)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function createInjectedClientDiscoverySdk() {
|
|
110
|
+
class ListFoundationModelsCommand {
|
|
111
|
+
constructor(input = {}) {
|
|
112
|
+
this.input = input;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
class ListInferenceProfilesCommand {
|
|
116
|
+
constructor(input = {}) {
|
|
117
|
+
this.input = input;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
createClient() {
|
|
122
|
+
throw new Error("clientFactory is required for injected Bedrock discovery commands");
|
|
123
|
+
},
|
|
124
|
+
createListFoundationModelsCommand: () => new ListFoundationModelsCommand({}),
|
|
125
|
+
createListInferenceProfilesCommand: (input) => new ListInferenceProfilesCommand(input)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const discoveryCache = /* @__PURE__ */ new Map();
|
|
129
|
+
let hasLoggedBedrockError = false;
|
|
130
|
+
function normalizeProviderFilter(filter) {
|
|
131
|
+
if (!filter || filter.length === 0) return [];
|
|
132
|
+
const normalized = new Set(filter.map((entry) => normalizeOptionalLowercaseString(entry)).filter((entry) => Boolean(entry)));
|
|
133
|
+
return Array.from(normalized).toSorted();
|
|
134
|
+
}
|
|
135
|
+
function buildCacheKey(params) {
|
|
136
|
+
return JSON.stringify(params);
|
|
137
|
+
}
|
|
138
|
+
function includesTextModalities(modalities) {
|
|
139
|
+
return (modalities ?? []).some((entry) => normalizeOptionalLowercaseString(entry) === "text");
|
|
140
|
+
}
|
|
141
|
+
function isActive(summary) {
|
|
142
|
+
const status = summary.modelLifecycle?.status;
|
|
143
|
+
return typeof status === "string" ? status.toUpperCase() === "ACTIVE" : false;
|
|
144
|
+
}
|
|
145
|
+
function mapInputModalities(summary) {
|
|
146
|
+
const inputs = summary.inputModalities ?? [];
|
|
147
|
+
const mapped = /* @__PURE__ */ new Set();
|
|
148
|
+
for (const modality of inputs) {
|
|
149
|
+
const lower = normalizeOptionalLowercaseString(modality);
|
|
150
|
+
if (lower === "text") mapped.add("text");
|
|
151
|
+
if (lower === "image") mapped.add("image");
|
|
152
|
+
}
|
|
153
|
+
if (mapped.size === 0) mapped.add("text");
|
|
154
|
+
return Array.from(mapped);
|
|
155
|
+
}
|
|
156
|
+
function inferReasoningSupport(summary) {
|
|
157
|
+
const haystack = normalizeLowercaseStringOrEmpty(`${summary.modelId ?? ""} ${summary.modelName ?? ""}`);
|
|
158
|
+
return haystack.includes("reasoning") || haystack.includes("thinking");
|
|
159
|
+
}
|
|
160
|
+
function resolveDefaultContextWindow(config) {
|
|
161
|
+
const value = Math.floor(config?.defaultContextWindow ?? DEFAULT_CONTEXT_WINDOW);
|
|
162
|
+
return value > 0 ? value : DEFAULT_CONTEXT_WINDOW;
|
|
163
|
+
}
|
|
164
|
+
function resolveDefaultMaxTokens(config) {
|
|
165
|
+
const value = Math.floor(config?.defaultMaxTokens ?? DEFAULT_MAX_TOKENS);
|
|
166
|
+
return value > 0 ? value : DEFAULT_MAX_TOKENS;
|
|
167
|
+
}
|
|
168
|
+
function matchesProviderFilter(summary, filter) {
|
|
169
|
+
if (filter.length === 0) return true;
|
|
170
|
+
const normalized = normalizeOptionalLowercaseString(summary.providerName ?? (typeof summary.modelId === "string" ? summary.modelId.split(".")[0] : void 0));
|
|
171
|
+
if (!normalized) return false;
|
|
172
|
+
return filter.includes(normalized);
|
|
173
|
+
}
|
|
174
|
+
function shouldIncludeSummary(summary, filter) {
|
|
175
|
+
if (!summary.modelId?.trim()) return false;
|
|
176
|
+
if (!matchesProviderFilter(summary, filter)) return false;
|
|
177
|
+
if (summary.responseStreamingSupported !== true) return false;
|
|
178
|
+
if (!includesTextModalities(summary.outputModalities)) return false;
|
|
179
|
+
if (!isActive(summary)) return false;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
function toModelDefinition(summary, defaults) {
|
|
183
|
+
const id = summary.modelId?.trim() ?? "";
|
|
184
|
+
return {
|
|
185
|
+
id,
|
|
186
|
+
name: summary.modelName?.trim() || id,
|
|
187
|
+
reasoning: inferReasoningSupport(summary),
|
|
188
|
+
input: mapInputModalities(summary),
|
|
189
|
+
cost: DEFAULT_COST,
|
|
190
|
+
contextWindow: resolveKnownContextWindow(id) ?? defaults.contextWindow,
|
|
191
|
+
maxTokens: defaults.maxTokens
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Resolve the base foundation model ID from an inference profile.
|
|
196
|
+
*
|
|
197
|
+
* System-defined profiles use a region prefix:
|
|
198
|
+
* "us.anthropic.claude-sonnet-4-6" → "anthropic.claude-sonnet-4-6"
|
|
199
|
+
*
|
|
200
|
+
* Application profiles carry the model ARN in their models[] array:
|
|
201
|
+
* models[0].modelArn = "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-6"
|
|
202
|
+
* → "anthropic.claude-sonnet-4-6"
|
|
203
|
+
*/
|
|
204
|
+
function resolveBaseModelId(profile) {
|
|
205
|
+
const firstArn = profile.models?.[0]?.modelArn;
|
|
206
|
+
if (firstArn) {
|
|
207
|
+
const arnMatch = /foundation-model\/(.+)$/.exec(firstArn);
|
|
208
|
+
if (arnMatch) return arnMatch[1];
|
|
209
|
+
}
|
|
210
|
+
if (profile.type === "SYSTEM_DEFINED") {
|
|
211
|
+
const id = profile.inferenceProfileId ?? "";
|
|
212
|
+
const prefixMatch = /^(?:us|eu|ap|apac|au|jp|global)\.(.+)$/i.exec(id);
|
|
213
|
+
if (prefixMatch) return prefixMatch[1];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Fetch raw inference profile summaries from the Bedrock control plane.
|
|
218
|
+
* Handles pagination. Best-effort: silently returns empty array if IAM lacks
|
|
219
|
+
* bedrock:ListInferenceProfiles permission.
|
|
220
|
+
*/
|
|
221
|
+
async function fetchInferenceProfileSummaries(client, createListInferenceProfilesCommand) {
|
|
222
|
+
try {
|
|
223
|
+
const profiles = [];
|
|
224
|
+
let nextToken;
|
|
225
|
+
do {
|
|
226
|
+
const response = await client.send(createListInferenceProfilesCommand({ nextToken }));
|
|
227
|
+
for (const summary of response.inferenceProfileSummaries ?? []) profiles.push(summary);
|
|
228
|
+
nextToken = response.nextToken;
|
|
229
|
+
} while (nextToken);
|
|
230
|
+
return profiles;
|
|
231
|
+
} catch (error) {
|
|
232
|
+
log.debug?.("Skipping inference profile discovery", { error: formatErrorMessage(error) });
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Convert raw inference profile summaries into model definitions.
|
|
238
|
+
*
|
|
239
|
+
* Each profile inherits capabilities (modalities, reasoning, context window,
|
|
240
|
+
* cost) from its underlying foundation model. This ensures that
|
|
241
|
+
* "us.anthropic.claude-sonnet-4-6" has the same capabilities as
|
|
242
|
+
* "anthropic.claude-sonnet-4-6" — including image input, reasoning support,
|
|
243
|
+
* and token limits.
|
|
244
|
+
*
|
|
245
|
+
* When the foundation model isn't found in the map (e.g. the model is only
|
|
246
|
+
* available via inference profiles in this region), safe defaults are used.
|
|
247
|
+
*/
|
|
248
|
+
function resolveInferenceProfiles(profiles, defaults, providerFilter, foundationModels) {
|
|
249
|
+
const discovered = [];
|
|
250
|
+
for (const profile of profiles) {
|
|
251
|
+
if (!profile.inferenceProfileId?.trim()) continue;
|
|
252
|
+
if (profile.status !== "ACTIVE") continue;
|
|
253
|
+
if (providerFilter.length > 0) {
|
|
254
|
+
if (!(profile.models ?? []).some((m) => {
|
|
255
|
+
const provider = m.modelArn?.split("/")?.[1]?.split(".")?.[0];
|
|
256
|
+
return provider ? providerFilter.includes(normalizeOptionalLowercaseString(provider) ?? "") : false;
|
|
257
|
+
})) continue;
|
|
258
|
+
}
|
|
259
|
+
const baseModelId = resolveBaseModelId(profile);
|
|
260
|
+
const baseModel = baseModelId ? foundationModels.get(normalizeLowercaseStringOrEmpty(baseModelId)) : void 0;
|
|
261
|
+
discovered.push({
|
|
262
|
+
id: profile.inferenceProfileId,
|
|
263
|
+
name: profile.inferenceProfileName?.trim() || profile.inferenceProfileId,
|
|
264
|
+
reasoning: baseModel?.reasoning ?? false,
|
|
265
|
+
input: baseModel?.input ?? ["text"],
|
|
266
|
+
cost: baseModel?.cost ?? DEFAULT_COST,
|
|
267
|
+
contextWindow: baseModel?.contextWindow ?? resolveKnownContextWindow(baseModelId ?? profile.inferenceProfileId ?? "") ?? defaults.contextWindow,
|
|
268
|
+
maxTokens: baseModel?.maxTokens ?? defaults.maxTokens
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return discovered;
|
|
272
|
+
}
|
|
273
|
+
function resetBedrockDiscoveryCacheForTest() {
|
|
274
|
+
discoveryCache.clear();
|
|
275
|
+
hasLoggedBedrockError = false;
|
|
276
|
+
}
|
|
277
|
+
async function discoverBedrockModels(params) {
|
|
278
|
+
const refreshIntervalSeconds = Math.max(0, Math.floor(params.config?.refreshInterval ?? DEFAULT_REFRESH_INTERVAL_SECONDS));
|
|
279
|
+
const providerFilter = normalizeProviderFilter(params.config?.providerFilter);
|
|
280
|
+
const defaultContextWindow = resolveDefaultContextWindow(params.config);
|
|
281
|
+
const defaultMaxTokens = resolveDefaultMaxTokens(params.config);
|
|
282
|
+
const cacheKey = buildCacheKey({
|
|
283
|
+
region: params.region,
|
|
284
|
+
providerFilter,
|
|
285
|
+
refreshIntervalSeconds,
|
|
286
|
+
defaultContextWindow,
|
|
287
|
+
defaultMaxTokens
|
|
288
|
+
});
|
|
289
|
+
const now = params.now?.() ?? Date.now();
|
|
290
|
+
if (refreshIntervalSeconds > 0) {
|
|
291
|
+
const cached = discoveryCache.get(cacheKey);
|
|
292
|
+
if (cached?.value && cached.expiresAt > now) return cached.value;
|
|
293
|
+
if (cached?.inFlight) return cached.inFlight;
|
|
294
|
+
}
|
|
295
|
+
const sdk = params.clientFactory ? createInjectedClientDiscoverySdk() : await loadBedrockDiscoverySdk();
|
|
296
|
+
const clientFactory = params.clientFactory ?? ((region) => sdk.createClient(region));
|
|
297
|
+
if (!params.clientFactory) await refreshAwsSharedConfigCacheForBedrock();
|
|
298
|
+
const client = clientFactory(params.region);
|
|
299
|
+
const discoveryPromise = (async () => {
|
|
300
|
+
const [rawFoundationResponse, profileSummaries] = await Promise.all([client.send(sdk.createListFoundationModelsCommand()), fetchInferenceProfileSummaries(client, (input) => sdk.createListInferenceProfilesCommand(input))]);
|
|
301
|
+
const foundationResponse = rawFoundationResponse;
|
|
302
|
+
const discovered = [];
|
|
303
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
304
|
+
const foundationModels = /* @__PURE__ */ new Map();
|
|
305
|
+
for (const summary of foundationResponse.modelSummaries ?? []) {
|
|
306
|
+
if (!shouldIncludeSummary(summary, providerFilter)) continue;
|
|
307
|
+
const def = toModelDefinition(summary, {
|
|
308
|
+
contextWindow: defaultContextWindow,
|
|
309
|
+
maxTokens: defaultMaxTokens
|
|
310
|
+
});
|
|
311
|
+
discovered.push(def);
|
|
312
|
+
const normalizedId = normalizeLowercaseStringOrEmpty(def.id);
|
|
313
|
+
seenIds.add(normalizedId);
|
|
314
|
+
foundationModels.set(normalizedId, def);
|
|
315
|
+
}
|
|
316
|
+
const inferenceProfiles = resolveInferenceProfiles(profileSummaries, {
|
|
317
|
+
contextWindow: defaultContextWindow,
|
|
318
|
+
maxTokens: defaultMaxTokens
|
|
319
|
+
}, providerFilter, foundationModels);
|
|
320
|
+
for (const profile of inferenceProfiles) {
|
|
321
|
+
const normalizedId = normalizeLowercaseStringOrEmpty(profile.id);
|
|
322
|
+
if (!seenIds.has(normalizedId)) {
|
|
323
|
+
discovered.push(profile);
|
|
324
|
+
seenIds.add(normalizedId);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return discovered.toSorted((a, b) => {
|
|
328
|
+
const aGlobal = a.id.startsWith("global.") ? 0 : 1;
|
|
329
|
+
const bGlobal = b.id.startsWith("global.") ? 0 : 1;
|
|
330
|
+
if (aGlobal !== bGlobal) return aGlobal - bGlobal;
|
|
331
|
+
return a.name.localeCompare(b.name);
|
|
332
|
+
});
|
|
333
|
+
})();
|
|
334
|
+
if (refreshIntervalSeconds > 0) discoveryCache.set(cacheKey, {
|
|
335
|
+
expiresAt: now + refreshIntervalSeconds * 1e3,
|
|
336
|
+
inFlight: discoveryPromise
|
|
337
|
+
});
|
|
338
|
+
try {
|
|
339
|
+
const value = await discoveryPromise;
|
|
340
|
+
if (refreshIntervalSeconds > 0) discoveryCache.set(cacheKey, {
|
|
341
|
+
expiresAt: now + refreshIntervalSeconds * 1e3,
|
|
342
|
+
value
|
|
343
|
+
});
|
|
344
|
+
return value;
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (refreshIntervalSeconds > 0) discoveryCache.delete(cacheKey);
|
|
347
|
+
if (!hasLoggedBedrockError) {
|
|
348
|
+
hasLoggedBedrockError = true;
|
|
349
|
+
log.warn("Failed to discover Bedrock models", { error: formatErrorMessage(error) });
|
|
350
|
+
}
|
|
351
|
+
return [];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async function resolveImplicitBedrockProvider(params) {
|
|
355
|
+
const env = params.env ?? process.env;
|
|
356
|
+
const discoveryConfig = {
|
|
357
|
+
...params.config?.models?.bedrockDiscovery,
|
|
358
|
+
...params.pluginConfig?.discovery
|
|
359
|
+
};
|
|
360
|
+
const enabled = discoveryConfig?.enabled;
|
|
361
|
+
const hasAwsCreds = resolveBedrockConfigApiKey(env) !== void 0;
|
|
362
|
+
if (enabled === false) return null;
|
|
363
|
+
if (enabled !== true && !hasAwsCreds) return null;
|
|
364
|
+
const region = discoveryConfig?.region ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? "us-east-1";
|
|
365
|
+
const models = await discoverBedrockModels({
|
|
366
|
+
region,
|
|
367
|
+
config: discoveryConfig,
|
|
368
|
+
clientFactory: params.clientFactory
|
|
369
|
+
});
|
|
370
|
+
if (models.length === 0) return null;
|
|
371
|
+
return {
|
|
372
|
+
baseUrl: `https://bedrock-runtime.${region}.amazonaws.com`,
|
|
373
|
+
api: "bedrock-converse-stream",
|
|
374
|
+
auth: "aws-sdk",
|
|
375
|
+
models
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
export { discoverBedrockModels, resetBedrockDiscoveryCacheForTest, resolveImplicitBedrockProvider };
|