fix(providers): required copilot vision confirmation
Keep non-personal Copilot endpoints text-only when discovery omits supports.vision, while preserving explicit vision support. Fixes #4779
This commit is contained in:
@@ -16,7 +16,12 @@ import { getBundledModels } from "../models";
|
||||
import type { Api, FetchImpl, Model, ModelSpec, OpenAICompat, Provider, ThinkingConfig } from "../types";
|
||||
import { discoveryFetch, isAnthropicOAuthToken, isRecord, toBoolean, toNumber, toPositiveNumber } from "../utils";
|
||||
import { coreWeaveProjectHeaders } from "../wire/coreweave";
|
||||
import { COPILOT_API_HEADERS, getGitHubCopilotBaseUrl, parseGitHubCopilotApiKey } from "../wire/github-copilot";
|
||||
import {
|
||||
COPILOT_API_HEADERS,
|
||||
getGitHubCopilotBaseUrl,
|
||||
isPersonalGitHubCopilotBaseUrl,
|
||||
parseGitHubCopilotApiKey,
|
||||
} from "../wire/github-copilot";
|
||||
import { createBundledReferenceMap, createReferenceResolver, toModelSpec } from "./bundled-references";
|
||||
|
||||
const MODELS_DEV_URL = "https://models.dev/api.json";
|
||||
@@ -3692,11 +3697,11 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
||||
const api = inferCopilotApi(defaults.id);
|
||||
const supportsVision = extractCopilotSupportsVision(entry);
|
||||
const input: ModelSpec<Api>["input"] =
|
||||
supportsVision === undefined
|
||||
? (reference?.input ?? defaults.input)
|
||||
: supportsVision
|
||||
? ["text", "image"]
|
||||
: ["text"];
|
||||
supportsVision === true
|
||||
? ["text", "image"]
|
||||
: supportsVision === false || !isPersonalGitHubCopilotBaseUrl(baseUrl)
|
||||
? ["text"]
|
||||
: (reference?.input ?? defaults.input);
|
||||
// With COPILOT_API_HEADERS the served window is the long-context
|
||||
// ceiling; the default tier ends at token_prices.default.context_max
|
||||
// prompt tokens. Cap the base entry to the default tier — the long
|
||||
|
||||
@@ -605,6 +605,40 @@ describe("github copilot vision endpoint policy", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("maps omitted upstream vision to text-only on non-personal Copilot endpoints", async () => {
|
||||
for (const endpoint of [
|
||||
{
|
||||
apiKey: businessApiKey,
|
||||
baseUrl: "https://api.business.githubcopilot.com",
|
||||
token: "ghu_business_token",
|
||||
},
|
||||
{
|
||||
apiKey: enterpriseApiKey,
|
||||
baseUrl: "https://copilot-api.ghe.example.com",
|
||||
token: "ghu_enterprise_token",
|
||||
},
|
||||
]) {
|
||||
const { models } = await discoverCopilotModels(
|
||||
{
|
||||
data: [
|
||||
tieredCopilotEntry({
|
||||
id: "claude-sonnet-4.6",
|
||||
name: "Claude Sonnet 4.6",
|
||||
window: 200_000,
|
||||
maxOutput: 32_000,
|
||||
}),
|
||||
],
|
||||
},
|
||||
endpoint.apiKey,
|
||||
endpoint.baseUrl,
|
||||
endpoint.token,
|
||||
);
|
||||
const model = models.find(candidate => candidate.id === "claude-sonnet-4.6");
|
||||
expect(model?.baseUrl).toBe(endpoint.baseUrl);
|
||||
expect(model?.input).toEqual(["text"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps vision on the canonical personal Copilot endpoint", async () => {
|
||||
const { models } = await discoverCopilotModels({
|
||||
data: [
|
||||
|
||||
Reference in New Issue
Block a user