Files
OmniRoute/open-sse/services/codexVerbosity.ts
Fadhil Yusuf 61bba3e3ca fix(codex): convert chat json schema to responses text format (#5933)
Integrated into release/v3.8.44 — converts Chat Completions json_schema response_format → Responses API text.format on the Codex path, and preserves existing text.format through verbosity normalization. Base redirected main→release; the openai-responses.ts split that landed this cycle was reconciled by re-applying the delta onto openai-responses/toResponses.ts. Validated locally: 48 translator-openai-responses-req + 8 codex-verbosity tests green.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-07-02 17:49:06 -03:00

60 lines
2.2 KiB
TypeScript

/**
* Codex (OpenAI Responses) verbosity normalization.
*
* The GPT-5 series added an output-verbosity control: Chat Completions exposes it
* as a top-level `verbosity` field, while the Responses API nests it as
* `text.verbosity` (low/medium/high). The CodexExecutor builds a Responses body and
* gates it through an allowlist that does NOT include `text`, so for translated
* requests both shapes are dropped silently and the hint never reaches upstream.
*
* This helper runs on the translated path (before the allowlist) and folds whichever
* shape arrived into a single, validated `text:{verbosity}`. `text` is then added to
* the allowlist so the hint survives. Other Responses `text` keys, such as
* `text.format` for structured output, are preserved.
*
* Ref: OpenAI GPT-5 docs (text.verbosity), Azure Foundry reasoning guide.
*/
type JsonRecord = Record<string, unknown>;
const VERBOSITY_LEVELS = new Set(["low", "medium", "high"]);
function asRecord(value: unknown): JsonRecord | null {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null;
}
function normalizeLevel(value: unknown): string | undefined {
if (typeof value !== "string") return undefined;
const level = value.toLowerCase();
return VERBOSITY_LEVELS.has(level) ? level : undefined;
}
/**
* Mutates `body` in place: resolves verbosity from `text.verbosity` (Responses) or
* the top-level `verbosity` (Chat Completions, which takes precedence when both are
* present), drops the Chat-only top-level field, and preserves any other existing
* Responses `text` configuration.
*/
export function normalizeCodexVerbosity(body: Record<string, unknown>): void {
const textRecord = asRecord(body.text);
let verbosity = textRecord ? normalizeLevel(textRecord.verbosity) : undefined;
const topLevel = normalizeLevel(body.verbosity);
if (topLevel) verbosity = topLevel;
delete body.verbosity;
const nextText = textRecord ? { ...textRecord } : {};
if (verbosity) {
nextText.verbosity = verbosity;
} else {
delete nextText.verbosity;
}
if (Object.keys(nextText).length > 0) {
body.text = nextText;
} else {
delete body.text;
}
}