fix(ai): support service tier in codex responses (#2996)
This commit is contained in:
committed by
GitHub
parent
4dda448a3f
commit
f829f80885
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user