fix(copilot): ensure available variants sync from api (#24734)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
parent
276d162044
commit
0acac216ae
@@ -58,7 +58,7 @@ function build(key: string, remote: Item, url: string, prev?: Model): Model {
|
|||||||
|
|
||||||
const isMsgApi = remote.supported_endpoints?.includes("/v1/messages")
|
const isMsgApi = remote.supported_endpoints?.includes("/v1/messages")
|
||||||
|
|
||||||
return {
|
const model: Model = {
|
||||||
id: key,
|
id: key,
|
||||||
providerID: "github-copilot",
|
providerID: "github-copilot",
|
||||||
api: {
|
api: {
|
||||||
@@ -107,8 +107,50 @@ function build(key: string, remote: Item, url: string, prev?: Model): Model {
|
|||||||
release_date:
|
release_date:
|
||||||
prev?.release_date ??
|
prev?.release_date ??
|
||||||
(remote.version.startsWith(`${remote.id}-`) ? remote.version.slice(remote.id.length + 1) : remote.version),
|
(remote.version.startsWith(`${remote.id}-`) ? remote.version.slice(remote.id.length + 1) : remote.version),
|
||||||
variants: prev?.variants ?? {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const efforts = remote.capabilities.supports.reasoning_effort
|
||||||
|
const variants: NonNullable<Model["variants"]> = {}
|
||||||
|
if (!isMsgApi && efforts?.length) {
|
||||||
|
efforts.forEach((effort) => {
|
||||||
|
variants[effort] = {
|
||||||
|
reasoningEffort: effort,
|
||||||
|
reasoningSummary: "auto",
|
||||||
|
include: ["reasoning.encrypted_content"],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (efforts?.length && remote.capabilities.supports.adaptive_thinking) {
|
||||||
|
efforts.forEach((effort) => {
|
||||||
|
variants[effort] = {
|
||||||
|
thinking: {
|
||||||
|
type: "adaptive",
|
||||||
|
...(model.api.id.includes("opus-4.7") ? { display: "summarized" } : {}),
|
||||||
|
},
|
||||||
|
effort,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (remote.capabilities.supports.max_thinking_budget) {
|
||||||
|
const max = remote.capabilities.supports.max_thinking_budget
|
||||||
|
variants["max"] = {
|
||||||
|
thinking: {
|
||||||
|
type: "enabled",
|
||||||
|
budgetTokens: max - 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
variants["high"] = {
|
||||||
|
thinking: {
|
||||||
|
type: "enabled",
|
||||||
|
budgetTokens: Math.floor(max / 2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(variants).length > 0) {
|
||||||
|
model.variants = variants
|
||||||
|
}
|
||||||
|
|
||||||
|
return model
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get(
|
export async function get(
|
||||||
|
|||||||
@@ -1358,7 +1358,9 @@ const layer: Layer.Layer<
|
|||||||
)
|
)
|
||||||
delete provider.models[modelID]
|
delete provider.models[modelID]
|
||||||
|
|
||||||
model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
|
if (!model.variants || Object.keys(model.variants).length === 0) {
|
||||||
|
model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
|
||||||
|
}
|
||||||
|
|
||||||
const configVariants = configProvider?.models?.[modelID]?.variants
|
const configVariants = configProvider?.models?.[modelID]?.variants
|
||||||
if (configVariants && model.variants) {
|
if (configVariants && model.variants) {
|
||||||
|
|||||||
@@ -630,16 +630,17 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||||||
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/anthropic
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/anthropic
|
||||||
case "@ai-sdk/google-vertex/anthropic":
|
case "@ai-sdk/google-vertex/anthropic":
|
||||||
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex#anthropic-provider
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex#anthropic-provider
|
||||||
|
|
||||||
if (model.providerID === "github-copilot") {
|
|
||||||
if (model.api.id.includes("opus-4.7")) {
|
|
||||||
return Object.fromEntries(["medium"].map((effort) => [effort, { reasoningEffort: effort }]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adaptiveEfforts) {
|
if (adaptiveEfforts) {
|
||||||
|
let efforts = [...adaptiveEfforts]
|
||||||
|
if (model.providerID === "github-copilot") {
|
||||||
|
if (model.api.id.includes("opus-4.7")) {
|
||||||
|
efforts = ["medium"]
|
||||||
|
}
|
||||||
|
// Efforts currently supported are: low, medium, high
|
||||||
|
efforts = efforts.filter((v) => v !== "max" && v !== "xhigh")
|
||||||
|
}
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
adaptiveEfforts.map((effort) => [
|
efforts.map((effort) => [
|
||||||
effort,
|
effort,
|
||||||
{
|
{
|
||||||
thinking: {
|
thinking: {
|
||||||
|
|||||||
@@ -117,6 +117,104 @@ test("preserves temperature support from existing provider models", async () =>
|
|||||||
expect(models["brand-new"].capabilities.temperature).toBe(true)
|
expect(models["brand-new"].capabilities.temperature).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("clears existing variants so refreshed models calculate provider-specific variants", async () => {
|
||||||
|
globalThis.fetch = mock(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
model_picker_enabled: true,
|
||||||
|
id: "claude-opus-4.7",
|
||||||
|
name: "Claude Opus 4.7",
|
||||||
|
version: "claude-opus-4.7-2026-04-16",
|
||||||
|
supported_endpoints: ["/v1/messages"],
|
||||||
|
capabilities: {
|
||||||
|
family: "claude-opus",
|
||||||
|
limits: {
|
||||||
|
max_context_window_tokens: 144000,
|
||||||
|
max_output_tokens: 64000,
|
||||||
|
max_prompt_tokens: 128000,
|
||||||
|
},
|
||||||
|
supports: {
|
||||||
|
adaptive_thinking: true,
|
||||||
|
streaming: true,
|
||||||
|
tool_calls: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
{ status: 200 },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) as unknown as typeof fetch
|
||||||
|
|
||||||
|
const models = await CopilotModels.get(
|
||||||
|
"https://api.githubcopilot.com",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"claude-opus-4.7": {
|
||||||
|
id: "claude-opus-4.7",
|
||||||
|
providerID: "github-copilot",
|
||||||
|
api: {
|
||||||
|
id: "claude-opus-4.7",
|
||||||
|
url: "https://api.githubcopilot.com",
|
||||||
|
npm: "@ai-sdk/github-copilot",
|
||||||
|
},
|
||||||
|
name: "Claude Opus 4.7",
|
||||||
|
family: "claude-opus",
|
||||||
|
capabilities: {
|
||||||
|
temperature: true,
|
||||||
|
reasoning: true,
|
||||||
|
attachment: true,
|
||||||
|
toolcall: true,
|
||||||
|
input: {
|
||||||
|
text: true,
|
||||||
|
audio: false,
|
||||||
|
image: true,
|
||||||
|
video: false,
|
||||||
|
pdf: false,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
text: true,
|
||||||
|
audio: false,
|
||||||
|
image: false,
|
||||||
|
video: false,
|
||||||
|
pdf: false,
|
||||||
|
},
|
||||||
|
interleaved: false,
|
||||||
|
},
|
||||||
|
cost: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cache: {
|
||||||
|
read: 0,
|
||||||
|
write: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
context: 144000,
|
||||||
|
input: 128000,
|
||||||
|
output: 64000,
|
||||||
|
},
|
||||||
|
options: {},
|
||||||
|
headers: {},
|
||||||
|
release_date: "2026-04-16",
|
||||||
|
variants: {
|
||||||
|
low: {
|
||||||
|
reasoningEffort: "low",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: "active",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(models["claude-opus-4.7"].api.npm).toBe("@ai-sdk/anthropic")
|
||||||
|
expect(models["claude-opus-4.7"].variants).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
test("remaps fallback oauth model urls to the enterprise host", async () => {
|
test("remaps fallback oauth model urls to the enterprise host", async () => {
|
||||||
globalThis.fetch = mock(() => Promise.reject(new Error("timeout"))) as unknown as typeof fetch
|
globalThis.fetch = mock(() => Promise.reject(new Error("timeout"))) as unknown as typeof fetch
|
||||||
|
|
||||||
|
|||||||
@@ -2917,6 +2917,28 @@ describe("ProviderTransform.variants", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("github copilot opus 4.7 returns only medium reasoning effort", () => {
|
||||||
|
const model = createMockModel({
|
||||||
|
id: "claude-opus-4.7",
|
||||||
|
providerID: "github-copilot",
|
||||||
|
api: {
|
||||||
|
id: "claude-opus-4.7",
|
||||||
|
url: "https://api.githubcopilot.com/v1",
|
||||||
|
npm: "@ai-sdk/anthropic",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const result = ProviderTransform.variants(model)
|
||||||
|
expect(result).toEqual({
|
||||||
|
medium: {
|
||||||
|
thinking: {
|
||||||
|
type: "adaptive",
|
||||||
|
display: "summarized",
|
||||||
|
},
|
||||||
|
effort: "medium",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("returns high and max with thinking config", () => {
|
test("returns high and max with thinking config", () => {
|
||||||
const model = createMockModel({
|
const model = createMockModel({
|
||||||
id: "anthropic/claude-4",
|
id: "anthropic/claude-4",
|
||||||
|
|||||||
Reference in New Issue
Block a user