Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6523268
feat: implement model-dependent tool definitions architecture and ref…
aishaneeshah Feb 4, 2026
b2d09c3
refactor: extract tool definitions and prepare architecture for model…
aishaneeshah Feb 4, 2026
e1c0e6d
refactor: remove hardcoded schema details from tool files and use ext…
aishaneeshah Feb 4, 2026
a4f780f
test: add tests for model-dependent tool definitions
aishaneeshah Feb 6, 2026
c5cb031
refactor: integrate resolver into read_file and shell tools
aishaneeshah Feb 6, 2026
bb59ca8
refactor: centralize all tool definitions in coreTools.ts
aishaneeshah Feb 6, 2026
2a44cad
style: reorganize coreTools.ts for better readability
aishaneeshah Feb 6, 2026
6227ba7
feat: implement late-binding tool updates in GeminiChat
aishaneeshah Feb 6, 2026
ee6a0fa
refactor: use parametersJsonSchema consistently across tool definitions
aishaneeshah Feb 8, 2026
62c46dd
test: fix GeminiChat mocks in client.test.ts
aishaneeshah Feb 8, 2026
58ecc29
style: move AfterModel hooks comment in GeminiChat
aishaneeshah Feb 8, 2026
991551b
fix(core): reset lastUsedModelId in startChat to prevent stale tool c…
aishaneeshah Feb 8, 2026
3f051bc
refactor(core): centralize core tool definitions and support model-sp…
aishaneeshah Feb 9, 2026
b7b2448
Merge main and resolve core tool conflicts
aishaneeshah Feb 9, 2026
b96b869
Merge main into refactor branch
aishaneeshah Feb 9, 2026
99cace9
refactor(core): centralized core tool definitions and infrastructure
aishaneeshah Feb 9, 2026
0b72c11
test(core): restore shell test snapshot to main version
aishaneeshah Feb 10, 2026
658dfdc
Merge main into refactor branch
aishaneeshah Feb 10, 2026
c077eca
fix(core): use lowercase string literals for schema types to ensure A…
aishaneeshah Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 29 additions & 30 deletions packages/core/src/tools/definitions/coreTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Type } from '@google/genai';
import type { ToolDefinition } from './types.js';
import * as os from 'node:os';

