mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
refactor: use model compat for anthropic tool payload normalization
This commit is contained in:
@@ -837,6 +837,9 @@ export function buildKimiCodingProvider(): ProviderConfig {
|
||||
cost: KIMI_CODING_DEFAULT_COST,
|
||||
contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS,
|
||||
compat: {
|
||||
requiresOpenAiAnthropicToolPayload: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -880,6 +880,57 @@ describe("applyExtraParamsToAgent", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses explicit compat metadata for anthropic tool payload normalization", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {
|
||||
tools: [
|
||||
{
|
||||
name: "read",
|
||||
description: "Read file",
|
||||
input_schema: { type: "object", properties: {} },
|
||||
},
|
||||
],
|
||||
};
|
||||
options?.onPayload?.(payload);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
const agent = { streamFn: baseStreamFn };
|
||||
|
||||
applyExtraParamsToAgent(
|
||||
agent,
|
||||
undefined,
|
||||
"custom-anthropic-proxy",
|
||||
"proxy-model",
|
||||
undefined,
|
||||
"low",
|
||||
);
|
||||
|
||||
const model = {
|
||||
api: "anthropic-messages",
|
||||
provider: "custom-anthropic-proxy",
|
||||
id: "proxy-model",
|
||||
compat: {
|
||||
requiresOpenAiAnthropicToolPayload: true,
|
||||
},
|
||||
} as Model<"anthropic-messages">;
|
||||
const context: Context = { messages: [] };
|
||||
void agent.streamFn?.(model, context, {});
|
||||
|
||||
expect(payloads).toHaveLength(1);
|
||||
expect(payloads[0]?.tools).toEqual([
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "read",
|
||||
description: "Read file",
|
||||
parameters: { type: "object", properties: {} },
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("removes invalid negative Google thinkingBudget and maps Gemini 3.1 to thinkingLevel", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
|
||||
@@ -794,7 +794,7 @@ function createMoonshotThinkingWrapper(
|
||||
function requiresAnthropicToolPayloadCompatibilityForModel(model: {
|
||||
api?: unknown;
|
||||
provider?: unknown;
|
||||
baseUrl?: unknown;
|
||||
compat?: unknown;
|
||||
}): boolean {
|
||||
if (model.api !== "anthropic-messages") {
|
||||
return false;
|
||||
@@ -807,19 +807,49 @@ function requiresAnthropicToolPayloadCompatibilityForModel(model: {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof model.baseUrl !== "string" || !model.baseUrl.trim()) {
|
||||
if (!model.compat || typeof model.compat !== "object" || Array.isArray(model.compat)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(model.baseUrl);
|
||||
const host = parsed.hostname.toLowerCase();
|
||||
const pathname = parsed.pathname.toLowerCase();
|
||||
return host.endsWith("kimi.com") && pathname.startsWith("/coding");
|
||||
} catch {
|
||||
const normalized = model.baseUrl.toLowerCase();
|
||||
return normalized.includes("kimi.com/coding");
|
||||
return (
|
||||
(model.compat as { requiresOpenAiAnthropicToolPayload?: unknown })
|
||||
.requiresOpenAiAnthropicToolPayload === true
|
||||
);
|
||||
}
|
||||
|
||||
function usesOpenAiFunctionAnthropicToolSchemaForModel(model: {
|
||||
provider?: unknown;
|
||||
compat?: unknown;
|
||||
}): boolean {
|
||||
if (typeof model.provider === "string" && usesOpenAiFunctionAnthropicToolSchema(model.provider)) {
|
||||
return true;
|
||||
}
|
||||
if (!model.compat || typeof model.compat !== "object" || Array.isArray(model.compat)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(model.compat as { requiresOpenAiAnthropicToolPayload?: unknown })
|
||||
.requiresOpenAiAnthropicToolPayload === true
|
||||
);
|
||||
}
|
||||
|
||||
function usesOpenAiStringModeAnthropicToolChoiceForModel(model: {
|
||||
provider?: unknown;
|
||||
compat?: unknown;
|
||||
}): boolean {
|
||||
if (
|
||||
typeof model.provider === "string" &&
|
||||
usesOpenAiStringModeAnthropicToolChoice(model.provider)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (!model.compat || typeof model.compat !== "object" || Array.isArray(model.compat)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(model.compat as { requiresOpenAiAnthropicToolPayload?: unknown })
|
||||
.requiresOpenAiAnthropicToolPayload === true
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeOpenAiFunctionAnthropicToolDefinition(
|
||||
@@ -903,19 +933,21 @@ function createAnthropicToolPayloadCompatibilityWrapper(
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
const provider = typeof model.provider === "string" ? model.provider : undefined;
|
||||
if (
|
||||
payload &&
|
||||
typeof payload === "object" &&
|
||||
requiresAnthropicToolPayloadCompatibilityForModel(model)
|
||||
) {
|
||||
const payloadObj = payload as Record<string, unknown>;
|
||||
if (Array.isArray(payloadObj.tools) && usesOpenAiFunctionAnthropicToolSchema(provider)) {
|
||||
if (
|
||||
Array.isArray(payloadObj.tools) &&
|
||||
usesOpenAiFunctionAnthropicToolSchemaForModel(model)
|
||||
) {
|
||||
payloadObj.tools = payloadObj.tools
|
||||
.map((tool) => normalizeOpenAiFunctionAnthropicToolDefinition(tool))
|
||||
.filter((tool): tool is Record<string, unknown> => !!tool);
|
||||
}
|
||||
if (usesOpenAiStringModeAnthropicToolChoice(provider)) {
|
||||
if (usesOpenAiStringModeAnthropicToolChoiceForModel(model)) {
|
||||
payloadObj.tool_choice = normalizeOpenAiStringModeAnthropicToolChoice(
|
||||
payloadObj.tool_choice,
|
||||
);
|
||||
|
||||
@@ -26,6 +26,7 @@ export type ModelCompatConfig = {
|
||||
requiresAssistantAfterToolResult?: boolean;
|
||||
requiresThinkingAsText?: boolean;
|
||||
requiresMistralToolIds?: boolean;
|
||||
requiresOpenAiAnthropicToolPayload?: boolean;
|
||||
};
|
||||
|
||||
export type ModelProviderAuthMode = "api-key" | "aws-sdk" | "oauth" | "token";
|
||||
|
||||
Reference in New Issue
Block a user