Files
cc-switch/tests/components/CodexFormFields.copilotCapabilities.test.ts
f9db9f7056 feat(codex): support GitHub Copilot managed accounts with Responses/Chat routing (#7157)
Add a GitHub Copilot preset for Codex that uses a managed GitHub account.
The local route resolves each request's model against the account's live
Copilot /models catalogue and picks the upstream protocol the model
advertises (native Responses first, Chat Completions as fallback in auto
mode), with an optional explicit protocol override.

- Response handling, the opaque-state rectifier and Stack web_search
  stripping follow the transport actually used for each request rather
  than the stored apiFormat.
- Fetching models fills the catalogue from the account's live prompt
  limit, parallel tool calls and reasoning levels; presets keep
  conservative fallbacks.
- Codex only treats a card as managed Copilot by its provider type, so
  self-hosted relays keep their own key and endpoint.
- Copilot request identity headers are shared between the Claude and
  Codex paths.

Co-authored-by: yuwk <52391318+ump45nose@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-10-08 00:10:45 +08:00

50 lines
1.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
isCopilotModelSupportedByCodex,
resolveCopilotReportedPromptLimit,
} from "@/components/providers/forms/CodexFormFields";
import type { CopilotModel } from "@/lib/api/copilot";
import type { CodexCopilotApiFormat } from "@/types";
import endpointCases from "../fixtures/copilot-endpoint-cases.json";
function model(supportedEndpoints?: string[]): CopilotModel {
return {
id: "model",
name: "Model",
vendor: "vendor",
model_picker_enabled: true,
supported_endpoints: supportedEndpoints,
};
}
describe("Codex Copilot capabilities", () => {
it.each(endpointCases)(
"matches the backend endpoint contract: $name",
({ endpoints, formats }) => {
const selections: CodexCopilotApiFormat[] = [
"auto",
"openai_responses",
"openai_chat",
];
for (const format of selections) {
expect(isCopilotModelSupportedByCodex(model(endpoints), format)).toBe(
formats.includes(format),
);
}
},
);
it("treats a reported prompt limit as authoritative and otherwise preserves the fallback", () => {
expect(resolveCopilotReportedPromptLimit("", 400_000)).toBe(400_000);
expect(resolveCopilotReportedPromptLimit(undefined, 1_000_000)).toBe(
1_000_000,
);
expect(resolveCopilotReportedPromptLimit(200_000, 400_000)).toBe(400_000);
expect(resolveCopilotReportedPromptLimit(200_000, undefined)).toBe(200_000);
});
it("rejects an absent capabilities field", () => {
expect(isCopilotModelSupportedByCodex(model())).toBe(false);
});
});