@openclaw/amazon-bedrock-mantle-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 ADDED
@@ -0,0 +1,2 @@
1
+ import { MANTLE_IAM_TOKEN_MARKER, discoverMantleModels, generateBearerTokenFromIam, getCachedIamToken, mergeImplicitMantleProvider, resetIamTokenCacheForTest, resetMantleDiscoveryCacheForTest, resolveImplicitMantleProvider, resolveMantleBearerToken, resolveMantleRuntimeBearerToken } from "./discovery.js";
2
+ export { MANTLE_IAM_TOKEN_MARKER, discoverMantleModels, generateBearerTokenFromIam, getCachedIamToken, mergeImplicitMantleProvider, resetIamTokenCacheForTest, resetMantleDiscoveryCacheForTest, resolveImplicitMantleProvider, resolveMantleBearerToken, resolveMantleRuntimeBearerToken };
@@ -0,0 +1,259 @@
1
+ import { createSubsystemLogger } from "openclaw/plugin-sdk/core";
2
+ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
3
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
4
+ //#region extensions/amazon-bedrock-mantle/discovery.ts
5
+ const log = createSubsystemLogger("bedrock-mantle-discovery");
6
+ const DEFAULT_COST = {
7
+ input: 0,
8
+ output: 0,
9
+ cacheRead: 0,
10
+ cacheWrite: 0
11
+ };
12
+ const DEFAULT_CONTEXT_WINDOW = 32e3;
13
+ const DEFAULT_MAX_TOKENS = 4096;
14
+ const DEFAULT_REFRESH_INTERVAL_SECONDS = 3600;
15
+ const MANTLE_IAM_TOKEN_MARKER = "__amazon_bedrock_mantle_iam__";
16
+ const MANTLE_SUPPORTED_REGIONS = [
17
+ "us-east-1",
18
+ "us-east-2",
19
+ "us-west-2",
20
+ "ap-northeast-1",
21
+ "ap-south-1",
22
+ "ap-southeast-3",
23
+ "eu-central-1",
24
+ "eu-west-1",
25
+ "eu-west-2",
26
+ "eu-south-1",
27
+ "eu-north-1",
28
+ "sa-east-1"
29
+ ];
30
+ function mantleEndpoint(region) {
31
+ return `https://bedrock-mantle.${region}.api.aws`;
32
+ }
33
+ function isSupportedRegion(region) {
34
+ return MANTLE_SUPPORTED_REGIONS.includes(region);
35
+ }
36
+ async function loadMantleBearerTokenProviderFactory() {
37
+ const { getTokenProvider } = await import("@aws/bedrock-token-generator");
38
+ return getTokenProvider;
39
+ }
40
+ /**
41
+ * Resolve a bearer token for Mantle authentication.
42
+ *
43
+ * Returns the value of AWS_BEARER_TOKEN_BEDROCK if set, undefined otherwise.
44
+ * When no explicit token is set, `resolveImplicitMantleProvider` will attempt
45
+ * to generate one from IAM credentials via `@aws/bedrock-token-generator`.
46
+ */
47
+ function resolveMantleBearerToken(env = process.env) {
48
+ const explicitToken = env.AWS_BEARER_TOKEN_BEDROCK?.trim();
49
+ if (explicitToken) return explicitToken;
50
+ }
51
+ /** Token cache for IAM-derived bearer tokens, keyed by region. */
52
+ const iamTokenCache = /* @__PURE__ */ new Map();
53
+ const IAM_TOKEN_TTL_MS = 72e5;
54
+ function resolveMantleRegion(env) {
55
+ return env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? "us-east-1";
56
+ }
57
+ function getCachedIamTokenEntry(region, now = Date.now()) {
58
+ const cached = iamTokenCache.get(region);
59
+ if (cached && cached.expiresAt > now) return cached;
60
+ }
61
+ /**
62
+ * Generate a bearer token from IAM credentials using `@aws/bedrock-token-generator`.
63
+ *
64
+ * Uses the AWS default credential chain (instance roles, SSO, access keys, EKS IRSA).
65
+ * Returns undefined if the package is not installed or credentials are unavailable.
66
+ */
67
+ async function generateBearerTokenFromIam(params) {
68
+ const now = params.now?.() ?? Date.now();
69
+ const cached = getCachedIamTokenEntry(params.region, now);
70
+ if (cached) return cached.token;
71
+ try {
72
+ const token = await (params.tokenProviderFactory ?? await loadMantleBearerTokenProviderFactory())({
73
+ region: params.region,
74
+ expiresInSeconds: 7200
75
+ })();
76
+ iamTokenCache.set(params.region, {
77
+ token,
78
+ expiresAt: now + IAM_TOKEN_TTL_MS
79
+ });
80
+ return token;
81
+ } catch (error) {
82
+ log.debug?.("Mantle IAM token generation unavailable", {
83
+ region: params.region,
84
+ error: formatErrorMessage(error)
85
+ });
86
+ return;
87
+ }
88
+ }
89
+ /**
90
+ * Read a cached IAM bearer token for the given region (sync, no generation).
91
+ *
92
+ * Returns the token if it exists and has not expired, undefined otherwise.
93
+ * Used by Mantle runtime auth and tests to inspect the current cache.
94
+ */
95
+ function getCachedIamToken(region) {
96
+ return getCachedIamTokenEntry(region)?.token;
97
+ }
98
+ async function resolveMantleRuntimeBearerToken(params) {
99
+ if (params.apiKey !== "__amazon_bedrock_mantle_iam__") return { apiKey: params.apiKey };
100
+ const now = params.now?.() ?? Date.now();
101
+ const region = resolveMantleRegion(params.env ?? process.env);
102
+ const cached = getCachedIamTokenEntry(region, now);
103
+ if (cached) return {
104
+ apiKey: cached.token,
105
+ expiresAt: cached.expiresAt
106
+ };
107
+ const token = await generateBearerTokenFromIam({
108
+ region,
109
+ now: params.now,
110
+ tokenProviderFactory: params.tokenProviderFactory
111
+ });
112
+ if (!token) return;
113
+ const refreshed = getCachedIamTokenEntry(region, now);
114
+ return {
115
+ apiKey: refreshed?.token ?? token,
116
+ expiresAt: refreshed?.expiresAt ?? now + IAM_TOKEN_TTL_MS
117
+ };
118
+ }
119
+ /** Reset the IAM token cache (for testing). */
120
+ function resetIamTokenCacheForTest() {
121
+ iamTokenCache.clear();
122
+ }
123
+ /** Model ID substrings that indicate reasoning/thinking support. */
124
+ const REASONING_PATTERNS = [
125
+ "thinking",
126
+ "reasoner",
127
+ "reasoning",
128
+ "deepseek.r",
129
+ "gpt-oss-120b",
130
+ "gpt-oss-safeguard-120b"
131
+ ];
132
+ function inferReasoningSupport(modelId) {
133
+ const lower = normalizeLowercaseStringOrEmpty(modelId);
134
+ return REASONING_PATTERNS.some((p) => lower.includes(p));
135
+ }
136
+ const discoveryCache = /* @__PURE__ */ new Map();
137
+ /** Clear the discovery cache (for testing). */
138
+ function resetMantleDiscoveryCacheForTest() {
139
+ discoveryCache.clear();
140
+ }
141
+ /**
142
+ * Discover available models from the Mantle `/v1/models` endpoint.
143
+ *
144
+ * The response is in standard OpenAI format:
145
+ * ```json
146
+ * { "data": [{ "id": "anthropic.claude-sonnet-4-6", "object": "model", "owned_by": "anthropic" }] }
147
+ * ```
148
+ *
149
+ * Results are cached per region for `DEFAULT_REFRESH_INTERVAL_SECONDS`.
150
+ * Returns an empty array if the request fails (no permission, network error, etc.).
151
+ */
152
+ async function discoverMantleModels(params) {
153
+ const { region, bearerToken, fetchFn = fetch, now = Date.now } = params;
154
+ const cacheKey = region;
155
+ const cached = discoveryCache.get(cacheKey);
156
+ if (cached && now() - cached.fetchedAt < DEFAULT_REFRESH_INTERVAL_SECONDS * 1e3) return cached.models;
157
+ const endpoint = `${mantleEndpoint(region)}/v1/models`;
158
+ try {
159
+ const response = await fetchFn(endpoint, {
160
+ method: "GET",
161
+ headers: {
162
+ Authorization: `Bearer ${bearerToken}`,
163
+ Accept: "application/json"
164
+ }
165
+ });
166
+ if (!response.ok) {
167
+ log.debug?.("Mantle model discovery failed", {
168
+ status: response.status,
169
+ statusText: response.statusText
170
+ });
171
+ return cached?.models ?? [];
172
+ }
173
+ const models = ((await response.json()).data ?? []).filter((m) => m.id?.trim()).map((m) => ({
174
+ id: m.id,
175
+ name: m.id,
176
+ reasoning: inferReasoningSupport(m.id),
177
+ input: ["text"],
178
+ cost: DEFAULT_COST,
179
+ contextWindow: DEFAULT_CONTEXT_WINDOW,
180
+ maxTokens: DEFAULT_MAX_TOKENS
181
+ })).toSorted((a, b) => a.id.localeCompare(b.id));
182
+ discoveryCache.set(cacheKey, {
183
+ models,
184
+ fetchedAt: now()
185
+ });
186
+ return models;
187
+ } catch (error) {
188
+ log.debug?.("Mantle model discovery error", { error: formatErrorMessage(error) });
189
+ return cached?.models ?? [];
190
+ }
191
+ }
192
+ /**
193
+ * Resolve an implicit Bedrock Mantle provider if authentication is available.
194
+ *
195
+ * Detection priority:
196
+ * 1. AWS_BEARER_TOKEN_BEDROCK env var → use directly
197
+ * 2. IAM credentials → generate bearer token via `@aws/bedrock-token-generator`
198
+ * - Region from AWS_REGION / AWS_DEFAULT_REGION / default us-east-1
199
+ * - Models discovered from `/v1/models`
200
+ */
201
+ async function resolveImplicitMantleProvider(params) {
202
+ const env = params.env ?? process.env;
203
+ if (params.pluginConfig?.discovery?.enabled === false) return null;
204
+ const region = resolveMantleRegion(env);
205
+ const explicitBearerToken = resolveMantleBearerToken(env);
206
+ if (!isSupportedRegion(region)) {
207
+ log.debug?.("Mantle not available in region", { region });
208
+ return null;
209
+ }
210
+ const bearerToken = explicitBearerToken ?? await generateBearerTokenFromIam({
211
+ region,
212
+ tokenProviderFactory: params.tokenProviderFactory
213
+ });
214
+ if (!bearerToken) return null;
215
+ const models = await discoverMantleModels({
216
+ region,
217
+ bearerToken,
218
+ fetchFn: params.fetchFn
219
+ });
220
+ if (models.length === 0) return null;
221
+ log.debug?.("Mantle provider resolved", {
222
+ region,
223
+ modelCount: models.length
224
+ });
225
+ const claudeModels = [{
226
+ id: "anthropic.claude-opus-4-7",
227
+ name: "Claude Opus 4.7",
228
+ api: "anthropic-messages",
229
+ reasoning: false,
230
+ input: ["text", "image"],
231
+ cost: {
232
+ input: 5,
233
+ output: 25,
234
+ cacheRead: .5,
235
+ cacheWrite: 6.25
236
+ },
237
+ contextWindow: 1e6,
238
+ maxTokens: 128e3
239
+ }];
240
+ const allModels = [...models, ...claudeModels];
241
+ return {
242
+ baseUrl: `${mantleEndpoint(region)}/v1`,
243
+ api: "openai-completions",
244
+ auth: "api-key",
245
+ apiKey: explicitBearerToken ? "env:AWS_BEARER_TOKEN_BEDROCK" : MANTLE_IAM_TOKEN_MARKER,
246
+ models: allModels
247
+ };
248
+ }
249
+ function mergeImplicitMantleProvider(params) {
250
+ const { existing, implicit } = params;
251
+ if (!existing) return implicit;
252
+ return {
253
+ ...implicit,
254
+ ...existing,
255
+ models: Array.isArray(existing.models) && existing.models.length > 0 ? existing.models : implicit.models
256
+ };
257
+ }
258
+ //#endregion
259
+ export { MANTLE_IAM_TOKEN_MARKER, discoverMantleModels, generateBearerTokenFromIam, getCachedIamToken, mergeImplicitMantleProvider, resetIamTokenCacheForTest, resetMantleDiscoveryCacheForTest, resolveImplicitMantleProvider, resolveMantleBearerToken, resolveMantleRuntimeBearerToken };
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import { registerBedrockMantlePlugin } from "./register.sync.runtime.js";
2
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
+ //#region extensions/amazon-bedrock-mantle/index.ts
4
+ var amazon_bedrock_mantle_default = definePluginEntry({
5
+ id: "amazon-bedrock-mantle",
6
+ name: "Amazon Bedrock Mantle Provider",
7
+ description: "Bundled Amazon Bedrock Mantle (OpenAI-compatible) provider plugin",
8
+ register(api) {
9
+ registerBedrockMantlePlugin(api);
10
+ }
11
+ });
12
+ //#endregion
13
+ export { amazon_bedrock_mantle_default as default };
@@ -0,0 +1,84 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ import { streamAnthropic } from "@earendil-works/pi-ai/anthropic";
3
+ //#region extensions/amazon-bedrock-mantle/mantle-anthropic.runtime.ts
4
+ const MANTLE_ANTHROPIC_BETA = "fine-grained-tool-streaming-2025-05-14";
5
+ function resolveMantleAnthropicBaseUrl(baseUrl) {
6
+ const trimmed = baseUrl.replace(/\/+$/, "");
7
+ if (trimmed.endsWith("/anthropic")) return trimmed;
8
+ if (trimmed.endsWith("/v1")) return `${trimmed.slice(0, -3)}/anthropic`;
9
+ return `${trimmed}/anthropic`;
10
+ }
11
+ function requiresDefaultSampling(modelId) {
12
+ return modelId.includes("claude-opus-4-7");
13
+ }
14
+ function mergeHeaders(...headerSources) {
15
+ const merged = {};
16
+ for (const headers of headerSources) if (headers) Object.assign(merged, headers);
17
+ return merged;
18
+ }
19
+ function buildMantleAnthropicBaseOptions(model, options, apiKey) {
20
+ return {
21
+ temperature: requiresDefaultSampling(model.id) ? void 0 : options?.temperature,
22
+ maxTokens: options?.maxTokens || Math.min(model.maxTokens, 32e3),
23
+ signal: options?.signal,
24
+ apiKey,
25
+ cacheRetention: options?.cacheRetention,
26
+ sessionId: options?.sessionId,
27
+ onPayload: options?.onPayload,
28
+ maxRetryDelayMs: options?.maxRetryDelayMs,
29
+ metadata: options?.metadata
30
+ };
31
+ }
32
+ function adjustMaxTokensForThinking(baseMaxTokens, modelMaxTokens, reasoningLevel, customBudgets) {
33
+ const budgets = {
34
+ minimal: 1024,
35
+ low: 2048,
36
+ medium: 8192,
37
+ high: 16384,
38
+ xhigh: 16384,
39
+ ...customBudgets
40
+ };
41
+ const minOutputTokens = 1024;
42
+ let thinkingBudget = budgets[reasoningLevel];
43
+ const maxTokens = Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);
44
+ if (maxTokens <= thinkingBudget) thinkingBudget = Math.max(0, maxTokens - minOutputTokens);
45
+ return {
46
+ maxTokens,
47
+ thinkingBudget
48
+ };
49
+ }
50
+ function createMantleAnthropicStreamFn(deps) {
51
+ return (model, context, options) => {
52
+ const apiKey = options?.apiKey ?? "";
53
+ const createClient = deps?.createClient ?? ((clientOptions) => new Anthropic(clientOptions));
54
+ const stream = deps?.stream ?? streamAnthropic;
55
+ const client = createClient({
56
+ apiKey: null,
57
+ authToken: apiKey,
58
+ baseURL: resolveMantleAnthropicBaseUrl(model.baseUrl),
59
+ dangerouslyAllowBrowser: true,
60
+ defaultHeaders: mergeHeaders({
61
+ accept: "application/json",
62
+ "anthropic-dangerous-direct-browser-access": "true",
63
+ "anthropic-beta": MANTLE_ANTHROPIC_BETA
64
+ }, model.headers, options?.headers)
65
+ });
66
+ const base = buildMantleAnthropicBaseOptions(model, options, apiKey);
67
+ const streamClient = client;
68
+ if (!options?.reasoning || requiresDefaultSampling(model.id)) return stream(model, context, {
69
+ ...base,
70
+ client: streamClient,
71
+ thinkingEnabled: false
72
+ });
73
+ const adjusted = adjustMaxTokensForThinking(base.maxTokens || 0, model.maxTokens, options.reasoning, options.thinkingBudgets);
74
+ return stream(model, context, {
75
+ ...base,
76
+ client: streamClient,
77
+ maxTokens: adjusted.maxTokens,
78
+ thinkingEnabled: true,
79
+ thinkingBudgetTokens: adjusted.thinkingBudget
80
+ });
81
+ };
82
+ }
83
+ //#endregion
84
+ export { createMantleAnthropicStreamFn, resolveMantleAnthropicBaseUrl };
@@ -0,0 +1,45 @@
1
+ import { mergeImplicitMantleProvider, resolveImplicitMantleProvider, resolveMantleBearerToken, resolveMantleRuntimeBearerToken } from "./discovery.js";
2
+ import { createMantleAnthropicStreamFn } from "./mantle-anthropic.runtime.js";
3
+ import { resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
4
+ //#region extensions/amazon-bedrock-mantle/register.sync.runtime.ts
5
+ function registerBedrockMantlePlugin(api) {
6
+ const providerId = "amazon-bedrock-mantle";
7
+ const startupPluginConfig = api.pluginConfig ?? {};
8
+ function resolveCurrentPluginConfig(config) {
9
+ return resolvePluginConfigObject(config, providerId) ?? (config ? void 0 : startupPluginConfig);
10
+ }
11
+ api.registerProvider({
12
+ id: providerId,
13
+ label: "Amazon Bedrock Mantle (OpenAI-compatible)",
14
+ docsPath: "/providers/bedrock-mantle",
15
+ auth: [],
16
+ catalog: {
17
+ order: "simple",
18
+ run: async (ctx) => {
19
+ const currentPluginConfig = resolveCurrentPluginConfig(ctx.config);
20
+ const implicit = await resolveImplicitMantleProvider({
21
+ env: ctx.env,
22
+ pluginConfig: currentPluginConfig
23
+ });
24
+ if (!implicit) return null;
25
+ return { provider: mergeImplicitMantleProvider({
26
+ existing: ctx.config.models?.providers?.[providerId],
27
+ implicit
28
+ }) };
29
+ }
30
+ },
31
+ resolveConfigApiKey: ({ env }) => resolveMantleBearerToken(env) ? "env:AWS_BEARER_TOKEN_BEDROCK" : void 0,
32
+ prepareRuntimeAuth: async ({ apiKey, env }) => await resolveMantleRuntimeBearerToken({
33
+ apiKey,
34
+ env
35
+ }),
36
+ createStreamFn: ({ model }) => model.api === "anthropic-messages" ? createMantleAnthropicStreamFn() : void 0,
37
+ matchesContextOverflowError: ({ errorMessage }) => /context_length_exceeded|max.*tokens.*exceeded/i.test(errorMessage),
38
+ classifyFailoverReason: ({ errorMessage }) => {
39
+ if (/rate_limit|too many requests|429/i.test(errorMessage)) return "rate_limit";
40
+ if (/overloaded|503|service.*unavailable/i.test(errorMessage)) return "overloaded";
41
+ }
42
+ });
43
+ }
44
+ //#endregion
45
+ export { registerBedrockMantlePlugin };
@@ -0,0 +1,34 @@
1
+ {
2
+ "id": "amazon-bedrock-mantle",
3
+ "activation": {
4
+ "onStartup": false
5
+ },
6
+ "enabledByDefault": true,
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "properties": {
11
+ "discovery": {
12
+ "type": "object",
13
+ "additionalProperties": false,
14
+ "properties": {
15
+ "enabled": {
16
+ "type": "boolean",
17
+ "description": "When false, skip implicit Mantle model discovery."
18
+ }
19
+ }
20
+ }
21
+ }
22
+ },
23
+ "uiHints": {
24
+ "discovery": {
25
+ "label": "Model Discovery",
26
+ "help": "Plugin-owned controls for Amazon Bedrock Mantle model auto-discovery."
27
+ },
28
+ "discovery.enabled": {
29
+ "label": "Enable Discovery",
30
+ "help": "When false, OpenClaw keeps the Amazon Bedrock Mantle plugin available but skips implicit startup discovery. Leave unset for default auto-detect behavior."
31
+ }
32
+ },
33
+ "providers": ["amazon-bedrock-mantle"]
34
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@openclaw/amazon-bedrock-mantle-provider",
3
+ "version": "2026.5.12-beta.7",
4
+ "description": "OpenClaw Amazon Bedrock Mantle (OpenAI-compatible) provider plugin",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/openclaw/openclaw"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@anthropic-ai/sdk": "0.95.2",
12
+ "@aws/bedrock-token-generator": "1.1.0",
13
+ "@earendil-works/pi-ai": "0.74.0"
14
+ },
15
+ "devDependencies": {
16
+ "@openclaw/plugin-sdk": "workspace:*"
17
+ },
18
+ "openclaw": {
19
+ "extensions": [
20
+ "./index.ts"
21
+ ],
22
+ "install": {
23
+ "npmSpec": "@openclaw/amazon-bedrock-mantle-provider",
24
+ "defaultChoice": "npm",
25
+ "minHostVersion": ">=2026.5.12-beta.6"
26
+ },
27
+ "compat": {
28
+ "pluginApi": ">=2026.5.12-beta.7"
29
+ },
30
+ "build": {
31
+ "openclawVersion": "2026.5.12-beta.7",
32
+ "bundledDist": false
33
+ },
34
+ "release": {
35
+ "publishToNpm": true
36
+ },
37
+ "runtimeExtensions": [
38
+ "./dist/index.js"
39
+ ]
40
+ },
41
+ "files": [
42
+ "dist/**",
43
+ "openclaw.plugin.json"
44
+ ],
45
+ "peerDependencies": {
46
+ "openclaw": ">=2026.5.12-beta.7"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "openclaw": {
50
+ "optional": true
51
+ }
52
+ }
53
+ }