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 * 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)
|
// NEVER convert to top-level runtime imports - breaks browser/Vite builds (web-ui)
|
||||||
let _os: typeof NodeOs | null = null;
|
let _os: typeof NodeOs | null = null;
|
||||||
@@ -25,6 +30,7 @@ import type {
|
|||||||
SimpleStreamOptions,
|
SimpleStreamOptions,
|
||||||
StreamFunction,
|
StreamFunction,
|
||||||
StreamOptions,
|
StreamOptions,
|
||||||
|
Usage,
|
||||||
} from "../types.js";
|
} from "../types.js";
|
||||||
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
||||||
import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.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 {
|
export interface OpenAICodexResponsesOptions extends StreamOptions {
|
||||||
reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||||
reasoningSummary?: "auto" | "concise" | "detailed" | "off" | "on" | null;
|
reasoningSummary?: "auto" | "concise" | "detailed" | "off" | "on" | null;
|
||||||
|
serviceTier?: ResponseCreateParamsStreaming["service_tier"];
|
||||||
textVerbosity?: "low" | "medium" | "high";
|
textVerbosity?: "low" | "medium" | "high";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +79,7 @@ interface RequestBody {
|
|||||||
parallel_tool_calls?: boolean;
|
parallel_tool_calls?: boolean;
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
reasoning?: { effort?: string; summary?: string };
|
reasoning?: { effort?: string; summary?: string };
|
||||||
|
service_tier?: ResponseCreateParamsStreaming["service_tier"];
|
||||||
text?: { verbosity?: string };
|
text?: { verbosity?: string };
|
||||||
include?: string[];
|
include?: string[];
|
||||||
prompt_cache_key?: string;
|
prompt_cache_key?: string;
|
||||||
@@ -251,7 +259,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
|||||||
}
|
}
|
||||||
|
|
||||||
stream.push({ type: "start", partial: output });
|
stream.push({ type: "start", partial: output });
|
||||||
await processStream(response, output, stream, model);
|
await processStream(response, output, stream, model, options);
|
||||||
|
|
||||||
if (options?.signal?.aborted) {
|
if (options?.signal?.aborted) {
|
||||||
throw new Error("Request was aborted");
|
throw new Error("Request was aborted");
|
||||||
@@ -319,6 +327,10 @@ function buildRequestBody(
|
|||||||
body.temperature = options.temperature;
|
body.temperature = options.temperature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.serviceTier !== undefined) {
|
||||||
|
body.service_tier = options.serviceTier;
|
||||||
|
}
|
||||||
|
|
||||||
if (context.tools) {
|
if (context.tools) {
|
||||||
body.tools = convertResponsesTools(context.tools, { strict: null });
|
body.tools = convertResponsesTools(context.tools, { strict: null });
|
||||||
}
|
}
|
||||||
@@ -342,6 +354,28 @@ function clampReasoningEffort(modelId: string, effort: string): string {
|
|||||||
return effort;
|
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 {
|
function resolveCodexUrl(baseUrl?: string): string {
|
||||||
const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;
|
const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;
|
||||||
const normalized = raw.replace(/\/+$/, "");
|
const normalized = raw.replace(/\/+$/, "");
|
||||||
@@ -366,8 +400,12 @@ async function processStream(
|
|||||||
output: AssistantMessage,
|
output: AssistantMessage,
|
||||||
stream: AssistantMessageEventStream,
|
stream: AssistantMessageEventStream,
|
||||||
model: Model<"openai-codex-responses">,
|
model: Model<"openai-codex-responses">,
|
||||||
|
options?: OpenAICodexResponsesOptions,
|
||||||
): Promise<void> {
|
): 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> {
|
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 }));
|
socket.send(JSON.stringify({ type: "response.create", ...body }));
|
||||||
onStart();
|
onStart();
|
||||||
stream.push({ type: "start", partial: output });
|
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) {
|
if (options?.signal?.aborted) {
|
||||||
keepConnection = false;
|
keepConnection = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user