mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-15 20:40:35 -07:00
feat(sdk): implements SessionContext for SDK tool calls (#18862)
This commit is contained in:
@@ -7,29 +7,38 @@
|
||||
import {
|
||||
Config,
|
||||
type ConfigParameters,
|
||||
AuthType,
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
GeminiEventType,
|
||||
type ToolCallRequestInfo,
|
||||
type ServerGeminiStreamEvent,
|
||||
type GeminiClient,
|
||||
type Content,
|
||||
scheduleAgentTools,
|
||||
getAuthTypeFromEnv,
|
||||
AuthType,
|
||||
type ToolRegistry,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import { type Tool, SdkTool, type z } from './tool.js';
|
||||
import { type Tool, SdkTool } from './tool.js';
|
||||
import { SdkAgentFilesystem } from './fs.js';
|
||||
import { SdkAgentShell } from './shell.js';
|
||||
import type { SessionContext } from './types.js';
|
||||
|
||||
export interface GeminiCliAgentOptions {
|
||||
instructions: string;
|
||||
tools?: Array<Tool<z.ZodType>>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
tools?: Array<Tool<any>>;
|
||||
model?: string;
|
||||
cwd?: string;
|
||||
debug?: boolean;
|
||||
recordResponses?: string;
|
||||
fakeResponses?: string;
|
||||
}
|
||||
|
||||
export class GeminiCliAgent {
|
||||
private readonly config: Config;
|
||||
private readonly tools: Array<Tool<z.ZodType>>;
|
||||
private config: Config;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private tools: Array<Tool<any>>;
|
||||
|
||||
constructor(options: GeminiCliAgentOptions) {
|
||||
const cwd = options.cwd || process.cwd();
|
||||
@@ -46,6 +55,8 @@ export class GeminiCliAgent {
|
||||
enableHooks: false,
|
||||
mcpEnabled: false,
|
||||
extensionsEnabled: false,
|
||||
recordResponses: options.recordResponses,
|
||||
fakeResponses: options.fakeResponses,
|
||||
};
|
||||
|
||||
this.config = new Config(configParams);
|
||||
@@ -67,18 +78,21 @@ export class GeminiCliAgent {
|
||||
const messageBus = this.config.getMessageBus();
|
||||
|
||||
for (const toolDef of this.tools) {
|
||||
const sdkTool = new SdkTool(toolDef, messageBus);
|
||||
const sdkTool = new SdkTool(toolDef, messageBus, this);
|
||||
registry.registerTool(sdkTool);
|
||||
}
|
||||
}
|
||||
|
||||
const client = this.config.getGeminiClient();
|
||||
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<GeminiClient['sendMessageStream']>[0] = [
|
||||
{ text: prompt },
|
||||
];
|
||||
const abortSignal = signal ?? new AbortController().signal;
|
||||
const sessionId = this.config.getSessionId();
|
||||
|
||||
while (true) {
|
||||
// sendMessageStream returns AsyncGenerator<ServerGeminiStreamEvent, Turn>
|
||||
@@ -107,12 +121,35 @@ export class GeminiCliAgent {
|
||||
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: this.config.getToolRegistry(),
|
||||
toolRegistry: scopedRegistry,
|
||||
signal: abortSignal,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Config as CoreConfig } from '@google/gemini-cli-core';
|
||||
import type { AgentFilesystem } from './types.js';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
export class SdkAgentFilesystem implements AgentFilesystem {
|
||||
constructor(private readonly config: CoreConfig) {}
|
||||
|
||||
async readFile(path: string): Promise<string | null> {
|
||||
const error = this.config.validatePathAccess(path, 'read');
|
||||
if (error) {
|
||||
// For now, if access is denied, we can either throw or return null.
|
||||
// Returning null makes sense for "file not found or readable".
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return await fs.readFile(path, 'utf-8');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async writeFile(path: string, content: string): Promise<void> {
|
||||
const error = this.config.validatePathAccess(path, 'write');
|
||||
if (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
await fs.writeFile(path, content, 'utf-8');
|
||||
}
|
||||
}
|
||||
@@ -6,3 +6,4 @@
|
||||
|
||||
export * from './agent.js';
|
||||
export * from './tool.js';
|
||||
export * from './types.js';
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Config as CoreConfig } from '@google/gemini-cli-core';
|
||||
import { ShellExecutionService, ShellTool } from '@google/gemini-cli-core';
|
||||
import type {
|
||||
AgentShell,
|
||||
AgentShellResult,
|
||||
AgentShellOptions,
|
||||
} from './types.js';
|
||||
|
||||
export class SdkAgentShell implements AgentShell {
|
||||
constructor(private readonly config: CoreConfig) {}
|
||||
|
||||
async exec(
|
||||
command: string,
|
||||
options?: AgentShellOptions,
|
||||
): Promise<AgentShellResult> {
|
||||
const cwd = options?.cwd || this.config.getWorkingDir();
|
||||
const abortController = new AbortController();
|
||||
|
||||
// Use ShellTool to check policy
|
||||
const shellTool = new ShellTool(this.config, this.config.getMessageBus());
|
||||
try {
|
||||
const invocation = shellTool.build({
|
||||
command,
|
||||
dir_path: cwd,
|
||||
});
|
||||
|
||||
const confirmation = await invocation.shouldConfirmExecute(
|
||||
abortController.signal,
|
||||
);
|
||||
if (confirmation) {
|
||||
throw new Error(
|
||||
'Command execution requires confirmation but no interactive session is available.',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
output: '',
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
exitCode: 1,
|
||||
error: error instanceof Error ? error : new Error(String(error)),
|
||||
};
|
||||
}
|
||||
|
||||
const handle = await ShellExecutionService.execute(
|
||||
command,
|
||||
cwd,
|
||||
() => {}, // No-op output event handler for now
|
||||
abortController.signal,
|
||||
false, // shouldUseNodePty: false for headless execution
|
||||
this.config.getShellExecutionConfig(),
|
||||
);
|
||||
|
||||
const result = await handle.result;
|
||||
|
||||
return {
|
||||
output: result.output,
|
||||
stdout: result.output, // ShellExecutionService combines stdout/stderr usually
|
||||
stderr: '', // ShellExecutionService currently combines, so stderr is empty or mixed
|
||||
exitCode: result.exitCode,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @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 { z } from 'zod';
|
||||
import { tool, ModelVisibleError } from './tool.js';
|
||||
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 Tool Integration', () => {
|
||||
it('handles tool execution success', async () => {
|
||||
const goldenFile = getGoldenPath('tool-success');
|
||||
|
||||
const agent = new GeminiCliAgent({
|
||||
instructions: 'You are a helpful assistant.',
|
||||
// If recording, use real model + record path.
|
||||
// If testing, use auto model + fake path.
|
||||
model: RECORD_MODE ? 'gemini-2.0-flash' : undefined,
|
||||
recordResponses: RECORD_MODE ? goldenFile : undefined,
|
||||
fakeResponses: RECORD_MODE ? undefined : goldenFile,
|
||||
tools: [
|
||||
tool(
|
||||
{
|
||||
name: 'add',
|
||||
description: 'Adds two numbers',
|
||||
inputSchema: z.object({ a: z.number(), b: z.number() }),
|
||||
},
|
||||
async ({ a, b }) => a + b,
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const events = [];
|
||||
const stream = agent.sendStream('What is 5 + 3?');
|
||||
|
||||
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(responseText).toContain('8');
|
||||
});
|
||||
|
||||
it('handles ModelVisibleError correctly', async () => {
|
||||
const goldenFile = getGoldenPath('tool-error-recovery');
|
||||
|
||||
const agent = new GeminiCliAgent({
|
||||
instructions: 'You are a helpful assistant.',
|
||||
model: RECORD_MODE ? 'gemini-2.0-flash' : undefined,
|
||||
recordResponses: RECORD_MODE ? goldenFile : undefined,
|
||||
fakeResponses: RECORD_MODE ? undefined : goldenFile,
|
||||
tools: [
|
||||
tool(
|
||||
{
|
||||
name: 'failVisible',
|
||||
description: 'Fails with a visible error if input is "fail"',
|
||||
inputSchema: z.object({ input: z.string() }),
|
||||
},
|
||||
async ({ input }) => {
|
||||
if (input === 'fail') {
|
||||
throw new ModelVisibleError('Tool failed visibly');
|
||||
}
|
||||
return 'Success';
|
||||
},
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const events = [];
|
||||
// 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(
|
||||
'Call the tool with "fail". If it fails, tell me the error message.',
|
||||
);
|
||||
|
||||
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('');
|
||||
|
||||
// The model should see the error "Tool failed visibly" and report it back.
|
||||
expect(responseText).toContain('Tool failed visibly');
|
||||
});
|
||||
|
||||
it('handles sendErrorsToModel: true correctly', async () => {
|
||||
const goldenFile = getGoldenPath('tool-catchall-error');
|
||||
|
||||
const agent = new GeminiCliAgent({
|
||||
instructions: 'You are a helpful assistant.',
|
||||
model: RECORD_MODE ? 'gemini-2.0-flash' : undefined,
|
||||
recordResponses: RECORD_MODE ? goldenFile : undefined,
|
||||
fakeResponses: RECORD_MODE ? undefined : goldenFile,
|
||||
tools: [
|
||||
tool(
|
||||
{
|
||||
name: 'checkSystemStatus',
|
||||
description: 'Checks the current system status',
|
||||
inputSchema: z.object({}),
|
||||
sendErrorsToModel: true,
|
||||
},
|
||||
async () => {
|
||||
throw new Error('Standard error caught');
|
||||
},
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const events = [];
|
||||
const stream = agent.sendStream(
|
||||
'Check the system status and report any errors.',
|
||||
);
|
||||
|
||||
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('');
|
||||
|
||||
// The model should report the caught standard error.
|
||||
expect(responseText.toLowerCase()).toContain('error');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { z } from 'zod';
|
||||
import { SdkTool, tool, ModelVisibleError } from './tool.js';
|
||||
import type { MessageBus } from '@google/gemini-cli-core';
|
||||
|
||||
// Mock MessageBus
|
||||
const mockMessageBus = {} as unknown as MessageBus;
|
||||
|
||||
describe('tool()', () => {
|
||||
it('creates a tool definition with defaults', () => {
|
||||
const definition = tool(
|
||||
{
|
||||
name: 'testTool',
|
||||
description: 'A test tool',
|
||||
inputSchema: z.object({ foo: z.string() }),
|
||||
},
|
||||
async () => 'result',
|
||||
);
|
||||
|
||||
expect(definition.name).toBe('testTool');
|
||||
expect(definition.description).toBe('A test tool');
|
||||
expect(definition.sendErrorsToModel).toBeUndefined();
|
||||
});
|
||||
|
||||
it('creates a tool definition with explicit configuration', () => {
|
||||
const definition = tool(
|
||||
{
|
||||
name: 'testTool',
|
||||
description: 'A test tool',
|
||||
inputSchema: z.object({ foo: z.string() }),
|
||||
sendErrorsToModel: true,
|
||||
},
|
||||
async () => 'result',
|
||||
);
|
||||
|
||||
expect(definition.sendErrorsToModel).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SdkTool Execution', () => {
|
||||
it('executes successfully', async () => {
|
||||
const definition = tool(
|
||||
{
|
||||
name: 'successTool',
|
||||
description: 'Always succeeds',
|
||||
inputSchema: z.object({ val: z.string() }),
|
||||
},
|
||||
async ({ val }) => `Success: ${val}`,
|
||||
);
|
||||
|
||||
const sdkTool = new SdkTool(definition, mockMessageBus);
|
||||
const invocation = sdkTool.createInvocationWithContext(
|
||||
{ val: 'test' },
|
||||
mockMessageBus,
|
||||
undefined,
|
||||
);
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
|
||||
expect(result.llmContent).toBe('Success: test');
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('throws standard Error by default', async () => {
|
||||
const definition = tool(
|
||||
{
|
||||
name: 'failTool',
|
||||
description: 'Always fails',
|
||||
inputSchema: z.object({}),
|
||||
},
|
||||
async () => {
|
||||
throw new Error('Standard error');
|
||||
},
|
||||
);
|
||||
|
||||
const sdkTool = new SdkTool(definition, mockMessageBus);
|
||||
const invocation = sdkTool.createInvocationWithContext(
|
||||
{},
|
||||
mockMessageBus,
|
||||
undefined,
|
||||
);
|
||||
|
||||
await expect(
|
||||
invocation.execute(new AbortController().signal),
|
||||
).rejects.toThrow('Standard error');
|
||||
});
|
||||
|
||||
it('catches ModelVisibleError and returns ToolResult error', async () => {
|
||||
const definition = tool(
|
||||
{
|
||||
name: 'visibleErrorTool',
|
||||
description: 'Fails with visible error',
|
||||
inputSchema: z.object({}),
|
||||
},
|
||||
async () => {
|
||||
throw new ModelVisibleError('Visible error');
|
||||
},
|
||||
);
|
||||
|
||||
const sdkTool = new SdkTool(definition, mockMessageBus);
|
||||
const invocation = sdkTool.createInvocationWithContext(
|
||||
{},
|
||||
mockMessageBus,
|
||||
undefined,
|
||||
);
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.error?.message).toBe('Visible error');
|
||||
expect(result.llmContent).toContain('Error: Visible error');
|
||||
});
|
||||
|
||||
it('catches standard Error when sendErrorsToModel is true', async () => {
|
||||
const definition = tool(
|
||||
{
|
||||
name: 'catchAllTool',
|
||||
description: 'Catches all errors',
|
||||
inputSchema: z.object({}),
|
||||
sendErrorsToModel: true,
|
||||
},
|
||||
async () => {
|
||||
throw new Error('Standard error');
|
||||
},
|
||||
);
|
||||
|
||||
const sdkTool = new SdkTool(definition, mockMessageBus);
|
||||
const invocation = sdkTool.createInvocationWithContext(
|
||||
{},
|
||||
mockMessageBus,
|
||||
undefined,
|
||||
);
|
||||
const result = await invocation.execute(new AbortController().signal);
|
||||
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.error?.message).toBe('Standard error');
|
||||
expect(result.llmContent).toContain('Error: Standard error');
|
||||
});
|
||||
});
|
||||
+59
-18
@@ -14,28 +14,42 @@ import {
|
||||
Kind,
|
||||
type MessageBus,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { SessionContext } from './types.js';
|
||||
|
||||
export { z };
|
||||
|
||||
export interface ToolDefinition<T extends z.ZodType> {
|
||||
export class ModelVisibleError extends Error {
|
||||
constructor(message: string | Error) {
|
||||
super(message instanceof Error ? message.message : message);
|
||||
this.name = 'ModelVisibleError';
|
||||
}
|
||||
}
|
||||
|
||||
export interface ToolDefinition<T extends z.ZodTypeAny> {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: T;
|
||||
sendErrorsToModel?: boolean;
|
||||
}
|
||||
|
||||
export interface Tool<T extends z.ZodType> extends ToolDefinition<T> {
|
||||
action: (params: z.infer<T>) => Promise<unknown>;
|
||||
export interface Tool<T extends z.ZodTypeAny> extends ToolDefinition<T> {
|
||||
action: (params: z.infer<T>, context?: SessionContext) => Promise<unknown>;
|
||||
}
|
||||
|
||||
class SdkToolInvocation<T extends z.ZodType> extends BaseToolInvocation<
|
||||
class SdkToolInvocation<T extends z.ZodTypeAny> extends BaseToolInvocation<
|
||||
z.infer<T>,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(
|
||||
params: z.infer<T>,
|
||||
messageBus: MessageBus,
|
||||
private readonly action: (params: z.infer<T>) => Promise<unknown>,
|
||||
private readonly action: (
|
||||
params: z.infer<T>,
|
||||
context?: SessionContext,
|
||||
) => Promise<unknown>,
|
||||
private readonly context: SessionContext | undefined,
|
||||
toolName: string,
|
||||
private readonly sendErrorsToModel: boolean = false,
|
||||
) {
|
||||
super(params, messageBus, toolName);
|
||||
}
|
||||
@@ -49,7 +63,7 @@ class SdkToolInvocation<T extends z.ZodType> extends BaseToolInvocation<
|
||||
_updateOutput?: (output: string) => void,
|
||||
): Promise<ToolResult> {
|
||||
try {
|
||||
const result = await this.action(this.params);
|
||||
const result = await this.action(this.params, this.context);
|
||||
const output =
|
||||
typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
||||
return {
|
||||
@@ -57,26 +71,31 @@ class SdkToolInvocation<T extends z.ZodType> extends BaseToolInvocation<
|
||||
returnDisplay: output,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
llmContent: `Error: ${errorMessage}`,
|
||||
returnDisplay: `Error: ${errorMessage}`,
|
||||
error: {
|
||||
message: errorMessage,
|
||||
},
|
||||
};
|
||||
if (this.sendErrorsToModel || error instanceof ModelVisibleError) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
llmContent: `Error: ${errorMessage}`,
|
||||
returnDisplay: `Error: ${errorMessage}`,
|
||||
error: {
|
||||
message: errorMessage,
|
||||
},
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SdkTool<T extends z.ZodType> extends BaseDeclarativeTool<
|
||||
export class SdkTool<T extends z.ZodTypeAny> extends BaseDeclarativeTool<
|
||||
z.infer<T>,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(
|
||||
private readonly definition: Tool<T>,
|
||||
messageBus: MessageBus,
|
||||
_agent?: unknown,
|
||||
private readonly context?: SessionContext,
|
||||
) {
|
||||
super(
|
||||
definition.name,
|
||||
@@ -88,6 +107,26 @@ export class SdkTool<T extends z.ZodType> extends BaseDeclarativeTool<
|
||||
);
|
||||
}
|
||||
|
||||
bindContext(context: SessionContext): SdkTool<T> {
|
||||
return new SdkTool(this.definition, this.messageBus, undefined, context);
|
||||
}
|
||||
|
||||
createInvocationWithContext(
|
||||
params: z.infer<T>,
|
||||
messageBus: MessageBus,
|
||||
context: SessionContext | undefined,
|
||||
toolName?: string,
|
||||
): ToolInvocation<z.infer<T>, ToolResult> {
|
||||
return new SdkToolInvocation(
|
||||
params,
|
||||
messageBus,
|
||||
this.definition.action,
|
||||
context || this.context,
|
||||
toolName || this.name,
|
||||
this.definition.sendErrorsToModel,
|
||||
);
|
||||
}
|
||||
|
||||
protected createInvocation(
|
||||
params: z.infer<T>,
|
||||
messageBus: MessageBus,
|
||||
@@ -97,14 +136,16 @@ export class SdkTool<T extends z.ZodType> extends BaseDeclarativeTool<
|
||||
params,
|
||||
messageBus,
|
||||
this.definition.action,
|
||||
this.context,
|
||||
toolName || this.name,
|
||||
this.definition.sendErrorsToModel,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function tool<T extends z.ZodType>(
|
||||
export function tool<T extends z.ZodTypeAny>(
|
||||
definition: ToolDefinition<T>,
|
||||
action: (params: z.infer<T>) => Promise<unknown>,
|
||||
action: (params: z.infer<T>, context?: SessionContext) => Promise<unknown>,
|
||||
): Tool<T> {
|
||||
return {
|
||||
...definition,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Content } from '@google/gemini-cli-core';
|
||||
import type { GeminiCliAgent } from './agent.js';
|
||||
|
||||
export interface AgentFilesystem {
|
||||
readFile(path: string): Promise<string | null>;
|
||||
writeFile(path: string, content: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface AgentShellOptions {
|
||||
env?: Record<string, string>;
|
||||
timeoutSeconds?: number;
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
export interface AgentShellResult {
|
||||
exitCode: number | null;
|
||||
output: string;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
export interface AgentShell {
|
||||
exec(cmd: string, options?: AgentShellOptions): Promise<AgentShellResult>;
|
||||
}
|
||||
|
||||
export interface SessionContext {
|
||||
sessionId: string;
|
||||
transcript: Content[];
|
||||
cwd: string;
|
||||
timestamp: string;
|
||||
fs: AgentFilesystem;
|
||||
shell: AgentShell;
|
||||
agent: GeminiCliAgent;
|
||||
}
|
||||
Reference in New Issue
Block a user