File size: 3,261 Bytes
7a1ad33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect } from 'vitest';
import { ContextGraphMapper } from './mapper.js';
import type { HistoryTurn } from '../../core/agentChatHistory.js';
import { hardenHistory } from '../../utils/historyHardening.js';

describe('ContextGraphMapper (Round-Trip Fidelity)', () => {
  it('should flawlessly round-trip a complex history containing parallel tool calls and responses', () => {
    // 1. Define a complex, worst-case scenario history
    const envId = 'd04923d38bb0f6017037e74183378ef4';
    const originalHistory: HistoryTurn[] = [
      {
        id: envId,
        content: {
          role: 'user',
          parts: [{ text: '<session_context>\nSystem Prompt here' }],
        },
      },
      {
        id: 'user_turn_1',
        content: {
          role: 'user',
          parts: [{ text: 'Please read file A and file B at the same time.' }],
        },
      },
      {
        id: 'model_turn_1',
        content: {
          role: 'model',
          parts: [
            { text: 'I will read both files concurrently.' },
            {
              functionCall: {
                id: 'call_A',
                name: 'read_file',
                args: { path: 'A.txt' },
              },
              thoughtSignature: 'synthetic_sig_xyz',
            },
            {
              functionCall: {
                id: 'call_B',
                name: 'read_file',
                args: { path: 'B.txt' },
              },
            },
          ],
        },
      },
      // Note: GeminiChat records these as separate sequential user turns initially
      {
        id: 'tool_resp_B_id',
        content: {
          role: 'user',
          parts: [
            {
              functionResponse: {
                id: 'call_B',
                name: 'read_file',
                response: { content: 'File B' },
              },
            },
          ],
        },
      },
      {
        id: 'tool_resp_A_id',
        content: {
          role: 'user',
          parts: [
            {
              functionResponse: {
                id: 'call_A',
                name: 'read_file',
                response: { content: 'File A' },
              },
            },
          ],
        },
      },
    ];

    // 2. We harden the original history first. The core agent loop feeds the hardener the pure history.
    // We want our round-tripped history to match what the hardener WOULD have produced natively.
    const hardenedOriginal = hardenHistory(originalHistory);

    // 3. Translate History -> Graph
    const mapper = new ContextGraphMapper();
    // Simulate the sync
    const nodes = mapper.sync(originalHistory);

    // 4. Translate Graph -> History
    const reconstructedHistory = mapper.fromGraph(nodes);

    // 5. Harden the reconstructed history (as the ContextManager does before sending to API)
    const hardenedReconstructed = hardenHistory(reconstructedHistory);

    // 6. Assert Absolute Equality
    // The round-trip through the Context Graph and Hardener must exactly equal
    // the original history put through the Hardener.
    expect(hardenedReconstructed).toEqual(hardenedOriginal);
  });
});