Files
gemini-cli/packages/sdk/src/agent.integration.test.ts
Michael Bleigh f1c0a695f8 refactor(sdk): introduce session-based architecture (#19180)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-20 00:47:35 +00:00

164 lines
5.1 KiB
TypeScript

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect } from 'vitest';
import { GeminiCliAgent } from './agent.js';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Set this to true locally when you need to update snapshots
const RECORD_MODE = process.env['RECORD_NEW_RESPONSES'] === 'true';
const getGoldenPath = (name: string) =>
path.resolve(__dirname, '../test-data', `${name}.json`);
describe('GeminiCliAgent Integration', () => {
it('handles static instructions', async () => {
const goldenFile = getGoldenPath('agent-static-instructions');
const agent = new GeminiCliAgent({
instructions: 'You are a pirate. Respond in pirate speak.',
model: 'gemini-2.0-flash',
recordResponses: RECORD_MODE ? goldenFile : undefined,
fakeResponses: RECORD_MODE ? undefined : goldenFile,
});
const session = agent.session();
expect(session.id).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
);
const events = [];
const stream = session.sendStream('Say hello.');
for await (const event of stream) {
events.push(event);
}
const textEvents = events.filter((e) => e.type === 'content');
const responseText = textEvents
.map((e) => (typeof e.value === 'string' ? e.value : ''))
.join('');
// Expect pirate speak
expect(responseText.toLowerCase()).toMatch(/ahoy|matey|arrr/);
}, 30000);
it('handles dynamic instructions', async () => {
const goldenFile = getGoldenPath('agent-dynamic-instructions');
let callCount = 0;
const agent = new GeminiCliAgent({
instructions: (_ctx) => {
callCount++;
return `You are a helpful assistant. The secret number is ${callCount}. Always mention the secret number when asked.`;
},
model: 'gemini-2.0-flash',
recordResponses: RECORD_MODE ? goldenFile : undefined,
fakeResponses: RECORD_MODE ? undefined : goldenFile,
});
const session = agent.session();
// First turn
const events1 = [];
const stream1 = session.sendStream('What is the secret number?');
for await (const event of stream1) {
events1.push(event);
}
const responseText1 = events1
.filter((e) => e.type === 'content')
.map((e) => (typeof e.value === 'string' ? e.value : ''))
.join('');
expect(responseText1).toContain('1');
// Second turn
const events2 = [];
const stream2 = session.sendStream('What is the secret number now?');
for await (const event of stream2) {
events2.push(event);
}
const responseText2 = events2
.filter((e) => e.type === 'content')
.map((e) => (typeof e.value === 'string' ? e.value : ''))
.join('');
expect(responseText2).toContain('2');
}, 30000);
it('resumes a session', async () => {
const goldenFile = getGoldenPath('agent-resume-session');
// Create initial session
const agent = new GeminiCliAgent({
instructions: 'You are a memory test. Remember the word "BANANA".',
model: 'gemini-2.0-flash',
recordResponses: RECORD_MODE ? goldenFile : undefined,
fakeResponses: RECORD_MODE ? undefined : goldenFile,
});
const session1 = agent.session({ sessionId: 'resume-test-fixed-id' });
const sessionId = session1.id;
const stream1 = session1.sendStream('What is the word?');
for await (const _ of stream1) {
// consume stream
}
// Resume session
// Allow some time for async writes if any
await new Promise((resolve) => setTimeout(resolve, 500));
const session2 = await agent.resumeSession(sessionId);
expect(session2.id).toBe(sessionId);
const events2 = [];
const stream2 = session2.sendStream('What is the word again?');
for await (const event of stream2) {
events2.push(event);
}
const responseText = events2
.filter((e) => e.type === 'content')
.map((e) => (typeof e.value === 'string' ? e.value : ''))
.join('');
expect(responseText).toContain('BANANA');
}, 30000);
it('throws on invalid instructions', () => {
// Missing instructions should be fine
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect(() => new GeminiCliAgent({} as any).session()).not.toThrow();
expect(() =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new GeminiCliAgent({ instructions: 123 as any }).session(),
).toThrow('Instructions must be a string or a function.');
});
it('propagates errors from dynamic instructions', async () => {
const agent = new GeminiCliAgent({
instructions: () => {
throw new Error('Dynamic instruction failure');
},
model: 'gemini-2.0-flash',
});
const session = agent.session();
const stream = session.sendStream('Say hello.');
await expect(async () => {
for await (const _event of stream) {
// Just consume the stream
}
}).rejects.toThrow('Dynamic instruction failure');
});
});