1
0
forked from boos/BoosAPI

[codex] strip codex responses max_output_tokens (#399)

* strip codex responses max_output_tokens

* address codex responses review comments
This commit is contained in:
Cita
2026-04-02 17:28:38 +08:00
committed by GitHub
parent 19bfed2e33
commit 6ad9ec6911
6 changed files with 165 additions and 68 deletions
@@ -98,6 +98,15 @@ describe('proxy route architecture boundaries', () => {
expect(source).not.toContain('shouldInjectDerivedPromptCacheKey'); expect(source).not.toContain('shouldInjectDerivedPromptCacheKey');
}); });
it('keeps codex responses normalization behind transformer helpers', () => {
const source = readSource('./upstreamEndpoint.ts');
expect(source).toContain("from '../../transformers/openai/responses/codexCompatibility.js'");
expect(source).not.toContain('function ensureCodexResponsesInstructions(');
expect(source).not.toContain('function ensureCodexResponsesStoreFalse(');
expect(source).not.toContain('function stripCodexUnsupportedResponsesFields(');
expect(source).not.toContain('function applyCodexResponsesCompatibility(');
});
it('keeps endpoint runtime snapshot helper out of the route layer', () => { it('keeps endpoint runtime snapshot helper out of the route layer', () => {
const source = readSource('./upstreamEndpoint.ts'); const source = readSource('./upstreamEndpoint.ts');
expect(source).not.toContain('function getUpstreamEndpointRuntimeStateSnapshot('); expect(source).not.toContain('function getUpstreamEndpointRuntimeStateSnapshot(');
@@ -210,7 +210,8 @@ describe('chat proxy codex oauth compatibility', () => {
expect(forwardedBody.store).toBe(false); expect(forwardedBody.store).toBe(false);
expect(forwardedBody.parallel_tool_calls).toBeUndefined(); expect(forwardedBody.parallel_tool_calls).toBeUndefined();
expect(forwardedBody.include).toBeUndefined(); expect(forwardedBody.include).toBeUndefined();
expect(forwardedBody.max_output_tokens).toBe(256); expect(forwardedBody.max_output_tokens).toBeUndefined();
expect(forwardedBody.max_tokens).toBeUndefined();
expect(forwardedBody.max_completion_tokens).toBeUndefined(); expect(forwardedBody.max_completion_tokens).toBeUndefined();
const body = response.json(); const body = response.json();
@@ -736,7 +736,9 @@ describe('buildUpstreamEndpointRequest', () => {
expect(request.body.store).toBe(false); expect(request.body.store).toBe(false);
expect(request.body.parallel_tool_calls).toBeUndefined(); expect(request.body.parallel_tool_calls).toBeUndefined();
expect(request.body.include).toBeUndefined(); expect(request.body.include).toBeUndefined();
expect(request.body.max_output_tokens).toBe(4096); expect(request.body.max_output_tokens).toBeUndefined();
expect(request.body.max_tokens).toBeUndefined();
expect(request.body.max_completion_tokens).toBeUndefined();
expect(request.body.temperature).toBe(0.2); expect(request.body.temperature).toBe(0.2);
expect(request.body.top_p).toBe(0.9); expect(request.body.top_p).toBe(0.9);
expect(request.body.user).toBe('drop-me'); expect(request.body.user).toBe('drop-me');
@@ -834,7 +836,7 @@ describe('buildUpstreamEndpointRequest', () => {
expect(request.body.prompt_cache_key).toBe('codex-cache-123'); expect(request.body.prompt_cache_key).toBe('codex-cache-123');
}); });
it('preserves native codex responses continuity and request fields without compatibility rewrites', () => { it('preserves native codex responses continuity while stripping unsupported token limit fields', () => {
const request = buildUpstreamEndpointRequest({ const request = buildUpstreamEndpointRequest({
endpoint: 'responses', endpoint: 'responses',
modelName: 'gpt-5.4', modelName: 'gpt-5.4',
@@ -854,6 +856,8 @@ describe('buildUpstreamEndpointRequest', () => {
previous_response_id: 'resp_prev_123', previous_response_id: 'resp_prev_123',
temperature: 0.3, temperature: 0.3,
top_p: 0.8, top_p: 0.8,
max_completion_tokens: 256,
max_tokens: 128,
max_output_tokens: 512, max_output_tokens: 512,
}, },
providerHeaders: { providerHeaders: {
@@ -873,7 +877,9 @@ describe('buildUpstreamEndpointRequest', () => {
expect(request.body.previous_response_id).toBe('resp_prev_123'); expect(request.body.previous_response_id).toBe('resp_prev_123');
expect(request.body.temperature).toBe(0.3); expect(request.body.temperature).toBe(0.3);
expect(request.body.top_p).toBe(0.8); expect(request.body.top_p).toBe(0.8);
expect(request.body.max_output_tokens).toBe(512); expect(request.body.max_completion_tokens).toBeUndefined();
expect(request.body.max_tokens).toBeUndefined();
expect(request.body.max_output_tokens).toBeUndefined();
}); });
it('applies configured codex header defaults with CLIProxyAPI-compatible precedence', () => { it('applies configured codex header defaults with CLIProxyAPI-compatible precedence', () => {
@@ -1002,6 +1008,9 @@ describe('buildUpstreamEndpointRequest', () => {
models: [{ name: 'gpt-5.4', protocol: 'codex' }], models: [{ name: 'gpt-5.4', protocol: 'codex' }],
params: { params: {
'text.verbosity': 'low', 'text.verbosity': 'low',
max_completion_tokens: 48,
max_tokens: 32,
max_output_tokens: 64,
store: true, store: true,
}, },
}, },
@@ -1038,6 +1047,9 @@ describe('buildUpstreamEndpointRequest', () => {
expect(request.body.reasoning).toEqual({ effort: 'high' }); expect(request.body.reasoning).toEqual({ effort: 'high' });
expect(request.body.text).toEqual({ verbosity: 'low' }); expect(request.body.text).toEqual({ verbosity: 'low' });
expect(request.body.safety_identifier).toBeUndefined(); expect(request.body.safety_identifier).toBeUndefined();
expect(request.body.max_completion_tokens).toBeUndefined();
expect(request.body.max_tokens).toBeUndefined();
expect(request.body.max_output_tokens).toBeUndefined();
expect(request.body.store).toBe(false); expect(request.body.store).toBe(false);
}); });
+3 -63
View File
@@ -17,6 +17,7 @@ import {
convertOpenAiBodyToResponsesBody as convertOpenAiBodyToResponsesBodyViaTransformer, convertOpenAiBodyToResponsesBody as convertOpenAiBodyToResponsesBodyViaTransformer,
sanitizeResponsesBodyForProxy as sanitizeResponsesBodyForProxyViaTransformer, sanitizeResponsesBodyForProxy as sanitizeResponsesBodyForProxyViaTransformer,
} from '../../transformers/openai/responses/conversion.js'; } from '../../transformers/openai/responses/conversion.js';
import { normalizeCodexResponsesBodyForProxy } from '../../transformers/openai/responses/codexCompatibility.js';
import { import {
convertOpenAiBodyToAnthropicMessagesBody, convertOpenAiBodyToAnthropicMessagesBody,
sanitizeAnthropicMessagesBody, sanitizeAnthropicMessagesBody,
@@ -370,61 +371,6 @@ function toFiniteNumber(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? value : null; return typeof value === 'number' && Number.isFinite(value) ? value : null;
} }
function ensureCodexResponsesInstructions(
body: Record<string, unknown>,
sitePlatform: string,
): Record<string, unknown> {
if (sitePlatform !== 'codex') return body;
if (typeof body.instructions === 'string') return body;
return {
...body,
instructions: '',
};
}
function ensureCodexResponsesStoreFalse(
body: Record<string, unknown>,
sitePlatform: string,
): Record<string, unknown> {
if (sitePlatform !== 'codex') return body;
return {
...body,
store: false,
};
}
function convertCodexSystemRoleToDeveloper(input: unknown): unknown {
if (!Array.isArray(input)) return input;
return input.map((item) => {
if (!isRecord(item)) return item;
if (asTrimmedString(item.type).toLowerCase() !== 'message') return item;
if (asTrimmedString(item.role).toLowerCase() !== 'system') return item;
return {
...item,
role: 'developer',
};
});
}
function applyCodexResponsesCompatibility(
body: Record<string, unknown>,
sitePlatform: string,
): Record<string, unknown> {
if (sitePlatform !== 'codex') return body;
const next: Record<string, unknown> = {
...body,
input: convertCodexSystemRoleToDeveloper(body.input),
};
if (typeof next.instructions !== 'string') {
next.instructions = '';
}
return next;
}
function normalizeEndpointTypes(value: unknown): UpstreamEndpoint[] { function normalizeEndpointTypes(value: unknown): UpstreamEndpoint[] {
const raw = asTrimmedString(value).toLowerCase(); const raw = asTrimmedString(value).toLowerCase();
if (!raw) return []; if (!raw) return [];
@@ -957,17 +903,11 @@ export function buildUpstreamEndpointRequest(input: {
if (preserveWebsocketIncrementalMode && rawBody.generate === false) { if (preserveWebsocketIncrementalMode && rawBody.generate === false) {
sanitizedResponsesBody.generate = false; sanitizedResponsesBody.generate = false;
} }
const body = ensureCodexResponsesStoreFalse( const body = normalizeCodexResponsesBodyForProxy(
ensureCodexResponsesInstructions(
applyCodexResponsesCompatibility(
sanitizedResponsesBody, sanitizedResponsesBody,
sitePlatform, sitePlatform,
),
sitePlatform,
),
sitePlatform,
); );
const configuredResponsesBody = ensureCodexResponsesStoreFalse( const configuredResponsesBody = normalizeCodexResponsesBodyForProxy(
applyConfiguredPayloadRules(body), applyConfiguredPayloadRules(body),
sitePlatform, sitePlatform,
); );
@@ -0,0 +1,45 @@
import { describe, expect, it } from 'vitest';
import { normalizeCodexResponsesBodyForProxy } from './codexCompatibility.js';
describe('normalizeCodexResponsesBodyForProxy', () => {
it('normalizes codex responses bodies before proxying upstream', () => {
const body = normalizeCodexResponsesBodyForProxy({
input: [
{
type: 'message',
role: 'system',
content: [{ type: 'input_text', text: 'be precise' }],
},
],
max_output_tokens: 512,
max_completion_tokens: 256,
max_tokens: 128,
temperature: 0.3,
store: true,
}, 'codex');
expect(body).toEqual({
input: [
{
type: 'message',
role: 'developer',
content: [{ type: 'input_text', text: 'be precise' }],
},
],
instructions: '',
store: false,
temperature: 0.3,
});
});
it('leaves non-codex bodies untouched', () => {
const source = {
input: 'hello',
max_output_tokens: 512,
};
const body = normalizeCodexResponsesBodyForProxy(source, 'openai');
expect(body).toBe(source);
});
});
@@ -0,0 +1,90 @@
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object';
}
function asTrimmedString(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
function ensureCodexResponsesInstructions(
body: Record<string, unknown>,
sitePlatform: string,
): Record<string, unknown> {
if (sitePlatform !== 'codex') return body;
if (typeof body.instructions === 'string') return body;
return {
...body,
instructions: '',
};
}
function ensureCodexResponsesStoreFalse(
body: Record<string, unknown>,
sitePlatform: string,
): Record<string, unknown> {
if (sitePlatform !== 'codex') return body;
return {
...body,
store: false,
};
}
function stripCodexUnsupportedResponsesFields(
body: Record<string, unknown>,
sitePlatform: string,
): Record<string, unknown> {
if (sitePlatform !== 'codex') return body;
const next = { ...body };
delete next.max_output_tokens;
delete next.max_completion_tokens;
delete next.max_tokens;
return next;
}
function convertCodexSystemRoleToDeveloper(input: unknown): unknown {
if (!Array.isArray(input)) return input;
return input.map((item) => {
if (!isRecord(item)) return item;
if (asTrimmedString(item.type).toLowerCase() !== 'message') return item;
if (asTrimmedString(item.role).toLowerCase() !== 'system') return item;
return {
...item,
role: 'developer',
};
});
}
function applyCodexResponsesCompatibility(
body: Record<string, unknown>,
sitePlatform: string,
): Record<string, unknown> {
if (sitePlatform !== 'codex') return body;
const next: Record<string, unknown> = {
...body,
input: convertCodexSystemRoleToDeveloper(body.input),
};
if (typeof next.instructions !== 'string') {
next.instructions = '';
}
return next;
}
export function normalizeCodexResponsesBodyForProxy(
body: Record<string, unknown>,
sitePlatform: string,
): Record<string, unknown> {
if (sitePlatform !== 'codex') return body;
return ensureCodexResponsesStoreFalse(
stripCodexUnsupportedResponsesFields(
ensureCodexResponsesInstructions(
applyCodexResponsesCompatibility(body, sitePlatform),
sitePlatform,
),
sitePlatform,
),
sitePlatform,
);
}