fix(catalog): bound Copilot endpoint probe with discovery timeout

Threaded the shared 10s discovery AbortSignal into the copilot_internal/user probe so a stalled endpoint falls back to the personal host instead of hanging startup or refresh.

Fixes #8507
This commit is contained in:
roboomp
2026-08-14 04:47:42 +00:00
parent c92ba97538
commit ce65a40539
3 changed files with 27 additions and 2 deletions
@@ -44,6 +44,9 @@ async function discoverCopilotModels(
const url = typeof input === "string" ? input : input.toString();
if (url === "https://api.github.com/copilot_internal/user") {
expect(getHeaderValue(init?.headers, "Authorization")).toBe(`token ${expectedAuthorizationToken}`);
// The probe must be bounded by the shared discovery deadline so a
// stalled endpoint cannot hang discovery (PR #8510 review).
expect(init?.signal).toBeInstanceOf(AbortSignal);
return Response.json({ endpoints: { api: expectedBaseUrl } });
}
expect(url).toBe(`${expectedBaseUrl}/models`);
@@ -86,6 +89,21 @@ describe("github copilot model limits mapping", () => {
);
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it("falls back to the personal endpoint when the raw-token probe fails", async () => {
const token = "ghu_valid_business_token";
const fetchMock = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
const url = typeof input === "string" ? input : input.toString();
if (url === "https://api.github.com/copilot_internal/user") {
expect(init?.signal).toBeInstanceOf(AbortSignal);
throw new DOMException("The operation timed out.", "TimeoutError");
}
expect(url).toBe("https://api.githubcopilot.com/models");
return Response.json({ data: [] });
});
const models = await githubCopilotModelManagerOptions({ apiKey: token, fetch: fetchMock }).fetchDynamicModels?.();
expect(models).toEqual([]);
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it("unwraps structured OAuth keys for discovery and routes enterprise discovery to the enterprise host", async () => {
const structuredApiKey = JSON.stringify({