Skip to content

Commit 19de5d1

Browse files
committed
refactor: move provider discovery config into plugins
1 parent 4613f12 commit 19de5d1

24 files changed

Lines changed: 295 additions & 285 deletions
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
8bbf281d0c63e38098b2132174b77ed58faf2083fb68cb88f90ebe76d7acda1b config-baseline.json
2-
7163170accb9a8b62455ede5437f057d5a9e9ab5da42010cf0f39cbad952071d config-baseline.core.json
1+
d5a737eb69a2b2b64526fa0197ef9fe576b1d5d4b949a5c610a8457d5f5706cd config-baseline.json
2+
b1a181b667568b5860a80945837d544fdec4f946fba34e871936ce0cd3eb689b config-baseline.core.json
33
3c999707b167138de34f6255e3488b99e404c5132d3fc5879a1fa12d815c31f5 config-baseline.channel.json
4-
76d011c68b8bc44ec862afa826dd8ddd7c577d89ce0b822eed306f8e1e9301ab config-baseline.plugin.json
4+
031b237717ca108ea2cd314413db4c91edfdfea55f808179e3066331f41af134 config-baseline.plugin.json

docs/gateway/configuration-reference.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,13 +2301,13 @@ OpenClaw uses the built-in model catalog. Add custom providers via `models.provi
23012301
- `models.providers.*.models.*.contextWindow`: native model context window metadata.
23022302
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native `contextWindow`.
23032303
- `models.providers.*.models.*.compat.supportsDeveloperRole`: optional compatibility hint. For `api: "openai-completions"` with a non-empty non-native `baseUrl` (host not `api.openai.com`), OpenClaw forces this to `false` at runtime. Empty/omitted `baseUrl` keeps default OpenAI behavior.
2304-
- `models.bedrockDiscovery`: Bedrock auto-discovery settings root.
2305-
- `models.bedrockDiscovery.enabled`: turn discovery polling on/off.
2306-
- `models.bedrockDiscovery.region`: AWS region for discovery.
2307-
- `models.bedrockDiscovery.providerFilter`: optional provider-id filter for targeted discovery.
2308-
- `models.bedrockDiscovery.refreshInterval`: polling interval for discovery refresh.
2309-
- `models.bedrockDiscovery.defaultContextWindow`: fallback context window for discovered models.
2310-
- `models.bedrockDiscovery.defaultMaxTokens`: fallback max output tokens for discovered models.
2304+
- `plugins.entries.amazon-bedrock.config.discovery`: Bedrock auto-discovery settings root.
2305+
- `plugins.entries.amazon-bedrock.config.discovery.enabled`: turn implicit discovery on/off.
2306+
- `plugins.entries.amazon-bedrock.config.discovery.region`: AWS region for discovery.
2307+
- `plugins.entries.amazon-bedrock.config.discovery.providerFilter`: optional provider-id filter for targeted discovery.
2308+
- `plugins.entries.amazon-bedrock.config.discovery.refreshInterval`: polling interval for discovery refresh.
2309+
- `plugins.entries.amazon-bedrock.config.discovery.defaultContextWindow`: fallback context window for discovered models.
2310+
- `plugins.entries.amazon-bedrock.config.discovery.defaultMaxTokens`: fallback max output tokens for discovered models.
23112311

23122312
### Provider examples
23132313

docs/help/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ for usage/billing and raise limits as needed.
652652
</Accordion>
653653

654654
<Accordion title="Is AWS Bedrock supported?">
655-
Yes. OpenClaw has a bundled **Amazon Bedrock (Converse)** provider. With AWS env markers present, OpenClaw can auto-discover the streaming/text Bedrock catalog and merge it as an implicit `amazon-bedrock` provider; otherwise you can explicitly enable `models.bedrockDiscovery.enabled` or add a manual provider entry. See [Amazon Bedrock](/providers/bedrock) and [Model providers](/providers/models). If you prefer a managed key flow, an OpenAI-compatible proxy in front of Bedrock is still a valid option.
655+
Yes. OpenClaw has a bundled **Amazon Bedrock (Converse)** provider. With AWS env markers present, OpenClaw can auto-discover the streaming/text Bedrock catalog and merge it as an implicit `amazon-bedrock` provider; otherwise you can explicitly enable `plugins.entries.amazon-bedrock.config.discovery.enabled` or add a manual provider entry. See [Amazon Bedrock](/providers/bedrock) and [Model providers](/providers/models). If you prefer a managed key flow, an OpenAI-compatible proxy in front of Bedrock is still a valid option.
656656
</Accordion>
657657

658658
<Accordion title="How does Codex auth work?">

docs/providers/bedrock.md

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,35 @@ cached (default: 1 hour).
2727

2828
How the implicit provider is enabled:
2929

30-
- If `models.bedrockDiscovery.enabled` is `true`, OpenClaw will try discovery
31-
even when no AWS env marker is present.
32-
- If `models.bedrockDiscovery.enabled` is unset, OpenClaw only auto-adds the
30+
- If `plugins.entries.amazon-bedrock.config.discovery.enabled` is `true`,
31+
OpenClaw will try discovery even when no AWS env marker is present.
32+
- If `plugins.entries.amazon-bedrock.config.discovery.enabled` is unset,
33+
OpenClaw only auto-adds the
3334
implicit Bedrock provider when it sees one of these AWS auth markers:
3435
`AWS_BEARER_TOKEN_BEDROCK`, `AWS_ACCESS_KEY_ID` +
3536
`AWS_SECRET_ACCESS_KEY`, or `AWS_PROFILE`.
3637
- The actual Bedrock runtime auth path still uses the AWS SDK default chain, so
3738
shared config, SSO, and IMDS instance-role auth can work even when discovery
3839
needed `enabled: true` to opt in.
3940

40-
Config options live under `models.bedrockDiscovery`:
41+
Config options live under `plugins.entries.amazon-bedrock.config.discovery`:
4142

4243
```json5
4344
{
44-
models: {
45-
bedrockDiscovery: {
46-
enabled: true,
47-
region: "us-east-1",
48-
providerFilter: ["anthropic", "amazon"],
49-
refreshInterval: 3600,
50-
defaultContextWindow: 32000,
51-
defaultMaxTokens: 4096,
45+
plugins: {
46+
entries: {
47+
"amazon-bedrock": {
48+
config: {
49+
discovery: {
50+
enabled: true,
51+
region: "us-east-1",
52+
providerFilter: ["anthropic", "amazon"],
53+
refreshInterval: 3600,
54+
defaultContextWindow: 32000,
55+
defaultMaxTokens: 4096,
56+
},
57+
},
58+
},
5259
},
5360
},
5461
}
@@ -120,20 +127,21 @@ export AWS_BEARER_TOKEN_BEDROCK="..."
120127
When running OpenClaw on an EC2 instance with an IAM role attached, the AWS SDK
121128
can use the instance metadata service (IMDS) for authentication. For Bedrock
122129
model discovery, OpenClaw only auto-enables the implicit provider from AWS env
123-
markers unless you explicitly set `models.bedrockDiscovery.enabled: true`.
130+
markers unless you explicitly set
131+
`plugins.entries.amazon-bedrock.config.discovery.enabled: true`.
124132

125133
Recommended setup for IMDS-backed hosts:
126134

127-
- Set `models.bedrockDiscovery.enabled` to `true`.
128-
- Set `models.bedrockDiscovery.region` (or export `AWS_REGION`).
135+
- Set `plugins.entries.amazon-bedrock.config.discovery.enabled` to `true`.
136+
- Set `plugins.entries.amazon-bedrock.config.discovery.region` (or export `AWS_REGION`).
129137
- You do **not** need a fake API key.
130138
- You only need `AWS_PROFILE=default` if you specifically want an env marker
131139
for auto mode or status surfaces.
132140

133141
```bash
134142
# Recommended: explicit discovery enable + region
135-
openclaw config set models.bedrockDiscovery.enabled true
136-
openclaw config set models.bedrockDiscovery.region us-east-1
143+
openclaw config set plugins.entries.amazon-bedrock.config.discovery.enabled true
144+
openclaw config set plugins.entries.amazon-bedrock.config.discovery.region us-east-1
137145

138146
# Optional: add an env marker if you want auto mode without explicit enable
139147
export AWS_PROFILE=default
@@ -176,8 +184,8 @@ aws ec2 associate-iam-instance-profile \
176184
--iam-instance-profile Name=EC2-Bedrock-Access
177185

178186
# 3. On the EC2 instance, enable discovery explicitly
179-
openclaw config set models.bedrockDiscovery.enabled true
180-
openclaw config set models.bedrockDiscovery.region us-east-1
187+
openclaw config set plugins.entries.amazon-bedrock.config.discovery.enabled true
188+
openclaw config set plugins.entries.amazon-bedrock.config.discovery.region us-east-1
181189

182190
# 4. Optional: add an env marker if you want auto mode without explicit enable
183191
echo 'export AWS_PROFILE=default' >> ~/.bashrc
@@ -194,7 +202,7 @@ openclaw models list
194202
- Automatic discovery needs the `bedrock:ListFoundationModels` permission.
195203
- If you rely on auto mode, set one of the supported AWS auth env markers on the
196204
gateway host. If you prefer IMDS/shared-config auth without env markers, set
197-
`models.bedrockDiscovery.enabled: true`.
205+
`plugins.entries.amazon-bedrock.config.discovery.enabled: true`.
198206
- OpenClaw surfaces the credential source in this order: `AWS_BEARER_TOKEN_BEDROCK`,
199207
then `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`, then `AWS_PROFILE`, then the
200208
default AWS SDK chain.

extensions/amazon-bedrock/discovery.test.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
mergeImplicitBedrockProvider,
66
resetBedrockDiscoveryCacheForTest,
77
resolveBedrockConfigApiKey,
8+
resolveImplicitBedrockProvider,
89
} from "./api.js";
910

1011
const sendMock = vi.fn();
@@ -147,9 +148,9 @@ describe("bedrock discovery", () => {
147148
expect(resolveBedrockConfigApiKey({} as NodeJS.ProcessEnv)).toBeUndefined();
148149

149150
// When AWS_PROFILE is explicitly set, it should return the marker.
150-
expect(
151-
resolveBedrockConfigApiKey({ AWS_PROFILE: "default" } as NodeJS.ProcessEnv),
152-
).toBe("AWS_PROFILE");
151+
expect(resolveBedrockConfigApiKey({ AWS_PROFILE: "default" } as NodeJS.ProcessEnv)).toBe(
152+
"AWS_PROFILE",
153+
);
153154
});
154155

155156
it("merges implicit Bedrock models into explicit provider overrides", () => {
@@ -179,4 +180,48 @@ describe("bedrock discovery", () => {
179180
}).models?.map((model) => model.id),
180181
).toEqual(["amazon.nova-micro-v1:0"]);
181182
});
183+
184+
it("prefers plugin-owned discovery config and still honors legacy fallback", async () => {
185+
mockSingleActiveSummary();
186+
187+
const pluginEnabled = await resolveImplicitBedrockProvider({
188+
config: {
189+
models: {
190+
bedrockDiscovery: {
191+
enabled: false,
192+
region: "us-west-2",
193+
},
194+
},
195+
},
196+
pluginConfig: {
197+
discovery: {
198+
enabled: true,
199+
region: "us-east-1",
200+
},
201+
},
202+
env: {} as NodeJS.ProcessEnv,
203+
clientFactory,
204+
});
205+
206+
expect(pluginEnabled?.baseUrl).toBe("https://bedrock-runtime.us-east-1.amazonaws.com");
207+
expect(sendMock).toHaveBeenCalledTimes(1);
208+
209+
mockSingleActiveSummary();
210+
211+
const legacyEnabled = await resolveImplicitBedrockProvider({
212+
config: {
213+
models: {
214+
bedrockDiscovery: {
215+
enabled: true,
216+
region: "us-west-2",
217+
},
218+
},
219+
},
220+
env: {} as NodeJS.ProcessEnv,
221+
clientFactory,
222+
});
223+
224+
expect(legacyEnabled?.baseUrl).toBe("https://bedrock-runtime.us-west-2.amazonaws.com");
225+
expect(sendMock).toHaveBeenCalledTimes(2);
226+
});
182227
});

extensions/amazon-bedrock/discovery.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,15 @@ export async function discoverBedrockModels(params: {
242242

243243
export async function resolveImplicitBedrockProvider(params: {
244244
config?: { models?: { bedrockDiscovery?: BedrockDiscoveryConfig } };
245+
pluginConfig?: { discovery?: BedrockDiscoveryConfig };
245246
env?: NodeJS.ProcessEnv;
247+
clientFactory?: (region: string) => BedrockClient;
246248
}): Promise<ModelProviderConfig | null> {
247249
const env = params.env ?? process.env;
248-
const discoveryConfig = params.config?.models?.bedrockDiscovery;
250+
const discoveryConfig = {
251+
...(params.config?.models?.bedrockDiscovery ?? {}),
252+
...(params.pluginConfig?.discovery ?? {}),
253+
};
249254
const enabled = discoveryConfig?.enabled;
250255
const hasAwsCreds = resolveAwsSdkEnvVarName(env) !== undefined;
251256
if (enabled === false) {
@@ -259,6 +264,7 @@ export async function resolveImplicitBedrockProvider(params: {
259264
const models = await discoverBedrockModels({
260265
region,
261266
config: discoveryConfig,
267+
clientFactory: params.clientFactory,
262268
});
263269
if (models.length === 0) {
264270
return null;

extensions/amazon-bedrock/index.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,35 @@ describe("amazon-bedrock provider plugin", () => {
152152
});
153153

154154
describe("guardrail config schema", () => {
155-
it("defines guardrail object with correct property types, required fields, and enums", () => {
155+
it("defines discovery and guardrail objects with the expected shape", () => {
156156
const pluginJson = JSON.parse(
157157
readFileSync(resolve(import.meta.dirname, "openclaw.plugin.json"), "utf-8"),
158158
);
159+
const discovery = pluginJson.configSchema?.properties?.discovery;
159160
const guardrail = pluginJson.configSchema?.properties?.guardrail;
160161

162+
expect(discovery).toBeDefined();
163+
expect(discovery.type).toBe("object");
164+
expect(discovery.additionalProperties).toBe(false);
165+
expect(discovery.properties.enabled).toEqual({ type: "boolean" });
166+
expect(discovery.properties.region).toEqual({ type: "string" });
167+
expect(discovery.properties.providerFilter).toEqual({
168+
type: "array",
169+
items: { type: "string" },
170+
});
171+
expect(discovery.properties.refreshInterval).toEqual({
172+
type: "integer",
173+
minimum: 0,
174+
});
175+
expect(discovery.properties.defaultContextWindow).toEqual({
176+
type: "integer",
177+
minimum: 1,
178+
});
179+
expect(discovery.properties.defaultMaxTokens).toEqual({
180+
type: "integer",
181+
minimum: 1,
182+
});
183+
161184
expect(guardrail).toBeDefined();
162185
expect(guardrail.type).toBe("object");
163186
expect(guardrail.additionalProperties).toBe(false);

extensions/amazon-bedrock/openclaw.plugin.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@
66
"type": "object",
77
"additionalProperties": false,
88
"properties": {
9+
"discovery": {
10+
"type": "object",
11+
"additionalProperties": false,
12+
"properties": {
13+
"enabled": { "type": "boolean" },
14+
"region": { "type": "string" },
15+
"providerFilter": {
16+
"type": "array",
17+
"items": { "type": "string" }
18+
},
19+
"refreshInterval": { "type": "integer", "minimum": 0 },
20+
"defaultContextWindow": { "type": "integer", "minimum": 1 },
21+
"defaultMaxTokens": { "type": "integer", "minimum": 1 }
22+
}
23+
},
924
"guardrail": {
1025
"type": "object",
1126
"additionalProperties": false,
@@ -18,5 +33,39 @@
1833
"required": ["guardrailIdentifier", "guardrailVersion"]
1934
}
2035
}
36+
},
37+
"uiHints": {
38+
"discovery": {
39+
"label": "Model Discovery",
40+
"help": "Plugin-owned controls for Amazon Bedrock model auto-discovery."
41+
},
42+
"discovery.enabled": {
43+
"label": "Enable Discovery",
44+
"help": "When false, OpenClaw keeps the Amazon Bedrock plugin available but skips implicit startup discovery. When true, discovery can run even without AWS auth env markers."
45+
},
46+
"discovery.region": {
47+
"label": "Discovery Region",
48+
"help": "AWS region to use for Bedrock model discovery. Defaults to AWS_REGION, AWS_DEFAULT_REGION, then us-east-1."
49+
},
50+
"discovery.providerFilter": {
51+
"label": "Provider Filter",
52+
"help": "Optional Bedrock provider-name allowlist for discovery, such as anthropic or amazon."
53+
},
54+
"discovery.refreshInterval": {
55+
"label": "Discovery Refresh Interval (s)",
56+
"help": "How long to cache Bedrock discovery results in seconds. Set to 0 to disable caching."
57+
},
58+
"discovery.defaultContextWindow": {
59+
"label": "Default Context Window",
60+
"help": "Fallback context window to assign to discovered Bedrock models."
61+
},
62+
"discovery.defaultMaxTokens": {
63+
"label": "Default Max Tokens",
64+
"help": "Fallback max output tokens to assign to discovered Bedrock models."
65+
},
66+
"guardrail": {
67+
"label": "Guardrail",
68+
"help": "Amazon Bedrock Guardrails settings applied to Bedrock model invocations."
69+
}
2170
}
2271
}

extensions/amazon-bedrock/register.sync.runtime.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ type GuardrailConfig = {
1919
trace?: "enabled" | "disabled" | "enabled_full";
2020
};
2121

22+
type AmazonBedrockPluginConfig = {
23+
discovery?: {
24+
enabled?: boolean;
25+
region?: string;
26+
providerFilter?: string[];
27+
refreshInterval?: number;
28+
defaultContextWindow?: number;
29+
defaultMaxTokens?: number;
30+
};
31+
guardrail?: GuardrailConfig;
32+
};
33+
2234
function createGuardrailWrapStreamFn(
2335
innerWrapStreamFn: (ctx: { modelId: string; streamFn?: StreamFn }) => StreamFn | null | undefined,
2436
guardrailConfig: GuardrailConfig,
@@ -57,9 +69,8 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
5769
const anthropicByModelReplayHooks = buildProviderReplayFamilyHooks({
5870
family: "anthropic-by-model",
5971
});
60-
const guardrail = (api.pluginConfig as Record<string, unknown> | undefined)?.guardrail as
61-
| GuardrailConfig
62-
| undefined;
72+
const pluginConfig = (api.pluginConfig ?? {}) as AmazonBedrockPluginConfig;
73+
const guardrail = pluginConfig.guardrail;
6374

6475
const baseWrapStreamFn = ({ modelId, streamFn }: { modelId: string; streamFn?: StreamFn }) =>
6576
isAnthropicBedrockModel(modelId) ? streamFn : createBedrockNoCacheWrapper(streamFn);
@@ -79,6 +90,7 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
7990
run: async (ctx) => {
8091
const implicit = await resolveImplicitBedrockProvider({
8192
config: ctx.config,
93+
pluginConfig,
8294
env: ctx.env,
8395
});
8496
if (!implicit) {

extensions/github-copilot/index.test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ vi.mock("./register.runtime.js", () => ({
1313
import plugin from "./index.js";
1414

1515
function registerProvider() {
16+
return registerProviderWithPluginConfig({});
17+
}
18+
19+
function registerProviderWithPluginConfig(pluginConfig: Record<string, unknown>) {
1620
const registerProviderMock = vi.fn();
1721

1822
plugin.register(
@@ -21,6 +25,7 @@ function registerProvider() {
2125
name: "GitHub Copilot",
2226
source: "test",
2327
config: {},
28+
pluginConfig,
2429
runtime: {} as never,
2530
registerProvider: registerProviderMock,
2631
}),
@@ -31,11 +36,11 @@ function registerProvider() {
3136
}
3237

3338
describe("github-copilot plugin", () => {
34-
it("skips catalog discovery when models.copilotDiscovery.enabled is false", async () => {
35-
const provider = registerProvider();
39+
it("skips catalog discovery when plugin discovery is disabled", async () => {
40+
const provider = registerProviderWithPluginConfig({ discovery: { enabled: false } });
3641

3742
const result = await provider.catalog.run({
38-
config: { models: { copilotDiscovery: { enabled: false } } },
43+
config: {},
3944
agentDir: "/tmp/agent",
4045
env: { GH_TOKEN: "gh_test_token" },
4146
resolveProviderApiKey: () => ({ apiKey: "gh_test_token" }),

0 commit comments

Comments
 (0)