adding responses api to oai

This commit is contained in:
2026-07-21 11:17:44 -05:00
parent 0e9131dafa
commit 08a11825ea
4 changed files with 334 additions and 0 deletions
+84
View File
@@ -87,6 +87,90 @@ describe('proxy', () => {
expect(fetchImpl.mock.calls[0][1].headers.accept).toBe('text/event-stream');
});
it('translates Responses requests and returns a Responses object', async () => {
const fetchImpl = vi.fn().mockResolvedValue(response(JSON.stringify({
id: 'chatcmpl-response', model: 'm',
choices: [{ message: { role: 'assistant', content: 'Hello from Responses' }, finish_reason: 'stop' }],
usage: { prompt_tokens: 8, completion_tokens: 3 },
})));
const app = createProxyApp({ keys: ['key'], fetchImpl });
const result = await request(app).post('/oai/v1/responses').send({
model: 'm', instructions: 'Be brief', max_output_tokens: 20,
input: [{ role: 'user', content: [{ type: 'input_text', text: 'Hello' }] }],
}).expect(200);
expect(result.body).toMatchObject({ object: 'response', status: 'completed', model: 'm' });
expect(result.body.output[0].content[0]).toMatchObject({ type: 'output_text', text: 'Hello from Responses' });
expect(result.body.usage).toEqual({ input_tokens: 8, output_tokens: 3, total_tokens: 11 });
expect(JSON.parse(fetchImpl.mock.calls[0][1].body)).toEqual({
model: 'm', max_tokens: 20,
messages: [{ role: 'system', content: 'Be brief' }, { role: 'user', content: [{ type: 'text', text: 'Hello' }] }],
});
});
it('translates Responses streaming events', async () => {
const fetchImpl = vi.fn().mockResolvedValue(response([
'data: {"id":"c1","model":"m","choices":[{"delta":{"role":"assistant","content":"Hi"}}]}',
'data: {"choices":[{"delta":{},"finish_reason":"stop"}]}',
'data: [DONE]', '',
].join('\n\n'), { headers: { 'content-type': 'text/event-stream' } }));
const app = createProxyApp({ keys: ['key'], fetchImpl });
const result = await request(app).post('/oai/v1/responses').send({ model: 'm', input: 'Hi', stream: true }).expect(200);
expect(result.text).toContain('event: response.created');
expect(result.text).toContain('event: response.output_text.delta');
expect(result.text).toContain('"delta":"Hi"');
expect(result.text).toContain('event: response.completed');
});
it('streams tool calls as completed Responses function_call items', async () => {
const fetchImpl = vi.fn().mockResolvedValue(response([
'data: {"id":"c-tool","model":"m","choices":[{"delta":{"tool_calls":[{"index":0,"id":"call-9","function":{"name":"read_file","arguments":"{\\"path\\":"}}]}}]}',
'data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"main.py\\"}"}}]}}]}',
'data: {"choices":[{"delta":{},"finish_reason":"tool_calls"}]}',
'data: [DONE]', '',
].join('\n\n'), { headers: { 'content-type': 'text/event-stream' } }));
const app = createProxyApp({ keys: ['key'], fetchImpl });
const result = await request(app).post('/oai/v1/responses').send({ model: 'm', input: 'explore', stream: true }).expect(200);
expect(result.text).toContain('event: response.output_item.added');
expect(result.text).toContain('"name":"read_file"');
expect(result.text).toContain('event: response.function_call_arguments.delta');
expect(result.text).toContain('event: response.function_call_arguments.done');
expect(result.text).toContain('"arguments":"{\\"path\\":\\"main.py\\"}"');
expect(result.text).toContain('event: response.output_item.done');
const completed = result.text.split('\n\n').find((line) => line.startsWith('event: response.completed'));
expect(completed).toContain('"type":"function_call"');
expect(completed).toContain('"call_id":"call-9"');
});
it('streams reasoning content as Responses reasoning events', async () => {
const fetchImpl = vi.fn().mockResolvedValue(response([
'data: {"id":"c-reason","model":"m","choices":[{"delta":{"reasoning_content":"think first "}}]}',
'data: {"choices":[{"delta":{"reasoning_content":"then answer"}}]}',
'data: {"choices":[{"delta":{"content":"done"},"finish_reason":"stop"}]}',
'data: [DONE]', '',
].join('\n\n'), { headers: { 'content-type': 'text/event-stream' } }));
const app = createProxyApp({ keys: ['key'], fetchImpl });
const result = await request(app).post('/oai/v1/responses').send({ model: 'm', input: 'Hi', stream: true }).expect(200);
expect(result.text).toContain('event: response.reasoning_summary_text.delta');
expect(result.text).toContain('"delta":"think first "');
expect(result.text).toContain('"delta":"then answer"');
expect(result.text).toContain('event: response.reasoning_summary_text.done');
expect(result.text).toContain('"output_index":1');
});
it('accepts nested Chat Completions function tools from Codex', async () => {
const fetchImpl = vi.fn().mockResolvedValue(response(JSON.stringify({
id: 'chatcmpl-tool', choices: [{ message: { role: 'assistant', content: 'done' }, finish_reason: 'stop' }], usage: {},
})));
const app = createProxyApp({ keys: ['key'], fetchImpl });
await request(app).post('/oai/v1/responses').send({
model: 'm', input: 'lookup', tools: [{ type: 'function', function: { name: 'lookup', parameters: { type: 'object' } } }],
}).expect(200);
expect(JSON.parse(fetchImpl.mock.calls[0][1].body).tools).toEqual([{
type: 'function', function: { name: 'lookup', parameters: { type: 'object' } },
}]);
});
it('propagates upstream errors', async () => {
const fetchImpl = vi.fn().mockResolvedValue(response('{"error":"bad key"}', { status: 401 }));
const app = createProxyApp({ keys: ['key'], fetchImpl });