1
0
forked from boos/BoosAPI

Merge pull request #150 from cita-777/fix/codex-responses-runtime-parity

fix: harden codex runtime header forwarding
This commit is contained in:
Cita
2026-03-18 15:17:43 +08:00
committed by GitHub
5 changed files with 62 additions and 7 deletions
+1 -1
View File
@@ -994,7 +994,7 @@ describe('oauth routes', { timeout: 15_000 }, () => {
success: true,
quota: expect.objectContaining({
status: 'unsupported',
providerMessage: 'current local CLIProxyAPI evidence does not expose official quota windows for antigravity oauth',
providerMessage: 'official quota windows are not exposed for antigravity oauth',
}),
});
});
@@ -173,6 +173,9 @@ describe('responses proxy codex oauth refresh', () => {
const response = await app.inject({
method: 'POST',
url: '/v1/responses',
headers: {
'user-agent': 'CodexClient/1.0',
},
payload: {
model: 'gpt-5.2-codex',
input: 'hello codex',
@@ -194,7 +197,7 @@ describe('responses proxy codex oauth refresh', () => {
expect(secondOptions.headers.Version || secondOptions.headers.version).toBe('0.101.0');
expect(String(secondOptions.headers.Session_id || secondOptions.headers.session_id || '')).toMatch(/^[0-9a-f-]{36}$/i);
expect(secondOptions.headers.Conversation_id || secondOptions.headers.conversation_id).toBeUndefined();
expect(secondOptions.headers['User-Agent'] || secondOptions.headers['user-agent']).toBe('codex_cli_rs/0.101.0 (Mac OS 26.0.1; arm64) Apple_Terminal/464');
expect(secondOptions.headers['User-Agent'] || secondOptions.headers['user-agent']).toBe('CodexClient/1.0');
expect(secondOptions.headers.Accept || secondOptions.headers.accept).toBe('text/event-stream');
expect(secondOptions.headers.Connection || secondOptions.headers.connection).toBe('Keep-Alive');
expect(response.json()?.output_text).toContain('ok after codex token refresh');
@@ -272,6 +275,50 @@ describe('responses proxy codex oauth refresh', () => {
expect(forwardedBody.prompt_cache_key).toBe('codex-cache-123');
});
it('strips generic downstream headers before forwarding codex responses upstream', async () => {
fetchMock.mockResolvedValue(new Response(JSON.stringify({
id: 'resp_codex_header_filter',
object: 'response',
model: 'gpt-5.2-codex',
status: 'completed',
output_text: 'ok with filtered headers',
usage: { input_tokens: 4, output_tokens: 2, total_tokens: 6 },
}), {
status: 200,
headers: { 'content-type': 'application/json' },
}));
const response = await app.inject({
method: 'POST',
url: '/v1/responses',
headers: {
'openai-beta': 'responses-2025-03-11',
'x-openai-client-user-agent': '{"client":"openclaw"}',
origin: 'https://openclaw.example',
referer: 'https://openclaw.example/app',
'user-agent': 'OpenClaw/1.0',
version: '0.202.0',
session_id: 'session-from-client',
},
payload: {
model: 'gpt-5.2-codex',
input: 'hello codex',
},
});
expect(response.statusCode).toBe(200);
expect(fetchMock).toHaveBeenCalledTimes(1);
const [, options] = fetchMock.mock.calls[0] as [string, any];
expect(options.headers.Version || options.headers.version).toBe('0.202.0');
expect(options.headers.Session_id || options.headers.session_id).toBe('session-from-client');
expect(options.headers['User-Agent'] || options.headers['user-agent']).toBe('OpenClaw/1.0');
expect(options.headers['openai-beta']).toBeUndefined();
expect(options.headers['x-openai-client-user-agent']).toBeUndefined();
expect(options.headers.origin).toBeUndefined();
expect(options.headers.referer).toBeUndefined();
});
it('records codex usage_limit_reached reset hints on upstream 429 failures', async () => {
fetchMock.mockResolvedValue(new Response(JSON.stringify({
error: {
+11 -3
View File
@@ -286,8 +286,15 @@ function buildCodexRuntimeHeaders(input: {
explicitSessionId?: string | null;
continuityKey?: string | null;
}): Record<string, string> {
const authorization = (
getInputHeader(input.baseHeaders, 'authorization')
|| getInputHeader(input.baseHeaders, 'Authorization')
|| ''
);
const originator = getInputHeader(input.providerHeaders, 'originator') || 'codex_cli_rs';
const accountId = getInputHeader(input.providerHeaders, 'chatgpt-account-id');
const version = getInputHeader(input.baseHeaders, 'version') || CODEX_CLIENT_VERSION;
const userAgent = getInputHeader(input.baseHeaders, 'user-agent') || CODEX_DEFAULT_USER_AGENT;
const explicitSessionId = asTrimmedString(input.explicitSessionId);
const continuityKey = asTrimmedString(input.continuityKey);
const sessionId = (
@@ -305,13 +312,14 @@ function buildCodexRuntimeHeaders(input: {
);
return {
...input.baseHeaders,
Authorization: authorization,
'Content-Type': 'application/json',
...(accountId ? { 'Chatgpt-Account-Id': accountId } : {}),
Originator: originator,
Version: CODEX_CLIENT_VERSION,
Version: version,
Session_id: sessionId,
...(conversationId ? { Conversation_id: conversationId } : {}),
'User-Agent': CODEX_DEFAULT_USER_AGENT,
'User-Agent': userAgent,
Accept: 'text/event-stream',
Connection: 'Keep-Alive',
};
+1 -1
View File
@@ -76,7 +76,7 @@ describe('oauth quota snapshot helpers', () => {
expect(snapshot).toEqual({
status: 'unsupported',
source: 'official',
providerMessage: 'current local CLIProxyAPI evidence does not expose official quota windows for antigravity oauth',
providerMessage: 'official quota windows are not exposed for antigravity oauth',
windows: {
fiveHour: {
supported: false,
+1 -1
View File
@@ -54,7 +54,7 @@ function buildProviderUnsupportedSnapshot(provider: string): OauthQuotaSnapshot
return {
status: 'unsupported',
source: 'official',
providerMessage: `current local CLIProxyAPI evidence does not expose official quota windows for ${provider} oauth`,
providerMessage: `official quota windows are not exposed for ${provider} oauth`,
windows: {
fiveHour: buildUnsupportedWindow('official 5h quota window is unavailable for this provider'),
sevenDay: buildUnsupportedWindow('official 7d quota window is unavailable for this provider'),