Files
gemini-cli/packages/core/src/agent/enterprise-agent-session.test.ts
T
Michael Bleigh fc8928c089 feat: add support for Gemini Enterprise (Discovery Engine) assistant
- Implemented EnterpriseAgentProtocol and EnterpriseAgentSession in core
- Authenticates seamlessly via Application Default Credentials (ADC)
- Added robust brace-counting JSON stream parser with partial chunk caching
- Extracted and rendered immersive docArtifacts (markdown tables) E2E
- Integrated with CLI config schema and enabled default 'all tools' execution
- Added comprehensive unit tests verifying all stream events (thoughts, tools, tables)

TAG=agy
CONV=81e82460-f8cd-4c7b-a037-2cbedda4d3c0
2026-05-18 00:59:55 +00:00

254 lines
7.2 KiB
TypeScript

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it, vi, beforeEach, afterEach, type Mock } from 'vitest';
import { EnterpriseAgentSession } from './enterprise-agent-session.js';
import type { Config } from '../config/config.js';
import type { AgentEvent } from './types.js';
import { GoogleAuth } from 'google-auth-library';
// Mock google-auth-library
vi.mock('google-auth-library', () => ({
GoogleAuth: vi.fn(),
}));
describe('EnterpriseAgentSession', () => {
let mockConfig: Config;
let globalFetch: typeof fetch;
beforeEach(() => {
vi.clearAllMocks();
(GoogleAuth as unknown as Mock).mockImplementation(() => ({
getClient: vi.fn().mockResolvedValue({
getAccessToken: vi.fn().mockResolvedValue({ token: 'fake-token' }),
}),
}));
mockConfig = {
getSessionId: vi.fn().mockReturnValue('test-session'),
getEnterpriseConfig: vi.fn().mockReturnValue({
projectId: 'test-project',
engineId: 'test-engine',
location: 'global',
}),
} as unknown as Config;
globalFetch = global.fetch;
});
afterEach(() => {
global.fetch = globalFetch;
vi.restoreAllMocks();
});
const mockFetchResponse = (chunks: string[]) => {
const stream = new ReadableStream({
start(controller) {
for (const chunk of chunks) {
controller.enqueue(new TextEncoder().encode(chunk));
}
controller.close();
},
});
global.fetch = vi.fn().mockResolvedValue({
ok: true,
body: stream,
headers: new Headers(),
} as Response);
};
it('should successfully call Enterprise API and stream responses', async () => {
const chunk1 = JSON.stringify({
sessionInfo: { session: 'projects/test-project/locations/global/collections/default_collection/engines/test-engine/sessions/s1' },
answer: {
replies: [
{
groundedContent: {
content: { text: 'Hello' },
},
},
],
},
}) + '\n';
const chunk2 = JSON.stringify({
answer: {
replies: [
{
groundedContent: {
content: { text: ' World' },
},
},
],
},
}) + '\n';
mockFetchResponse([chunk1, chunk2]);
const session = new EnterpriseAgentSession({ config: mockConfig });
const { streamId } = await session.send({
message: { content: [{ type: 'text', text: 'hi' }] },
});
expect(streamId).toBe('enterprise-stream-1');
const events: AgentEvent[] = [];
for await (const event of session.stream({ streamId: streamId! })) {
events.push(event);
}
expect(events.map(e => e.type)).toEqual([
'agent_start',
'message', // Hello
'message', // World
'agent_end',
]);
const messages = events.filter((e): e is AgentEvent<'message'> => e.type === 'message' && e.role === 'agent');
expect(messages[0].content).toEqual([{ type: 'text', text: 'Hello' }]);
expect(messages[1].content).toEqual([{ type: 'text', text: ' World' }]);
});
it('should handle thoughts', async () => {
const chunk = JSON.stringify({
answer: {
replies: [
{
groundedContent: {
content: { text: 'Thinking...', thought: true },
},
},
{
groundedContent: {
content: { text: 'Final answer' },
},
},
],
},
}) + '\n';
mockFetchResponse([chunk]);
const session = new EnterpriseAgentSession({ config: mockConfig });
const { streamId } = await session.send({
message: { content: [{ type: 'text', text: 'hi' }] },
});
const events: AgentEvent[] = [];
for await (const event of session.stream({ streamId: streamId! })) {
events.push(event);
}
const thoughts = events.filter((e): e is AgentEvent<'message'> => e.type === 'message' && e.content[0]?.type === 'thought');
expect(thoughts).toHaveLength(1);
expect(thoughts[0].content).toEqual([{ type: 'thought', thought: 'Thinking...' }]);
const texts = events.filter((e): e is AgentEvent<'message'> => e.type === 'message' && e.content[0]?.type === 'text' && e.role === 'agent');
expect(texts).toHaveLength(1);
expect(texts[0].content).toEqual([{ type: 'text', text: 'Final answer' }]);
});
it('should handle tool requests and responses (executable code)', async () => {
const chunk1 = JSON.stringify({
answer: {
replies: [
{
groundedContent: {
content: {
executableCode: { code: 'print("hello")' },
},
},
},
],
},
}) + '\n';
const chunk2 = JSON.stringify({
answer: {
replies: [
{
groundedContent: {
content: {
codeExecutionResult: { outcome: 'OUTCOME_OK', output: 'hello\n' },
},
},
},
],
},
}) + '\n';
mockFetchResponse([chunk1, chunk2]);
const session = new EnterpriseAgentSession({ config: mockConfig });
const { streamId } = await session.send({
message: { content: [{ type: 'text', text: 'run code' }] },
});
const events: AgentEvent[] = [];
for await (const event of session.stream({ streamId: streamId! })) {
events.push(event);
}
expect(events.map(e => e.type)).toEqual([
'agent_start',
'tool_request',
'tool_response',
'agent_end',
]);
const toolReq = events.find(e => e.type === 'tool_request') as AgentEvent<'tool_request'>;
expect(toolReq.name).toBe('python_interpreter');
expect(toolReq.args).toEqual({ code: 'print("hello")' });
const toolResp = events.find(e => e.type === 'tool_response') as AgentEvent<'tool_response'>;
expect(toolResp.name).toBe('python_interpreter');
expect(toolResp.content).toEqual([{ type: 'text', text: 'hello\n' }]);
expect(toolResp.isError).toBe(false);
});
it('should handle immersive artifacts (tables/docs)', async () => {
const chunk = JSON.stringify({
answer: {
replies: [
{
groundedContent: {
content: { text: 'Here is the table:\n' },
},
immersiveArtifact: [
{
docArtifact: { text: '| Col 1 | Col 2 |\n|---|---|\n| Val 1 | Val 2 |' },
},
],
},
],
},
}) + '\n';
mockFetchResponse([chunk]);
const session = new EnterpriseAgentSession({ config: mockConfig });
const { streamId } = await session.send({
message: { content: [{ type: 'text', text: 'show table' }] },
});
const events: AgentEvent[] = [];
for await (const event of session.stream({ streamId: streamId! })) {
events.push(event);
}
const texts = events.filter((e): e is AgentEvent<'message'> => e.type === 'message' && e.role === 'agent');
expect(texts).toHaveLength(2);
expect(texts[0].content).toEqual([{ type: 'text', text: 'Here is the table:\n' }]);
expect(texts[1].content).toEqual([{ type: 'text', text: '| Col 1 | Col 2 |\n|---|---|\n| Val 1 | Val 2 |' }]);
});
});