mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
110eb787a1
* fix(github-copilot): preserve catalog thinking efforts in requests Unify discovered and bundled capability mapping with the provider thinking policy. Preserve supported xhigh/max Responses efforts and map minimal to the supported low minimum, while respecting explicit account opt-outs and transport limits. Fixes #107792 Co-authored-by: Pluviobyte <Pluviobyte@users.noreply.github.com> * fix(github-copilot): resolve nullable thinking policy transport Accept the public policy API context and resolve missing transports before enforcing Claude and Gemini effort restrictions. Cover undefined and null API values without changing explicit Responses routes. * refactor(github-copilot): normalize manifest models as one catalog Use the canonical batch model provider builder after the single-row helper was removed on main. Preserve model transport and compatibility decoration without a legacy API shim. * refactor(github-copilot): decorate owned catalog rows in place Keep the normalized manifest batch as the sole owner of runtime rows and apply transport metadata directly, avoiding redundant row copies. --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: Pluviobyte <Pluviobyte@users.noreply.github.com>
136 lines
5.2 KiB
TypeScript
136 lines
5.2 KiB
TypeScript
// Github Copilot plugin module implements model metadata behavior.
|
|
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
|
|
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
|
import { supportsClaudeAdaptiveThinking } from "openclaw/plugin-sdk/provider-model-shared";
|
|
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
import manifest from "./openclaw.plugin.json" with { type: "json" };
|
|
|
|
type CopilotRuntimeApi = "anthropic-messages" | "openai-completions" | "openai-responses";
|
|
type CopilotReasoningCompat = {
|
|
supportsReasoningEffort?: boolean;
|
|
supportedReasoningEfforts?: readonly string[] | null;
|
|
};
|
|
|
|
// Provider-owned general-purpose preference. Setup verifies it against the
|
|
// authenticated live catalog instead of assuming every account can use it.
|
|
export const DEFAULT_COPILOT_MODEL = "github-copilot/claude-sonnet-5";
|
|
|
|
const COPILOT_CHAT_COMPLETIONS_COMPAT: ModelDefinitionConfig["compat"] = {
|
|
supportsStore: false,
|
|
supportsDeveloperRole: false,
|
|
supportsUsageInStreaming: false,
|
|
maxTokensField: "max_tokens",
|
|
};
|
|
const manifestCatalog = manifest.modelCatalog.providers["github-copilot"];
|
|
const manifestModels = buildManifestModelProviderConfig({
|
|
providerId: "github-copilot",
|
|
catalog: manifestCatalog,
|
|
}).models;
|
|
for (const model of manifestModels) {
|
|
model.api = resolveCopilotTransportApi(model.id);
|
|
model.compat = { ...resolveCopilotModelCompat(model.id), ...model.compat };
|
|
}
|
|
|
|
const STATIC_MODEL_OVERRIDES = new Map<string, Partial<ModelDefinitionConfig>>([
|
|
...manifestModels.map((model) => [model.id, model] as const),
|
|
// These two non-catalog ids preserve metadata for legacy configured refs and
|
|
// account discovery responses. They are intentionally not picker entries.
|
|
[
|
|
"claude-opus-4.6-1m",
|
|
{
|
|
name: "Claude Opus 4.6 (1M context)",
|
|
api: "anthropic-messages",
|
|
reasoning: true,
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 64_000,
|
|
thinkingLevelMap: { xhigh: null, max: null },
|
|
compat: { supportedReasoningEfforts: ["low", "medium", "high"] },
|
|
},
|
|
],
|
|
[
|
|
"claude-opus-4.7-1m-internal",
|
|
{
|
|
name: "Claude Opus 4.7 (1M context)",
|
|
api: "anthropic-messages",
|
|
reasoning: true,
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 64_000,
|
|
thinkingLevelMap: { xhigh: "xhigh", max: null },
|
|
compat: { supportedReasoningEfforts: ["low", "medium", "high", "xhigh"] },
|
|
},
|
|
],
|
|
]);
|
|
|
|
function isCopilotGeminiModelId(modelId: string): boolean {
|
|
return /(?:^|[-_.])gemini(?:$|[-_.])/.test(modelId);
|
|
}
|
|
|
|
function isCopilotClaude45ModelId(modelId: string): boolean {
|
|
return /^claude-(?:haiku|opus|sonnet)-4[.-]5(?:$|[-.])/.test(modelId);
|
|
}
|
|
|
|
export function resolveCopilotTransportApi(modelId: string): CopilotRuntimeApi {
|
|
const normalized = normalizeOptionalLowercaseString(modelId) ?? "";
|
|
if (normalized.includes("claude")) {
|
|
return "anthropic-messages";
|
|
}
|
|
if (isCopilotGeminiModelId(normalized)) {
|
|
return "openai-completions";
|
|
}
|
|
return "openai-responses";
|
|
}
|
|
|
|
export function resolveCopilotModelCompat(
|
|
modelId: string,
|
|
): ModelDefinitionConfig["compat"] | undefined {
|
|
const normalized = normalizeOptionalLowercaseString(modelId) ?? "";
|
|
if (isCopilotGeminiModelId(normalized)) {
|
|
return { ...COPILOT_CHAT_COMPLETIONS_COMPAT };
|
|
}
|
|
// Copilot's Claude 4.5 endpoints reject Anthropic's eager tool extension,
|
|
// while current Claude 4.6+ endpoints accept it.
|
|
if (isCopilotClaude45ModelId(normalized)) {
|
|
return { supportsEagerToolInputStreaming: false };
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function resolveCopilotThinkingLevelMap(
|
|
modelId: string,
|
|
compat?: CopilotReasoningCompat | null,
|
|
api?: string | null,
|
|
): ModelDefinitionConfig["thinkingLevelMap"] | undefined {
|
|
const normalizedModelId = normalizeOptionalLowercaseString(modelId) ?? "";
|
|
const runtimeApi = api ?? resolveCopilotTransportApi(normalizedModelId);
|
|
const staticCompat = resolveStaticCopilotModelOverride(normalizedModelId)?.compat;
|
|
// A declared account catalog is authoritative, including explicit opt-outs;
|
|
// the manifest only supplies effort metadata when discovery has none.
|
|
const efforts =
|
|
compat?.supportsReasoningEffort === false
|
|
? []
|
|
: (compat?.supportedReasoningEfforts ?? staticCompat?.supportedReasoningEfforts);
|
|
if (!Array.isArray(efforts)) {
|
|
return undefined;
|
|
}
|
|
const supported = new Set(efforts.map(normalizeOptionalLowercaseString));
|
|
const supportsEffort =
|
|
runtimeApi !== "anthropic-messages" ||
|
|
supportsClaudeAdaptiveThinking({ id: normalizedModelId });
|
|
return {
|
|
// Keep the public minimal setting usable when this route starts its native ladder at low.
|
|
...(runtimeApi === "openai-responses" && !supported.has("minimal") && supported.has("low")
|
|
? { minimal: "low" }
|
|
: {}),
|
|
xhigh: supportsEffort && supported.has("xhigh") ? "xhigh" : null,
|
|
// Chat Completions currently translates max to xhigh rather than preserving it.
|
|
max:
|
|
supportsEffort && runtimeApi !== "openai-completions" && supported.has("max") ? "max" : null,
|
|
};
|
|
}
|
|
|
|
export function resolveStaticCopilotModelOverride(
|
|
modelId: string,
|
|
): Partial<ModelDefinitionConfig> | undefined {
|
|
return STATIC_MODEL_OVERRIDES.get(normalizeOptionalLowercaseString(modelId) ?? "");
|
|
}
|