fix(anthropic): scope api default normalization

This commit is contained in:
Peter Steinberger
2026-04-21 07:48:21 +01:00
parent 0532feb0d3
commit 0d305839e5
6 changed files with 74 additions and 4 deletions

View File

@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Exec/YOLO: stop rejecting gateway-host exec in `security=full` plus `ask=off` mode via the Python/Node script preflight hardening path, so promptless YOLO exec once again runs direct interpreter stdin and heredoc forms such as `node <<'NODE' ... NODE`.
- Anthropic/plugins: scope Anthropic `api: "anthropic-messages"` defaulting to Anthropic-owned providers, so `openai-codex` and other providers without an explicit `api` no longer get rewritten to the wrong transport. Fixes #64534.
- fix(qqbot): add SSRF guard to direct-upload URL paths in uploadC2CMedia and uploadGroupMedia [AI-assisted]. (#69595) Thanks @pgondhi987.
- fix(gateway): enforce allowRequestSessionKey gate on template-rendered mapping sessionKeys. (#69381) Thanks @pgondhi987.
- Webchat/images: treat inline image attachments as media for empty-turn gating while still ignoring metadata-only blank turns. (#69474) Thanks @Jaswir.

View File

@@ -159,6 +159,16 @@ export function normalizeAnthropicProviderConfig<T extends { api?: string; model
return { ...providerConfig, api: ANTHROPIC_PROVIDER_API };
}
export function normalizeAnthropicProviderConfigForProvider<
T extends { api?: string; models?: unknown[] },
>(params: { provider: string; providerConfig: T }): T {
const provider = normalizeProviderId(params.provider);
if (provider !== "anthropic" && provider !== CLAUDE_CLI_BACKEND_ID) {
return params.providerConfig;
}
return normalizeAnthropicProviderConfig(params.providerConfig);
}
export function applyAnthropicConfigDefaults(params: {
config: OpenClawConfig;
env: NodeJS.ProcessEnv;

View File

@@ -100,6 +100,36 @@ describe("anthropic provider replay hooks", () => {
});
});
it("defaults Claude CLI provider api through plugin config normalization", async () => {
const provider = await registerSingleProviderPlugin(anthropicPlugin);
expect(
provider.normalizeConfig?.({
provider: "claude-cli",
providerConfig: {
models: [{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }],
},
} as never),
).toMatchObject({
api: "anthropic-messages",
});
});
it("does not default non-Anthropic provider api through plugin config normalization", async () => {
const provider = await registerSingleProviderPlugin(anthropicPlugin);
const providerConfig = {
baseUrl: "https://chatgpt.com/backend-api/codex",
models: [{ id: "gpt-5.4", name: "GPT-5.4" }],
};
expect(
provider.normalizeConfig?.({
provider: "openai-codex",
providerConfig,
} as never),
).toBe(providerConfig);
});
it("applies Anthropic pruning defaults through plugin hooks", async () => {
const provider = await registerSingleProviderPlugin(anthropicPlugin);

View File

@@ -35,6 +35,34 @@ describe("anthropic provider policy public artifact", () => {
});
});
it("normalizes Claude CLI provider config", () => {
expect(
normalizeConfig({
provider: "claude-cli",
providerConfig: {
baseUrl: "https://api.anthropic.com",
models: [createModel("claude-sonnet-4-6", "Claude Sonnet 4.6")],
},
}),
).toMatchObject({
api: "anthropic-messages",
});
});
it("does not normalize non-Anthropic provider config", () => {
const providerConfig = {
baseUrl: "https://chatgpt.com/backend-api/codex",
models: [createModel("gpt-5.4", "GPT-5.4")],
};
expect(
normalizeConfig({
provider: "openai-codex",
providerConfig,
}),
).toBe(providerConfig);
});
it("applies Anthropic API-key defaults without loading the full provider plugin", () => {
const nextConfig = applyConfigDefaults({
config: {

View File

@@ -1,11 +1,11 @@
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types";
import {
applyAnthropicConfigDefaults,
normalizeAnthropicProviderConfig,
normalizeAnthropicProviderConfigForProvider,
} from "./config-defaults.js";
export function normalizeConfig(params: { provider: string; providerConfig: ModelProviderConfig }) {
return normalizeAnthropicProviderConfig(params.providerConfig);
return normalizeAnthropicProviderConfigForProvider(params);
}
export function applyConfigDefaults(params: Parameters<typeof applyAnthropicConfigDefaults>[0]) {

View File

@@ -34,7 +34,7 @@ import {
} from "./cli-shared.js";
import {
applyAnthropicConfigDefaults,
normalizeAnthropicProviderConfig,
normalizeAnthropicProviderConfigForProvider,
} from "./config-defaults.js";
import { anthropicMediaUnderstandingProvider } from "./media-understanding-provider.js";
import { buildAnthropicReplayPolicy } from "./replay-policy.js";
@@ -483,7 +483,8 @@ export function buildAnthropicProvider(): ProviderPlugin {
},
}),
],
normalizeConfig: ({ providerConfig }) => normalizeAnthropicProviderConfig(providerConfig),
normalizeConfig: ({ provider, providerConfig }) =>
normalizeAnthropicProviderConfigForProvider({ provider, providerConfig }),
applyConfigDefaults: ({ config, env }) => applyAnthropicConfigDefaults({ config, env }),
resolveDynamicModel: (ctx) => resolveAnthropicForwardCompatModel(ctx),
resolveSyntheticAuth: ({ provider }) =>