mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-23 07:01:40 +00:00
248 lines
7.7 KiB
TypeScript
248 lines
7.7 KiB
TypeScript
import type { OpenClawConfig } from "../config/config.js";
|
|
import { resolveProviderCapabilitiesWithPlugin as resolveProviderCapabilitiesWithPluginRuntime } from "../plugins/provider-runtime.js";
|
|
import { normalizeProviderId } from "./provider-id.js";
|
|
|
|
export type ProviderCapabilities = {
|
|
anthropicToolSchemaMode: "native" | "openai-functions";
|
|
anthropicToolChoiceMode: "native" | "openai-string-modes";
|
|
openAiPayloadNormalizationMode: "default" | "moonshot-thinking";
|
|
providerFamily: "default" | "openai" | "anthropic";
|
|
preserveAnthropicThinkingSignatures: boolean;
|
|
openAiCompatTurnValidation: boolean;
|
|
geminiThoughtSignatureSanitization: boolean;
|
|
transcriptToolCallIdMode: "default" | "strict9";
|
|
transcriptToolCallIdModelHints: string[];
|
|
geminiThoughtSignatureModelHints: string[];
|
|
dropThinkingBlockModelHints: string[];
|
|
};
|
|
|
|
export type ProviderCapabilityLookupOptions = {
|
|
config?: OpenClawConfig;
|
|
workspaceDir?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
};
|
|
|
|
const DEFAULT_PROVIDER_CAPABILITIES: ProviderCapabilities = {
|
|
anthropicToolSchemaMode: "native",
|
|
anthropicToolChoiceMode: "native",
|
|
openAiPayloadNormalizationMode: "default",
|
|
providerFamily: "default",
|
|
preserveAnthropicThinkingSignatures: true,
|
|
openAiCompatTurnValidation: true,
|
|
geminiThoughtSignatureSanitization: false,
|
|
transcriptToolCallIdMode: "default",
|
|
transcriptToolCallIdModelHints: [],
|
|
geminiThoughtSignatureModelHints: [],
|
|
dropThinkingBlockModelHints: [],
|
|
};
|
|
|
|
const CORE_PROVIDER_CAPABILITIES: Record<string, Partial<ProviderCapabilities>> = {
|
|
"anthropic-vertex": {
|
|
providerFamily: "anthropic",
|
|
dropThinkingBlockModelHints: ["claude"],
|
|
},
|
|
"amazon-bedrock": {
|
|
providerFamily: "anthropic",
|
|
dropThinkingBlockModelHints: ["claude"],
|
|
},
|
|
};
|
|
|
|
const PLUGIN_CAPABILITIES_FALLBACKS: Record<string, Partial<ProviderCapabilities>> = {
|
|
anthropic: {
|
|
providerFamily: "anthropic",
|
|
dropThinkingBlockModelHints: ["claude"],
|
|
},
|
|
mistral: {
|
|
transcriptToolCallIdMode: "strict9",
|
|
transcriptToolCallIdModelHints: [
|
|
"mistral",
|
|
"mixtral",
|
|
"codestral",
|
|
"pixtral",
|
|
"devstral",
|
|
"ministral",
|
|
"mistralai",
|
|
],
|
|
},
|
|
moonshot: {
|
|
openAiPayloadNormalizationMode: "moonshot-thinking",
|
|
},
|
|
kimi: {
|
|
openAiPayloadNormalizationMode: "moonshot-thinking",
|
|
},
|
|
opencode: {
|
|
openAiCompatTurnValidation: false,
|
|
geminiThoughtSignatureSanitization: true,
|
|
geminiThoughtSignatureModelHints: ["gemini"],
|
|
},
|
|
"opencode-go": {
|
|
openAiCompatTurnValidation: false,
|
|
geminiThoughtSignatureSanitization: true,
|
|
geminiThoughtSignatureModelHints: ["gemini"],
|
|
},
|
|
openai: {
|
|
providerFamily: "openai",
|
|
},
|
|
};
|
|
|
|
const defaultResolveProviderCapabilitiesWithPlugin = resolveProviderCapabilitiesWithPluginRuntime;
|
|
const providerCapabilityDeps = {
|
|
resolveProviderCapabilitiesWithPlugin: defaultResolveProviderCapabilitiesWithPlugin,
|
|
};
|
|
|
|
export const __testing = {
|
|
setResolveProviderCapabilitiesWithPluginForTest(
|
|
resolveProviderCapabilitiesWithPlugin?: typeof defaultResolveProviderCapabilitiesWithPlugin,
|
|
): void {
|
|
providerCapabilityDeps.resolveProviderCapabilitiesWithPlugin =
|
|
resolveProviderCapabilitiesWithPlugin ?? defaultResolveProviderCapabilitiesWithPlugin;
|
|
},
|
|
resetDepsForTests(): void {
|
|
providerCapabilityDeps.resolveProviderCapabilitiesWithPlugin =
|
|
defaultResolveProviderCapabilitiesWithPlugin;
|
|
},
|
|
};
|
|
|
|
export function resolveProviderCapabilities(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): ProviderCapabilities {
|
|
const normalized = normalizeProviderId(provider ?? "");
|
|
const pluginCapabilities = normalized
|
|
? providerCapabilityDeps.resolveProviderCapabilitiesWithPlugin({
|
|
provider: normalized,
|
|
config: options?.config,
|
|
workspaceDir: options?.workspaceDir,
|
|
env: options?.env,
|
|
})
|
|
: undefined;
|
|
return {
|
|
...DEFAULT_PROVIDER_CAPABILITIES,
|
|
...CORE_PROVIDER_CAPABILITIES[normalized],
|
|
...PLUGIN_CAPABILITIES_FALLBACKS[normalized],
|
|
...pluginCapabilities,
|
|
};
|
|
}
|
|
|
|
export function preservesAnthropicThinkingSignatures(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): boolean {
|
|
return resolveProviderCapabilities(provider, options).preserveAnthropicThinkingSignatures;
|
|
}
|
|
|
|
export function requiresOpenAiCompatibleAnthropicToolPayload(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): boolean {
|
|
const capabilities = resolveProviderCapabilities(provider, options);
|
|
return (
|
|
capabilities.anthropicToolSchemaMode !== "native" ||
|
|
capabilities.anthropicToolChoiceMode !== "native"
|
|
);
|
|
}
|
|
|
|
export function usesOpenAiFunctionAnthropicToolSchema(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): boolean {
|
|
return (
|
|
resolveProviderCapabilities(provider, options).anthropicToolSchemaMode === "openai-functions"
|
|
);
|
|
}
|
|
|
|
export function usesOpenAiStringModeAnthropicToolChoice(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): boolean {
|
|
return (
|
|
resolveProviderCapabilities(provider, options).anthropicToolChoiceMode === "openai-string-modes"
|
|
);
|
|
}
|
|
|
|
export function supportsOpenAiCompatTurnValidation(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): boolean {
|
|
return resolveProviderCapabilities(provider, options).openAiCompatTurnValidation;
|
|
}
|
|
|
|
export function usesMoonshotThinkingPayloadCompat(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): boolean {
|
|
return (
|
|
resolveProviderCapabilities(provider, options).openAiPayloadNormalizationMode ===
|
|
"moonshot-thinking"
|
|
);
|
|
}
|
|
|
|
export function sanitizesGeminiThoughtSignatures(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): boolean {
|
|
return resolveProviderCapabilities(provider, options).geminiThoughtSignatureSanitization;
|
|
}
|
|
|
|
function modelIncludesAnyHint(modelId: string | null | undefined, hints: string[]): boolean {
|
|
const normalized = (modelId ?? "").toLowerCase();
|
|
return Boolean(normalized) && hints.some((hint) => normalized.includes(hint));
|
|
}
|
|
|
|
export function isOpenAiProviderFamily(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): boolean {
|
|
return resolveProviderCapabilities(provider, options).providerFamily === "openai";
|
|
}
|
|
|
|
export function isAnthropicProviderFamily(
|
|
provider?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): boolean {
|
|
return resolveProviderCapabilities(provider, options).providerFamily === "anthropic";
|
|
}
|
|
|
|
export function shouldDropThinkingBlocksForModel(params: {
|
|
provider?: string | null;
|
|
modelId?: string | null;
|
|
config?: OpenClawConfig;
|
|
workspaceDir?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
}): boolean {
|
|
return modelIncludesAnyHint(
|
|
params.modelId,
|
|
resolveProviderCapabilities(params.provider, params).dropThinkingBlockModelHints,
|
|
);
|
|
}
|
|
|
|
export function shouldSanitizeGeminiThoughtSignaturesForModel(params: {
|
|
provider?: string | null;
|
|
modelId?: string | null;
|
|
config?: OpenClawConfig;
|
|
workspaceDir?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
}): boolean {
|
|
const capabilities = resolveProviderCapabilities(params.provider, params);
|
|
return (
|
|
capabilities.geminiThoughtSignatureSanitization &&
|
|
modelIncludesAnyHint(params.modelId, capabilities.geminiThoughtSignatureModelHints)
|
|
);
|
|
}
|
|
|
|
export function resolveTranscriptToolCallIdMode(
|
|
provider?: string | null,
|
|
modelId?: string | null,
|
|
options?: ProviderCapabilityLookupOptions,
|
|
): "strict9" | undefined {
|
|
const capabilities = resolveProviderCapabilities(provider, options);
|
|
const mode = capabilities.transcriptToolCallIdMode;
|
|
if (mode === "strict9") {
|
|
return mode;
|
|
}
|
|
if (modelIncludesAnyHint(modelId, capabilities.transcriptToolCallIdModelHints)) {
|
|
return "strict9";
|
|
}
|
|
return undefined;
|
|
}
|