Expand All @@ -25,21 +24,21 @@ export const READ_FILE_DEFINITION: ToolDefinition = {
name: READ_FILE_TOOL_NAME,
description: `Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'offset' and 'limit' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges.`,
parametersJsonSchema: {
type: Type.OBJECT,
type: 'object',
properties: {
file_path: {
description: 'The path to the file to read.',
type: Type.STRING,
type: 'string',
},
offset: {
description:
"Optional: For text files, the 0-based line number to start reading from. Requires 'limit' to be set. Use for paginating through large files.",
type: Type.NUMBER,
type: 'number',
},
limit: {
description:
"Optional: For text files, maximum number of lines to read. Use with 'offset' to paginate through large files. If omitted, reads the entire file (if feasible, up to a default limit).",
type: Type.NUMBER,
type: 'number',
},
},
required: ['file_path'],
Expand All @@ -58,15 +57,15 @@ export const WRITE_FILE_DEFINITION: ToolDefinition = {

The user has the ability to modify \`content\`. If modified, this will be stated in the response.`,
parametersJsonSchema: {
type: Type.OBJECT,
type: 'object',
properties: {
file_path: {
description: 'The path to the file to write to.',
type: Type.STRING,
type: 'string',
},
content: {
description: 'The content to write to the file.',
type: Type.STRING,
type: 'string',
},
},
required: ['file_path', 'content'],
Expand All @@ -84,20 +83,20 @@ export const GREP_DEFINITION: ToolDefinition = {
description:
'Searches for a regular expression pattern within file contents. Max 100 matches.',
parametersJsonSchema: {
type: Type.OBJECT,
type: 'object',
properties: {
pattern: {
description: `The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*').`,
type: Type.STRING,
type: 'string',
},
dir_path: {
description:
'Optional: The absolute path to the directory to search within. If omitted, searches the current working directory.',
type: Type.STRING,
type: 'string',
},
include: {
description: `Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).`,
type: Type.STRING,
type: 'string',
},
},
required: ['pattern'],
Expand All @@ -115,32 +114,32 @@ export const GLOB_DEFINITION: ToolDefinition = {
description:
'Efficiently finds files matching specific glob patterns (e.g., `src/**/*.ts`, `**/*.md`), returning absolute paths sorted by modification time (newest first). Ideal for quickly locating files based on their name or path structure, especially in large codebases.',
parametersJsonSchema: {
type: Type.OBJECT,
type: 'object',
properties: {
pattern: {
description:
"The glob pattern to match against (e.g., '**/*.py', 'docs/*.md').",
type: Type.STRING,
type: 'string',
},
dir_path: {
description:
'Optional: The absolute path to the directory to search within. If omitted, searches the root directory.',
type: Type.STRING,
type: 'string',
},
case_sensitive: {
description:
'Optional: Whether the search should be case-sensitive. Defaults to false.',
type: Type.BOOLEAN,
type: 'boolean',
},
respect_git_ignore: {
description:
'Optional: Whether to respect .gitignore patterns when finding files. Only available in git repositories. Defaults to true.',
type: Type.BOOLEAN,
type: 'boolean',
},
respect_gemini_ignore: {
description:
'Optional: Whether to respect .geminiignore patterns when finding files. Defaults to true.',
type: Type.BOOLEAN,
type: 'boolean',
},
},
required: ['pattern'],
Expand All @@ -158,33 +157,33 @@ export const LS_DEFINITION: ToolDefinition = {
description:
'Lists the names of files and subdirectories directly within a specified directory path. Can optionally ignore entries matching provided glob patterns.',
parametersJsonSchema: {
type: Type.OBJECT,
type: 'object',
properties: {
dir_path: {
description: 'The path to the directory to list',
type: Type.STRING,
type: 'string',
},
ignore: {
description: 'List of glob patterns to ignore',
items: {
type: Type.STRING,
type: 'string',
},
type: Type.ARRAY,
type: 'array',
},
file_filtering_options: {
description:
'Optional: Whether to respect ignore patterns from .gitignore or .geminiignore',
type: Type.OBJECT,
type: 'object',
properties: {
respect_git_ignore: {
description:
'Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.',
type: Type.BOOLEAN,
type: 'boolean',
},
respect_gemini_ignore: {
description:
'Optional: Whether to respect .geminiignore patterns when listing files. Defaults to true.',
type: Type.BOOLEAN,
type: 'boolean',
},
},
},
Expand Down Expand Up @@ -262,24 +261,24 @@ export function getShellDefinition(
enableEfficiency,
),
parametersJsonSchema: {
type: Type.OBJECT,
type: 'object',
properties: {
command: {
type: Type.STRING,
type: 'string',
description: getCommandDescription(),
},
description: {
type: Type.STRING,
type: 'string',
description:
'Brief description of the command for the user. Be specific and concise. Ideally a single sentence. Can be up to 3 sentences for clarity. No line breaks.',
},
dir_path: {
type: Type.STRING,
type: 'string',
description:
'(OPTIONAL) The path of the directory to run the command in. If not provided, the project root directory is used. Must be a directory within the workspace and must already exist.',
},
is_background: {
type: Type.BOOLEAN,
type: 'boolean',
description:
'Set to true if this command should be run in the background (e.g. for long-running servers or watchers). The command will be started, allowed to run for a brief moment to check for immediate errors, and then moved to the background.',
},
Expand Down
44 changes: 38 additions & 6 deletions packages/core/src/tools/definitions/resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,45 @@ describe('resolveToolDeclaration', () => {
expect(result).toEqual(mockDefinition.base);
});

it('should return the base definition when a modelId is provided (current implementation)', () => {
const result = resolveToolDeclaration(mockDefinition, 'gemini-1.5-pro');
expect(result).toEqual(mockDefinition.base);
it('should return overridden description when modelId matches override criteria', () => {
const definitionWithOverride: ToolDefinition = {
...mockDefinition,
overrides: (modelId: string) => {
if (modelId === 'special-model') {
return { description: 'Overridden description' };
}
return undefined;
},
};

const result = resolveToolDeclaration(
definitionWithOverride,
'special-model',
);
expect(result.description).toBe('Overridden description');
expect(result.name).toBe(mockDefinition.base.name);
});

it('should return the same object reference as base (current implementation)', () => {
const result = resolveToolDeclaration(mockDefinition);
expect(result).toBe(mockDefinition.base);
it('should return base definition when modelId does not match override criteria', () => {
const definitionWithOverride: ToolDefinition = {
...mockDefinition,
overrides: (modelId: string) => {
if (modelId === 'special-model') {
return { description: 'Overridden description' };
}
return undefined;
},
};

const result = resolveToolDeclaration(
definitionWithOverride,
'regular-model',
);
expect(result.description).toBe(mockDefinition.base.description);
});

it('should return the base definition when a modelId is provided but no overrides exist', () => {
const result = resolveToolDeclaration(mockDefinition, 'gemini-1.5-pro');
expect(result).toEqual(mockDefinition.base);
});
});
20 changes: 16 additions & 4 deletions packages/core/src/tools/definitions/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,25 @@ import type { ToolDefinition } from './types.js';
/**
* Resolves the declaration for a tool.
*
* @param definition The tool definition containing the base declaration.
* @param _modelId Optional model identifier (ignored in this plain refactor).
* @param definition The tool definition containing the base declaration and optional overrides.
* @param modelId Optional model identifier to apply specific overrides.
* @returns The FunctionDeclaration to be sent to the API.
*/
export function resolveToolDeclaration(
definition: ToolDefinition,
_modelId?: string,
modelId?: string,
): FunctionDeclaration {
return definition.base;
if (!modelId || !definition.overrides) {
return definition.base;
}

const override = definition.overrides(modelId);
if (!override) {
return definition.base;
}

return {
...definition.base,
...override,
};
}
5 changes: 5 additions & 0 deletions packages/core/src/tools/definitions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ import { type FunctionDeclaration } from '@google/genai';
export interface ToolDefinition {
/** The base declaration for the tool. */
base: FunctionDeclaration;

/**
* Optional overrides for specific model families or versions.
*/
overrides?: (modelId: string) => Partial<FunctionDeclaration> | undefined;
}
40 changes: 8 additions & 32 deletions packages/core/src/tools/glob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { ToolErrorType } from './tool-error.js';
import { GLOB_TOOL_NAME } from './tool-names.js';
import { getErrorMessage } from '../utils/errors.js';
import { debugLogger } from '../utils/debugLogger.js';
import { GLOB_DEFINITION } from './definitions/coreTools.js';
import { resolveToolDeclaration } from './definitions/resolver.js';

// Subset of 'Path' interface provided by 'glob' that we can implement for testing
export interface GlobPath {
Expand Down Expand Up @@ -270,39 +272,9 @@ export class GlobTool extends BaseDeclarativeTool<GlobToolParams, ToolResult> {
super(
GlobTool.Name,
'FindFiles',
'Efficiently finds files matching specific glob patterns (e.g., `src/**/*.ts`, `**/*.md`), returning absolute paths sorted by modification time (newest first). Ideal for quickly locating files based on their name or path structure, especially in large codebases.',
GLOB_DEFINITION.base.description!,
Kind.Search,
{
properties: {
pattern: {
description:
"The glob pattern to match against (e.g., '**/*.py', 'docs/*.md').",
type: 'string',
},
dir_path: {
description:
'Optional: The absolute path to the directory to search within. If omitted, searches the root directory.',
type: 'string',
},
case_sensitive: {
description:
'Optional: Whether the search should be case-sensitive. Defaults to false.',
type: 'boolean',
},
respect_git_ignore: {
description:
'Optional: Whether to respect .gitignore patterns when finding files. Only available in git repositories. Defaults to true.',
type: 'boolean',
},
respect_gemini_ignore: {
description:
'Optional: Whether to respect .geminiignore patterns when finding files. Defaults to true.',
type: 'boolean',
},
},
required: ['pattern'],
type: 'object',
},
GLOB_DEFINITION.base.parametersJsonSchema,
messageBus,
true,
false,
Expand Down Expand Up @@ -365,4 +337,8 @@ export class GlobTool extends BaseDeclarativeTool<GlobToolParams, ToolResult> {
_toolDisplayName,
);
}

override getSchema(modelId?: string) {
return resolveToolDeclaration(GLOB_DEFINITION, modelId);
}
}
28 changes: 8 additions & 20 deletions packages/core/src/tools/grep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import type { FileExclusions } from '../utils/ignorePatterns.js';
import { ToolErrorType } from './tool-error.js';
import { GREP_TOOL_NAME } from './tool-names.js';
import { debugLogger } from '../utils/debugLogger.js';
import { GREP_DEFINITION } from './definitions/coreTools.js';
import { resolveToolDeclaration } from './definitions/resolver.js';

// --- Interfaces ---

Expand Down Expand Up @@ -579,27 +581,9 @@ export class GrepTool extends BaseDeclarativeTool<GrepToolParams, ToolResult> {
super(
GrepTool.Name,
'SearchText',
'Searches for a regular expression pattern within file contents. Max 100 matches.',
GREP_DEFINITION.base.description!,
Kind.Search,
{
properties: {
pattern: {
description: `The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*').`,
type: 'string',
},
dir_path: {
description:
'Optional: The absolute path to the directory to search within. If omitted, searches the current working directory.',
type: 'string',
},
include: {
description: `Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).`,
type: 'string',
},
},
required: ['pattern'],
type: 'object',
},
GREP_DEFINITION.base.parametersJsonSchema,
messageBus,
true,
false,
Expand Down Expand Up @@ -665,4 +649,8 @@ export class GrepTool extends BaseDeclarativeTool<GrepToolParams, ToolResult> {
_toolDisplayName,
);
}

override getSchema(modelId?: string) {
return resolveToolDeclaration(GREP_DEFINITION, modelId);
}
}
Loading
Loading