|
2 | 2 | // |
3 | 3 | // SPDX-License-Identifier: Apache-2.0 |
4 | 4 | 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'; |
6 | 7 | import type { ModelUsage } from '../metrics/model_usage.js'; |
7 | 8 | import type { |
8 | 9 | AgentSessionOptions, |
@@ -291,6 +292,85 @@ describe('sessionReportToJSON', () => { |
291 | 292 | expect((payload.options as Record<string, unknown>).user_away_timeout).toBe(15); |
292 | 293 | }); |
293 | 294 |
|
| 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 | + |
294 | 374 | it('exports AgentSessionUsage from the voice barrel', () => { |
295 | 375 | const usage: AgentSessionUsage = { modelUsage: [] }; |
296 | 376 | const eventType: AgentSessionEventTypes = AgentSessionEventTypes.SessionUsageUpdated; |
|
0 commit comments