Download src/server/__tests__/proxy-streaming.test.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 18.3 kB
-
https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/server/__tests__/proxy-streaming.test.ts
- Command line
-
hf download hf://chenbhao/codev@afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/server/__tests__/proxy-streaming.test.ts
-
curl -L -o proxy-streaming.test.ts https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/server/__tests__/proxy-streaming.test.ts
18.3 kB
| /** | |
| * Unit tests for proxy streaming SSE transformation | |
| */ | |
| import { describe, test, expect } from 'bun:test' | |
| import { openaiChatStreamToAnthropic } from '../proxy/streaming/openaiChatStreamToAnthropic.js' | |
| import { openaiResponsesStreamToAnthropic } from '../proxy/streaming/openaiResponsesStreamToAnthropic.js' | |
| // βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function makeStream(chunks: string[]): ReadableStream<Uint8Array> { | |
| const encoder = new TextEncoder() | |
| return new ReadableStream({ | |
| start(controller) { | |
| for (const chunk of chunks) { | |
| controller.enqueue(encoder.encode(chunk)) | |
| } | |
| controller.close() | |
| }, | |
| }) | |
| } | |
| async function collectSse(stream: ReadableStream<Uint8Array>): Promise<Array<{ event: string; data: Record<string, unknown> }>> { | |
| const decoder = new TextDecoder() | |
| const reader = stream.getReader() | |
| let text = '' | |
| while (true) { | |
| const { done, value } = await reader.read() | |
| if (done) break | |
| text += decoder.decode(value, { stream: true }) | |
| } | |
| const events: Array<{ event: string; data: Record<string, unknown> }> = [] | |
| const blocks = text.split('\n\n').filter(Boolean) | |
| for (const block of blocks) { | |
| const lines = block.split('\n') | |
| let event = '' | |
| let data = '' | |
| for (const line of lines) { | |
| if (line.startsWith('event: ')) event = line.slice(7) | |
| if (line.startsWith('data: ')) data = line.slice(6) | |
| } | |
| if (event && data) { | |
| try { | |
| events.push({ event, data: JSON.parse(data) }) | |
| } catch { | |
| // skip unparseable | |
| } | |
| } | |
| } | |
| return events | |
| } | |
| // βββ OpenAI Chat Completions SSE β Anthropic SSE βββββββββββββββ | |
| describe('openaiChatStreamToAnthropic', () => { | |
| test('basic text streaming', async () => { | |
| const sseChunks = [ | |
| 'data: {"id":"c1","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c1","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c1","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"content":" world"},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c1","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', | |
| 'data: [DONE]\n\n', | |
| ] | |
| const upstream = makeStream(sseChunks) | |
| const anthropicStream = openaiChatStreamToAnthropic(upstream, 'gpt-4') | |
| const events = await collectSse(anthropicStream) | |
| // Should have: message_start, content_block_start, content_block_delta x2, message_delta, content_block_stop, message_stop | |
| const eventTypes = events.map((e) => e.event) | |
| expect(eventTypes[0]).toBe('message_start') | |
| expect(eventTypes).toContain('content_block_start') | |
| expect(eventTypes).toContain('content_block_delta') | |
| expect(eventTypes).toContain('message_delta') | |
| expect(eventTypes).toContain('message_stop') | |
| // Check message_start | |
| const msgStart = events.find((e) => e.event === 'message_start')! | |
| expect((msgStart.data.message as Record<string, unknown>).model).toBe('gpt-4') | |
| expect((msgStart.data.message as Record<string, unknown>).role).toBe('assistant') | |
| // Check text deltas | |
| const textDeltas = events.filter((e) => e.event === 'content_block_delta') | |
| const texts = textDeltas.map((e) => (e.data.delta as Record<string, unknown>).text) | |
| expect(texts).toContain('Hello') | |
| expect(texts).toContain(' world') | |
| // Check stop reason | |
| const msgDelta = events.find((e) => e.event === 'message_delta')! | |
| expect((msgDelta.data.delta as Record<string, unknown>).stop_reason).toBe('end_turn') | |
| }) | |
| test('tool call streaming', async () => { | |
| const sseChunks = [ | |
| 'data: {"id":"c2","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":null},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c2","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c2","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\\"city\\""}}]},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c2","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":\\"NYC\\"}"}}]},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c2","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}\n\n', | |
| 'data: [DONE]\n\n', | |
| ] | |
| const upstream = makeStream(sseChunks) | |
| const anthropicStream = openaiChatStreamToAnthropic(upstream, 'gpt-4') | |
| const events = await collectSse(anthropicStream) | |
| // Should have content_block_start with type tool_use | |
| const toolStart = events.find( | |
| (e) => e.event === 'content_block_start' && (e.data.content_block as Record<string, unknown>)?.type === 'tool_use', | |
| ) | |
| expect(toolStart).toBeDefined() | |
| expect((toolStart!.data.content_block as Record<string, unknown>).name).toBe('get_weather') | |
| expect((toolStart!.data.content_block as Record<string, unknown>).id).toBe('call_1') | |
| // Should have input_json_delta | |
| const jsonDeltas = events.filter( | |
| (e) => e.event === 'content_block_delta' && (e.data.delta as Record<string, unknown>)?.type === 'input_json_delta', | |
| ) | |
| expect(jsonDeltas.length).toBeGreaterThan(0) | |
| // Stop reason should be tool_use | |
| const msgDelta = events.find((e) => e.event === 'message_delta')! | |
| expect((msgDelta.data.delta as Record<string, unknown>).stop_reason).toBe('tool_use') | |
| }) | |
| test('tool call streaming preserves object arguments from local proxies', async () => { | |
| const sseChunks = [ | |
| 'data: {"id":"c-write","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_write","type":"function","function":{"name":"Write","arguments":{"file_path":"/tmp/issue-288.txt","content":"ok"}}}]},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c-write","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}\n\n', | |
| 'data: [DONE]\n\n', | |
| ] | |
| const events = await collectSse(openaiChatStreamToAnthropic(makeStream(sseChunks), 'gpt-4')) | |
| const jsonDeltas = events.filter( | |
| (e) => e.event === 'content_block_delta' && (e.data.delta as Record<string, unknown>)?.type === 'input_json_delta', | |
| ) | |
| expect(jsonDeltas).toHaveLength(1) | |
| expect((jsonDeltas[0].data.delta as Record<string, unknown>).partial_json).toBe( | |
| '{"file_path":"/tmp/issue-288.txt","content":"ok"}', | |
| ) | |
| const blockStops = events.filter((e) => e.event === 'content_block_stop') | |
| expect(blockStops).toHaveLength(1) | |
| expect(blockStops[0].data.index).toBe(0) | |
| }) | |
| test('empty stream (just DONE)', async () => { | |
| const upstream = makeStream(['data: [DONE]\n\n']) | |
| const anthropicStream = openaiChatStreamToAnthropic(upstream, 'gpt-4') | |
| const events = await collectSse(anthropicStream) | |
| // Should at least have message_stop | |
| expect(events.some((e) => e.event === 'message_stop')).toBe(true) | |
| }) | |
| test('event ordering: content_block_stop before message_delta', async () => { | |
| const sseChunks = [ | |
| 'data: {"id":"c3","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c3","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"content":"Hi"},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c3","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', | |
| 'data: [DONE]\n\n', | |
| ] | |
| const upstream = makeStream(sseChunks) | |
| const events = await collectSse(openaiChatStreamToAnthropic(upstream, 'gpt-4')) | |
| const types = events.map((e) => e.event) | |
| // content_block_stop MUST appear before message_delta | |
| const stopIdx = types.indexOf('content_block_stop') | |
| const deltaIdx = types.indexOf('message_delta') | |
| expect(stopIdx).toBeGreaterThan(-1) | |
| expect(deltaIdx).toBeGreaterThan(-1) | |
| expect(stopIdx).toBeLessThan(deltaIdx) | |
| // message_delta before message_stop | |
| const msgStopIdx = types.indexOf('message_stop') | |
| expect(deltaIdx).toBeLessThan(msgStopIdx) | |
| }) | |
| test('reasoning_content (DeepSeek, OpenRouter, XAI)', async () => { | |
| const sseChunks = [ | |
| 'data: {"id":"c4","object":"chat.completion.chunk","created":0,"model":"deepseek-chat","choices":[{"index":0,"delta":{"role":"assistant","content":"","reasoning_content":"Let me think"},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c4","object":"chat.completion.chunk","created":0,"model":"deepseek-chat","choices":[{"index":0,"delta":{"reasoning_content":" about this..."},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c4","object":"chat.completion.chunk","created":0,"model":"deepseek-chat","choices":[{"index":0,"delta":{"content":"Hello!"},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c4","object":"chat.completion.chunk","created":0,"model":"deepseek-chat","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', | |
| 'data: [DONE]\n\n', | |
| ] | |
| const upstream = makeStream(sseChunks) | |
| const events = await collectSse(openaiChatStreamToAnthropic(upstream, 'deepseek-chat')) | |
| // Should have thinking block | |
| const thinkingStart = events.find( | |
| (e) => e.event === 'content_block_start' && (e.data.content_block as Record<string, unknown>)?.type === 'thinking', | |
| ) | |
| expect(thinkingStart).toBeDefined() | |
| // Should have thinking deltas | |
| const thinkingDeltas = events.filter( | |
| (e) => e.event === 'content_block_delta' && (e.data.delta as Record<string, unknown>)?.type === 'thinking_delta', | |
| ) | |
| expect(thinkingDeltas.length).toBeGreaterThan(0) | |
| // Should have text block after thinking | |
| const textStart = events.find( | |
| (e) => e.event === 'content_block_start' && (e.data.content_block as Record<string, unknown>)?.type === 'text', | |
| ) | |
| expect(textStart).toBeDefined() | |
| // Text should come after thinking in index order | |
| expect((textStart!.data as Record<string, unknown>).index).toBeGreaterThan( | |
| (thinkingStart!.data as Record<string, unknown>).index as number, | |
| ) | |
| }) | |
| test('reasoning field (GLM-5, Cerebras, Groq)', async () => { | |
| const sseChunks = [ | |
| 'data: {"id":"c5","object":"chat.completion.chunk","created":0,"model":"glm-5","choices":[{"index":0,"delta":{"role":"assistant","reasoning":"Thinking here"},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c5","object":"chat.completion.chunk","created":0,"model":"glm-5","choices":[{"index":0,"delta":{"content":"Result"},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c5","object":"chat.completion.chunk","created":0,"model":"glm-5","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', | |
| 'data: [DONE]\n\n', | |
| ] | |
| const upstream = makeStream(sseChunks) | |
| const events = await collectSse(openaiChatStreamToAnthropic(upstream, 'glm-5')) | |
| // Should produce thinking delta from "reasoning" field | |
| const thinkingDeltas = events.filter( | |
| (e) => e.event === 'content_block_delta' && (e.data.delta as Record<string, unknown>)?.type === 'thinking_delta', | |
| ) | |
| expect(thinkingDeltas.length).toBe(1) | |
| expect((thinkingDeltas[0].data.delta as Record<string, unknown>).thinking).toBe('Thinking here') | |
| }) | |
| test('thinking_blocks (OpenAI o-series)', async () => { | |
| const sseChunks = [ | |
| 'data: {"id":"c6","object":"chat.completion.chunk","created":0,"model":"o3","choices":[{"index":0,"delta":{"role":"assistant","thinking_blocks":[{"type":"thinking","thinking":"Deep thought"}]},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c6","object":"chat.completion.chunk","created":0,"model":"o3","choices":[{"index":0,"delta":{"content":"Answer"},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c6","object":"chat.completion.chunk","created":0,"model":"o3","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', | |
| 'data: [DONE]\n\n', | |
| ] | |
| const upstream = makeStream(sseChunks) | |
| const events = await collectSse(openaiChatStreamToAnthropic(upstream, 'o3')) | |
| const thinkingDeltas = events.filter( | |
| (e) => e.event === 'content_block_delta' && (e.data.delta as Record<string, unknown>)?.type === 'thinking_delta', | |
| ) | |
| expect(thinkingDeltas.length).toBe(1) | |
| expect((thinkingDeltas[0].data.delta as Record<string, unknown>).thinking).toBe('Deep thought') | |
| }) | |
| test('text + tool transition closes text block first', async () => { | |
| const sseChunks = [ | |
| 'data: {"id":"c7","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"content":"Let me search"},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c7","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_x","type":"function","function":{"name":"search","arguments":"{\\"q\\":\\"test\\"}"}}]},"finish_reason":null}]}\n\n', | |
| 'data: {"id":"c7","object":"chat.completion.chunk","created":0,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}\n\n', | |
| 'data: [DONE]\n\n', | |
| ] | |
| const upstream = makeStream(sseChunks) | |
| const events = await collectSse(openaiChatStreamToAnthropic(upstream, 'gpt-4')) | |
| const types = events.map((e) => e.event) | |
| // Should see: text block start, text delta, text block stop, tool block start, ... | |
| const firstBlockStop = types.indexOf('content_block_stop') | |
| const toolBlockStart = types.findIndex( | |
| (_, i) => events[i].event === 'content_block_start' && (events[i].data.content_block as Record<string, unknown>)?.type === 'tool_use', | |
| ) | |
| expect(firstBlockStop).toBeLessThan(toolBlockStart) | |
| }) | |
| }) | |
| // βββ OpenAI Responses SSE β Anthropic SSE ββββββββββββββββββββββ | |
| describe('openaiResponsesStreamToAnthropic', () => { | |
| test('basic text streaming', async () => { | |
| const sseChunks = [ | |
| 'event: response.created\ndata: {"id":"r1","model":"gpt-4o","status":"in_progress"}\n\n', | |
| 'event: response.output_item.added\ndata: {"output_index":0,"item":{"type":"message","role":"assistant"}}\n\n', | |
| 'event: response.content_part.added\ndata: {"output_index":0,"content_index":0,"part":{"type":"output_text","text":""}}\n\n', | |
| 'event: response.output_text.delta\ndata: {"output_index":0,"content_index":0,"delta":"Hello"}\n\n', | |
| 'event: response.output_text.delta\ndata: {"output_index":0,"content_index":0,"delta":" world"}\n\n', | |
| 'event: response.output_text.done\ndata: {"output_index":0,"content_index":0,"text":"Hello world"}\n\n', | |
| 'event: response.completed\ndata: {"response":{"id":"r1","model":"gpt-4o","status":"completed","usage":{"input_tokens":10,"output_tokens":5}}}\n\n', | |
| ] | |
| const upstream = makeStream(sseChunks) | |
| const anthropicStream = openaiResponsesStreamToAnthropic(upstream, 'gpt-4o') | |
| const events = await collectSse(anthropicStream) | |
| const eventTypes = events.map((e) => e.event) | |
| expect(eventTypes[0]).toBe('message_start') | |
| expect(eventTypes).toContain('content_block_start') | |
| expect(eventTypes).toContain('content_block_delta') | |
| expect(eventTypes).toContain('content_block_stop') | |
| expect(eventTypes).toContain('message_delta') | |
| expect(eventTypes).toContain('message_stop') | |
| // Check text deltas | |
| const textDeltas = events.filter((e) => e.event === 'content_block_delta') | |
| const texts = textDeltas.map((e) => (e.data.delta as Record<string, unknown>).text) | |
| expect(texts).toContain('Hello') | |
| expect(texts).toContain(' world') | |
| const msgDelta = events.find((e) => e.event === 'message_delta')! | |
| expect(msgDelta.data.usage).toEqual({ | |
| input_tokens: 10, | |
| output_tokens: 5, | |
| }) | |
| }) | |
| test('function call streaming', async () => { | |
| const sseChunks = [ | |
| 'event: response.created\ndata: {"id":"r2","model":"gpt-4o","status":"in_progress"}\n\n', | |
| 'event: response.output_item.added\ndata: {"output_index":0,"item":{"type":"function_call","id":"fc_1","call_id":"call_1","name":"search"}}\n\n', | |
| 'event: response.function_call_arguments.delta\ndata: {"item_id":"fc_1","delta":"{\\"q\\":"}\n\n', | |
| 'event: response.function_call_arguments.delta\ndata: {"item_id":"fc_1","delta":"\\"test\\"}"}\n\n', | |
| 'event: response.function_call_arguments.done\ndata: {"item_id":"fc_1","arguments":"{\\"q\\":\\"test\\"}"}\n\n', | |
| 'event: response.completed\ndata: {"response":{"id":"r2","model":"gpt-4o","status":"completed","usage":{"input_tokens":10,"output_tokens":5}}}\n\n', | |
| ] | |
| const upstream = makeStream(sseChunks) | |
| const anthropicStream = openaiResponsesStreamToAnthropic(upstream, 'gpt-4o') | |
| const events = await collectSse(anthropicStream) | |
| // Should have tool_use content_block_start | |
| const toolStart = events.find( | |
| (e) => e.event === 'content_block_start' && (e.data.content_block as Record<string, unknown>)?.type === 'tool_use', | |
| ) | |
| expect(toolStart).toBeDefined() | |
| expect((toolStart!.data.content_block as Record<string, unknown>).name).toBe('search') | |
| // Should have input_json_delta | |
| const jsonDeltas = events.filter( | |
| (e) => e.event === 'content_block_delta' && (e.data.delta as Record<string, unknown>)?.type === 'input_json_delta', | |
| ) | |
| expect(jsonDeltas.length).toBeGreaterThan(0) | |
| // Stop reason should be tool_use | |
| const msgDelta = events.find((e) => e.event === 'message_delta')! | |
| expect((msgDelta.data.delta as Record<string, unknown>).stop_reason).toBe('tool_use') | |
| }) | |
| }) | |