/** * Integration tests for ToolSanitizationProxy * * Tests the full proxy flow: request sanitization → upstream forwarding → response restoration */ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'bun:test'; import * as http from 'http'; import { ToolSanitizationProxy } from '../../../src/cliproxy/tool-sanitization-proxy'; // Mock upstream server that echoes requests let mockUpstream: http.Server; let mockUpstreamPort: number; let lastRequest: { body: unknown; headers: http.IncomingHttpHeaders } | null = null; // Track response to send back let mockResponse: { status: number; body: unknown; stream?: boolean } = { status: 200, body: { content: [] }, }; beforeAll(async () => { await new Promise((resolve) => { mockUpstream = http.createServer((req, res) => { const chunks: Buffer[] = []; req.on('data', (chunk) => chunks.push(chunk)); req.on('end', () => { const body = Buffer.concat(chunks).toString('utf8'); lastRequest = { body: body ? JSON.parse(body) : null, headers: req.headers, }; if (mockResponse.stream) { // SSE streaming response res.writeHead(mockResponse.status, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }); const events = mockResponse.body as string[]; for (const event of events) { res.write(event); } res.end(); } else { // Buffered JSON response res.writeHead(mockResponse.status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(mockResponse.body)); } }); }); mockUpstream.listen(0, '127.0.0.1', () => { const addr = mockUpstream.address(); mockUpstreamPort = typeof addr === 'object' && addr ? addr.port : 0; resolve(); }); }); }); afterAll(() => { mockUpstream?.close(); }); beforeEach(() => { lastRequest = null; mockResponse = { status: 200, body: { content: [] } }; }); describe('ToolSanitizationProxy Integration', () => { describe('Proxy Lifecycle', () => { it('starts on ephemeral port and stops cleanly', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); expect(port).toBeGreaterThan(0); expect(proxy.getPort()).toBe(port); proxy.stop(); expect(proxy.getPort()).toBeNull(); }); it('returns same port on multiple start calls', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port1 = await proxy.start(); const port2 = await proxy.start(); expect(port1).toBe(port2); proxy.stop(); }); }); describe('Request Sanitization', () => { it('sanitizes duplicate segment tool names in request', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'test-model', tools: [ { name: 'gitmcp__plus-pro-components__plus-pro-components', description: 'Test tool', }, { name: 'valid_tool', description: 'Valid tool' }, ], messages: [{ role: 'user', content: 'test' }], }), }); expect(response.ok).toBe(true); expect(lastRequest).not.toBeNull(); const sentTools = (lastRequest!.body as Record).tools as Array<{ name: string; }>; expect(sentTools[0].name).toBe('gitmcp__plus-pro-components'); expect(sentTools[1].name).toBe('valid_tool'); } finally { proxy.stop(); } }); it('truncates tool names exceeding 64 characters', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { const longName = 'a'.repeat(100); await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tools: [{ name: longName }], }), }); const sentTools = (lastRequest!.body as Record).tools as Array<{ name: string; }>; expect(sentTools[0].name.length).toBe(64); expect(sentTools[0].name).toContain('_'); // Has hash separator } finally { proxy.stop(); } }); it('passes through requests without tools unchanged', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { const originalBody = { model: 'test-model', messages: [{ role: 'user', content: 'Hello' }], }; await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(originalBody), }); expect(lastRequest!.body).toEqual(originalBody); } finally { proxy.stop(); } }); it('normalizes dotted Claude thinking model IDs for root/composite routes', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4.6-thinking', messages: [{ role: 'user', content: 'test' }], }), }); expect(lastRequest).not.toBeNull(); expect((lastRequest!.body as Record).model).toBe('claude-sonnet-4-6'); } finally { proxy.stop(); } }); it('keeps dotted non-thinking model IDs unchanged', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4.5', messages: [{ role: 'user', content: 'test' }], }), }); expect(lastRequest).not.toBeNull(); expect((lastRequest!.body as Record).model).toBe('claude-sonnet-4.5'); } finally { proxy.stop(); } }); it('normalizes dotted Claude major.minor IDs on antigravity provider route', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/api/provider/agy/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-opus-4.6', messages: [{ role: 'user', content: 'test' }], }), }); expect(lastRequest).not.toBeNull(); expect((lastRequest!.body as Record).model).toBe('claude-opus-4-6'); } finally { proxy.stop(); } }); it('rejects denylisted AGY 4.5 model IDs on antigravity provider route', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { const response = await fetch(`http://127.0.0.1:${port}/api/provider/agy/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4.5', messages: [{ role: 'user', content: 'test' }], }), }); expect(response.status).toBe(400); const body = (await response.json()) as { error: string }; expect(body.error).toContain('denylist'); expect(lastRequest).toBeNull(); } finally { proxy.stop(); } }); it('does not rewrite AGY legacy aliases on non-AGY provider routes', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/api/provider/claude/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-6-thinking', messages: [{ role: 'user', content: 'test' }], }), }); expect(lastRequest).not.toBeNull(); expect((lastRequest!.body as Record).model).toBe( 'claude-sonnet-4-6-thinking' ); } finally { proxy.stop(); } }); it('normalizes legacy iFlow aliases on explicit iflow provider routes', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/api/provider/iflow/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'kimi-k2.5', messages: [{ role: 'user', content: 'test' }], }), }); expect(lastRequest).not.toBeNull(); expect((lastRequest!.body as Record).model).toBe('kimi-k2'); } finally { proxy.stop(); } }); it('strips Gemini-unsupported top-level tool fields while keeping schema sanitization', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/api/provider/gemini/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'gemini-2.5-pro', tools: [ { name: 'tool__with__duplicate__duplicate', description: 'Test description', strict: true, input_examples: [{ command: 'ls -la' }], type: 'custom', cache_control: { type: 'ephemeral' }, defer_loading: true, input_schema: { type: 'object', properties: { command: { type: 'string', examples: ['ls -la'], }, }, }, }, ], }), }); const sentTools = (lastRequest!.body as Record).tools as Array< Record >; expect(sentTools[0].name).toBe('tool__with__duplicate'); expect(sentTools[0].description).toBe('Test description'); expect(sentTools[0].strict).toBeUndefined(); expect(sentTools[0].input_examples).toBeUndefined(); expect(sentTools[0].type).toBeUndefined(); expect(sentTools[0].cache_control).toBeUndefined(); expect(sentTools[0].defer_loading).toBeUndefined(); expect(sentTools[0].input_schema).toEqual({ type: 'object', properties: { command: { type: 'string', }, }, }); } finally { proxy.stop(); } }); it('strips only Codex-unsupported top-level tool fields before forwarding', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/api/provider/codex/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'gpt-5.3-codex', tools: [ { name: 'codex_tool', description: 'Codex test', cache_control: { type: 'ephemeral' }, defer_loading: true, input_schema: { type: 'object', properties: { prompt: { type: 'string', examples: ['fix the failing test'], }, }, }, }, ], }), }); const sentTools = (lastRequest!.body as Record).tools as Array< Record >; expect(sentTools[0].name).toBe('codex_tool'); expect(sentTools[0].description).toBe('Codex test'); expect(sentTools[0].cache_control).toBeUndefined(); expect(sentTools[0].defer_loading).toBe(true); expect(sentTools[0].input_schema).toEqual({ type: 'object', properties: { prompt: { type: 'string', }, }, }); } finally { proxy.stop(); } }); for (const model of [ 'gpt-5.3-codex-xhigh', 'gpt-5.1-codex-mini', 'gpt-5.1-codex', 'gpt-5-codex', ]) { it(`strips only Codex-unsupported top-level tool fields on root model-routed request (${model})`, async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model, tools: [ { name: 'root_codex_tool', description: 'Root-routed Codex test', cache_control: { type: 'ephemeral' }, defer_loading: true, input_schema: { type: 'object', properties: { prompt: { type: 'string', examples: ['fix the failing test'], }, }, }, }, ], }), }); const sentTools = (lastRequest!.body as Record).tools as Array< Record >; expect(sentTools[0].name).toBe('root_codex_tool'); expect(sentTools[0].description).toBe('Root-routed Codex test'); expect(sentTools[0].cache_control).toBeUndefined(); expect(sentTools[0].defer_loading).toBe(true); expect(sentTools[0].input_schema).toEqual({ type: 'object', properties: { prompt: { type: 'string', }, }, }); } finally { proxy.stop(); } }); } it('preserves top-level tool fields for non-target root routes', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-6', tools: [ { name: 'claude_tool__duplicate__duplicate', description: 'Anthropic passthrough test', cache_control: { type: 'ephemeral' }, defer_loading: true, input_schema: { type: 'object', properties: { prompt: { type: 'string', examples: ['keep these top-level fields'], }, }, }, }, ], }), }); const sentTools = (lastRequest!.body as Record).tools as Array< Record >; expect(sentTools[0].name).toBe('claude_tool__duplicate'); expect(sentTools[0].description).toBe('Anthropic passthrough test'); expect(sentTools[0].cache_control).toEqual({ type: 'ephemeral' }); expect(sentTools[0].defer_loading).toBe(true); expect(sentTools[0].input_schema).toEqual({ type: 'object', properties: { prompt: { type: 'string', }, }, }); } finally { proxy.stop(); } }); it('preserves other tool properties during sanitization', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tools: [ { name: 'foo__bar__bar', description: 'Test description', input_schema: { type: 'object', properties: { x: { type: 'string', examples: ['demo'] } }, }, }, ], }), }); const sentTools = (lastRequest!.body as Record).tools as Array<{ name: string; description: string; input_schema: object; }>; expect(sentTools[0].name).toBe('foo__bar'); expect(sentTools[0].description).toBe('Test description'); expect(sentTools[0].input_schema).toEqual({ type: 'object', properties: { x: { type: 'string' } }, }); } finally { proxy.stop(); } }); }); describe('Response Restoration (Buffered)', () => { it('restores original tool names in tool_use response blocks', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); // Mock upstream returns sanitized name in tool_use mockResponse = { status: 200, body: { content: [ { type: 'text', text: 'Using tool...' }, { type: 'tool_use', id: 'call_123', name: 'foo__bar', input: { query: 'test' } }, ], }, }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tools: [{ name: 'foo__bar__bar' }], }), }); const data = (await response.json()) as { content: Array<{ type: string; name?: string }> }; expect(data.content[0].type).toBe('text'); expect(data.content[1].type).toBe('tool_use'); expect(data.content[1].name).toBe('foo__bar__bar'); // Restored! } finally { proxy.stop(); } }); it('leaves unknown tool names unchanged in response', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); mockResponse = { status: 200, body: { content: [{ type: 'tool_use', id: 'call_456', name: 'unknown_tool', input: {} }], }, }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tools: [{ name: 'foo__bar__bar' }], }), }); const data = (await response.json()) as { content: Array<{ name: string }> }; expect(data.content[0].name).toBe('unknown_tool'); } finally { proxy.stop(); } }); }); describe('Response Restoration (Streaming)', () => { it('restores tool names in SSE streaming responses', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); // Mock SSE events with sanitized tool name mockResponse = { status: 200, stream: true, body: [ 'event: content_block_start\n', `data: ${JSON.stringify({ type: 'content_block_start', content_block: { type: 'tool_use', id: 'call_789', name: 'foo__bar' } })}\n\n`, 'event: content_block_stop\n', 'data: {"type":"content_block_stop"}\n\n', ], }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stream: true, tools: [{ name: 'foo__bar__bar' }], }), }); const text = await response.text(); expect(text).toContain('foo__bar__bar'); // Restored in SSE expect(text).not.toContain('"name":"foo__bar"'); // Original sanitized name replaced } finally { proxy.stop(); } }); it('passes through SSE events when no sanitization occurred', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); mockResponse = { status: 200, stream: true, body: [ 'event: message_start\n', 'data: {"type":"message_start"}\n\n', 'event: content_block_delta\n', 'data: {"type":"content_block_delta","delta":{"text":"Hello"}}\n\n', ], }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stream: true, tools: [{ name: 'valid_tool' }], // No sanitization needed }), }); const text = await response.text(); expect(text).toContain('message_start'); expect(text).toContain('content_block_delta'); } finally { proxy.stop(); } }); }); describe('Error Handling', () => { it('returns 400 for invalid JSON request body', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: 'not valid json{', }); expect(response.status).toBe(400); const data = (await response.json()) as { error: string }; expect(data.error).toContain('Invalid JSON'); } finally { proxy.stop(); } }); it('forwards upstream errors correctly', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); mockResponse = { status: 429, body: { error: { type: 'rate_limit_error', message: 'Too many requests' } }, }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tools: [] }), }); expect(response.status).toBe(429); } finally { proxy.stop(); } }); it('handles upstream connection errors gracefully', async () => { const originalNoProxy = process.env.NO_PROXY; const originalNoProxyLower = process.env.no_proxy; process.env.NO_PROXY = '127.0.0.1,localhost'; process.env.no_proxy = '127.0.0.1,localhost'; const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: 'http://127.0.0.1:1', // Invalid port timeoutMs: 1000, }); const port = await proxy.start(); try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tools: [] }), }); expect(response.status).toBe(502); } finally { proxy.stop(); if (originalNoProxy === undefined) { delete process.env.NO_PROXY; } else { process.env.NO_PROXY = originalNoProxy; } if (originalNoProxyLower === undefined) { delete process.env.no_proxy; } else { process.env.no_proxy = originalNoProxyLower; } } }); }); describe('Non-JSON Passthrough', () => { it('forwards GET requests without modification', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); mockResponse = { status: 200, body: { status: 'ok' } }; try { const response = await fetch(`http://127.0.0.1:${port}/health`); expect(response.ok).toBe(true); } finally { proxy.stop(); } }); }); describe('Multiple Tools Sanitization', () => { it('sanitizes multiple tools and tracks all mappings', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); mockResponse = { status: 200, body: { content: [ { type: 'tool_use', id: '1', name: 'tool_a__x', input: {} }, { type: 'tool_use', id: '2', name: 'tool_b__y', input: {} }, ], }, }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tools: [{ name: 'tool_a__x__x' }, { name: 'tool_b__y__y' }, { name: 'tool_c_valid' }], }), }); // Verify request was sanitized const sentTools = (lastRequest!.body as Record).tools as Array<{ name: string; }>; expect(sentTools[0].name).toBe('tool_a__x'); expect(sentTools[1].name).toBe('tool_b__y'); expect(sentTools[2].name).toBe('tool_c_valid'); // Verify response was restored const data = (await response.json()) as { content: Array<{ name: string }> }; expect(data.content[0].name).toBe('tool_a__x__x'); expect(data.content[1].name).toBe('tool_b__y__y'); } finally { proxy.stop(); } }); }); describe('Empty Response Safety Net (Issue #350)', () => { it('injects synthetic response when streaming 200 has no content blocks', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); // Upstream sends message_start + message_delta + message_stop but NO content blocks mockResponse = { status: 200, stream: true, body: [ 'event: message_start\n', 'data: {"type":"message_start","message":{"id":"msg_test","type":"message","role":"assistant","content":[],"model":"test","stop_reason":null}}\n\n', 'event: message_delta\n', 'data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"input_tokens":10,"output_tokens":0}}\n\n', 'event: message_stop\n', 'data: {"type":"message_stop"}\n\n', ], }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stream: true, tools: [{ name: 'valid_tool' }], }), }); const text = await response.text(); // Should contain the synthetic error message expect(text).toContain('[Proxy Error]'); expect(text).toContain('content_block_start'); expect(text).toContain('content_block_delta'); expect(text).toContain('content_block_stop'); // Should NOT have duplicate lifecycle events (upstream already sent them) const messageStartCount = (text.match(/"type":"message_start"/g) || []).length; expect(messageStartCount).toBe(1); const messageDeltaCount = (text.match(/"type":"message_delta"/g) || []).length; expect(messageDeltaCount).toBe(1); const messageStopCount = (text.match(/"type":"message_stop"/g) || []).length; expect(messageStopCount).toBe(1); } finally { proxy.stop(); } }); it('does NOT inject synthetic response when content blocks exist', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); mockResponse = { status: 200, stream: true, body: [ 'event: message_start\n', 'data: {"type":"message_start","message":{"id":"msg_ok","type":"message","role":"assistant","content":[],"model":"test"}}\n\n', 'event: content_block_start\n', 'data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}\n\n', 'event: content_block_delta\n', 'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}\n\n', 'event: content_block_stop\n', 'data: {"type":"content_block_stop","index":0}\n\n', 'event: message_delta\n', 'data: {"type":"message_delta","delta":{"stop_reason":"end_turn"}}\n\n', 'event: message_stop\n', 'data: {"type":"message_stop"}\n\n', ], }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stream: true, tools: [{ name: 'valid_tool' }], }), }); const text = await response.text(); expect(text).toContain('Hello'); expect(text).not.toContain('[Proxy Error]'); } finally { proxy.stop(); } }); it('does NOT inject synthetic response for 4xx/5xx errors', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); // 429 rate limit with SSE body but no content blocks mockResponse = { status: 429, stream: true, body: [ 'event: error\n', 'data: {"type":"error","error":{"type":"rate_limit_error","message":"Too many requests"}}\n\n', ], }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stream: true, tools: [{ name: 'valid_tool' }], }), }); expect(response.status).toBe(429); const text = await response.text(); expect(text).not.toContain('[Proxy Error]'); expect(text).toContain('rate_limit_error'); } finally { proxy.stop(); } }); it('injects synthetic response via SSE processing path (with sanitized tool names)', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); // Upstream returns empty content — no content_block_start events // Using sanitized tool name ('foo__bar__bar' → 'foo__bar') triggers the SSE processing path mockResponse = { status: 200, stream: true, body: [ 'event: message_start\n', 'data: {"type":"message_start","message":{"id":"msg_sse","type":"message","role":"assistant","content":[],"model":"test","stop_reason":null}}\n\n', 'event: message_delta\n', 'data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"input_tokens":10,"output_tokens":0}}\n\n', 'event: message_stop\n', 'data: {"type":"message_stop"}\n\n', ], }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stream: true, tools: [{ name: 'foo__bar__bar' }], // Triggers sanitization → SSE processing path }), }); const text = await response.text(); expect(text).toContain('[Proxy Error]'); expect(text).toContain('content_block_start'); // No duplicate lifecycle events const messageStartCount = (text.match(/"type":"message_start"/g) || []).length; expect(messageStartCount).toBe(1); const messageDeltaCount = (text.match(/"type":"message_delta"/g) || []).length; expect(messageDeltaCount).toBe(1); const messageStopCount = (text.match(/"type":"message_stop"/g) || []).length; expect(messageStopCount).toBe(1); } finally { proxy.stop(); } }); it('injects synthetic response when upstream sends only message_start (real failure mode)', async () => { const proxy = new ToolSanitizationProxy({ upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`, }); const port = await proxy.start(); // Real failure: upstream sends message_start then abruptly ends — no message_delta or message_stop mockResponse = { status: 200, stream: true, body: [ 'event: message_start\n', 'data: {"type":"message_start","message":{"id":"msg_abrupt","type":"message","role":"assistant","content":[],"model":"test","stop_reason":null}}\n\n', ], }; try { const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stream: true, tools: [{ name: 'valid_tool' }], }), }); const text = await response.text(); expect(text).toContain('[Proxy Error]'); expect(text).toContain('content_block_start'); expect(text).toContain('message_delta'); expect(text).toContain('message_stop'); // Only 1 of each — upstream sent message_start, synthetic provides the rest const messageStartCount = (text.match(/"type":"message_start"/g) || []).length; expect(messageStartCount).toBe(1); const messageDeltaCount = (text.match(/"type":"message_delta"/g) || []).length; expect(messageDeltaCount).toBe(1); const messageStopCount = (text.match(/"type":"message_stop"/g) || []).length; expect(messageStopCount).toBe(1); } finally { proxy.stop(); } }); }); });