fix(ai): support service tier in codex responses (#2996)

This commit is contained in:
Markus Ylisiurunen
2026-04-13 12:04:11 +03:00
committed by GitHub
parent 4dda448a3f
commit f829f80885

View File

@@ -1,5 +1,10 @@
import type * as NodeOs from "node:os";
import type { Tool as OpenAITool, ResponseInput, ResponseStreamEvent } from "openai/resources/responses/responses.js";
import type {
Tool as OpenAITool,
ResponseCreateParamsStreaming,
ResponseInput,
ResponseStreamEvent,
} from "openai/resources/responses/responses.js";
// NEVER convert to top-level runtime imports - breaks browser/Vite builds (web-ui)
let _os: typeof NodeOs | null = null;
@@ -25,6 +30,7 @@ import type {
SimpleStreamOptions,
StreamFunction,
StreamOptions,
Usage,
} from "../types.js";
import { AssistantMessageEventStream } from "../utils/event-stream.js";
import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
@@ -56,6 +62,7 @@ const CODEX_RESPONSE_STATUSES = new Set<CodexResponseStatus>([
export interface OpenAICodexResponsesOptions extends StreamOptions {
reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
reasoningSummary?: "auto" | "concise" | "detailed" | "off" | "on" | null;
serviceTier?: ResponseCreateParamsStreaming["service_tier"];
textVerbosity?: "low" | "medium" | "high";
}
@@ -72,6 +79,7 @@ interface RequestBody {
parallel_tool_calls?: boolean;
temperature?: number;
reasoning?: { effort?: string; summary?: string };
service_tier?: ResponseCreateParamsStreaming["service_tier"];
text?: { verbosity?: string };
include?: string[];
prompt_cache_key?: string;
@@ -251,7 +259,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
}
stream.push({ type: "start", partial: output });
await processStream(response, output, stream, model);
await processStream(response, output, stream, model, options);
if (options?.signal?.aborted) {
throw new Error("Request was aborted");
@@ -319,6 +327,10 @@ function buildRequestBody(
body.temperature = options.temperature;
}
if (options?.serviceTier !== undefined) {
body.service_tier = options.serviceTier;
}
if (context.tools) {
body.tools = convertResponsesTools(context.tools, { strict: null });
}
@@ -342,6 +354,28 @@ function clampReasoningEffort(modelId: string, effort: string): string {
return effort;
}
function getServiceTierCostMultiplier(serviceTier: ResponseCreateParamsStreaming["service_tier"] | undefined): number {
switch (serviceTier) {
case "flex":
return 0.5;
case "priority":
return 2;
default:
return 1;
}
}
function applyServiceTierPricing(usage: Usage, serviceTier: ResponseCreateParamsStreaming["service_tier"] | undefined) {
const multiplier = getServiceTierCostMultiplier(serviceTier);
if (multiplier === 1) return;
usage.cost.input *= multiplier;
usage.cost.output *= multiplier;
usage.cost.cacheRead *= multiplier;
usage.cost.cacheWrite *= multiplier;
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
}
function resolveCodexUrl(baseUrl?: string): string {
const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;
const normalized = raw.replace(/\/+$/, "");
@@ -366,8 +400,12 @@ async function processStream(
output: AssistantMessage,
stream: AssistantMessageEventStream,
model: Model<"openai-codex-responses">,
options?: OpenAICodexResponsesOptions,
): Promise<void> {
await processResponsesStream(mapCodexEvents(parseSSE(response)), output, stream, model);
await processResponsesStream(mapCodexEvents(parseSSE(response)), output, stream, model, {
serviceTier: options?.serviceTier,
applyServiceTierPricing,
});
}
async function* mapCodexEvents(events: AsyncIterable<Record<string, unknown>>): AsyncGenerator<ResponseStreamEvent> {
@@ -806,7 +844,10 @@ async function processWebSocketStream(
socket.send(JSON.stringify({ type: "response.create", ...body }));
onStart();
stream.push({ type: "start", partial: output });
await processResponsesStream(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, model);
await processResponsesStream(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, model, {
serviceTier: options?.serviceTier,
applyServiceTierPricing,
});
if (options?.signal?.aborted) {
keepConnection = false;
}