Files
hermes-ui/tests/server/agent-runner-adapters.test.ts
T
toor 1e9ea89d86
Build / build (push) Canceled after 0s
NPM Lockfile Check / npm ci --ignore-scripts (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s
chore: bootstrap hermes-ui skeleton from 0.6.29 snapshot
Import the client, server, skills, tests, and build scripts as the starting
point for the rebuild, then trim the surfaces this project does not ship:
Electron desktop packaging, the marketing website, and the ESP32 firmware
tree. CI keeps the web build, e2e, lockfile, release, and Docker workflows.

Rename the package and CLI entry points to hermes-ui, drop the prepare hook so
npm ci no longer triggers a build, and drop the removed tsconfig and nodemon
references. Rewrite AGENTS.md, ARCHITECTURE.md, DEVELOPMENT.md, and the READMEs
so the documented package map and hard rules match this repository.

reference_code/ is ignored: it is a read-only snapshot, not an input to the build.
2026-10-16 10:34:35 +08:00

688 lines
26 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
anthropicMessageToResponses,
openAiChatToResponses,
responsesToAnthropicMessages,
responsesToOpenAiChat,
} from '../../packages/server/src/services/agent-runner/adapters/responses'
import {
anthropicToOpenAiChat,
anthropicToOpenAiResponses,
openAiResponsesToAnthropicMessage,
openAiToAnthropicMessage,
} from '../../packages/server/src/services/agent-runner/adapters/anthropic'
import {
openAiChatSseToAnthropicEvents,
openAiResponsesSseToAnthropicEvents,
type AnthropicStreamEvent,
} from '../../packages/server/src/services/agent-runner/adapters/anthropic-stream'
import {
anthropicMessagesSseToResponsesEvents,
openAiChatSseToResponsesEvents,
openAiResponsesSseToResponsesEvents,
type CanonicalResponsesEvent,
} from '../../packages/server/src/services/agent-runner/adapters/responses-stream'
const target = { model: 'test-model' }
const codexTarget = { model: 'test-model', annotateMcpToolNamespaces: true }
const anthropicTarget = { provider: 'deepseek', model: 'deepseek-reasoner', baseUrl: 'https://api.deepseek.com/v1' }
describe('agent runner Responses adapters', () => {
it('converts Responses input to OpenAI Chat messages and tools', () => {
const body = {
instructions: 'be terse',
max_output_tokens: 16,
temperature: 0.2,
top_p: 0.9,
input: [
{ role: 'user', content: [{ type: 'input_text', text: 'hello' }] },
{ role: 'developer', content: [{ type: 'input_text', text: 'rules' }] },
{ type: 'function_call', call_id: 'call_1', name: 'search', arguments: '{"q":"x"}' },
{ type: 'function_call_output', call_id: 'call_1', output: 'found' },
],
tools: [{ type: 'function', name: 'search', description: 'Search', parameters: { type: 'object' } }],
}
expect(responsesToOpenAiChat(body, target)).toMatchObject({
model: 'test-model',
max_tokens: 16,
temperature: 0.2,
top_p: 0.9,
stream: false,
messages: [
{ role: 'system', content: 'be terse' },
{ role: 'user', content: 'hello' },
{ role: 'system', content: 'rules' },
{
role: 'assistant',
content: null,
tool_calls: [{
id: 'call_1',
type: 'function',
function: { name: 'search', arguments: '{"q":"x"}' },
}],
},
{ role: 'tool', tool_call_id: 'call_1', content: 'found' },
],
tools: [{
type: 'function',
function: { name: 'search', description: 'Search', parameters: { type: 'object' } },
}],
})
})
it('groups parallel Responses function calls before Chat tool results', () => {
const body = {
input: [
{ role: 'user', content: [{ type: 'input_text', text: 'check repo' }] },
{ type: 'function_call', call_id: 'call_1', name: 'read_file', arguments: '{"path":"a.ts"}' },
{ type: 'function_call', call_id: 'call_2', name: 'search', arguments: '{"q":"todo"}' },
{ type: 'function_call_output', call_id: 'call_2', output: 'matches' },
{ type: 'function_call_output', call_id: 'call_1', output: 'file text' },
{ role: 'user', content: [{ type: 'input_text', text: 'continue' }] },
],
}
expect(responsesToOpenAiChat(body, target).messages).toEqual([
{ role: 'user', content: 'check repo' },
{
role: 'assistant',
content: null,
tool_calls: [
{
id: 'call_1',
type: 'function',
function: { name: 'read_file', arguments: '{"path":"a.ts"}' },
},
{
id: 'call_2',
type: 'function',
function: { name: 'search', arguments: '{"q":"todo"}' },
},
],
},
{ role: 'tool', tool_call_id: 'call_1', content: 'file text' },
{ role: 'tool', tool_call_id: 'call_2', content: 'matches' },
{ role: 'user', content: 'continue' },
])
})
it('drops incomplete Responses function call history for Chat providers', () => {
const body = {
input: [
{ role: 'user', content: [{ type: 'input_text', text: 'hello' }] },
{ type: 'function_call', call_id: 'call_missing', name: 'search', arguments: '{"q":"x"}' },
{ role: 'user', content: [{ type: 'input_text', text: 'next turn' }] },
{ type: 'function_call_output', call_id: 'orphan_call', output: 'orphan' },
],
}
expect(responsesToOpenAiChat(body, target).messages).toEqual([
{ role: 'user', content: 'hello' },
{ role: 'user', content: 'next turn' },
])
})
it('converts Responses input to Anthropic messages', () => {
const body = {
instructions: 'system text',
input: [
{ role: 'user', content: [{ type: 'input_text', text: 'hello' }] },
{ type: 'function_call', call_id: 'call_1', name: 'lookup', arguments: '{"id":1}' },
{ type: 'function_call_output', call_id: 'call_1', output: [{ text: 'ok' }] },
],
tools: [{ type: 'function', name: 'lookup', description: 'Lookup', parameters: { type: 'object' } }],
}
expect(responsesToAnthropicMessages(body, target, true)).toMatchObject({
model: 'test-model',
system: 'system text',
max_tokens: 4096,
stream: true,
messages: [
{ role: 'user', content: [{ type: 'text', text: 'hello' }] },
{ role: 'assistant', content: [{ type: 'tool_use', id: 'call_1', name: 'lookup', input: { id: 1 } }] },
{ role: 'user', content: [{ type: 'tool_result', tool_use_id: 'call_1', content: 'ok' }] },
],
tools: [{ name: 'lookup', description: 'Lookup', input_schema: { type: 'object' } }],
})
})
it('expands Hermes MCP namespace tools for Chat and Anthropic providers', () => {
const body = {
input: [{ role: 'user', content: [{ type: 'input_text', text: 'list devices' }] }],
tools: [{ type: 'namespace', name: 'mcp__hermes_studio', description: 'Hermes tools' }],
}
expect(responsesToOpenAiChat(body, target).tools).toEqual(expect.arrayContaining([
expect.objectContaining({
type: 'function',
function: expect.objectContaining({
name: 'hermes_studio_lan_devices_scan',
parameters: expect.objectContaining({
properties: expect.objectContaining({
profile: expect.any(Object),
token: expect.any(Object),
}),
}),
}),
}),
]))
expect(responsesToAnthropicMessages(body, target).tools).toEqual(expect.arrayContaining([
expect.objectContaining({
name: 'hermes_studio_lan_devices_scan',
input_schema: expect.objectContaining({
properties: expect.objectContaining({
profile: expect.any(Object),
token: expect.any(Object),
}),
}),
}),
]))
})
it('keeps unknown MCP namespaces callable through a generic function fallback', () => {
const body = {
input: [{ role: 'user', content: [{ type: 'input_text', text: 'call custom mcp' }] }],
tools: [{ type: 'namespace', name: 'mcp__custom_server', description: 'Custom server tools' }],
}
expect(responsesToOpenAiChat(body, target).tools).toEqual(expect.arrayContaining([
expect.objectContaining({
type: 'function',
function: expect.objectContaining({
name: 'mcp__custom_server',
parameters: expect.objectContaining({
required: ['tool', 'arguments'],
}),
}),
}),
]))
})
it('converts OpenAI Chat responses to Responses output', () => {
expect(openAiChatToResponses({
id: 'chatcmpl_1',
created: 123,
choices: [{
message: {
reasoning_content: 'think',
content: 'hi',
tool_calls: [{
id: 'call_1',
function: { name: 'lookup', arguments: '{"id":1}' },
}],
},
}],
usage: { prompt_tokens: 2, completion_tokens: 3, total_tokens: 5 },
}, target)).toMatchObject({
id: 'chatcmpl_1',
object: 'response',
created_at: 123,
model: 'test-model',
output: [
{ type: 'reasoning', summary: [{ type: 'summary_text', text: 'think' }] },
{ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'hi', annotations: [] }] },
{ type: 'function_call', call_id: 'call_1', name: 'lookup', arguments: '{"id":1}' },
],
usage: { input_tokens: 2, output_tokens: 3, total_tokens: 5 },
})
})
it('marks expanded Hermes MCP Chat tool calls with their Responses namespace', () => {
expect(openAiChatToResponses({
id: 'chatcmpl_1',
created: 123,
choices: [{
message: {
tool_calls: [{
id: 'call_1',
function: { name: 'hermes_studio_lan_devices_scan', arguments: '{"profile":"default"}' },
}],
},
}],
}, target)).toMatchObject({
output: [{
type: 'function_call',
call_id: 'call_1',
name: 'hermes_studio_lan_devices_scan',
namespace: 'mcp__hermes_studio',
}],
})
})
it('normalizes generic MCP namespace function calls back to Responses MCP calls', () => {
expect(openAiChatToResponses({
id: 'chatcmpl_1',
created: 123,
choices: [{
message: {
tool_calls: [{
id: 'call_1',
function: {
name: 'mcp__custom_server',
arguments: '{"tool":"custom_lookup","arguments":{"id":1}}',
},
}],
},
}],
}, target)).toMatchObject({
output: [{
type: 'function_call',
call_id: 'call_1',
name: 'custom_lookup',
arguments: '{"id":1}',
namespace: 'mcp__custom_server',
}],
})
})
it('converts Anthropic messages to Responses output', () => {
expect(anthropicMessageToResponses({
id: 'msg_1',
content: [
{ type: 'thinking', thinking: 'anthropic think' },
{ type: 'text', text: 'hi' },
{ type: 'tool_use', id: 'toolu_1', name: 'lookup', input: { id: 1 } },
],
usage: { input_tokens: 4, output_tokens: 5 },
}, target)).toMatchObject({
id: 'msg_1',
object: 'response',
model: 'test-model',
output: [
{ type: 'reasoning', summary: [{ type: 'summary_text', text: 'anthropic think' }] },
{ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'hi', annotations: [] }] },
{ type: 'function_call', call_id: 'toolu_1', name: 'lookup', arguments: '{"id":1}' },
],
usage: { input_tokens: 4, output_tokens: 5, total_tokens: 9 },
})
})
it('marks expanded Hermes MCP Anthropic tool calls with their Responses namespace', () => {
expect(anthropicMessageToResponses({
id: 'msg_1',
content: [
{ type: 'tool_use', id: 'toolu_1', name: 'hermes_studio_lan_devices_list', input: { profile: 'default' } },
],
usage: { input_tokens: 1, output_tokens: 1 },
}, target)).toMatchObject({
output: [{
type: 'function_call',
call_id: 'toolu_1',
name: 'hermes_studio_lan_devices_list',
namespace: 'mcp__hermes_studio',
}],
})
})
})
async function* encodedChunks(chunks: string[]): AsyncGenerator<Uint8Array> {
const encoder = new TextEncoder()
for (const chunk of chunks) yield encoder.encode(chunk)
}
async function collectEvents(events: AsyncIterable<CanonicalResponsesEvent>): Promise<CanonicalResponsesEvent[]> {
const collected: CanonicalResponsesEvent[] = []
for await (const event of events) collected.push(event)
return collected
}
async function collectAnthropicEvents(events: AsyncIterable<AnthropicStreamEvent>): Promise<AnthropicStreamEvent[]> {
const collected: AnthropicStreamEvent[] = []
for await (const event of events) collected.push(event)
return collected
}
describe('agent runner Responses stream adapters', () => {
it('normalizes OpenAI Chat SSE text and tool calls to Responses events', async () => {
const events = await collectEvents(openAiChatSseToResponsesEvents(encodedChunks([
'data: {"choices":[{"delta":{"reasoning_content":"think"}}]}\n\n',
'data: {"choices":[{"delta":{"content":"he"}}]}\n\n',
'data: {"choices":[{"delta":{"content":"llo"}}]}\r\n\r\n',
'data: {"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_1","function":{"name":"lookup","arguments":"{\\"id\\":"}}]}}]}\n\n',
'data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"1}"}}]}}]}\n\n',
'data: {"id":"chatcmpl_usage","choices":[],"usage":{"prompt_tokens":120,"completion_tokens":7,"prompt_tokens_details":{"cached_tokens":30}}}\n\n',
'data: [DONE]\n\n',
]), codexTarget))
expect(events.map(event => event.type)).toEqual([
'response.created',
'response.reasoning.delta',
'response.output_item.added',
'response.content_part.added',
'response.output_text.delta',
'response.output_text.delta',
'response.output_item.added',
'response.function_call_arguments.delta',
'response.function_call_arguments.delta',
'response.output_text.done',
'response.content_part.done',
'response.output_item.done',
'response.output_item.done',
'response.completed',
])
expect(events[1].data).toMatchObject({ delta: 'think' })
expect(events[4].data).toMatchObject({ delta: 'he' })
expect(events[5].data).toMatchObject({ delta: 'llo' })
expect(events[6].data).toMatchObject({
item: { type: 'function_call', call_id: 'call_1', name: 'lookup' },
})
expect(events[13].data).toMatchObject({
response: {
model: 'test-model',
status: 'completed',
id: expect.stringMatching(/^resp_/),
usage: {
input_tokens: 120,
output_tokens: 7,
total_tokens: 127,
input_tokens_details: { cached_tokens: 30 },
},
output: [
{ type: 'reasoning', summary: [{ type: 'summary_text', text: 'think' }] },
{ type: 'message', content: [{ type: 'output_text', text: 'hello' }] },
{ type: 'function_call', call_id: 'call_1', name: 'lookup', arguments: '{"id":1}' },
],
},
})
expect((events[13].data as any).response.id).toBe((events[0].data as any).response.id)
})
it('marks expanded Hermes MCP Chat SSE tool calls with their Responses namespace', async () => {
const events = await collectEvents(openAiChatSseToResponsesEvents(encodedChunks([
'data: {"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_1","function":{"name":"hermes_studio_lan_devices_scan","arguments":"{}"}}]}}]}\n\n',
'data: [DONE]\n\n',
]), codexTarget))
expect(events).toEqual(expect.arrayContaining([
expect.objectContaining({
type: 'response.output_item.done',
data: expect.objectContaining({
item: expect.objectContaining({
type: 'function_call',
call_id: 'call_1',
name: 'hermes_studio_lan_devices_scan',
namespace: 'mcp__hermes_studio',
}),
}),
}),
]))
})
it('normalizes Anthropic Messages SSE text and tool calls to Responses events', async () => {
const events = await collectEvents(anthropicMessagesSseToResponsesEvents(encodedChunks([
'event: message_start\ndata: {"type":"message_start","message":{"id":"msg_1","usage":{"input_tokens":80,"cache_read_input_tokens":20}}}\n\n',
'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"think"}}\n\n',
'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hi"}}\n\n',
'event: content_block_start\ndata: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_1","name":"lookup","input":{}}}\r\n\r\n',
'event: content_block_delta\ndata: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\\"id\\":1}"}}\n\n',
'event: message_delta\ndata: {"type":"message_delta","usage":{"output_tokens":9}}\n\n',
]), codexTarget))
expect(events.map(event => event.type)).toEqual([
'response.created',
'response.reasoning.delta',
'response.output_item.added',
'response.content_part.added',
'response.output_text.delta',
'response.output_item.added',
'response.function_call_arguments.delta',
'response.output_text.done',
'response.content_part.done',
'response.output_item.done',
'response.output_item.done',
'response.completed',
])
expect(events[1].data).toMatchObject({ delta: 'think' })
expect(events[2].data).toMatchObject({ item: { id: 'msg_msg_1' } })
expect(events[5].data).toMatchObject({
item: { type: 'function_call', call_id: 'toolu_1', name: 'lookup' },
})
expect(events[11].data).toMatchObject({
response: {
id: 'msg_1',
usage: { input_tokens: 80, cache_read_input_tokens: 20, output_tokens: 9 },
output: [
{ type: 'reasoning', summary: [{ type: 'summary_text', text: 'think' }] },
{ type: 'message', content: [{ type: 'output_text', text: 'hi' }] },
{ type: 'function_call', call_id: 'toolu_1', name: 'lookup', arguments: '{"id":1}' },
],
},
})
})
it('marks expanded Hermes MCP Anthropic SSE tool calls with their Responses namespace', async () => {
const events = await collectEvents(anthropicMessagesSseToResponsesEvents(encodedChunks([
'event: message_start\ndata: {"type":"message_start","message":{"id":"msg_1"}}\n\n',
'event: content_block_start\ndata: {"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"toolu_1","name":"hermes_studio_lan_devices_list","input":{}}}\n\n',
'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{\\"profile\\":\\"default\\"}"}}\n\n',
'event: message_stop\ndata: {"type":"message_stop"}\n\n',
]), codexTarget))
expect(events).toEqual(expect.arrayContaining([
expect.objectContaining({
type: 'response.output_item.done',
data: expect.objectContaining({
item: expect.objectContaining({
type: 'function_call',
call_id: 'toolu_1',
name: 'hermes_studio_lan_devices_list',
namespace: 'mcp__hermes_studio',
}),
}),
}),
]))
})
it('passes native Responses SSE events through as canonical events', async () => {
const events = await collectEvents(openAiResponsesSseToResponsesEvents(encodedChunks([
'event: response.created\r\ndata: {"response":{"id":"resp_1"}}\r\n\r\n',
'data: {"type":"response.output_text.delta","delta":"hi"}\n\n',
'data: [DONE]\n\n',
])))
expect(events).toEqual([
{
type: 'response.created',
data: { type: 'response.created', response: { id: 'resp_1' } },
},
{
type: 'response.output_text.delta',
data: { type: 'response.output_text.delta', delta: 'hi' },
},
])
})
})
describe('agent runner Anthropic adapters', () => {
it('converts Anthropic messages to OpenAI Chat with reasoning_content', () => {
const body = {
system: 'system text',
max_tokens: 32,
temperature: 0.1,
messages: [
{ role: 'user', content: [{ type: 'text', text: 'hello' }] },
{
role: 'assistant',
content: [
{ type: 'thinking', thinking: 'need tool' },
{ type: 'tool_use', id: 'toolu_1', name: 'lookup', input: { id: 1 } },
],
},
{ role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_1', content: 'ok' }] },
],
tools: [{ name: 'lookup', description: 'Lookup', input_schema: { type: 'object' } }],
}
expect(anthropicToOpenAiChat(body, anthropicTarget)).toMatchObject({
model: 'deepseek-reasoner',
max_tokens: 32,
temperature: 0.1,
stream: false,
messages: [
{ role: 'system', content: 'system text' },
{ role: 'user', content: 'hello' },
{
role: 'assistant',
content: null,
reasoning_content: 'need tool',
tool_calls: [{
id: 'toolu_1',
type: 'function',
function: { name: 'lookup', arguments: '{"id":1}' },
}],
},
{ role: 'tool', tool_call_id: 'toolu_1', content: 'ok' },
],
tools: [{
type: 'function',
function: { name: 'lookup', description: 'Lookup', parameters: { type: 'object' } },
}],
})
})
it('converts Anthropic messages to Responses input', () => {
expect(anthropicToOpenAiResponses({
system: 'system text',
max_tokens: 64,
messages: [
{ role: 'user', content: 'hello' },
{ role: 'assistant', content: [{ type: 'tool_use', id: 'toolu_1', name: 'lookup', input: { id: 1 } }] },
{ role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_1', content: 'ok' }] },
],
tools: [{ name: 'lookup', input_schema: { type: 'object' } }],
}, anthropicTarget, true)).toMatchObject({
model: 'deepseek-reasoner',
instructions: 'system text',
max_output_tokens: 64,
stream: true,
store: false,
input: [
{ role: 'user', content: 'hello' },
{ type: 'function_call', call_id: 'toolu_1', name: 'lookup', arguments: '{"id":1}' },
{ type: 'function_call_output', call_id: 'toolu_1', output: 'ok' },
],
tools: [{ type: 'function', name: 'lookup', parameters: { type: 'object' } }],
})
})
it('converts OpenAI Chat responses to Anthropic messages', () => {
expect(openAiToAnthropicMessage({
id: 'chatcmpl_1',
choices: [{
finish_reason: 'tool_calls',
message: {
reasoning_content: 'thinking',
content: 'hi',
tool_calls: [{ id: 'call_1', function: { name: 'lookup', arguments: '{"id":1}' } }],
},
}],
usage: { prompt_tokens: 3, completion_tokens: 4 },
}, anthropicTarget)).toMatchObject({
id: 'chatcmpl_1',
type: 'message',
role: 'assistant',
model: 'deepseek-reasoner',
content: [
{ type: 'thinking', thinking: 'thinking' },
{ type: 'text', text: 'hi' },
{ type: 'tool_use', id: 'call_1', name: 'lookup', input: { id: 1 } },
],
stop_reason: 'tool_use',
usage: { input_tokens: 3, output_tokens: 4 },
})
})
it('converts Responses output to Anthropic messages', () => {
expect(openAiResponsesToAnthropicMessage({
id: 'resp_1',
status: 'completed',
output: [
{ type: 'message', content: [{ type: 'output_text', text: 'hi' }] },
{ type: 'function_call', call_id: 'call_1', name: 'lookup', arguments: '{"id":1}' },
],
usage: { input_tokens: 5, output_tokens: 6 },
}, anthropicTarget)).toMatchObject({
id: 'resp_1',
type: 'message',
role: 'assistant',
model: 'deepseek-reasoner',
content: [
{ type: 'text', text: 'hi' },
{ type: 'tool_use', id: 'call_1', name: 'lookup', input: { id: 1 } },
],
stop_reason: 'tool_use',
usage: { input_tokens: 5, output_tokens: 6 },
})
})
})
describe('agent runner Anthropic stream adapters', () => {
it('normalizes OpenAI Chat SSE to Anthropic Messages events', async () => {
const events = await collectAnthropicEvents(openAiChatSseToAnthropicEvents(encodedChunks([
'data: {"choices":[{"delta":{"reasoning_content":"think"}}]}\n\n',
'data: {"choices":[{"delta":{"content":"hi"}}]}\n\n',
'data: {"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_1","function":{"name":"lookup","arguments":"{\\"id\\":"}}]}}]}\r\n\r\n',
'data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"1}"}}]},"finish_reason":"tool_calls"}]}\n\n',
'data: {"choices":[],"usage":{"prompt_tokens":120,"completion_tokens":7}}\n\n',
]), anthropicTarget))
expect(events.map(event => event.type)).toEqual([
'message_start',
'content_block_start',
'content_block_delta',
'content_block_stop',
'content_block_start',
'content_block_delta',
'content_block_stop',
'content_block_start',
'content_block_delta',
'content_block_delta',
'content_block_stop',
'message_delta',
'message_stop',
])
expect(events[1].data).toMatchObject({ content_block: { type: 'thinking' } })
expect(events[2].data).toMatchObject({ delta: { type: 'thinking_delta', thinking: 'think' } })
expect(events[5].data).toMatchObject({ delta: { type: 'text_delta', text: 'hi' } })
expect(events[7].data).toMatchObject({ content_block: { type: 'tool_use', id: 'call_1', name: 'lookup' } })
expect(events[11].data).toMatchObject({
delta: { stop_reason: 'tool_use', stop_sequence: null },
usage: { output_tokens: 7 },
})
})
it('normalizes Responses SSE to Anthropic Messages events', async () => {
const events = await collectAnthropicEvents(openAiResponsesSseToAnthropicEvents(encodedChunks([
'data: {"type":"response.created","response":{"id":"resp_1"}}\n\n',
'data: {"type":"response.output_text.delta","delta":"hi"}\n\n',
'data: {"type":"response.output_text.done"}\n\n',
'data: {"type":"response.output_item.added","output_index":1,"item":{"type":"function_call","call_id":"call_1","name":"lookup"}}\n\n',
'data: {"type":"response.function_call_arguments.delta","item_id":"call_1","delta":"{\\"id\\":1}"}\n\n',
'data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_1","name":"lookup","arguments":"{\\"id\\":1}"}}\n\n',
'data: {"type":"response.completed","response":{"status":"completed","usage":{"output_tokens":3}}}\n\n',
]), anthropicTarget))
expect(events.map(event => event.type)).toEqual([
'message_start',
'content_block_start',
'content_block_delta',
'content_block_stop',
'content_block_start',
'content_block_delta',
'content_block_stop',
'message_delta',
'message_stop',
])
expect(events[2].data).toMatchObject({ delta: { type: 'text_delta', text: 'hi' } })
expect(events[4].data).toMatchObject({ content_block: { type: 'tool_use', id: 'call_1', name: 'lookup' } })
expect(events[5].data).toMatchObject({ delta: { type: 'input_json_delta', partial_json: '{"id":1}' } })
expect(events[7].data).toMatchObject({
delta: { stop_reason: 'tool_use', stop_sequence: null },
usage: { output_tokens: 3 },
})
})
})