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: [