adding responses api to oai
This commit is contained in:
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user