diff --git a/eslint.config.js b/eslint.config.js index 48af3775f2..b05072cfc0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -38,6 +38,7 @@ export default tseslint.config( 'dist/**', 'evals/**', 'packages/test-utils/**', + '.gemini/skills/**', ], }, eslint.configs.recommended, diff --git a/packages/core/src/config/storage.test.ts b/packages/core/src/config/storage.test.ts index afb3eaeeeb..15b49d12f1 100644 --- a/packages/core/src/config/storage.test.ts +++ b/packages/core/src/config/storage.test.ts @@ -180,6 +180,86 @@ describe('Storage – additional helpers', () => { expect(storageWithSession.getProjectTempPlansDir()).toBe(expected); }); + describe('Session and JSON Loading', () => { + beforeEach(async () => { + await storage.initialize(); + }); + + it('listProjectChatFiles returns sorted sessions from chats directory', async () => { + const readdirSpy = vi + .spyOn(fs.promises, 'readdir') + /* eslint-disable @typescript-eslint/no-explicit-any */ + .mockResolvedValue([ + 'session-1.json', + 'session-2.json', + 'not-a-session.txt', + ] as any); + + const statSpy = vi + .spyOn(fs.promises, 'stat') + .mockImplementation(async (p: any) => { + if (p.toString().endsWith('session-1.json')) { + return { + mtime: new Date('2026-02-01'), + mtimeMs: 1000, + } as any; + } + return { + mtime: new Date('2026-02-02'), + mtimeMs: 2000, + } as any; + }); + /* eslint-enable @typescript-eslint/no-explicit-any */ + + const sessions = await storage.listProjectChatFiles(); + + expect(readdirSpy).toHaveBeenCalledWith(expect.stringContaining('chats')); + expect(sessions).toHaveLength(2); + // Sorted by mtime desc + expect(sessions[0].filePath).toBe(path.join('chats', 'session-2.json')); + expect(sessions[1].filePath).toBe(path.join('chats', 'session-1.json')); + expect(sessions[0].lastUpdated).toBe( + new Date('2026-02-02').toISOString(), + ); + + readdirSpy.mockRestore(); + statSpy.mockRestore(); + }); + + it('loadProjectTempFile loads and parses JSON from relative path', async () => { + const readFileSpy = vi + .spyOn(fs.promises, 'readFile') + .mockResolvedValue(JSON.stringify({ hello: 'world' })); + + const result = await storage.loadProjectTempFile<{ hello: string }>( + 'some/file.json', + ); + + expect(readFileSpy).toHaveBeenCalledWith( + expect.stringContaining(path.join(PROJECT_SLUG, 'some/file.json')), + 'utf8', + ); + expect(result).toEqual({ hello: 'world' }); + + readFileSpy.mockRestore(); + }); + + it('loadProjectTempFile returns null if file does not exist', async () => { + const error = new Error('File not found'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (error as any).code = 'ENOENT'; + const readFileSpy = vi + .spyOn(fs.promises, 'readFile') + .mockRejectedValue(error); + + const result = await storage.loadProjectTempFile('missing.json'); + + expect(result).toBeNull(); + + readFileSpy.mockRestore(); + }); + }); + describe('getPlansDir', () => { interface TestCase { name: string; diff --git a/packages/core/src/config/storage.ts b/packages/core/src/config/storage.ts index 3a079f3b7e..bd04123c34 100644 --- a/packages/core/src/config/storage.ts +++ b/packages/core/src/config/storage.ts @@ -295,6 +295,63 @@ export class Storage { return path.join(this.getProjectTempDir(), 'tasks'); } + async listProjectChatFiles(): Promise< + Array<{ filePath: string; lastUpdated: string }> + > { + const chatsDir = path.join(this.getProjectTempDir(), 'chats'); + try { + const files = await fs.promises.readdir(chatsDir); + const jsonFiles = files.filter((f) => f.endsWith('.json')); + + const sessions = await Promise.all( + jsonFiles.map(async (file) => { + const absolutePath = path.join(chatsDir, file); + const stats = await fs.promises.stat(absolutePath); + return { + filePath: path.join('chats', file), + lastUpdated: stats.mtime.toISOString(), + mtimeMs: stats.mtimeMs, + }; + }), + ); + + return sessions + .sort((a, b) => b.mtimeMs - a.mtimeMs) + .map(({ filePath, lastUpdated }) => ({ filePath, lastUpdated })); + } catch (e) { + // If directory doesn't exist, return empty + if ( + e instanceof Error && + 'code' in e && + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + (e as NodeJS.ErrnoException).code === 'ENOENT' + ) { + return []; + } + throw e; + } + } + + async loadProjectTempFile(filePath: string): Promise { + const absolutePath = path.join(this.getProjectTempDir(), filePath); + try { + const content = await fs.promises.readFile(absolutePath, 'utf8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + return JSON.parse(content) as T; + } catch (e) { + // If file doesn't exist, return null + if ( + e instanceof Error && + 'code' in e && + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + (e as NodeJS.ErrnoException).code === 'ENOENT' + ) { + return null; + } + throw e; + } + } + getExtensionsDir(): string { return path.join(this.getGeminiDir(), 'extensions'); } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 36d10d3832..8ecd8cef7c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -180,7 +180,7 @@ export { OAuthUtils } from './mcp/oauth-utils.js'; // Export telemetry functions export * from './telemetry/index.js'; -export { sessionId } from './utils/session.js'; +export { sessionId, createSessionId } from './utils/session.js'; export * from './utils/compatibility.js'; export * from './utils/browser.js'; export { Storage } from './config/storage.js'; diff --git a/packages/core/src/utils/session.ts b/packages/core/src/utils/session.ts index 96cdbbf48c..2a0ec52115 100644 --- a/packages/core/src/utils/session.ts +++ b/packages/core/src/utils/session.ts @@ -7,3 +7,7 @@ import { randomUUID } from 'node:crypto'; export const sessionId = randomUUID(); + +export function createSessionId(): string { + return randomUUID(); +} diff --git a/packages/sdk/SDK_DESIGN.md b/packages/sdk/SDK_DESIGN.md index d8c8512991..d3031f32b8 100644 --- a/packages/sdk/SDK_DESIGN.md +++ b/packages/sdk/SDK_DESIGN.md @@ -8,7 +8,8 @@ ## `Simple Example` -> **Status:** Implemented. `GeminiCliAgent` supports `cwd` and `sendStream`. +> **Status:** Implemented. `GeminiCliAgent` supports `session()` and +> `resumeSession()`. Equivalent to `gemini -p "what does this project do?"`. Loads all workspace and user settings. @@ -20,10 +21,14 @@ const simpleAgent = new GeminiCliAgent({ cwd: '/path/to/some/dir', }); -for await (const chunk of simpleAgent.sendStream( - 'what does this project do?', -)) { - console.log(chunk); // equivalent to JSON streaming chunks (probably?) for now +// Create a new empty session +const session = simpleAgent.session(); + +// Resume a specific session by ID +// const session = await simpleAgent.resumeSession('some-session-id'); + +for await (const chunk of session.sendStream('what does this project do?')) { + console.log(chunk); // equivalent to JSON streaming chunks } ``` @@ -268,8 +273,9 @@ export interface SessionContext { // helpers to access files and run shell commands while adhering to policies/validation fs: AgentFilesystem; shell: AgentShell; - // the agent itself is passed as context + // the agent and session are passed as context agent: GeminiCliAgent; + session: GeminiCliSession; } export interface AgentFilesystem { diff --git a/packages/sdk/src/agent.integration.test.ts b/packages/sdk/src/agent.integration.test.ts index 5226e30e06..064cd9fad7 100644 --- a/packages/sdk/src/agent.integration.test.ts +++ b/packages/sdk/src/agent.integration.test.ts @@ -30,8 +30,12 @@ describe('GeminiCliAgent Integration', () => { 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 = agent.sendStream('Say hello.'); + const stream = session.sendStream('Say hello.'); for await (const event of stream) { events.push(event); @@ -60,9 +64,11 @@ describe('GeminiCliAgent Integration', () => { fakeResponses: RECORD_MODE ? undefined : goldenFile, }); + const session = agent.session(); + // First turn - const stream1 = agent.sendStream('What is the secret number?'); const events1 = []; + const stream1 = session.sendStream('What is the secret number?'); for await (const event of stream1) { events1.push(event); } @@ -72,11 +78,10 @@ describe('GeminiCliAgent Integration', () => { .join(''); expect(responseText1).toContain('1'); - expect(callCount).toBe(1); // Second turn - const stream2 = agent.sendStream('What is the secret number now?'); const events2 = []; + const stream2 = session.sendStream('What is the secret number now?'); for await (const event of stream2) { events2.push(event); } @@ -85,57 +90,60 @@ describe('GeminiCliAgent Integration', () => { .map((e) => (typeof e.value === 'string' ? e.value : '')) .join(''); - // Should still be 1 because instructions are only loaded once per session - expect(responseText2).toContain('1'); - expect(callCount).toBe(1); + expect(responseText2).toContain('2'); }, 30000); - it('handles async dynamic instructions', async () => { - const goldenFile = getGoldenPath('agent-async-instructions'); + it('resumes a session', async () => { + const goldenFile = getGoldenPath('agent-resume-session'); - let callCount = 0; + // Create initial session const agent = new GeminiCliAgent({ - instructions: async (_ctx) => { - await new Promise((resolve) => setTimeout(resolve, 10)); // Simulate async work - callCount++; - return `You are a helpful assistant. The secret number is ${callCount}. Always mention the secret number when asked.`; - }, + 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, }); - // First turn - const stream1 = agent.sendStream('What is the secret number?'); - const events1 = []; - for await (const event of stream1) { - events1.push(event); + 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 } - const responseText1 = events1 - .filter((e) => e.type === 'content') - .map((e) => (typeof e.value === 'string' ? e.value : '')) - .join(''); - expect(responseText1).toContain('1'); - expect(callCount).toBe(1); + // 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); - // Second turn - const stream2 = agent.sendStream('What is the secret number now?'); const events2 = []; + const stream2 = session2.sendStream('What is the word again?'); for await (const event of stream2) { events2.push(event); } - const responseText2 = events2 + + const responseText = events2 .filter((e) => e.type === 'content') .map((e) => (typeof e.value === 'string' ? e.value : '')) .join(''); - // Should still be 1 because instructions are only loaded once per session - expect(responseText2).toContain('1'); - expect(callCount).toBe(1); + expect(responseText).toContain('BANANA'); }, 30000); - it('throws when dynamic instructions fail', async () => { + 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'); @@ -143,7 +151,8 @@ describe('GeminiCliAgent Integration', () => { model: 'gemini-2.0-flash', }); - const stream = agent.sendStream('Say hello.'); + const session = agent.session(); + const stream = session.sendStream('Say hello.'); await expect(async () => { for await (const _event of stream) { diff --git a/packages/sdk/src/agent.ts b/packages/sdk/src/agent.ts index 7db03a98f5..6e713c0fe1 100644 --- a/packages/sdk/src/agent.ts +++ b/packages/sdk/src/agent.ts @@ -4,245 +4,81 @@ * SPDX-License-Identifier: Apache-2.0 */ +import * as path from 'node:path'; import { - Config, - type ConfigParameters, - AuthType, - PREVIEW_GEMINI_MODEL_AUTO, - GeminiEventType, - type ToolCallRequestInfo, - type ServerGeminiStreamEvent, - type GeminiClient, - type Content, - scheduleAgentTools, - getAuthTypeFromEnv, - type ToolRegistry, - loadSkillsFromDir, - ActivateSkillTool, + Storage, + createSessionId, + type ResumedSessionData, + type ConversationRecord, } from '@google/gemini-cli-core'; -import { type Tool, SdkTool } from './tool.js'; -import { SdkAgentFilesystem } from './fs.js'; -import { SdkAgentShell } from './shell.js'; -import type { SessionContext } from './types.js'; -import type { SkillReference } from './skills.js'; - -export type SystemInstructions = - | string - | ((context: SessionContext) => string | Promise); - -export interface GeminiCliAgentOptions { - instructions: SystemInstructions; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - tools?: Array>; - skills?: SkillReference[]; - model?: string; - cwd?: string; - debug?: boolean; - recordResponses?: string; - fakeResponses?: string; -} +import { GeminiCliSession } from './session.js'; +import type { GeminiCliAgentOptions } from './types.js'; export class GeminiCliAgent { - private readonly config: Config; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readonly tools: Array>; - private readonly skillRefs: SkillReference[]; - private readonly instructions: SystemInstructions; - private instructionsLoaded = false; + private options: GeminiCliAgentOptions; constructor(options: GeminiCliAgentOptions) { - this.instructions = options.instructions; - const cwd = options.cwd || process.cwd(); - this.tools = options.tools || []; - this.skillRefs = options.skills || []; - - const initialMemory = - typeof this.instructions === 'string' ? this.instructions : ''; - - const configParams: ConfigParameters = { - sessionId: `sdk-${Date.now()}`, - targetDir: cwd, - cwd, - debugMode: options.debug ?? false, - model: options.model || PREVIEW_GEMINI_MODEL_AUTO, - userMemory: initialMemory, - // Minimal config - enableHooks: false, - mcpEnabled: false, - extensionsEnabled: false, - recordResponses: options.recordResponses, - fakeResponses: options.fakeResponses, - skillsSupport: true, - adminSkillsEnabled: true, - }; - - this.config = new Config(configParams); + this.options = options; } - async *sendStream( - prompt: string, - signal?: AbortSignal, - ): AsyncGenerator { - // Lazy initialization of auth and client - if (!this.config.getContentGenerator()) { - const authType = getAuthTypeFromEnv() || AuthType.COMPUTE_ADC; + session(options?: { sessionId?: string }): GeminiCliSession { + const sessionId = options?.sessionId || createSessionId(); + return new GeminiCliSession(this.options, sessionId, this); + } - await this.config.refreshAuth(authType); - await this.config.initialize(); + async resumeSession(sessionId: string): Promise { + const cwd = this.options.cwd || process.cwd(); + const storage = new Storage(cwd); + await storage.initialize(); - // Load additional skills from options - if (this.skillRefs.length > 0) { - const skillManager = this.config.getSkillManager(); + let conversation: ConversationRecord | undefined; + let filePath: string | undefined; - const loadPromises = this.skillRefs.map(async (ref) => { - try { - if (ref.type === 'dir') { - return await loadSkillsFromDir(ref.path); - } - } catch (e) { - // eslint-disable-next-line no-console - console.error(`Failed to load skills from ${ref.path}:`, e); - } - return []; - }); + const sessions = await storage.listProjectChatFiles(); - const loadedSkills = (await Promise.all(loadPromises)).flat(); - - if (loadedSkills.length > 0) { - skillManager.addSkills(loadedSkills); - } - } - - // Re-register ActivateSkillTool if we have skills (either built-in/workspace or manually loaded) - // This is required because ActivateSkillTool captures the set of available skills at construction time. - const skillManager = this.config.getSkillManager(); - if (skillManager.getSkills().length > 0) { - const registry = this.config.getToolRegistry(); - const toolName = ActivateSkillTool.Name; - // Config.initialize already registers it, but we might have added more skills. - // Re-registering updates the schema with new skills. - if (registry.getTool(toolName)) { - registry.unregisterTool(toolName); - } - registry.registerTool( - new ActivateSkillTool(this.config, this.config.getMessageBus()), - ); - } - - // Register tools now that registry exists - const registry = this.config.getToolRegistry(); - const messageBus = this.config.getMessageBus(); - - for (const toolDef of this.tools) { - const sdkTool = new SdkTool(toolDef, messageBus, this); - registry.registerTool(sdkTool); - } + if (sessions.length === 0) { + throw new Error( + `No sessions found in ${path.join(storage.getProjectTempDir(), 'chats')}`, + ); } - const client = this.config.getGeminiClient(); - const abortSignal = signal ?? new AbortController().signal; - const sessionId = this.config.getSessionId(); + const truncatedId = sessionId.slice(0, 8); + // Optimization: filenames include first 8 chars of sessionId. + // Filter sessions that might match. + const candidates = sessions.filter((s) => s.filePath.includes(truncatedId)); - const fs = new SdkAgentFilesystem(this.config); - const shell = new SdkAgentShell(this.config); + // If optimization fails (e.g. old files), check all? + // Assuming filenames always follow convention if created by this tool. + // But we can fallback to checking all if needed, but let's stick to candidates first. + // If candidates is empty, maybe fallback to all. + const filesToCheck = candidates.length > 0 ? candidates : sessions; - let request: Parameters[0] = [ - { text: prompt }, - ]; - - if (!this.instructionsLoaded && typeof this.instructions === 'function') { - const context: SessionContext = { - sessionId, - transcript: client.getHistory(), - cwd: this.config.getWorkingDir(), - timestamp: new Date().toISOString(), - fs, - shell, - agent: this, - }; - try { - const newInstructions = await this.instructions(context); - this.config.setUserMemory(newInstructions); - client.updateSystemInstruction(); - this.instructionsLoaded = true; - } catch (e) { - const error = - e instanceof Error - ? e - : new Error(`Error resolving dynamic instructions: ${String(e)}`); - throw error; - } - } - - while (true) { - // sendMessageStream returns AsyncGenerator - const stream = client.sendMessageStream(request, abortSignal, sessionId); - - const toolCallsToSchedule: ToolCallRequestInfo[] = []; - - for await (const event of stream) { - yield event; - if (event.type === GeminiEventType.ToolCallRequest) { - const toolCall = event.value; - let args = toolCall.args; - if (typeof args === 'string') { - args = JSON.parse(args); - } - toolCallsToSchedule.push({ - ...toolCall, - args, - isClientInitiated: false, - prompt_id: sessionId, - }); - } - } - - if (toolCallsToSchedule.length === 0) { + for (const sessionFile of filesToCheck) { + const loaded = await storage.loadProjectTempFile( + sessionFile.filePath, + ); + if (loaded && loaded.sessionId === sessionId) { + conversation = loaded; + filePath = path.join(storage.getProjectTempDir(), sessionFile.filePath); break; } - - // Prepare SessionContext - const transcript: Content[] = client.getHistory(); - const context: SessionContext = { - sessionId, - transcript, - cwd: this.config.getWorkingDir(), - timestamp: new Date().toISOString(), - fs, - shell, - agent: this, - }; - - // Create a scoped registry for this turn to bind context safely - const originalRegistry = this.config.getToolRegistry(); - const scopedRegistry: ToolRegistry = Object.create(originalRegistry); - scopedRegistry.getTool = (name: string) => { - const tool = originalRegistry.getTool(name); - if (tool instanceof SdkTool) { - return tool.bindContext(context); - } - return tool; - }; - - const completedCalls = await scheduleAgentTools( - this.config, - toolCallsToSchedule, - { - schedulerId: sessionId, - toolRegistry: scopedRegistry, - signal: abortSignal, - }, - ); - - const functionResponses = completedCalls.flatMap( - (call) => call.response.responseParts, - ); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - request = functionResponses as unknown as Parameters< - GeminiClient['sendMessageStream'] - >[0]; } + + if (!conversation || !filePath) { + throw new Error(`Session with ID ${sessionId} not found`); + } + + const resumedData: ResumedSessionData = { + conversation, + filePath, + }; + + return new GeminiCliSession( + this.options, + conversation.sessionId, + this, + resumedData, + ); } } diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index f1b9e020f5..91e7a080f0 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -5,6 +5,7 @@ */ export * from './agent.js'; +export * from './session.js'; export * from './tool.js'; export * from './skills.js'; export * from './types.js'; diff --git a/packages/sdk/src/session.ts b/packages/sdk/src/session.ts new file mode 100644 index 0000000000..d5e6b9ee8a --- /dev/null +++ b/packages/sdk/src/session.ts @@ -0,0 +1,270 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + Config, + type ConfigParameters, + AuthType, + PREVIEW_GEMINI_MODEL_AUTO, + GeminiEventType, + type ToolCallRequestInfo, + type ServerGeminiStreamEvent, + type GeminiClient, + type Content, + scheduleAgentTools, + getAuthTypeFromEnv, + type ToolRegistry, + loadSkillsFromDir, + ActivateSkillTool, + type ResumedSessionData, + PolicyDecision, +} from '@google/gemini-cli-core'; + +import { type Tool, SdkTool } from './tool.js'; +import { SdkAgentFilesystem } from './fs.js'; +import { SdkAgentShell } from './shell.js'; +import type { + SessionContext, + GeminiCliAgentOptions, + SystemInstructions, +} from './types.js'; +import type { SkillReference } from './skills.js'; +import type { GeminiCliAgent } from './agent.js'; + +export class GeminiCliSession { + private readonly config: Config; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private readonly tools: Array>; + private readonly skillRefs: SkillReference[]; + private readonly instructions: SystemInstructions | undefined; + private client: GeminiClient | undefined; + private initialized = false; + + constructor( + options: GeminiCliAgentOptions, + private readonly sessionId: string, + private readonly agent: GeminiCliAgent, + private readonly resumedData?: ResumedSessionData, + ) { + this.instructions = options.instructions; + const cwd = options.cwd || process.cwd(); + this.tools = options.tools || []; + this.skillRefs = options.skills || []; + + let initialMemory = ''; + if (typeof this.instructions === 'string') { + initialMemory = this.instructions; + } else if (this.instructions && typeof this.instructions !== 'function') { + throw new Error('Instructions must be a string or a function.'); + } + + const configParams: ConfigParameters = { + sessionId: this.sessionId, + targetDir: cwd, + cwd, + debugMode: options.debug ?? false, + model: options.model || PREVIEW_GEMINI_MODEL_AUTO, + userMemory: initialMemory, + // Minimal config + enableHooks: false, + mcpEnabled: false, + extensionsEnabled: false, + recordResponses: options.recordResponses, + fakeResponses: options.fakeResponses, + skillsSupport: true, + adminSkillsEnabled: true, + policyEngineConfig: { + // TODO: Revisit this default when we have a mechanism for wiring up approvals + defaultDecision: PolicyDecision.ALLOW, + }, + }; + + this.config = new Config(configParams); + } + + get id(): string { + return this.sessionId; + } + + async initialize(): Promise { + if (this.initialized) return; + + const authType = getAuthTypeFromEnv() || AuthType.COMPUTE_ADC; + + await this.config.refreshAuth(authType); + await this.config.initialize(); + + // Load additional skills from options + if (this.skillRefs.length > 0) { + const skillManager = this.config.getSkillManager(); + + const loadPromises = this.skillRefs.map(async (ref) => { + try { + if (ref.type === 'dir') { + return await loadSkillsFromDir(ref.path); + } + } catch (e) { + // TODO: refactor this to use a proper logger interface + // eslint-disable-next-line no-console + console.error(`Failed to load skills from ${ref.path}:`, e); + } + return []; + }); + + const loadedSkills = (await Promise.all(loadPromises)).flat(); + + if (loadedSkills.length > 0) { + skillManager.addSkills(loadedSkills); + } + } + + // Re-register ActivateSkillTool if we have skills + const skillManager = this.config.getSkillManager(); + if (skillManager.getSkills().length > 0) { + const registry = this.config.getToolRegistry(); + const toolName = ActivateSkillTool.Name; + if (registry.getTool(toolName)) { + registry.unregisterTool(toolName); + } + registry.registerTool( + new ActivateSkillTool(this.config, this.config.getMessageBus()), + ); + } + + // Register tools + const registry = this.config.getToolRegistry(); + const messageBus = this.config.getMessageBus(); + + for (const toolDef of this.tools) { + const sdkTool = new SdkTool(toolDef, messageBus, this.agent, undefined); + registry.registerTool(sdkTool); + } + + this.client = this.config.getGeminiClient(); + + if (this.resumedData) { + const history: Content[] = this.resumedData.conversation.messages.map( + (m) => { + const role = m.type === 'gemini' ? 'model' : 'user'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let parts: any[] = []; + if (Array.isArray(m.content)) { + parts = m.content; + } else if (m.content) { + parts = [{ text: String(m.content) }]; + } + return { role, parts }; + }, + ); + await this.client.resumeChat(history, this.resumedData); + } + + this.initialized = true; + } + + async *sendStream( + prompt: string, + signal?: AbortSignal, + ): AsyncGenerator { + if (!this.initialized || !this.client) { + await this.initialize(); + } + const client = this.client!; + const abortSignal = signal ?? new AbortController().signal; + const sessionId = this.config.getSessionId(); + + const fs = new SdkAgentFilesystem(this.config); + const shell = new SdkAgentShell(this.config); + + let request: Parameters[0] = [ + { text: prompt }, + ]; + + while (true) { + if (typeof this.instructions === 'function') { + const context: SessionContext = { + sessionId, + transcript: client.getHistory(), + cwd: this.config.getWorkingDir(), + timestamp: new Date().toISOString(), + fs, + shell, + agent: this.agent, + session: this, + }; + const newInstructions = await this.instructions(context); + this.config.setUserMemory(newInstructions); + client.updateSystemInstruction(); + } + + const stream = client.sendMessageStream(request, abortSignal, sessionId); + + const toolCallsToSchedule: ToolCallRequestInfo[] = []; + + for await (const event of stream) { + yield event; + if (event.type === GeminiEventType.ToolCallRequest) { + const toolCall = event.value; + let args = toolCall.args; + if (typeof args === 'string') { + args = JSON.parse(args); + } + toolCallsToSchedule.push({ + ...toolCall, + args, + isClientInitiated: false, + prompt_id: sessionId, + }); + } + } + + if (toolCallsToSchedule.length === 0) { + break; + } + + const transcript: Content[] = client.getHistory(); + const context: SessionContext = { + sessionId, + transcript, + cwd: this.config.getWorkingDir(), + timestamp: new Date().toISOString(), + fs, + shell, + agent: this.agent, + session: this, + }; + + const originalRegistry = this.config.getToolRegistry(); + const scopedRegistry: ToolRegistry = Object.create(originalRegistry); + scopedRegistry.getTool = (name: string) => { + const tool = originalRegistry.getTool(name); + if (tool instanceof SdkTool) { + return tool.bindContext(context); + } + return tool; + }; + + const completedCalls = await scheduleAgentTools( + this.config, + toolCallsToSchedule, + { + schedulerId: sessionId, + toolRegistry: scopedRegistry, + signal: abortSignal, + }, + ); + + const functionResponses = completedCalls.flatMap( + (call) => call.response.responseParts, + ); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + request = functionResponses as unknown as Parameters< + GeminiClient['sendMessageStream'] + >[0]; + } + } +} diff --git a/packages/sdk/src/skills.integration.test.ts b/packages/sdk/src/skills.integration.test.ts index a91480ff30..48f0e0fb06 100644 --- a/packages/sdk/src/skills.integration.test.ts +++ b/packages/sdk/src/skills.integration.test.ts @@ -39,8 +39,9 @@ describe('GeminiCliAgent Skills Integration', () => { // 1. Ask to activate the skill const events = []; + const session = agent.session(); // The prompt explicitly asks to activate the skill by name - const stream = agent.sendStream( + const stream = session.sendStream( 'Activate the pirate-skill and then tell me a joke.', ); @@ -72,7 +73,8 @@ describe('GeminiCliAgent Skills Integration', () => { // 1. Ask to activate the skill const events = []; - const stream = agent.sendStream( + const session = agent.session(); + const stream = session.sendStream( 'Activate the pirate-skill and confirm it is active.', ); diff --git a/packages/sdk/src/tool.integration.test.ts b/packages/sdk/src/tool.integration.test.ts index 1ec9d73abd..cc0a7c2454 100644 --- a/packages/sdk/src/tool.integration.test.ts +++ b/packages/sdk/src/tool.integration.test.ts @@ -45,7 +45,8 @@ describe('GeminiCliAgent Tool Integration', () => { }); const events = []; - const stream = agent.sendStream('What is 5 + 3?'); + const session = agent.session(); + const stream = session.sendStream('What is 5 + 3?'); for await (const event of stream) { events.push(event); @@ -85,9 +86,10 @@ describe('GeminiCliAgent Tool Integration', () => { }); const events = []; + const session = agent.session(); // Force the model to trigger the error first, then hopefully recover or at least acknowledge it. // The prompt is crafted to make the model try 'fail' first. - const stream = agent.sendStream( + const stream = session.sendStream( 'Call the tool with "fail". If it fails, tell me the error message.', ); @@ -128,7 +130,8 @@ describe('GeminiCliAgent Tool Integration', () => { }); const events = []; - const stream = agent.sendStream( + const session = agent.session(); + const stream = session.sendStream( 'Check the system status and report any errors.', ); diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index d7e013d66c..9b6bf7093a 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -5,7 +5,26 @@ */ import type { Content } from '@google/gemini-cli-core'; +import type { Tool } from './tool.js'; +import type { SkillReference } from './skills.js'; import type { GeminiCliAgent } from './agent.js'; +import type { GeminiCliSession } from './session.js'; + +export type SystemInstructions = + | string + | ((context: SessionContext) => string | Promise); + +export interface GeminiCliAgentOptions { + instructions: SystemInstructions; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + tools?: Array>; + skills?: SkillReference[]; + model?: string; + cwd?: string; + debug?: boolean; + recordResponses?: string; + fakeResponses?: string; +} export interface AgentFilesystem { readFile(path: string): Promise; @@ -38,4 +57,5 @@ export interface SessionContext { fs: AgentFilesystem; shell: AgentShell; agent: GeminiCliAgent; + session: GeminiCliSession; } diff --git a/packages/sdk/test-data/agent-dynamic-instructions.json b/packages/sdk/test-data/agent-dynamic-instructions.json index 833467ad84..cbafd27206 100644 --- a/packages/sdk/test-data/agent-dynamic-instructions.json +++ b/packages/sdk/test-data/agent-dynamic-instructions.json @@ -1,4 +1,24 @@ {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9831,"totalTokenCount":9831,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9831}]}},{"candidates":[{"content":{"parts":[{"text":" 1.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7098,"candidatesTokenCount":8,"totalTokenCount":7106,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7098}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} -{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9848,"totalTokenCount":9848,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9848}]}},{"candidates":[{"content":{"parts":[{"text":" 1.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7113,"candidatesTokenCount":8,"totalTokenCount":7121,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7113}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9848,"totalTokenCount":9848,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9848}]}},{"candidates":[{"content":{"parts":[{"text":" 2.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7113,"candidatesTokenCount":8,"totalTokenCount":7121,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7113}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9853,"totalTokenCount":9853,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9853}]}},{"candidates":[{"content":{"parts":[{"text":" 1.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7120,"candidatesTokenCount":8,"totalTokenCount":7128,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7120}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} -{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9870,"totalTokenCount":9870,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9870}]}},{"candidates":[{"content":{"parts":[{"text":" 1.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7135,"candidatesTokenCount":8,"totalTokenCount":7143,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7135}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9870,"totalTokenCount":9870,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9870}]}},{"candidates":[{"content":{"parts":[{"text":" 2.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7135,"candidatesTokenCount":8,"totalTokenCount":7143,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7135}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10028,"totalTokenCount":10028,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10028}]}},{"candidates":[{"content":{"parts":[{"text":" 1."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7193,"candidatesTokenCount":7,"totalTokenCount":7200,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7193}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10044,"totalTokenCount":10044,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10044}]}},{"candidates":[{"content":{"parts":[{"text":" 2."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7207,"candidatesTokenCount":7,"totalTokenCount":7214,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7207}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10039,"totalTokenCount":10039,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10039}]}},{"candidates":[{"content":{"parts":[{"text":" 1."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7204,"candidatesTokenCount":7,"totalTokenCount":7211,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7204}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10055,"totalTokenCount":10055,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10055}]}},{"candidates":[{"content":{"parts":[{"text":" 2."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7218,"candidatesTokenCount":7,"totalTokenCount":7225,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7218}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10039,"totalTokenCount":10039,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10039}]}},{"candidates":[{"content":{"parts":[{"text":" secret number is 1."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7204,"candidatesTokenCount":7,"totalTokenCount":7211,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7204}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10055,"totalTokenCount":10055,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10055}]}},{"candidates":[{"content":{"parts":[{"text":" 2."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7218,"candidatesTokenCount":7,"totalTokenCount":7225,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7218}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10039,"totalTokenCount":10039,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10039}]}},{"candidates":[{"content":{"parts":[{"text":" secret number is 1."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7204,"candidatesTokenCount":7,"totalTokenCount":7211,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7204}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10055,"totalTokenCount":10055,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10055}]}},{"candidates":[{"content":{"parts":[{"text":" 2.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7218,"candidatesTokenCount":8,"totalTokenCount":7226,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7218}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10039,"totalTokenCount":10039,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10039}]}},{"candidates":[{"content":{"parts":[{"text":" secret number is 1."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7204,"candidatesTokenCount":7,"totalTokenCount":7211,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7204}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10055,"totalTokenCount":10055,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10055}]}},{"candidates":[{"content":{"parts":[{"text":" 2."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7218,"candidatesTokenCount":7,"totalTokenCount":7225,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7218}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10039,"totalTokenCount":10039,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10039}]}},{"candidates":[{"content":{"parts":[{"text":" 1."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7204,"candidatesTokenCount":7,"totalTokenCount":7211,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7204}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10055,"totalTokenCount":10055,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10055}]}},{"candidates":[{"content":{"parts":[{"text":" 2."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7218,"candidatesTokenCount":7,"totalTokenCount":7225,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7218}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10039,"totalTokenCount":10039,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10039}]}},{"candidates":[{"content":{"parts":[{"text":" secret number is 1."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7204,"candidatesTokenCount":7,"totalTokenCount":7211,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7204}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10055,"totalTokenCount":10055,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10055}]}},{"candidates":[{"content":{"parts":[{"text":" 2."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7218,"candidatesTokenCount":7,"totalTokenCount":7225,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7218}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10123,"totalTokenCount":10123,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10123}]}},{"candidates":[{"content":{"parts":[{"text":" 1.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7188,"candidatesTokenCount":8,"totalTokenCount":7196,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7188}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10140,"totalTokenCount":10140,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10140}]}},{"candidates":[{"content":{"parts":[{"text":" 2.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7203,"candidatesTokenCount":8,"totalTokenCount":7211,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7203}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10123,"totalTokenCount":10123,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10123}]}},{"candidates":[{"content":{"parts":[{"text":" 1.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7188,"candidatesTokenCount":8,"totalTokenCount":7196,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7188}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10140,"totalTokenCount":10140,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10140}]}},{"candidates":[{"content":{"parts":[{"text":" 2.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7203,"candidatesTokenCount":8,"totalTokenCount":7211,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7203}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10112,"totalTokenCount":10112,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10112}]}},{"candidates":[{"content":{"parts":[{"text":" 1."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7177,"candidatesTokenCount":7,"totalTokenCount":7184,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7177}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The secret number is"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10128,"totalTokenCount":10128,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10128}]}},{"candidates":[{"content":{"parts":[{"text":" 2."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7191,"candidatesTokenCount":7,"totalTokenCount":7198,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7191}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]} diff --git a/packages/sdk/test-data/agent-resume-session.json b/packages/sdk/test-data/agent-resume-session.json new file mode 100644 index 0000000000..f9d521898e --- /dev/null +++ b/packages/sdk/test-data/agent-resume-session.json @@ -0,0 +1,2 @@ +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"BAN"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10103,"totalTokenCount":10103,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10103}]}},{"candidates":[{"content":{"parts":[{"text":"ANA\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7168,"candidatesTokenCount":3,"totalTokenCount":7171,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7168}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":3}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"BANANA\n"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10124,"totalTokenCount":10124,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10124}]}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7187,"candidatesTokenCount":3,"totalTokenCount":7190,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7187}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":3}]}}]} diff --git a/packages/sdk/test-data/agent-static-instructions.json b/packages/sdk/test-data/agent-static-instructions.json index 733c1915e7..216960da38 100644 --- a/packages/sdk/test-data/agent-static-instructions.json +++ b/packages/sdk/test-data/agent-static-instructions.json @@ -1 +1,4 @@ {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Ah"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9828,"totalTokenCount":9828,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9828}]}},{"candidates":[{"content":{"parts":[{"text":"oy, matey! Ready to chart a course through the code?"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7095,"candidatesTokenCount":15,"totalTokenCount":7110,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7095}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":15}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Ah"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10109,"totalTokenCount":10109,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10109}]}},{"candidates":[{"content":{"parts":[{"text":"oy, matey! Ready to plunder the codebase, are ye?\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7174,"candidatesTokenCount":16,"totalTokenCount":7190,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7174}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":16}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Ah"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10109,"totalTokenCount":10109,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10109}]}},{"candidates":[{"content":{"parts":[{"text":"oy, matey! Ready to shiver some timbers and get to work?\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7174,"candidatesTokenCount":18,"totalTokenCount":7192,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7174}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":18}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Ah"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10098,"totalTokenCount":10098,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10098}]}},{"candidates":[{"content":{"parts":[{"text":"oy, matey! Ready to chart a course through these here files, are we?\n"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10098,"totalTokenCount":10098,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10098}]}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7163,"candidatesTokenCount":20,"totalTokenCount":7183,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7163}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":20}]}}]} diff --git a/packages/sdk/test-data/skill-dir-success.json b/packages/sdk/test-data/skill-dir-success.json index f14658426f..63a7267093 100644 --- a/packages/sdk/test-data/skill-dir-success.json +++ b/packages/sdk/test-data/skill-dir-success.json @@ -1,2 +1,8 @@ {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"activate_skill","args":{"name":"pirate-skill"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7160,"candidatesTokenCount":8,"totalTokenCount":7168,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7160}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Arrr, why"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10048,"totalTokenCount":10048,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10048}]}},{"candidates":[{"content":{"parts":[{"text":" don't pirates play poker? Because they always have a hidden ace up their sleeves"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10048,"totalTokenCount":10048,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10048}]}},{"candidates":[{"content":{"parts":[{"text":"!\\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7284,"candidatesTokenCount":23,"totalTokenCount":7307,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7284}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":23}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"activate_skill","args":{"name":"pirate-skill"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7185,"candidatesTokenCount":8,"totalTokenCount":7193,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7185}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I am"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10179,"totalTokenCount":10179,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10179}]}},{"candidates":[{"content":{"parts":[{"text":" sorry, I cannot activate the skill at this time because it requires user confirmation."}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10179,"totalTokenCount":10179,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10179}]}},{"candidates":[{"content":{"parts":[{"text":" Would you like me to proceed with telling you a joke without activating the skill?"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10179,"totalTokenCount":10179,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10179}]}},{"candidates":[{"content":{"parts":[{"text":"\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7218,"candidatesTokenCount":35,"totalTokenCount":7253,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7218}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":35}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"activate_skill","args":{"name":"pirate-skill"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7185,"candidatesTokenCount":8,"totalTokenCount":7193,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7185}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Arrr, why"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10275,"totalTokenCount":10275,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10275}]}},{"candidates":[{"content":{"parts":[{"text":" don't pirates play cards? Because someone's always standin' on the"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10275,"totalTokenCount":10275,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10275}]}},{"candidates":[{"content":{"parts":[{"text":" deck!\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7309,"candidatesTokenCount":24,"totalTokenCount":7333,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7309}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":24}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"activate_skill","args":{"name":"pirate-skill"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7174,"candidatesTokenCount":8,"totalTokenCount":7182,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7174}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Ar"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10264,"totalTokenCount":10264,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10264}]}},{"candidates":[{"content":{"parts":[{"text":"rr, why don't pirates play cards? Because someone always be standin' on the"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10264,"totalTokenCount":10264,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10264}]}},{"candidates":[{"content":{"parts":[{"text":" deck!\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7298,"candidatesTokenCount":23,"totalTokenCount":7321,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7298}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":23}]}}]} diff --git a/packages/sdk/test-data/skill-root-success.json b/packages/sdk/test-data/skill-root-success.json index 1048a2c627..4fe1e3fe6e 100644 --- a/packages/sdk/test-data/skill-root-success.json +++ b/packages/sdk/test-data/skill-root-success.json @@ -1,2 +1,8 @@ {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"activate_skill","args":{"name":"pirate-skill"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7170,"candidatesTokenCount":8,"totalTokenCount":7178,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7170}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Ar"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10058,"totalTokenCount":10058,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10058}]}},{"candidates":[{"content":{"parts":[{"text":"rr, the pirate skill be active, matey!"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7294,"candidatesTokenCount":12,"totalTokenCount":7306,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7294}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":12}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"activate_skill","args":{"name":"pirate-skill"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7184,"candidatesTokenCount":8,"totalTokenCount":7192,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7184}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10178,"totalTokenCount":10178,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10178}]}},{"candidates":[{"content":{"parts":[{"text":" am unable to activate the skill without user confirmation. I will await further instructions.\n"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10178,"totalTokenCount":10178,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10178}]}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7217,"candidatesTokenCount":18,"totalTokenCount":7235,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7217}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":18}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"activate_skill","args":{"name":"pirate-skill"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7184,"candidatesTokenCount":8,"totalTokenCount":7192,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7184}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Ar"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10274,"totalTokenCount":10274,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10274}]}},{"candidates":[{"content":{"parts":[{"text":"rr, the pirate-skill be active, aye!\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7308,"candidatesTokenCount":13,"totalTokenCount":7321,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7308}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":13}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"activate_skill","args":{"name":"pirate-skill"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7173,"candidatesTokenCount":8,"totalTokenCount":7181,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7173}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":8}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Ar"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10263,"totalTokenCount":10263,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10263}]}},{"candidates":[{"content":{"parts":[{"text":"rr, pirate skill activated, aye!"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7297,"candidatesTokenCount":9,"totalTokenCount":7306,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7297}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":9}]}}]} diff --git a/packages/sdk/test-data/tool-catchall-error.json b/packages/sdk/test-data/tool-catchall-error.json index 43c3b44d8b..8572c8be42 100644 --- a/packages/sdk/test-data/tool-catchall-error.json +++ b/packages/sdk/test-data/tool-catchall-error.json @@ -1,2 +1,8 @@ {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"checkSystemStatus","args":{}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7070,"candidatesTokenCount":3,"totalTokenCount":7073,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7070}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":3}]}}]} {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The system status check"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9850,"totalTokenCount":9850,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9850}]}},{"candidates":[{"content":{"parts":[{"text":" returned an error. It says `Error: Standard error caught`."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7082,"candidatesTokenCount":17,"totalTokenCount":7099,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7082}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":17}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"checkSystemStatus","args":{}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7184,"candidatesTokenCount":3,"totalTokenCount":7187,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7184}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":3}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I am unable to"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10183,"totalTokenCount":10183,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10183}]}},{"candidates":[{"content":{"parts":[{"text":" check the system status because it requires user confirmation in interactive mode. Is there anything else I"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10183,"totalTokenCount":10183,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10183}]}},{"candidates":[{"content":{"parts":[{"text":" can help with?"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7213,"candidatesTokenCount":26,"totalTokenCount":7239,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7213}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":26}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"checkSystemStatus","args":{}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7184,"candidatesTokenCount":3,"totalTokenCount":7187,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7184}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":3}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The system status check"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10164,"totalTokenCount":10164,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10164}]}},{"candidates":[{"content":{"parts":[{"text":" reported an error: \"Standard error caught\".\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7194,"candidatesTokenCount":14,"totalTokenCount":7208,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7194}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":14}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"checkSystemStatus","args":{}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7184,"candidatesTokenCount":3,"totalTokenCount":7187,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7184}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":3}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"There"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10164,"totalTokenCount":10164,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10164}]}},{"candidates":[{"content":{"parts":[{"text":" is an error reported in the system status.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7194,"candidatesTokenCount":11,"totalTokenCount":7205,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7194}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":11}]}}]} diff --git a/packages/sdk/test-data/tool-error-recovery.json b/packages/sdk/test-data/tool-error-recovery.json index 4e36d24aa7..9b17bf52d3 100644 --- a/packages/sdk/test-data/tool-error-recovery.json +++ b/packages/sdk/test-data/tool-error-recovery.json @@ -1,2 +1,8 @@ {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"failVisible","args":{"input":"fail"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7073,"candidatesTokenCount":4,"totalTokenCount":7077,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7073}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":4}]}}]} {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9867,"totalTokenCount":9867,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9867}]}},{"candidates":[{"content":{"parts":[{"text":" tool failed visibly with the error message: \"Error: Tool failed visibly\"."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7085,"candidatesTokenCount":16,"totalTokenCount":7101,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7085}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":16}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"failVisible","args":{"input":"fail"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7198,"candidatesTokenCount":4,"totalTokenCount":7202,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7198}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":4}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10210,"totalTokenCount":10210,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10210}]}},{"candidates":[{"content":{"parts":[{"text":" tool execution for \"failVisible\" requires user confirmation, which is not supported in non-"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10210,"totalTokenCount":10210,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10210}]}},{"candidates":[{"content":{"parts":[{"text":"interactive mode."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7226,"candidatesTokenCount":22,"totalTokenCount":7248,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7226}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":22}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"failVisible","args":{"input":"fail"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7198,"candidatesTokenCount":4,"totalTokenCount":7202,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7198}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":4}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10192,"totalTokenCount":10192,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10192}]}},{"candidates":[{"content":{"parts":[{"text":" tool failed visibly. The error message is \"Tool failed visibly\"."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7208,"candidatesTokenCount":14,"totalTokenCount":7222,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7208}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":14}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"failVisible","args":{"input":"fail"}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7187,"candidatesTokenCount":4,"totalTokenCount":7191,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7187}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":4}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The tool failed with"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10181,"totalTokenCount":10181,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10181}]}},{"candidates":[{"content":{"parts":[{"text":" the error message \"Tool failed visibly\"."}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7197,"candidatesTokenCount":12,"totalTokenCount":7209,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7197}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":12}]}}]} diff --git a/packages/sdk/test-data/tool-success.json b/packages/sdk/test-data/tool-success.json index 1b17993fe4..d90d09cd0d 100644 --- a/packages/sdk/test-data/tool-success.json +++ b/packages/sdk/test-data/tool-success.json @@ -1,2 +1,8 @@ {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"add","args":{"a":5,"b":3}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7045,"candidatesTokenCount":5,"totalTokenCount":7050,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7045}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]} {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"8"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":9849,"totalTokenCount":9849,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9849}]}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7053,"candidatesTokenCount":1,"totalTokenCount":7054,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7053}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":1}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"add","args":{"b":3,"a":5}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7179,"candidatesTokenCount":5,"totalTokenCount":7184,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7179}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10204,"totalTokenCount":10204,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10204}]}},{"candidates":[{"content":{"parts":[{"text":" am unable to execute the add tool in non-interactive mode.\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7206,"candidatesTokenCount":15,"totalTokenCount":7221,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7206}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":15}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"add","args":{"a":5,"b":3}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7179,"candidatesTokenCount":5,"totalTokenCount":7184,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7179}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"8"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7187,"candidatesTokenCount":1,"totalTokenCount":7188,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7187}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":1}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"add","args":{"a":5,"b":3}}}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7168,"candidatesTokenCount":5,"totalTokenCount":7173,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7168}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"8"}],"role":"model"}}],"usageMetadata":{"promptTokenCount":10174,"totalTokenCount":10174,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10174}]}},{"candidates":[{"content":{"parts":[{"text":"\n"}],"role":"model"},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7176,"candidatesTokenCount":2,"totalTokenCount":7178,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7176}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":2}]}}]}