mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-18 18:32:32 +00:00
chore(context): polish toGraph determinism and fix part type coverage
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { ContextGraphBuilder } from './toGraph.js';
|
||||
import type { Content } from '@google/genai';
|
||||
import type { BaseConcreteNode } from './types.js';
|
||||
@@ -38,6 +38,8 @@ describe('ContextGraphBuilder', () => {
|
||||
});
|
||||
|
||||
it('should generate completely deterministic graph structure and UUIDs across JSON serialization cycles', () => {
|
||||
vi.spyOn(Date, 'now').mockReturnValue(0);
|
||||
|
||||
const complexHistory: Content[] = [
|
||||
{ role: 'user', parts: [{ text: 'Step 1: complex analysis' }] },
|
||||
{
|
||||
@@ -86,11 +88,14 @@ describe('ContextGraphBuilder', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +62,26 @@ function isFunctionResponsePart(
|
||||
);
|
||||
}
|
||||
|
||||
function isExecutableCodePart(
|
||||
part: Part,
|
||||
): part is Part & { executableCode: { code: string } } {
|
||||
return (
|
||||
typeof part.executableCode === 'object' &&
|
||||
part.executableCode !== null &&
|
||||
typeof part.executableCode.code === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
function isCodeExecutionResultPart(
|
||||
part: Part,
|
||||
): part is Part & { codeExecutionResult: { outcome: string; output: string } } {
|
||||
return (
|
||||
typeof part.codeExecutionResult === 'object' &&
|
||||
part.codeExecutionResult !== null &&
|
||||
typeof part.codeExecutionResult.output === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a stable ID for an object reference using a WeakMap.
|
||||
* Falls back to content-based hashing for Part-like objects to ensure
|
||||
@@ -116,6 +136,16 @@ export function getStableId(
|
||||
)
|
||||
.digest('hex');
|
||||
id = `resp_h_${contentHash}_${turnSalt}_${partIdx}`;
|
||||
} else if (isExecutableCodePart(part)) {
|
||||
contentHash = createHash('sha256')
|
||||
.update(`exec:${part.executableCode.code}`)
|
||||
.digest('hex');
|
||||
id = `exec_${contentHash}_${turnSalt}_${partIdx}`;
|
||||
} else if (isCodeExecutionResultPart(part)) {
|
||||
contentHash = createHash('sha256')
|
||||
.update(`result:${part.codeExecutionResult.output}`)
|
||||
.digest('hex');
|
||||
id = `result_${contentHash}_${turnSalt}_${partIdx}`;
|
||||
}
|
||||
|
||||
if (contentHash) {
|
||||
@@ -194,7 +224,7 @@ export class ContextGraphBuilder {
|
||||
apiId || getStableId(part, this.nodeIdentityMap, turnSalt, partIdx);
|
||||
const node: ConcreteNode = {
|
||||
id,
|
||||
timestamp: 0, // Using 0 for deterministic structural equality. Actual time is applied by orchestrator.
|
||||
timestamp: Date.now(),
|
||||
type: isFunctionResponsePart(part)
|
||||
? NodeType.TOOL_EXECUTION
|
||||
: NodeType.USER_PROMPT,
|
||||
@@ -215,7 +245,7 @@ export class ContextGraphBuilder {
|
||||
apiId || getStableId(part, this.nodeIdentityMap, turnSalt, partIdx);
|
||||
const node: ConcreteNode = {
|
||||
id,
|
||||
timestamp: 0, // Using 0 for deterministic structural equality. Actual time is applied by orchestrator.
|
||||
timestamp: Date.now(),
|
||||
type: isFunctionCallPart(part)
|
||||
? NodeType.TOOL_EXECUTION
|
||||
: NodeType.AGENT_THOUGHT,
|
||||
|
||||
Reference in New Issue
Block a user