feat(core): update Copilot for token-based billing (#30181)

This commit is contained in:
Aiden Cline
2026-06-01 14:55:23 -05:00
committed by GitHub
parent fa23fb5d38
commit ae92f3158f
10 changed files with 340 additions and 77 deletions

View File

@@ -57,7 +57,7 @@ test("preserves temperature support from existing provider models", async () =>
),
) as unknown as typeof fetch
const models = await CopilotModels.get(
const result = await CopilotModels.get(
"https://api.githubcopilot.com",
{},
{
@@ -112,11 +112,81 @@ test("preserves temperature support from existing provider models", async () =>
},
},
)
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("clears existing variants so refreshed models calculate provider-specific variants", async () => {
globalThis.fetch = mock(() =>
Promise.resolve(
@@ -150,7 +220,7 @@ test("clears existing variants so refreshed models calculate provider-specific v
),
) as unknown as typeof fetch
const models = await CopilotModels.get(
const result = await CopilotModels.get(
"https://api.githubcopilot.com",
{},
{
@@ -210,6 +280,7 @@ test("clears existing variants so refreshed models calculate provider-specific v
},
},
)
const models = result.models
expect(models["claude-opus-4.7"].api.npm).toBe("@ai-sdk/anthropic")
expect(models["claude-opus-4.7"].variants).toBeUndefined()

View File

@@ -1674,6 +1674,20 @@ describe("SessionNs.getUsage", () => {
expect(result.cost).toBe(3 + 1.5)
})
test("uses authoritative Copilot billed cost when provided", () => {
const result = SessionNs.getUsage({
model: createModel({
context: 100_000,
output: 32_000,
cost: { input: 3, output: 15, cache: { read: 0.3, write: 0.3 } },
}),
usage: usage({ inputTokens: 11_774, outputTokens: 39, totalTokens: 11_813 }),
metadata: { copilot: { totalNanoAiu: 4_473_525_000 } },
})
expect(result.cost).toBe(0.04473525)
})
test("uses matching context cost tier before over-200k fallback", () => {
const model = createModel({
context: 1_000_000,

View File

@@ -500,6 +500,57 @@ describe("session.llm.ai-sdk adapter", () => {
expect(result.tokens.cache.write).toBe(300)
expect(result.tokens.cache.read).toBe(200)
})
test("captures Copilot billed usage from raw Anthropic message deltas per step", async () => {
const events = await adapt([
uncheckedAdapterEvent({
type: "raw",
rawValue: {
type: "message_delta",
copilot_usage: { total_nano_aiu: 4_473_525_000 },
},
}),
{
type: "finish-step",
response: { id: "msg_test", timestamp: new Date(0), modelId: "claude-sonnet-4.6" },
finishReason: "stop",
rawFinishReason: "end_turn",
usage: {
inputTokens: 11_774,
outputTokens: 39,
totalTokens: 11_813,
inputTokenDetails: { noCacheTokens: 3, cacheReadTokens: 0, cacheWriteTokens: 11_771 },
outputTokenDetails: { textTokens: 39, reasoningTokens: undefined },
},
providerMetadata: { anthropic: { cacheCreationInputTokens: 11_771 } },
},
{
type: "finish-step",
response: { id: "msg_follow_up", timestamp: new Date(0), modelId: "claude-sonnet-4.6" },
finishReason: "stop",
rawFinishReason: "end_turn",
usage: {
inputTokens: 1,
outputTokens: 1,
totalTokens: 2,
inputTokenDetails: { noCacheTokens: 1, cacheReadTokens: 0, cacheWriteTokens: 0 },
outputTokenDetails: { textTokens: 1, reasoningTokens: undefined },
},
providerMetadata: { anthropic: {} },
},
])
expect(events[0]).toMatchObject({
type: "step-finish",
providerMetadata: {
anthropic: { cacheCreationInputTokens: 11_771 },
copilot: { totalNanoAiu: 4_473_525_000 },
},
})
expect(events[1]).toMatchObject({ type: "step-finish", providerMetadata: { anthropic: {} } })
if (events[1].type !== "step-finish") throw new Error("expected step-finish")
expect(events[1].providerMetadata?.copilot).toBeUndefined()
})
})
type Capture = {