diff --git a/docs/providers/github-copilot.md b/docs/providers/github-copilot.md index 849d1245e885..c51cd96c5fb6 100644 --- a/docs/providers/github-copilot.md +++ b/docs/providers/github-copilot.md @@ -263,6 +263,17 @@ configured default model is never replaced. transport based on the model ref. + + Use `/think xhigh` or `/think max` when the selected model exposes that + level. Copilot's live catalog determines the supported efforts for your + account, and OpenClaw preserves those efforts in Responses requests. + When a Responses model starts its native effort range at `low`, `minimal` + maps to `low` instead of sending an unsupported value. + Explicit live limits take precedence over the bundled catalog. Gemini's + Chat Completions transport does not expose `max`. + See [Thinking levels](/tools/thinking) for session and per-message controls. + + OpenClaw sends Copilot-compatible request headers with a Copilot CLI request identity, marks tool-result follow-up turns as agent-initiated, and sets the diff --git a/extensions/github-copilot/index.ts b/extensions/github-copilot/index.ts index 1eb38be008ae..bf29ff1de8e2 100644 --- a/extensions/github-copilot/index.ts +++ b/extensions/github-copilot/index.ts @@ -26,7 +26,7 @@ import { } from "./domain.js"; import { createGithubCopilotDynamicModelHooks } from "./dynamic-models.js"; import { githubCopilotMemoryEmbeddingProviderAdapter } from "./embeddings.js"; -import { DEFAULT_COPILOT_MODEL, resolveCopilotExtendedThinkingLevels } from "./model-metadata.js"; +import { DEFAULT_COPILOT_MODEL } from "./model-metadata.js"; import { PROVIDER_ID } from "./models.js"; import { buildGithubCopilotAuthDoctorHint, @@ -35,6 +35,7 @@ import { parseGithubCopilotApiKey, refreshGithubCopilotOAuth, } from "./oauth.js"; +import { resolveThinkingProfile } from "./provider-policy-api.js"; import { buildGithubCopilotReplayPolicy, sanitizeGithubCopilotReplayHistory, @@ -701,19 +702,7 @@ export default definePluginEntry({ wrapStreamFn: wrapCopilotProviderStream, buildReplayPolicy: buildGithubCopilotReplayPolicy, sanitizeReplayHistory: sanitizeGithubCopilotReplayHistory, - resolveThinkingProfile: ({ modelId, compat }) => { - const extendedLevels = resolveCopilotExtendedThinkingLevels(modelId, compat); - return { - levels: [ - { id: "off" }, - { id: "minimal" }, - { id: "low" }, - { id: "medium" }, - { id: "high" }, - ...extendedLevels.map((id) => ({ id })), - ], - }; - }, + resolveThinkingProfile, prepareRuntimeAuth: async (ctx) => { const source = parseGithubCopilotApiKey(ctx.apiKey); const { resolveCopilotRuntimeAuth } = await loadGithubCopilotRuntime(); diff --git a/extensions/github-copilot/model-metadata.ts b/extensions/github-copilot/model-metadata.ts index 57a09b700a77..67d21f4dfafd 100644 --- a/extensions/github-copilot/model-metadata.ts +++ b/extensions/github-copilot/model-metadata.ts @@ -1,10 +1,13 @@ // 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; }; @@ -18,16 +21,18 @@ const COPILOT_CHAT_COMPLETIONS_COMPAT: ModelDefinitionConfig["compat"] = { supportsUsageInStreaming: false, maxTokensField: "max_tokens", }; -const COPILOT_XHIGH_MODEL_IDS = new Set([ - "gpt-5.6-sol", - "gpt-5.6-terra", - "gpt-5.6-luna", - "gpt-5.5", - "gpt-5.4", - "gpt-5.3-codex", -]); +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>([ + ...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. [ @@ -54,44 +59,6 @@ const STATIC_MODEL_OVERRIDES = new Map>([ compat: { supportedReasoningEfforts: ["low", "medium", "high", "xhigh"] }, }, ], - [ - "gpt-5.3-codex", - { - name: "GPT-5.3-Codex", - api: "openai-responses", - reasoning: true, - input: ["text", "image"], - cost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 }, - contextWindow: 400_000, - contextTokens: 272_000, - maxTokens: 128_000, - }, - ], - [ - "gpt-5.4", - { - name: "GPT-5.4", - api: "openai-responses", - reasoning: true, - input: ["text", "image"], - cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }, - contextWindow: 1_050_000, - maxTokens: 128_000, - }, - ], - [ - "gpt-5.5", - { - name: "GPT-5.5", - api: "openai-responses", - reasoning: true, - input: ["text", "image"], - cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, - contextWindow: 1_050_000, - contextTokens: 272_000, - maxTokens: 128_000, - }, - ], ]); function isCopilotGeminiModelId(modelId: string): boolean { @@ -128,44 +95,37 @@ export function resolveCopilotModelCompat( return undefined; } -function compatSupportsEffort( - compat: CopilotReasoningCompat | null | undefined, - effort: "xhigh" | "max", -): boolean { - return ( - Array.isArray(compat?.supportedReasoningEfforts) && - compat.supportedReasoningEfforts.some( - (candidate) => normalizeOptionalLowercaseString(candidate) === effort, - ) - ); -} - -export function resolveCopilotExtendedThinkingLevels( +export function resolveCopilotThinkingLevelMap( modelId: string, compat?: CopilotReasoningCompat | null, -): Array<"xhigh" | "max"> { + api?: string | null, +): ModelDefinitionConfig["thinkingLevelMap"] | undefined { const normalizedModelId = normalizeOptionalLowercaseString(modelId) ?? ""; + const runtimeApi = api ?? resolveCopilotTransportApi(normalizedModelId); const staticCompat = resolveStaticCopilotModelOverride(normalizedModelId)?.compat; - const isClaudeModel = normalizedModelId.includes("claude"); - const supportsAdaptiveClaudeEffort = - !isClaudeModel || supportsClaudeAdaptiveThinking({ id: normalizedModelId }); - const levels: Array<"xhigh" | "max"> = []; - if ( - supportsAdaptiveClaudeEffort && - (COPILOT_XHIGH_MODEL_IDS.has(normalizedModelId) || - compatSupportsEffort(compat, "xhigh") || - compatSupportsEffort(staticCompat, "xhigh")) - ) { - levels.push("xhigh"); + // 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; } - if ( - isClaudeModel && - supportsAdaptiveClaudeEffort && - (compatSupportsEffort(compat, "max") || compatSupportsEffort(staticCompat, "max")) - ) { - levels.push("max"); - } - return levels; + 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( diff --git a/extensions/github-copilot/models.test.ts b/extensions/github-copilot/models.test.ts index 40cc337b64a7..8861d9d8aeed 100644 --- a/extensions/github-copilot/models.test.ts +++ b/extensions/github-copilot/models.test.ts @@ -1,7 +1,9 @@ +import { streamSimpleOpenAIResponses } from "@openclaw/ai/internal/openai"; // Github Copilot tests cover models plugin behavior. import { expectDefined } from "@openclaw/normalization-core"; import { createProviderUsageFetch, makeResponse } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it, vi } from "vitest"; +import { resolveThinkingProfile } from "./provider-policy-api.js"; import { CopilotRuntimeAuthError } from "./runtime-auth-error.js"; import { resolveCopilotRuntimeAuth } from "./runtime-auth.js"; import { fetchCopilotUsage } from "./usage.js"; @@ -75,6 +77,8 @@ describe("resolveCopilotForwardCompatModel", () => { contextWindow: 400_000, contextTokens: 272_000, maxTokens: 128_000, + thinkingLevelMap: { minimal: "low", xhigh: "xhigh", max: null }, + compat: { supportedReasoningEfforts: ["low", "medium", "high", "xhigh"] }, }); }); @@ -90,6 +94,8 @@ describe("resolveCopilotForwardCompatModel", () => { cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }, contextWindow: 1_050_000, maxTokens: 128_000, + thinkingLevelMap: { minimal: "low", xhigh: "xhigh", max: null }, + compat: { supportedReasoningEfforts: ["none", "low", "medium", "high", "xhigh"] }, }); }); @@ -106,6 +112,11 @@ describe("resolveCopilotForwardCompatModel", () => { contextWindow: 1_050_000, contextTokens: 272_000, maxTokens: 128_000, + thinkingLevelMap: { minimal: "low", xhigh: "xhigh", max: null }, + compat: { + codeMode: "capable", + supportedReasoningEfforts: ["none", "low", "medium", "high", "xhigh"], + }, }); }); @@ -121,10 +132,10 @@ describe("resolveCopilotForwardCompatModel", () => { }); it("creates synthetic model for arbitrary unknown model ID", () => { - const ctx = createMockCtx("gpt-5.4-mini"); + const ctx = createMockCtx("future-model"); const result = requireResolvedModel(ctx); - expect(result.id).toBe("gpt-5.4-mini"); - expect(result.name).toBe("gpt-5.4-mini"); + expect(result.id).toBe("future-model"); + expect(result.name).toBe("future-model"); expect((result as unknown as Record).api).toBe("openai-responses"); expect((result as unknown as Record).input).toEqual(["text", "image"]); }); @@ -163,19 +174,19 @@ describe("resolveCopilotForwardCompatModel", () => { }); it("sets reasoning=false for non-reasoning model IDs including mid-string o1/o3", () => { - for (const id of [ - "gpt-5.4-mini", - "claude-sonnet-4.6", - "gpt-4o", - "mycodexmodel", - "audio-o1-hd", - "turbo-o3-voice", - ]) { + for (const id of ["gpt-4o", "mycodexmodel", "audio-o1-hd", "turbo-o3-voice"]) { const ctx = createMockCtx(id); const result = requireResolvedModel(ctx); expect((result as unknown as Record).reasoning).toBe(false); } }); + + it.each(["gpt-5.4-mini", "claude-sonnet-5"])( + "uses manifest reasoning metadata for %s instead of synthesizing an unknown model", + (modelId) => { + expect(requireResolvedModel(createMockCtx(modelId)).reasoning).toBe(true); + }, + ); }); describe("fetchCopilotUsage", () => { @@ -650,6 +661,92 @@ describe("fetchCopilotModelCatalog", () => { }); } + it.each([ + { + source: "live", + reasoning: "minimal", + efforts: ["none", "low", "medium", "high", "xhigh", "max"], + expected: "low", + }, + { source: "static", reasoning: "minimal", efforts: [], expected: "low" }, + { + source: "native minimal live", + reasoning: "minimal", + efforts: ["minimal", "low", "high"], + expected: "minimal", + }, + { + source: "live", + reasoning: "xhigh", + efforts: ["low", "high", "xhigh", "max"], + expected: "xhigh", + }, + { source: "live", reasoning: "max", efforts: ["low", "high", "xhigh", "max"], expected: "max" }, + { source: "static", reasoning: "xhigh", efforts: [], expected: "xhigh" }, + { source: "static", reasoning: "max", efforts: [], expected: "max" }, + { + source: "limited live", + reasoning: "xhigh", + efforts: ["low", "medium", "high"], + expected: "high", + }, + { + source: "limited live", + reasoning: "max", + efforts: ["low", "medium", "high"], + expected: "high", + }, + { source: "empty live", reasoning: "max", efforts: [], expected: undefined }, + ] as const)( + "keeps $source catalog policy and $reasoning Responses effort aligned", + async ({ source, reasoning, efforts, expected }) => { + const [entry] = await fetchSelectionFixture([ + { + id: "gpt-5.6-luna", + vendor: "OpenAI", + capabilities: { + type: "chat", + supports: { reasoning_effort: efforts }, + }, + }, + ]); + const model = { + ...(source === "static" + ? requireResolvedModel(createMockCtx("gpt-5.6-luna")) + : expectDefined(entry, "discovered Copilot model")), + provider: "github-copilot", + api: "openai-responses" as const, + baseUrl: "https://api.githubcopilot.com", + }; + const profile = resolveThinkingProfile({ + provider: model.provider, + modelId: model.id, + api: model.api, + compat: model.compat, + }); + expect(profile?.levels.some(({ id }) => id === reasoning)).toBe( + reasoning === "minimal" || expected === reasoning, + ); + const onPayload = vi.fn(() => { + throw new Error("captured before sending"); + }); + + const result = await streamSimpleOpenAIResponses( + model, + { messages: [{ role: "user", content: "Reply OK", timestamp: 1 }] }, + { apiKey: "test-token", reasoning, onPayload }, + ).result(); + + expect(result.errorMessage).toBe("captured before sending"); + expect(onPayload).toHaveBeenCalledWith( + expected + ? expect.objectContaining({ reasoning: { effort: expected, summary: "auto" } }) + : expect.not.objectContaining({ reasoning: expect.anything() }), + model, + ); + }, + ); + it("selects the preferred model only when the authenticated catalog marks it eligible", async () => { const models = await fetchSelectionFixture([ selectableModelEntry({ id: "fallback", contextWindow: 1_000_000 }), @@ -733,6 +830,7 @@ describe("fetchCopilotModelCatalog", () => { contextTokens: 272000, maxTokens: 128000, compat: { supportedReasoningEfforts: ["low", "medium", "high"] }, + thinkingLevelMap: { minimal: "low", xhigh: null, max: null }, }); const codex = out.find((m) => m.id === "gpt-5.3-codex"); diff --git a/extensions/github-copilot/models.ts b/extensions/github-copilot/models.ts index 895006b01fcb..25a12f89c58c 100644 --- a/extensions/github-copilot/models.ts +++ b/extensions/github-copilot/models.ts @@ -6,16 +6,14 @@ import type { import { buildCopilotIdeHeaders } from "openclaw/plugin-sdk/provider-auth"; import { readProviderJsonArrayFieldResponse } from "openclaw/plugin-sdk/provider-http"; import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared"; -import { - normalizeModelCompat, - supportsClaudeAdaptiveThinking, -} from "openclaw/plugin-sdk/provider-model-shared"; +import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-model-shared"; import { asPositiveSafeInteger, normalizeOptionalLowercaseString, } from "openclaw/plugin-sdk/string-coerce-runtime"; import { resolveCopilotModelCompat, + resolveCopilotThinkingLevelMap, resolveCopilotTransportApi, resolveStaticCopilotModelOverride, } from "./model-metadata.js"; @@ -233,7 +231,7 @@ function mergeCopilotCompat( ), ] : []; - if (supportedReasoningEfforts.length === 0) { + if (!Array.isArray(reasoningEfforts)) { return base; } return { @@ -242,22 +240,6 @@ function mergeCopilotCompat( }; } -function resolveCopilotThinkingLevelMap( - api: ModelDefinitionConfig["api"], - modelId: string, - compat: ModelDefinitionConfig["compat"] | undefined, -): ModelDefinitionConfig["thinkingLevelMap"] | undefined { - const efforts = compat?.supportedReasoningEfforts; - if (api !== "anthropic-messages" || !Array.isArray(efforts)) { - return undefined; - } - const supportsAdaptiveEffort = supportsClaudeAdaptiveThinking({ id: modelId }); - return { - xhigh: supportsAdaptiveEffort && efforts.includes("xhigh") ? "xhigh" : null, - max: supportsAdaptiveEffort && efforts.includes("max") ? "max" : null, - }; -} - function mapCopilotApiModelToDefinition( entry: CopilotApiModelEntry, ): CopilotCatalogModel | undefined { @@ -290,7 +272,7 @@ function mapCopilotApiModelToDefinition( const maxTokens = asPositiveSafeInteger(limits?.max_output_tokens) ?? DEFAULT_MAX_TOKENS; const compat = mergeCopilotCompat(resolveCopilotModelCompat(id), supports?.reasoning_effort); const api = resolveCopilotApiForVendor(entry.vendor, id); - const thinkingLevelMap = resolveCopilotThinkingLevelMap(api, id, compat); + const thinkingLevelMap = resolveCopilotThinkingLevelMap(id, compat, api); const definition: CopilotCatalogModel = { id, diff --git a/extensions/github-copilot/openclaw.plugin.json b/extensions/github-copilot/openclaw.plugin.json index 1c477f9bd08b..dc4e60aed25f 100644 --- a/extensions/github-copilot/openclaw.plugin.json +++ b/extensions/github-copilot/openclaw.plugin.json @@ -134,39 +134,44 @@ "id": "gpt-5.6-sol", "name": "GPT-5.6 Sol", "reasoning": true, + "thinkingLevelMap": { "minimal": "low", "xhigh": "xhigh", "max": "max" }, "input": ["text", "image"], "contextWindow": 1050000, "contextTokens": 922000, "maxTokens": 128000, "cost": { "input": 5, "output": 30, "cacheRead": 0.5, "cacheWrite": 0 }, - "compat": { "codeMode": "capable" } + "compat": { "codeMode": "capable", "supportedReasoningEfforts": ["none", "low", "medium", "high", "xhigh", "max"] } }, { "id": "gpt-5.6-terra", "name": "GPT-5.6 Terra", "reasoning": true, + "thinkingLevelMap": { "minimal": "low", "xhigh": "xhigh", "max": "max" }, "input": ["text", "image"], "contextWindow": 1050000, "contextTokens": 922000, "maxTokens": 128000, "cost": { "input": 2.5, "output": 15, "cacheRead": 0.25, "cacheWrite": 0 }, - "compat": { "codeMode": "capable" } + "compat": { "codeMode": "capable", "supportedReasoningEfforts": ["none", "low", "medium", "high", "xhigh", "max"] } }, { "id": "gpt-5.6-luna", "name": "GPT-5.6 Luna", "reasoning": true, + "thinkingLevelMap": { "minimal": "low", "xhigh": "xhigh", "max": "max" }, "input": ["text", "image"], "contextWindow": 1050000, "contextTokens": 922000, "maxTokens": 128000, "cost": { "input": 1, "output": 6, "cacheRead": 0.1, "cacheWrite": 0 }, - "compat": { "codeMode": "capable" } + "compat": { "codeMode": "capable", "supportedReasoningEfforts": ["none", "low", "medium", "high", "xhigh", "max"] } }, { "id": "gpt-5.3-codex", "name": "GPT-5.3-Codex", "reasoning": true, + "thinkingLevelMap": { "minimal": "low", "xhigh": "xhigh", "max": null }, + "compat": { "supportedReasoningEfforts": ["low", "medium", "high", "xhigh"] }, "input": ["text", "image"], "contextWindow": 400000, "contextTokens": 272000, @@ -179,6 +184,8 @@ "status": "deprecated", "replacedBy": "gpt-5.6-terra", "reasoning": true, + "thinkingLevelMap": { "minimal": "low", "xhigh": "xhigh", "max": null }, + "compat": { "supportedReasoningEfforts": ["none", "low", "medium", "high", "xhigh"] }, "input": ["text", "image"], "contextWindow": 1050000, "maxTokens": 128000, @@ -190,12 +197,13 @@ "status": "deprecated", "replacedBy": "gpt-5.6-sol", "reasoning": true, + "thinkingLevelMap": { "minimal": "low", "xhigh": "xhigh", "max": null }, "input": ["text", "image"], "contextWindow": 1050000, "contextTokens": 272000, "maxTokens": 128000, "cost": { "input": 5, "output": 30, "cacheRead": 0.5, "cacheWrite": 0 }, - "compat": { "codeMode": "capable" } + "compat": { "codeMode": "capable", "supportedReasoningEfforts": ["none", "low", "medium", "high", "xhigh"] } }, { "id": "gpt-5.4-mini", diff --git a/extensions/github-copilot/provider-policy-api.test.ts b/extensions/github-copilot/provider-policy-api.test.ts index e5499cebcb07..768a474d4956 100644 --- a/extensions/github-copilot/provider-policy-api.test.ts +++ b/extensions/github-copilot/provider-policy-api.test.ts @@ -51,26 +51,55 @@ describe("github-copilot provider-policy-api", () => { ).toContain("max"); }); - it("does not expose max for non-Anthropic Copilot transports", () => { + it("appends max when GPT catalog compat advertises it", () => { expect( resolveThinkingProfile({ provider: "github-copilot", - modelId: "future-copilot-model", + modelId: "gpt-5.6-sol", + compat: { supportedReasoningEfforts: ["low", "medium", "high", "max"] }, + })?.levels.map((level) => level.id), + ).toContain("max"); + }); + + it.each([undefined, null])("does not expose older Claude adaptive effort with api=%s", (api) => { + expect( + resolveThinkingProfile({ + provider: "github-copilot", + modelId: "claude-opus-4-5", + api, compat: { supportedReasoningEfforts: ["low", "medium", "high", "max"] }, })?.levels.map((level) => level.id), ).not.toContain("max"); }); - it("does not expose adaptive effort for older Claude models", () => { + it.each([ + { supportedReasoningEfforts: ["low", "medium", "high"] }, + { supportedReasoningEfforts: [] }, + { supportsReasoningEffort: false, supportedReasoningEfforts: ["xhigh", "max"] }, + ])("honors explicit catalog limits before static GPT metadata: %j", (compat) => { expect( resolveThinkingProfile({ provider: "github-copilot", - modelId: "claude-opus-4-5", - compat: { supportedReasoningEfforts: ["low", "medium", "high", "max"] }, - })?.levels.map((level) => level.id), - ).not.toContain("max"); + modelId: "gpt-5.6-luna", + compat, + })?.levels.map(({ id }) => id), + ).toEqual(["off", "minimal", "low", "medium", "high"]); }); + it.each(["openai-completions", undefined, null])( + "does not expose Gemini max with api=%s", + (api) => { + expect( + resolveThinkingProfile({ + provider: "github-copilot", + modelId: "gemini-3.6-flash", + api, + compat: { supportedReasoningEfforts: ["low", "medium", "high", "max"] }, + })?.levels.map(({ id }) => id), + ).not.toContain("max"); + }, + ); + it("appends xhigh for static Copilot metadata overrides", () => { expect( resolveThinkingProfile({ diff --git a/extensions/github-copilot/provider-policy-api.ts b/extensions/github-copilot/provider-policy-api.ts index 01f14352e0ad..d89194fc1a14 100644 --- a/extensions/github-copilot/provider-policy-api.ts +++ b/extensions/github-copilot/provider-policy-api.ts @@ -1,12 +1,17 @@ // Github Copilot API module exposes the plugin public contract. import type { ProviderDefaultThinkingPolicyContext } from "openclaw/plugin-sdk/core"; -import { resolveCopilotExtendedThinkingLevels } from "./model-metadata.js"; +import { resolveCopilotThinkingLevelMap } from "./model-metadata.js"; export function resolveThinkingProfile(context: ProviderDefaultThinkingPolicyContext) { if (context.provider.trim().toLowerCase() !== "github-copilot") { return null; } - const extendedLevels = resolveCopilotExtendedThinkingLevels(context.modelId, context.compat); + const thinkingLevelMap = resolveCopilotThinkingLevelMap( + context.modelId, + context.compat, + context.api, + ); + const extendedLevels = (["xhigh", "max"] as const).filter((id) => thinkingLevelMap?.[id]); return { levels: [