fix(gateway): fall back to lastCallUsage on /v1/chat/completions

This commit is contained in:
RenzoMXD
2026-04-24 22:11:55 +02:00
committed by Peter Steinberger
parent 139dfd97bb
commit bad38150cb
2 changed files with 99 additions and 5 deletions

View File

@@ -1,7 +1,12 @@
import { randomUUID } from "node:crypto";
import type { IncomingMessage, ServerResponse } from "node:http";
import type { ImageContent } from "../agents/command/types.js";
import { normalizeUsage, toOpenAiChatCompletionsUsage } from "../agents/usage.js";
import {
hasNonzeroUsage,
normalizeUsage,
toOpenAiChatCompletionsUsage,
type NormalizedUsage,
} from "../agents/usage.js";
import { createDefaultDeps } from "../cli/deps.js";
import { agentCommandFromIngress } from "../commands/agent.js";
import type { GatewayHttpChatCompletionsConfig } from "../config/types.gateway.js";
@@ -369,6 +374,7 @@ async function resolveImagesForRequest(
export const __testOnlyOpenAiHttp = {
resolveImagesForRequest,
resolveOpenAiChatCompletionsLimits,
resolveChatCompletionUsage,
};
function buildAgentPrompt(
@@ -466,16 +472,26 @@ type AgentUsageMeta = {
total?: number;
};
function resolveRawAgentUsage(result: unknown): AgentUsageMeta | undefined {
return (
function resolveAgentRunUsage(result: unknown): NormalizedUsage | undefined {
const agentMeta = (
result as {
meta?: {
agentMeta?: {
usage?: AgentUsageMeta;
lastCallUsage?: AgentUsageMeta;
};
};
} | null
)?.meta?.agentMeta?.usage;
)?.meta?.agentMeta;
const primary = normalizeUsage(agentMeta?.usage);
if (hasNonzeroUsage(primary)) {
return primary;
}
const fallback = normalizeUsage(agentMeta?.lastCallUsage);
if (hasNonzeroUsage(fallback)) {
return fallback;
}
return primary ?? fallback;
}
function resolveChatCompletionUsage(result: unknown): {
@@ -483,7 +499,7 @@ function resolveChatCompletionUsage(result: unknown): {
completion_tokens: number;
total_tokens: number;
} {
return toOpenAiChatCompletionsUsage(normalizeUsage(resolveRawAgentUsage(result)));
return toOpenAiChatCompletionsUsage(resolveAgentRunUsage(result));
}
function resolveIncludeUsageForStreaming(payload: OpenAiChatCompletionRequest): boolean {

View File

@@ -0,0 +1,78 @@
import { describe, expect, it } from "vitest";
import { __testOnlyOpenAiHttp } from "./openai-http.js";
const { resolveChatCompletionUsage } = __testOnlyOpenAiHttp;
describe("resolveChatCompletionUsage", () => {
it("maps agentMeta.usage to OpenAI prompt/completion/total fields", () => {
const result = {
meta: {
agentMeta: {
usage: { input: 120, output: 42, cacheRead: 10, total: 172 },
},
},
};
expect(resolveChatCompletionUsage(result)).toEqual({
prompt_tokens: 130,
completion_tokens: 42,
total_tokens: 172,
});
});
it("falls back to agentMeta.lastCallUsage when agentMeta.usage is missing", () => {
const result = {
meta: {
agentMeta: {
lastCallUsage: { input: 80, output: 20, total: 100 },
},
},
};
expect(resolveChatCompletionUsage(result)).toEqual({
prompt_tokens: 80,
completion_tokens: 20,
total_tokens: 100,
});
});
it("falls back to agentMeta.lastCallUsage when agentMeta.usage is all zero", () => {
const result = {
meta: {
agentMeta: {
usage: { input: 0, output: 0, total: 0 },
lastCallUsage: { input: 55, output: 7, total: 62 },
},
},
};
expect(resolveChatCompletionUsage(result)).toEqual({
prompt_tokens: 55,
completion_tokens: 7,
total_tokens: 62,
});
});
it("returns zeros when both agentMeta.usage and lastCallUsage are absent", () => {
const result = { meta: { agentMeta: {} } };
expect(resolveChatCompletionUsage(result)).toEqual({
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
});
});
it("returns zeros when the result has no meta at all", () => {
expect(resolveChatCompletionUsage({})).toEqual({
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
});
expect(resolveChatCompletionUsage(null)).toEqual({
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
});
});
});