| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- import { afterEach, expect, mock, test } from "bun:test"
- import { CopilotModels } from "@/plugin/github-copilot/models"
- import { CopilotAuthPlugin } from "@/plugin/github-copilot/copilot"
- const originalFetch = globalThis.fetch
- afterEach(() => {
- globalThis.fetch = originalFetch
- })
- test("preserves temperature support from existing provider models", async () => {
- globalThis.fetch = mock(() =>
- Promise.resolve(
- new Response(
- JSON.stringify({
- data: [
- {
- model_picker_enabled: true,
- id: "gpt-4o",
- name: "GPT-4o",
- version: "gpt-4o-2024-05-13",
- capabilities: {
- family: "gpt",
- limits: {
- max_context_window_tokens: 64000,
- max_output_tokens: 16384,
- max_prompt_tokens: 64000,
- },
- supports: {
- streaming: true,
- tool_calls: true,
- },
- },
- },
- {
- model_picker_enabled: true,
- id: "brand-new",
- name: "Brand New",
- version: "brand-new-2026-04-01",
- capabilities: {
- family: "test",
- limits: {
- max_context_window_tokens: 32000,
- max_output_tokens: 8192,
- max_prompt_tokens: 32000,
- },
- supports: {
- streaming: true,
- tool_calls: false,
- },
- },
- },
- ],
- }),
- { status: 200 },
- ),
- ),
- ) as unknown as typeof fetch
- const result = await CopilotModels.get(
- "https://api.githubcopilot.com",
- {},
- {
- "gpt-4o": {
- id: "gpt-4o",
- providerID: "github-copilot",
- api: {
- id: "gpt-4o",
- url: "https://api.githubcopilot.com",
- npm: "@ai-sdk/openai-compatible",
- },
- name: "GPT-4o",
- family: "gpt",
- capabilities: {
- temperature: true,
- reasoning: false,
- 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: 64000,
- output: 16384,
- },
- options: {},
- headers: {},
- release_date: "2024-05-13",
- variants: {},
- status: "active",
- },
- },
- )
- const models = result.models
- expect(models["gpt-4o"].capabilities.temperature).toBe(true)
- expect(models["brand-new"].capabilities.temperature).toBe(true)
- })
- test("converts Copilot AIC token prices to USD per million tokens", async () => {
- globalThis.fetch = mock(() =>
- Promise.resolve(
- new Response(
- JSON.stringify({
- data: [
- {
- model_picker_enabled: true,
- id: "gpt-5",
- name: "GPT-5",
- version: "gpt-5-2026-06-01",
- billing: {
- token_prices: {
- batch_size: 500000,
- default: {
- input_price: 500,
- output_price: 3000,
- cache_price: 50,
- },
- },
- },
- capabilities: {
- family: "gpt",
- limits: {
- max_context_window_tokens: 200000,
- max_output_tokens: 16384,
- max_prompt_tokens: 200000,
- },
- supports: {
- streaming: true,
- tool_calls: true,
- },
- },
- },
- {
- model_picker_enabled: true,
- id: "incomplete-internal-model",
- name: "Incomplete Internal Model",
- version: "incomplete-internal-model-2026-06-01",
- capabilities: {
- family: "internal",
- supports: {},
- },
- },
- {
- model_picker_enabled: false,
- id: "ignored-non-chat-record",
- },
- ],
- }),
- { status: 200 },
- ),
- ),
- ) as unknown as typeof fetch
- const models = (await CopilotModels.get("https://api.githubcopilot.com")).models
- expect(models["gpt-5"].cost).toEqual({
- input: 10,
- output: 60,
- cache: {
- read: 1,
- write: 0,
- },
- })
- expect(models["incomplete-internal-model"]).toBeUndefined()
- expect(models["ignored-non-chat-record"]).toBeUndefined()
- })
- test("uses zero cost when Copilot reports a zero billing batch size", async () => {
- globalThis.fetch = mock(() =>
- Promise.resolve(
- new Response(
- JSON.stringify({
- data: [
- {
- model_picker_enabled: true,
- id: "mercury-alpha",
- name: "Mercury Alpha",
- version: "mercury-alpha-2026-07-09",
- billing: {
- token_prices: {
- batch_size: 0,
- default: {
- input_price: 0,
- output_price: 0,
- cache_price: 0,
- },
- },
- },
- capabilities: {
- family: "mercury",
- limits: {
- max_context_window_tokens: 128000,
- max_output_tokens: 16384,
- max_prompt_tokens: 128000,
- },
- supports: {
- streaming: true,
- tool_calls: true,
- },
- },
- },
- ],
- }),
- { status: 200 },
- ),
- ),
- ) as unknown as typeof fetch
- const model = (await CopilotModels.get("https://api.githubcopilot.com")).models["mercury-alpha"]
- expect(model.cost).toEqual({
- input: 0,
- output: 0,
- cache: {
- read: 0,
- write: 0,
- },
- })
- expect(JSON.stringify(model)).not.toContain("null")
- })
- test("records Copilot advertised responses endpoint for non-GPT model IDs", async () => {
- globalThis.fetch = mock(() =>
- Promise.resolve(
- new Response(
- JSON.stringify({
- data: [
- {
- model_picker_enabled: true,
- id: "mai-code-1-flash-picker",
- name: "MAI-Code-1-Flash",
- version: "mai-code-1-flash-picker",
- supported_endpoints: ["/responses"],
- capabilities: {
- family: "oswe-vscode-modelD",
- limits: {
- max_context_window_tokens: 256000,
- max_output_tokens: 128000,
- max_prompt_tokens: 128000,
- },
- supports: {
- streaming: true,
- structured_outputs: true,
- tool_calls: true,
- },
- },
- },
- ],
- }),
- { status: 200 },
- ),
- ),
- ) as unknown as typeof fetch
- const model = (await CopilotModels.get("https://api.githubcopilot.com")).models["mai-code-1-flash-picker"]
- expect("endpoint" in model.api ? model.api.endpoint : undefined).toBe("responses")
- })
- 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 result = 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",
- },
- },
- )
- const models = result.models
- 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 () => {
- globalThis.fetch = mock(() => Promise.reject(new Error("timeout"))) as unknown as typeof fetch
- const hooks = await CopilotAuthPlugin({
- client: {} as never,
- project: {} as never,
- directory: "",
- worktree: "",
- experimental_workspace: {
- register() {},
- },
- serverUrl: new URL("https://example.com"),
- $: {} as never,
- })
- const models = await hooks.provider!.models!(
- {
- id: "github-copilot",
- models: {
- claude: {
- id: "claude",
- providerID: "github-copilot",
- api: {
- id: "claude-sonnet-4.5",
- url: "https://api.githubcopilot.com/v1",
- npm: "@ai-sdk/anthropic",
- },
- },
- },
- } as never,
- {
- auth: {
- type: "oauth",
- refresh: "token",
- access: "token",
- expires: Date.now() + 60_000,
- enterpriseUrl: "ghe.example.com",
- } as never,
- },
- )
- expect(models.claude.api.url).toBe("https://copilot-api.ghe.example.com")
- expect(models.claude.api.npm).toBe("@ai-sdk/github-copilot")
- })
|