mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix(amazon-bedrock): refresh live discovery and guardrail config
This commit is contained in:
@@ -113,6 +113,7 @@ Docs: https://docs.openclaw.ai
|
||||
- GitHub Copilot: re-read plugin discovery config from the live runtime snapshot, so toggling `plugins.entries.github-copilot.config.discovery.enabled` takes effect without a restart. Thanks @vincentkoc.
|
||||
- Ollama: re-read plugin discovery config from the live runtime snapshot, so toggling `plugins.entries.ollama.config.discovery.enabled` takes effect without a restart. Thanks @vincentkoc.
|
||||
- OpenAI: re-read the plugin prompt-overlay personality from live runtime config, so GPT-5 system prompt contributions update without a restart when `plugins.entries.openai.config.personality` changes. Thanks @vincentkoc.
|
||||
- Amazon Bedrock: re-read live discovery and guardrail plugin config, so toggling `plugins.entries.amazon-bedrock.config.discovery` or `plugins.entries.amazon-bedrock.config.guardrail` takes effect without a restart. Thanks @vincentkoc.
|
||||
- Agents/subagents: drop bare `NO_REPLY` from the parent turn when the session still has pending spawned children, so direct-conversation surfaces such as Telegram DMs no longer rewrite the sentinel into visible fallback chatter while waiting for the child completion event. (#69942) Thanks @neeravmakwana.
|
||||
- Plugins/install: keep bundled plugin dependencies off npm install while repairing them when plugins activate from a packaged install, including Feishu/Lark, Browser, and direct bundled channel setup-entry loads.
|
||||
- CLI/channels: skip and cache bundled channel plugin, setup, and secrets load failures during read-only discovery, so one broken unused bundled channel cannot crash `openclaw status` or bootstrap secret scans.
|
||||
|
||||
@@ -7,9 +7,15 @@ import type { PluginRuntime } from "../../src/plugins/runtime/types.js";
|
||||
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
|
||||
import amazonBedrockPlugin from "./index.js";
|
||||
|
||||
type InferenceProfileResult = { models?: Array<{ modelArn?: string }> } | Error;
|
||||
type BedrockClientResult =
|
||||
| {
|
||||
models?: Array<{ modelArn?: string }>;
|
||||
modelSummaries?: Array<Record<string, unknown>>;
|
||||
inferenceProfileSummaries?: Array<Record<string, unknown>>;
|
||||
}
|
||||
| Error;
|
||||
|
||||
const inferenceProfileResults: InferenceProfileResult[] = [];
|
||||
const inferenceProfileResults: BedrockClientResult[] = [];
|
||||
const bedrockClientConfigs: Array<Record<string, unknown>> = [];
|
||||
const sendGetInferenceProfile = vi.fn(async () => {
|
||||
const next = inferenceProfileResults.shift();
|
||||
@@ -24,6 +30,14 @@ vi.mock("@aws-sdk/client-bedrock", () => {
|
||||
constructor(readonly input: { inferenceProfileIdentifier: string }) {}
|
||||
}
|
||||
|
||||
class ListFoundationModelsCommand {
|
||||
constructor(readonly input: Record<string, unknown> = {}) {}
|
||||
}
|
||||
|
||||
class ListInferenceProfilesCommand {
|
||||
constructor(readonly input: Record<string, unknown> = {}) {}
|
||||
}
|
||||
|
||||
class BedrockClient {
|
||||
constructor(config: Record<string, unknown> = {}) {
|
||||
bedrockClientConfigs.push(config);
|
||||
@@ -35,6 +49,8 @@ vi.mock("@aws-sdk/client-bedrock", () => {
|
||||
return {
|
||||
BedrockClient,
|
||||
GetInferenceProfileCommand,
|
||||
ListFoundationModelsCommand,
|
||||
ListInferenceProfilesCommand,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -113,10 +129,12 @@ function callWrappedStream(
|
||||
provider: RegisteredProviderPlugin,
|
||||
modelId: string,
|
||||
modelDescriptor: never,
|
||||
config?: OpenClawConfig,
|
||||
): Record<string, unknown> {
|
||||
const wrapped = provider.wrapStreamFn?.({
|
||||
provider: "amazon-bedrock",
|
||||
modelId,
|
||||
config,
|
||||
streamFn: spyStreamFn,
|
||||
} as never);
|
||||
|
||||
@@ -138,6 +156,31 @@ function callWrappedStream(
|
||||
return result;
|
||||
}
|
||||
|
||||
async function runCatalog(
|
||||
provider: RegisteredProviderPlugin,
|
||||
config: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv = {} as NodeJS.ProcessEnv,
|
||||
) {
|
||||
return provider.catalog?.run({
|
||||
config,
|
||||
env,
|
||||
} as never);
|
||||
}
|
||||
|
||||
function runtimePluginConfig(config?: Record<string, unknown>): OpenClawConfig {
|
||||
return {
|
||||
plugins: {
|
||||
entries: config
|
||||
? {
|
||||
"amazon-bedrock": {
|
||||
config,
|
||||
},
|
||||
}
|
||||
: {},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
describe("amazon-bedrock provider plugin", () => {
|
||||
beforeEach(() => {
|
||||
inferenceProfileResults.length = 0;
|
||||
@@ -354,6 +397,91 @@ describe("amazon-bedrock provider plugin", () => {
|
||||
// Non-Anthropic models should also get cacheRetention: "none"
|
||||
expect(result).toMatchObject({ cacheRetention: "none" });
|
||||
});
|
||||
|
||||
it("uses live plugin config to inject guardrailConfig after startup disable", async () => {
|
||||
const provider = await registerWithConfig(undefined);
|
||||
const result = callWrappedStream(
|
||||
provider,
|
||||
NON_ANTHROPIC_MODEL,
|
||||
MODEL_DESCRIPTOR,
|
||||
runtimePluginConfig({
|
||||
guardrail: {
|
||||
guardrailIdentifier: "live-guardrail",
|
||||
guardrailVersion: "7",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result._capturedPayload).toEqual({
|
||||
guardrailConfig: {
|
||||
guardrailIdentifier: "live-guardrail",
|
||||
guardrailVersion: "7",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not revive startup guardrail config when the live plugin entry is removed", async () => {
|
||||
const provider = await registerWithConfig({
|
||||
guardrail: {
|
||||
guardrailIdentifier: "startup-guardrail",
|
||||
guardrailVersion: "5",
|
||||
},
|
||||
});
|
||||
const result = callWrappedStream(
|
||||
provider,
|
||||
NON_ANTHROPIC_MODEL,
|
||||
MODEL_DESCRIPTOR,
|
||||
runtimePluginConfig(undefined),
|
||||
);
|
||||
|
||||
expect(result).not.toHaveProperty("_capturedPayload");
|
||||
expect(result).toMatchObject({ cacheRetention: "none" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("discovery config", () => {
|
||||
it("uses live plugin config to re-enable discovery after startup disable", async () => {
|
||||
inferenceProfileResults.push(
|
||||
{
|
||||
modelSummaries: [
|
||||
{
|
||||
modelId: NON_ANTHROPIC_MODEL,
|
||||
modelName: "Nova Micro",
|
||||
providerName: "Amazon",
|
||||
inputModalities: ["TEXT"],
|
||||
outputModalities: ["TEXT"],
|
||||
responseStreamingSupported: true,
|
||||
modelLifecycle: { status: "ACTIVE" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
inferenceProfileSummaries: [],
|
||||
},
|
||||
);
|
||||
const provider = await registerWithConfig({
|
||||
discovery: {
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
const catalog = await runCatalog(
|
||||
provider,
|
||||
runtimePluginConfig({
|
||||
discovery: {
|
||||
enabled: true,
|
||||
region: "us-east-1",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(catalog).toMatchObject({
|
||||
provider: {
|
||||
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
|
||||
api: "bedrock-converse-stream",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("application inference profile cache point injection", () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import { resolvePluginConfigObject } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
ANTHROPIC_BY_MODEL_REPLAY_HOOKS,
|
||||
@@ -249,8 +250,17 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
|
||||
/ModelStreamErrorException.*(?:Input is too long|too many input tokens)/i,
|
||||
] as const;
|
||||
const anthropicByModelReplayHooks = ANTHROPIC_BY_MODEL_REPLAY_HOOKS;
|
||||
const pluginConfig = (api.pluginConfig ?? {}) as AmazonBedrockPluginConfig;
|
||||
const guardrail = pluginConfig.guardrail;
|
||||
const startupPluginConfig = (api.pluginConfig ?? {}) as AmazonBedrockPluginConfig;
|
||||
|
||||
function resolveCurrentPluginConfig(
|
||||
config: { plugins?: { entries?: Record<string, unknown> } } | undefined,
|
||||
): AmazonBedrockPluginConfig | undefined {
|
||||
const runtimePluginConfig = resolvePluginConfigObject(config, providerId);
|
||||
return (
|
||||
(runtimePluginConfig as AmazonBedrockPluginConfig | undefined) ??
|
||||
(config ? undefined : startupPluginConfig)
|
||||
);
|
||||
}
|
||||
|
||||
api.registerMemoryEmbeddingProvider(bedrockMemoryEmbeddingProviderAdapter);
|
||||
|
||||
@@ -266,11 +276,6 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
|
||||
return createBedrockNoCacheWrapper(streamFn);
|
||||
};
|
||||
|
||||
const cacheWrapStreamFn =
|
||||
guardrail?.guardrailIdentifier && guardrail?.guardrailVersion
|
||||
? createGuardrailWrapStreamFn(baseWrapStreamFn, guardrail)
|
||||
: baseWrapStreamFn;
|
||||
|
||||
/** Extract the AWS region from a bedrock-runtime baseUrl. */
|
||||
function extractRegionFromBaseUrl(baseUrl: string | undefined): string | undefined {
|
||||
if (!baseUrl) {
|
||||
@@ -321,9 +326,10 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: async (ctx) => {
|
||||
const currentPluginConfig = resolveCurrentPluginConfig(ctx.config);
|
||||
const implicit = await resolveImplicitBedrockProvider({
|
||||
config: ctx.config,
|
||||
pluginConfig,
|
||||
pluginConfig: currentPluginConfig,
|
||||
env: ctx.env,
|
||||
});
|
||||
if (!implicit) {
|
||||
@@ -340,8 +346,12 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
|
||||
resolveConfigApiKey: ({ env }) => resolveBedrockConfigApiKey(env),
|
||||
...anthropicByModelReplayHooks,
|
||||
wrapStreamFn: ({ modelId, config, model, streamFn }) => {
|
||||
const currentGuardrail = resolveCurrentPluginConfig(config)?.guardrail;
|
||||
// Apply cache + guardrail wrapping.
|
||||
const wrapped = cacheWrapStreamFn({ modelId, streamFn });
|
||||
const wrapped =
|
||||
currentGuardrail?.guardrailIdentifier && currentGuardrail?.guardrailVersion
|
||||
? createGuardrailWrapStreamFn(baseWrapStreamFn, currentGuardrail)({ modelId, streamFn })
|
||||
: baseWrapStreamFn({ modelId, streamFn });
|
||||
const region = resolveBedrockRegion(config) ?? extractRegionFromBaseUrl(model?.baseUrl);
|
||||
const mayNeedCacheInjection =
|
||||
isBedrockAppInferenceProfile(modelId) && !piAiWouldInjectCachePoints(modelId);
|
||||
|
||||
Reference in New Issue
Block a user