Skip to content

Commit c3fa1cb

Browse files
committed
test(report): snapshot the snake_case (Python wire) chat history
Pin the js->python field mapping the report layer applies (args->arguments, callId->call_id, isError->is_error, createdAt->created_at, groupId->group_id, thoughtSignature->thought_signature) across message, function_call, and function_call_output items, complementing the camelCase chat_context snapshot.
1 parent b63d31d commit c3fa1cb

2 files changed

Lines changed: 128 additions & 1 deletion

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`sessionReportToJSON > serializes the full chat history to the Python snake_case wire format > chat-history-python-wire 1`] = `
4+
{
5+
"items": [
6+
{
7+
"content": [
8+
"Check out this image and audio:",
9+
],
10+
"created_at": 3000000000,
11+
"id": "msg_user_1",
12+
"interrupted": false,
13+
"role": "user",
14+
"type": "message",
15+
},
16+
{
17+
"arguments": "{"location": "Paris, France", "unit": "celsius"}",
18+
"call_id": "call_weather_123",
19+
"created_at": 3000000001,
20+
"group_id": "grp_1",
21+
"id": "func_call_1",
22+
"name": "get_weather",
23+
"thought_signature": "sig_abc",
24+
"type": "function_call",
25+
},
26+
{
27+
"call_id": "call_weather_123",
28+
"created_at": 3000000002,
29+
"id": "func_output_1",
30+
"is_error": false,
31+
"name": "get_weather",
32+
"output": "{"temperature": 22, "condition": "partly cloudy"}",
33+
"type": "function_call_output",
34+
},
35+
{
36+
"content": [
37+
"It is 22°C and partly cloudy in Paris.",
38+
],
39+
"created_at": 3000000003,
40+
"id": "msg_assistant_1",
41+
"interrupted": false,
42+
"role": "assistant",
43+
"type": "message",
44+
},
45+
],
46+
}
47+
`;

agents/src/voice/report.test.ts

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44
import { describe, expect, it } from 'vitest';
5-
import { ChatContext } from '../llm/chat_context.js';
5+
import type { AudioContent, ImageContent } from '../llm/chat_context.js';
6+
import { ChatContext, FunctionCall, FunctionCallOutput } from '../llm/chat_context.js';
67
import type { ModelUsage } from '../metrics/model_usage.js';
78
import type {
89
AgentSessionOptions,
@@ -291,6 +292,85 @@ describe('sessionReportToJSON', () => {
291292
expect((payload.options as Record<string, unknown>).user_away_timeout).toBe(15);
292293
});
293294

295+
it('serializes the full chat history to the Python snake_case wire format', () => {
296+
// Mirrors the camelCase fixtures snapshotted in chat_context.test.ts, but asserts the
297+
// *converted* output: `chat_history` is what the report layer (toSnakeCaseDeep) emits, so
298+
// this locks down the js->python field mapping for every chat-item type — message,
299+
// multimodal content (image/audio), function_call (args->arguments), and
300+
// function_call_output (isError->is_error).
301+
const chatHistory = new ChatContext();
302+
303+
chatHistory.addMessage({
304+
id: 'msg_user_1',
305+
role: 'user',
306+
content: [
307+
'Check out this image and audio:',
308+
{
309+
id: 'img_test_1',
310+
type: 'image_content',
311+
image: 'https://example.com/test-image.jpg',
312+
inferenceDetail: 'high',
313+
inferenceWidth: 1024,
314+
inferenceHeight: 768,
315+
mimeType: 'image/jpeg',
316+
_cache: {},
317+
} satisfies ImageContent,
318+
{
319+
type: 'audio_content',
320+
frame: [],
321+
transcript: 'This is a test audio transcript',
322+
} satisfies AudioContent,
323+
],
324+
createdAt: 3000000000,
325+
});
326+
327+
chatHistory.insert(
328+
new FunctionCall({
329+
id: 'func_call_1',
330+
callId: 'call_weather_123',
331+
name: 'get_weather',
332+
args: '{"location": "Paris, France", "unit": "celsius"}',
333+
groupId: 'grp_1',
334+
thoughtSignature: 'sig_abc',
335+
createdAt: 3000000001,
336+
}),
337+
);
338+
339+
chatHistory.insert(
340+
new FunctionCallOutput({
341+
id: 'func_output_1',
342+
callId: 'call_weather_123',
343+
name: 'get_weather',
344+
output: '{"temperature": 22, "condition": "partly cloudy"}',
345+
isError: false,
346+
createdAt: 3000000002,
347+
}),
348+
);
349+
350+
chatHistory.addMessage({
351+
id: 'msg_assistant_1',
352+
role: 'assistant',
353+
content: 'It is 22°C and partly cloudy in Paris.',
354+
interrupted: false,
355+
createdAt: 3000000003,
356+
});
357+
358+
const report = createSessionReport({
359+
jobId: 'job',
360+
roomId: 'room-id',
361+
room: 'room',
362+
options: baseOptions(),
363+
events: [],
364+
chatHistory,
365+
enableRecording: false,
366+
timestamp: 0,
367+
startedAt: 0,
368+
});
369+
370+
const payload = sessionReportToJSON(report);
371+
expect(payload.chat_history).toMatchSnapshot('chat-history-python-wire');
372+
});
373+
294374
it('exports AgentSessionUsage from the voice barrel', () => {
295375
const usage: AgentSessionUsage = { modelUsage: [] };
296376
const eventType: AgentSessionEventTypes = AgentSessionEventTypes.SessionUsageUpdated;

0 commit comments

Comments
 (0)