[codex] fix cross-database JSON boundary handling (#283)

* fix: normalize cross-database json boundaries

* fix: address json boundary review comments

---------

Co-authored-by: apple <apple@appledeMacBook-Pro.local>
This commit is contained in:
Cita
2026-03-26 20:21:19 +08:00
committed by GitHub
parent 7e1fb8eb1b
commit b3387dfba7
30 changed files with 1134 additions and 97 deletions
@@ -120,7 +120,7 @@ function createSelectedChannel(options?: {
siteUrl?: string;
sitePlatform?: string;
username?: string;
extraConfig?: string;
extraConfig?: unknown;
tokenValue?: string;
actualModel?: string;
}) {
@@ -1435,6 +1435,114 @@ describe('responses websocket transport', () => {
it('falls back to the HTTP responses route when codex upstream websocket is globally disabled', async () => {
(config as any).codexUpstreamWebsocketEnabled = false;
fetchMock
.mockResolvedValueOnce(createSseResponse([
'event: response.completed\n',
'data: {"type":"response.completed","response":{"id":"resp_http_1","model":"gpt-5.4","status":"completed","output":[{"id":"msg_1","type":"message","role":"assistant","status":"completed","content":[{"type":"output_text","text":"first"}]}],"usage":{"input_tokens":3,"output_tokens":1,"total_tokens":4}}}\n\n',
'data: [DONE]\n\n',
]))
.mockResolvedValueOnce(createSseResponse([
'event: response.completed\n',
'data: {"type":"response.completed","response":{"id":"resp_http_2","model":"gpt-5.4","status":"completed","output":[{"id":"msg_2","type":"message","role":"assistant","status":"completed","content":[{"type":"output_text","text":"second"}]}],"usage":{"input_tokens":5,"output_tokens":1,"total_tokens":6}}}\n\n',
'data: [DONE]\n\n',
]));
const socket = createClientSocket(baseUrl);
await waitForSocketOpen(socket);
const firstResponsePromise = waitForSocketMessageMatching(
socket,
(message) => message?.type === 'response.completed' && message?.response?.id === 'resp_http_1',
);
socket.send(JSON.stringify({
type: 'response.create',
model: 'gpt-5.4',
instructions: 'be helpful',
input: [],
}));
const firstMessage = await firstResponsePromise;
const secondResponsePromise = waitForSocketMessageMatching(
socket,
(message) => message?.type === 'response.completed' && message?.response?.id === 'resp_http_2',
);
socket.send(JSON.stringify({
type: 'response.create',
model: 'gpt-5.4',
previous_response_id: 'resp_http_1',
input: [
{
id: 'tool_out_1',
type: 'function_call_output',
call_id: 'call_1',
output: 'tool result',
},
],
}));
const secondMessage = await secondResponsePromise;
socket.close();
expect(firstMessage).toMatchObject({
type: 'response.completed',
response: {
id: 'resp_http_1',
},
});
expect(secondMessage).toMatchObject({
type: 'response.completed',
response: {
id: 'resp_http_2',
},
});
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(fetchMock.mock.calls[0]?.[1]).toMatchObject({
method: 'POST',
body: JSON.stringify({
model: 'gpt-5.4',
instructions: 'be helpful',
input: [],
stream: true,
store: false,
}),
});
const [, secondOptions] = fetchMock.mock.calls[1] as [string, RequestInit];
const secondBody = JSON.parse(String(secondOptions.body));
expect(secondBody.previous_response_id).toBeUndefined();
expect(secondBody.instructions).toBe('be helpful');
expect(secondBody.stream).toBe(true);
expect(secondBody.store).toBe(false);
expect(secondBody.input).toEqual([
{
id: 'msg_1',
type: 'message',
role: 'assistant',
status: 'completed',
content: [{ type: 'output_text', text: 'first' }],
},
{
id: 'tool_out_1',
type: 'function_call_output',
call_id: 'call_1',
output: 'tool result',
},
]);
});
it('disables codex websocket incremental transport when the selected account marks websockets as disabled', async () => {
const selectedChannel = createSelectedChannel({
extraConfig: {
credentialMode: 'session',
websockets: false,
oauth: {
provider: 'codex',
accountId: 'chatgpt-account-123',
email: 'codex-user@example.com',
},
},
});
selectChannelMock.mockReturnValue(selectedChannel);
previewSelectedChannelMock.mockResolvedValue(selectedChannel);
fetchMock
.mockResolvedValueOnce(createSseResponse([
'event: response.completed\n',
+44 -1
View File
@@ -57,6 +57,32 @@ function headerValueToTrimmedString(value: unknown): string {
return '';
}
function toBooleanLike(value: unknown): boolean | null {
if (typeof value === 'boolean') return value;
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase();
if (normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on') return true;
if (normalized === 'false' || normalized === '0' || normalized === 'no' || normalized === 'off') return false;
}
return null;
}
function parseExtraConfigRecord(extraConfig: unknown): Record<string, unknown> | null {
if (isRecord(extraConfig)) return extraConfig;
if (typeof extraConfig !== 'string') return null;
try {
const parsed = JSON.parse(extraConfig);
return isRecord(parsed) ? parsed : null;
} catch {
return null;
}
}
function readNestedRecord(value: unknown, key: string): Record<string, unknown> | null {
if (!isRecord(value)) return null;
const nested = value[key];
return isRecord(nested) ? nested : null;
}
function selectedChannelModelMatches(
selectedChannel: SelectedChannel | null,
requestModel: string,
@@ -76,7 +102,24 @@ function selectedChannelSupportsCodexWebsocketTransport(
const platform = asTrimmedString(selectedChannel.site?.platform).toLowerCase();
if (platform !== 'codex') return false;
if (!selectedChannelModelMatches(selectedChannel, requestModel)) return false;
return !!config.codexUpstreamWebsocketEnabled;
if (!config.codexUpstreamWebsocketEnabled) return false;
const extraConfig = parseExtraConfigRecord(selectedChannel.account.extraConfig);
const oauth = readNestedRecord(extraConfig, 'oauth');
const providerData = readNestedRecord(oauth, 'providerData');
const candidateFlags = [
extraConfig?.websockets,
readNestedRecord(extraConfig, 'attributes')?.websockets,
readNestedRecord(extraConfig, 'metadata')?.websockets,
providerData?.websockets,
readNestedRecord(providerData, 'attributes')?.websockets,
readNestedRecord(providerData, 'metadata')?.websockets,
];
for (const candidate of candidateFlags) {
const parsed = toBooleanLike(candidate);
if (parsed !== null) return parsed;
}
return true;
}
function selectedChannelSupportsIncrementalInput(