Compare commits

...

4 Commits

Author SHA1 Message Date
Aiden Cline 5076497e11 fix(core): retain raw copilot completion usage 2026-08-11 00:37:09 -05:00
Aiden Cline f31582b907 fix(core): preserve raw copilot usage 2026-08-11 00:34:05 -05:00
Aiden Cline c1abcce820 test(core): use observed copilot usage fixtures 2026-08-11 00:20:54 -05:00
Aiden Cline 24d8e41aab fix(core): normalize copilot reasoning usage 2026-08-11 00:14:08 -05:00
2 changed files with 23 additions and 10 deletions
@@ -285,9 +285,8 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
cacheWrite: undefined,
},
outputTokens: {
total: responseBody.usage?.completion_tokens ?? undefined,
...outputUsage(responseBody.usage),
text: undefined,
reasoning: responseBody.usage?.completion_tokens_details?.reasoning_tokens ?? undefined,
},
raw: responseBody.usage ?? undefined,
},
@@ -357,6 +356,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
cachedTokens: number | undefined
}
totalTokens: number | undefined
rawCompletionTokens: number | undefined
} = {
completionTokens: undefined,
completionTokensDetails: {
@@ -369,6 +369,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
cachedTokens: undefined,
},
totalTokens: undefined,
rawCompletionTokens: undefined,
}
let isFirstChunk = true
const providerOptionsName = this.providerOptionsName
@@ -432,11 +433,11 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
} = value.usage
usage.promptTokens = prompt_tokens ?? undefined
usage.completionTokens = completion_tokens ?? undefined
usage.rawCompletionTokens = completion_tokens ?? undefined
const output = outputUsage(value.usage)
usage.completionTokens = output.total
usage.completionTokensDetails.reasoningTokens = output.reasoning
usage.totalTokens = total_tokens ?? undefined
if (completion_tokens_details?.reasoning_tokens != null) {
usage.completionTokensDetails.reasoningTokens = completion_tokens_details?.reasoning_tokens
}
if (completion_tokens_details?.accepted_prediction_tokens != null) {
usage.completionTokensDetails.acceptedPredictionTokens =
completion_tokens_details?.accepted_prediction_tokens
@@ -708,7 +709,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
},
raw: {
prompt_tokens: usage.promptTokens ?? null,
completion_tokens: usage.completionTokens ?? null,
completion_tokens: usage.rawCompletionTokens ?? null,
total_tokens: usage.totalTokens ?? null,
},
},
@@ -727,6 +728,7 @@ const openaiCompatibleTokenUsageSchema = z
.object({
prompt_tokens: z.number().nullish(),
completion_tokens: z.number().nullish(),
reasoning_tokens: z.number().nullish(),
total_tokens: z.number().nullish(),
prompt_tokens_details: z
.object({
@@ -743,6 +745,17 @@ const openaiCompatibleTokenUsageSchema = z
})
.nullish()
function outputUsage(usage: z.infer<typeof openaiCompatibleTokenUsageSchema>) {
const nested = usage?.completion_tokens_details?.reasoning_tokens
return {
total:
usage?.completion_tokens == null
? undefined
: usage.completion_tokens + (nested == null ? (usage.reasoning_tokens ?? 0) : 0),
reasoning: nested ?? usage?.reasoning_tokens ?? undefined,
}
}
// limited version of the schema, focussed on what is needed for the implementation
// this approach limits breakages when the API changes and increases efficiency
const OpenAICompatibleChatResponseSchema = z.object({
@@ -204,7 +204,7 @@ describe("doStream", () => {
finishReason: { unified: "tool-calls" },
usage: {
inputTokens: { total: 19581 },
outputTokens: { total: 53 },
outputTokens: { total: 187, reasoning: 134 },
},
})
})
@@ -259,7 +259,7 @@ describe("doStream", () => {
finishReason: { unified: "stop" },
usage: {
inputTokens: { total: 5778 },
outputTokens: { total: 59 },
outputTokens: { total: 154, reasoning: 95 },
},
providerMetadata: {
copilot: {
@@ -391,7 +391,7 @@ describe("doStream", () => {
finishReason: { unified: "tool-calls" },
usage: {
inputTokens: { total: 3767 },
outputTokens: { total: 19 },
outputTokens: { total: 30, reasoning: 11 },
},
})
})