File size: 4,129 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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, vi } from 'vitest';
import { ContextGraphBuilder } from './toGraph.js';
import type { BaseConcreteNode } from './types.js';
import { NodeIdService } from './nodeIdService.js';
import type { HistoryTurn } from '../../core/agentChatHistory.js';

describe('ContextGraphBuilder', () => {
  describe('toGraph', () => {
    it('should skip legacy <session_context> headers even if they appear later in the history', () => {
      const history: HistoryTurn[] = [
        {
          id: '1',
          content: { role: 'user', parts: [{ text: 'Message 1' }] },
        },
        {
          id: '2',
          content: { role: 'model', parts: [{ text: 'Reply 1' }] },
        },
        {
          id: '3',
          content: {
            role: 'user',
            parts: [
              {
                text: '<session_context>\nThis is the Gemini CLI\nSome context...',
              },
            ],
          },
        },
        {
          id: '4',
          content: { role: 'user', parts: [{ text: 'Message 2' }] },
        },
      ];

      const builder = new ContextGraphBuilder(new NodeIdService());
      const nodes = builder.processHistory(history);

      // We expect the first two messages and the last one to be present
      // The session context message should be filtered out
      expect(nodes.length).toBe(3);
      expect((nodes[0] as BaseConcreteNode).payload.text).toBe('Message 1');
      expect((nodes[1] as BaseConcreteNode).payload.text).toBe('Reply 1');
      expect((nodes[2] as BaseConcreteNode).payload.text).toBe('Message 2');
    });

    it('should generate completely deterministic graph structure and UUIDs across JSON serialization cycles', () => {
      vi.spyOn(Date, 'now').mockReturnValue(0);

      const complexHistory: HistoryTurn[] = [
        {
          id: 'turn-1',
          content: {
            role: 'user',
            parts: [{ text: 'Step 1: complex analysis' }],
          },
        },
        {
          id: 'turn-2',
          content: {
            role: 'model',
            parts: [
              { text: 'Thinking about the tool to use.' },
              {
                functionCall: {
                  name: 'fetch_data',
                  args: { query: 'test data' },
                },
              },
            ],
          },
        },
        {
          id: 'turn-3',
          content: {
            role: 'user',
            parts: [
              {
                functionResponse: {
                  name: 'fetch_data',
                  response: { status: 'success', data: [1, 2, 3] },
                },
              },
            ],
          },
        },
        {
          id: 'turn-4',
          content: { role: 'model', parts: [{ text: 'Analysis complete.' }] },
        },
      ];

      // 1. Initial Graph Generation
      const builder1 = new ContextGraphBuilder(new NodeIdService());
      const nodes1 = builder1.processHistory(complexHistory);

      // 2. Serialize and Deserialize (Simulating saving and loading from disk)
      const serializedHistory = JSON.stringify(complexHistory);
      const parsedHistory = JSON.parse(serializedHistory) as HistoryTurn[];

      // 3. Second Graph Generation from parsed JSON
      const builder2 = new ContextGraphBuilder(new NodeIdService());
      const nodes2 = builder2.processHistory(parsedHistory);

      // Assertion: The arrays must be completely identical, including all generated UUIDs
      expect(nodes1).toEqual(nodes2);

      // Sanity check to ensure IDs are actually populated and consistent
      expect(nodes1.length).toBeGreaterThan(0);
      nodes1.forEach((node, index) => {
        expect(node.id).toBeDefined();
        expect(node.id).toBe(nodes2[index].id);
        expect(node.timestamp).toBe(0);
        if ('turnId' in node) {
          expect(node.turnId).toBeDefined();
          expect(node.turnId).toBe((nodes2[index] as BaseConcreteNode).turnId);
        }
      });

      vi.restoreAllMocks();
    });
  });
});