mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-07 10:34:44 +00:00
fix(providers): apply auth patch deletions
This commit is contained in:
@@ -167,7 +167,22 @@ describe("Bedrock profile endpoint resolution", () => {
|
||||
});
|
||||
|
||||
describe("Bedrock thinking effort mapping", () => {
|
||||
it("caps max effort at high for Claude Sonnet 4.6", () => {
|
||||
it("forces adaptive thinking for mandatory Claude models when callers omit reasoning", () => {
|
||||
const model = bedrockModel({
|
||||
id: "anthropic.claude-sonnet-4-6-v1:0",
|
||||
name: "Claude Sonnet 4.6",
|
||||
reasoning: true,
|
||||
});
|
||||
const options = testing.resolveSimpleBedrockOptions(model, {});
|
||||
|
||||
expect(options.reasoning).toBe("high");
|
||||
expect(testing.buildAdditionalModelRequestFields(model, options)).toEqual({
|
||||
thinking: { type: "adaptive", display: "summarized" },
|
||||
output_config: { effort: "high" },
|
||||
});
|
||||
});
|
||||
|
||||
it("clamps max effort for Claude models without native max support", () => {
|
||||
expect(
|
||||
testing.mapThinkingLevelToEffort(
|
||||
bedrockModel({
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
type ToolResultMessage,
|
||||
} from "openclaw/plugin-sdk/llm";
|
||||
import {
|
||||
isClaudeAdaptiveThinkingDefaultModelId,
|
||||
resolveClaudeFable5ModelIdentity,
|
||||
resolveClaudeModelIdentity,
|
||||
supportsClaudeAdaptiveThinking,
|
||||
@@ -351,29 +352,38 @@ export const streamSimpleBedrock: StreamFunction<"bedrock-converse-stream", Simp
|
||||
model: Model<"bedrock-converse-stream">,
|
||||
context: Context,
|
||||
options?: SimpleStreamOptions,
|
||||
) => {
|
||||
) => streamBedrock(model, context, resolveSimpleBedrockOptions(model, options));
|
||||
|
||||
function resolveSimpleBedrockOptions(
|
||||
model: Model<"bedrock-converse-stream">,
|
||||
options?: SimpleStreamOptions,
|
||||
): BedrockOptions {
|
||||
const base = buildBaseOptions(model, options, undefined);
|
||||
if (usesClaudeFable5BedrockContract(model)) {
|
||||
return streamBedrock(model, context, {
|
||||
return {
|
||||
...base,
|
||||
reasoning: options?.reasoning ?? "high",
|
||||
thinkingBudgets: options?.thinkingBudgets,
|
||||
} satisfies BedrockOptions);
|
||||
} satisfies BedrockOptions;
|
||||
}
|
||||
if (!options?.reasoning) {
|
||||
return streamBedrock(model, context, {
|
||||
const reasoning =
|
||||
isAnthropicClaudeModel(model) && requiresMandatoryAdaptiveThinking(model)
|
||||
? "high"
|
||||
: undefined;
|
||||
return {
|
||||
...base,
|
||||
reasoning: undefined,
|
||||
} satisfies BedrockOptions);
|
||||
reasoning,
|
||||
} satisfies BedrockOptions;
|
||||
}
|
||||
|
||||
if (isAnthropicClaudeModel(model)) {
|
||||
if (supportsAdaptiveThinking(model)) {
|
||||
return streamBedrock(model, context, {
|
||||
return {
|
||||
...base,
|
||||
reasoning: options.reasoning,
|
||||
thinkingBudgets: options.thinkingBudgets,
|
||||
} satisfies BedrockOptions);
|
||||
} satisfies BedrockOptions;
|
||||
}
|
||||
|
||||
// Undefined means the caller did not request an output cap; let the helper use the model cap.
|
||||
@@ -385,7 +395,7 @@ export const streamSimpleBedrock: StreamFunction<"bedrock-converse-stream", Simp
|
||||
options.thinkingBudgets,
|
||||
);
|
||||
|
||||
return streamBedrock(model, context, {
|
||||
return {
|
||||
...base,
|
||||
maxTokens: adjusted.maxTokens,
|
||||
reasoning: options.reasoning,
|
||||
@@ -393,15 +403,15 @@ export const streamSimpleBedrock: StreamFunction<"bedrock-converse-stream", Simp
|
||||
...options.thinkingBudgets,
|
||||
[clampReasoning(options.reasoning)!]: adjusted.thinkingBudget,
|
||||
},
|
||||
} satisfies BedrockOptions);
|
||||
} satisfies BedrockOptions;
|
||||
}
|
||||
|
||||
return streamBedrock(model, context, {
|
||||
return {
|
||||
...base,
|
||||
reasoning: options.reasoning,
|
||||
thinkingBudgets: options.thinkingBudgets,
|
||||
} satisfies BedrockOptions);
|
||||
};
|
||||
} satisfies BedrockOptions;
|
||||
}
|
||||
|
||||
function handleContentBlockStart(
|
||||
event: ContentBlockStartEvent,
|
||||
@@ -565,6 +575,14 @@ function supportsAdaptiveThinking(model: Model<"bedrock-converse-stream">): bool
|
||||
);
|
||||
}
|
||||
|
||||
function requiresMandatoryAdaptiveThinking(model: Model<"bedrock-converse-stream">): boolean {
|
||||
const profileModelId = resolveClaudeProfileNameModelId(model.name);
|
||||
return (
|
||||
isClaudeAdaptiveThinkingDefaultModelId(resolveClaudeModelIdentity(model)) ||
|
||||
(profileModelId ? isClaudeAdaptiveThinkingDefaultModelId(profileModelId) : false)
|
||||
);
|
||||
}
|
||||
|
||||
function supportsNativeXhighEffort(model: Model<"bedrock-converse-stream">): boolean {
|
||||
const profileModelId = resolveClaudeProfileNameModelId(model.name);
|
||||
return (
|
||||
@@ -1071,9 +1089,11 @@ function createImageBlock(mimeType: string, data: string) {
|
||||
|
||||
/** Test-only hooks for Bedrock runtime conversion and endpoint policy. */
|
||||
export const testing = {
|
||||
buildAdditionalModelRequestFields,
|
||||
convertMessages,
|
||||
getConfiguredBedrockRegion,
|
||||
hasConfiguredBedrockProfile,
|
||||
mapThinkingLevelToEffort,
|
||||
resolveSimpleBedrockOptions,
|
||||
shouldUseExplicitBedrockEndpoint,
|
||||
};
|
||||
|
||||
@@ -1307,11 +1307,11 @@ describe("microsoft-foundry plugin", () => {
|
||||
const provider = result.configPatch?.models?.providers?.["microsoft-foundry"] as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
expect(provider).toMatchObject({
|
||||
authHeader: true,
|
||||
apiKey: null,
|
||||
headers: null,
|
||||
});
|
||||
expect(provider?.authHeader).toBe(true);
|
||||
expect(Object.hasOwn(provider ?? {}, "apiKey")).toBe(true);
|
||||
expect(Object.hasOwn(provider ?? {}, "headers")).toBe(true);
|
||||
expect(provider?.apiKey).toBeUndefined();
|
||||
expect(provider?.headers).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -111,8 +111,8 @@ type FoundryModelCapabilities = {
|
||||
};
|
||||
|
||||
type FoundryProviderConfigPatch = Omit<ModelProviderConfig, "apiKey" | "headers"> & {
|
||||
apiKey?: SecretInput | null;
|
||||
headers?: Record<string, SecretInput> | null;
|
||||
apiKey?: SecretInput | undefined;
|
||||
headers?: Record<string, SecretInput> | undefined;
|
||||
};
|
||||
|
||||
function normalizeModelInput(input?: unknown): Array<"text" | "image"> {
|
||||
@@ -481,7 +481,7 @@ function buildFoundryProviderConfig(
|
||||
: {}),
|
||||
}
|
||||
: isEntraIdAuth
|
||||
? { authHeader: true, apiKey: null, headers: null }
|
||||
? { authHeader: true, apiKey: undefined, headers: undefined }
|
||||
: {}),
|
||||
models: deployments.map((deployment) => {
|
||||
const capabilities = resolveFoundryModelCapabilities(
|
||||
|
||||
@@ -118,6 +118,43 @@ describe("applyProviderAuthConfigPatch", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("deletes provider auth fields marked undefined by auth patches", () => {
|
||||
const baseLocal = {
|
||||
models: {
|
||||
providers: {
|
||||
"microsoft-foundry": {
|
||||
baseUrl: "https://example.services.ai.azure.com/openai/v1",
|
||||
api: "anthropic-messages",
|
||||
authHeader: false,
|
||||
apiKey: "FOUNDRY_API_KEY",
|
||||
headers: { "api-key": "FOUNDRY_API_KEY" },
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig;
|
||||
const patch = {
|
||||
models: {
|
||||
providers: {
|
||||
"microsoft-foundry": {
|
||||
authHeader: true,
|
||||
apiKey: undefined,
|
||||
headers: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const next = applyProviderAuthConfigPatch(baseLocal, patch);
|
||||
const provider = next.models?.providers?.["microsoft-foundry"] as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
|
||||
expect(provider).toMatchObject({ authHeader: true });
|
||||
expect(provider).not.toHaveProperty("apiKey");
|
||||
expect(provider).not.toHaveProperty("headers");
|
||||
});
|
||||
|
||||
it("normalizes retired Google Gemini model refs from provider config patches", () => {
|
||||
const patch = {
|
||||
agents: {
|
||||
|
||||
@@ -94,6 +94,22 @@ function mergeConfigPatch<T>(base: T, patch: unknown): T {
|
||||
return next as T;
|
||||
}
|
||||
|
||||
function deleteUndefinedPatchLeaves<T>(target: T, patch: unknown): T {
|
||||
if (!isPlainRecord(target) || !isPlainRecord(patch)) {
|
||||
return target;
|
||||
}
|
||||
|
||||
const targetRecord = target as Record<string, unknown>;
|
||||
for (const [key, value] of Object.entries(patch)) {
|
||||
if (value === undefined) {
|
||||
delete targetRecord[key];
|
||||
continue;
|
||||
}
|
||||
deleteUndefinedPatchLeaves(targetRecord[key], value);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function normalizeAgentModelConfigForWrite(value: unknown): unknown {
|
||||
if (typeof value === "string") {
|
||||
return normalizeAgentModelRefForConfig(value);
|
||||
@@ -259,7 +275,9 @@ export function applyProviderAuthConfigPatch(
|
||||
patch: unknown,
|
||||
options?: { replaceDefaultModels?: boolean },
|
||||
): OpenClawConfig {
|
||||
const merged = normalizeConfigModelRefsForWrite(mergeConfigPatch(cfg, patch));
|
||||
const merged = normalizeConfigModelRefsForWrite(
|
||||
deleteUndefinedPatchLeaves(mergeConfigPatch(cfg, patch), patch),
|
||||
);
|
||||
if (!options?.replaceDefaultModels || !isPlainRecord(patch)) {
|
||||
return merged;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user