@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 +2 -0
- package/dist/discovery.js +259 -0
- package/dist/index.js +13 -0
- package/dist/mantle-anthropic.runtime.js +84 -0
- package/dist/register.sync.runtime.js +45 -0
- package/openclaw.plugin.json +34 -0
- package/package.json +53 -0
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
|
+
}
|