Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c283ff0
feat: implement model-dependent tool definitions architecture and ref…
aishaneeshah Feb 4, 2026
3dd3f58
refactor: extract tool definitions and prepare architecture for model…
aishaneeshah Feb 4, 2026
950b13b
refactor: remove hardcoded schema details from tool files and use ext…
aishaneeshah Feb 4, 2026
3a6b8c1
Merge remote-tracking branch 'origin/main' into feature/model-depende…
aishaneeshah Feb 6, 2026
9c27dfc
test: add tests for model-dependent tool definitions
aishaneeshah Feb 6, 2026
e66862d
refactor: integrate resolver into read_file and shell tools
aishaneeshah Feb 6, 2026
6d59880
refactor: centralize all tool definitions in coreTools.ts
aishaneeshah Feb 6, 2026
0b8df5f
style: reorganize coreTools.ts for better readability
aishaneeshah Feb 6, 2026
90becb3
feat: implement late-binding tool updates in GeminiChat
aishaneeshah Feb 6, 2026
980dbd5
Merge remote-tracking branch 'origin/main' into feature/model-depende…
aishaneeshah Feb 8, 2026
0b01c04
refactor: use parametersJsonSchema consistently across tool definitions
aishaneeshah Feb 8, 2026
1e3e0e0
test: fix GeminiChat mocks in client.test.ts
aishaneeshah Feb 8, 2026
86a0838
Merge remote-tracking branch 'origin/main' into feature/model-depende…
aishaneeshah Feb 8, 2026
6159268
style: move AfterModel hooks comment in GeminiChat
aishaneeshah Feb 8, 2026
4e98b3a
fix(core): reset lastUsedModelId in startChat to prevent stale tool c…
aishaneeshah Feb 8, 2026
13edd58
Merge main into refactor branch and resolve conflicts in shell.ts
aishaneeshah Feb 9, 2026
b1486eb
test(core): update tool snapshots for centralized definitions
aishaneeshah Feb 9, 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
26 changes: 26 additions & 0 deletions packages/core/src/core/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ describe('Gemini Client (client.ts)', () => {
it('should call chat.addHistory with the provided content', async () => {
const mockChat = {
addHistory: vi.fn(),
setTools: vi.fn(),
} as unknown as GeminiChat;
client['chat'] = mockChat;

Expand Down Expand Up @@ -389,6 +390,7 @@ describe('Gemini Client (client.ts)', () => {
getHistory: mockGetHistory,
addHistory: vi.fn(),
setHistory: vi.fn(),
setTools: vi.fn(),
getLastPromptTokenCount: vi.fn(),
} as unknown as GeminiChat;
});
Expand Down Expand Up @@ -805,6 +807,7 @@ describe('Gemini Client (client.ts)', () => {

const mockChat = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
} as unknown as GeminiChat;
Expand Down Expand Up @@ -868,6 +871,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -926,6 +930,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -1003,6 +1008,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -1119,6 +1125,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -1167,6 +1174,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -1232,6 +1240,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -1289,6 +1298,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -1349,6 +1359,7 @@ ${JSON.stringify(
const lastPromptTokenCount = 900;
const mockChat: Partial<GeminiChat> = {
getLastPromptTokenCount: vi.fn().mockReturnValue(lastPromptTokenCount),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
};
client['chat'] = mockChat as GeminiChat;
Expand Down Expand Up @@ -1409,6 +1420,7 @@ ${JSON.stringify(
const lastPromptTokenCount = 900;
const mockChat: Partial<GeminiChat> = {
getLastPromptTokenCount: vi.fn().mockReturnValue(lastPromptTokenCount),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
};
client['chat'] = mockChat as GeminiChat;
Expand Down Expand Up @@ -1467,6 +1479,7 @@ ${JSON.stringify(
.fn()
.mockReturnValue([{ role: 'user', parts: [{ text: 'old' }] }]),
addHistory: vi.fn(),
setTools: vi.fn(),
getChatRecordingService: vi.fn().mockReturnValue({
getConversation: vi.fn(),
getConversationFilePath: vi.fn(),
Expand All @@ -1479,6 +1492,7 @@ ${JSON.stringify(
.fn()
.mockReturnValue([{ role: 'user', parts: [{ text: 'old' }] }]),
addHistory: vi.fn(),
setTools: vi.fn(),
getChatRecordingService: vi.fn().mockReturnValue({
getConversation: vi.fn(),
getConversationFilePath: vi.fn(),
Expand Down Expand Up @@ -1616,6 +1630,7 @@ ${JSON.stringify(
const lastPromptTokenCount = 10000;
const mockChat: Partial<GeminiChat> = {
getLastPromptTokenCount: vi.fn().mockReturnValue(lastPromptTokenCount),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
};
client['chat'] = mockChat as GeminiChat;
Expand Down Expand Up @@ -1689,6 +1704,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -1892,6 +1908,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -1947,6 +1964,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -1984,6 +2002,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -2028,6 +2047,7 @@ ${JSON.stringify(
const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setHistory: vi.fn(),
setTools: vi.fn(),
// Assume history is not empty for delta checks
getHistory: vi
.fn()
Expand Down Expand Up @@ -2443,6 +2463,7 @@ ${JSON.stringify(
addHistory: vi.fn(),
getHistory: vi.fn().mockReturnValue([]), // Default empty history
setHistory: vi.fn(),
setTools: vi.fn(),
getLastPromptTokenCount: vi.fn(),
};
client['chat'] = mockChat as GeminiChat;
Expand Down Expand Up @@ -2783,6 +2804,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -2820,6 +2842,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -2857,6 +2880,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -3069,6 +3093,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down Expand Up @@ -3103,6 +3128,7 @@ ${JSON.stringify(

const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
setTools: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
getLastPromptTokenCount: vi.fn(),
};
Expand Down
27 changes: 25 additions & 2 deletions packages/core/src/core/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,20 @@ export class GeminiClient {
this.forceFullIdeContext = true;
}

async setTools(): Promise<void> {
private lastUsedModelId?: string;

async setTools(modelId?: string): Promise<void> {
if (!this.chat) {
return;
}

if (modelId && modelId === this.lastUsedModelId) {
return;
}
this.lastUsedModelId = modelId;

const toolRegistry = this.config.getToolRegistry();
const toolDeclarations = toolRegistry.getFunctionDeclarations();
const toolDeclarations = toolRegistry.getFunctionDeclarations(modelId);
const tools: Tool[] = [{ functionDeclarations: toolDeclarations }];
this.getChat().setTools(tools);
}
Expand Down Expand Up @@ -321,6 +332,7 @@ export class GeminiClient {
): Promise<GeminiChat> {
this.forceFullIdeContext = true;
this.hasFailedCompressionAttempt = false;
this.lastUsedModelId = undefined;

const toolRegistry = this.config.getToolRegistry();
const toolDeclarations = toolRegistry.getFunctionDeclarations();
Expand All @@ -339,6 +351,13 @@ export class GeminiClient {
tools,
history,
resumedSessionData,
async (modelId: string) => {
this.lastUsedModelId = modelId;
const toolRegistry = this.config.getToolRegistry();
const toolDeclarations =
toolRegistry.getFunctionDeclarations(modelId);
return [{ functionDeclarations: toolDeclarations }];
},
);
} catch (error) {
await reportError(
Expand Down Expand Up @@ -653,6 +672,10 @@ export class GeminiClient {
yield { type: GeminiEventType.ModelInfo, value: modelToUse };
}
this.currentSequenceModel = modelToUse;

// Update tools with the final modelId to ensure model-dependent descriptions are used.
await this.setTools(modelToUse);

const resultStream = turn.run(
modelConfigKey,
request,
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/core/geminiChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ export class GeminiChat {
private tools: Tool[] = [],
private history: Content[] = [],
resumedSessionData?: ResumedSessionData,
private readonly onModelChanged?: (modelId: string) => Promise<Tool[]>,
) {
validateHistory(history);
this.chatRecordingService = new ChatRecordingService(config);
Expand Down Expand Up @@ -580,6 +581,10 @@ export class GeminiChat {
}
}

if (this.onModelChanged) {
this.tools = await this.onModelChanged(modelToUse);
}

// Track final request parameters for AfterModel hooks
lastModelToUse = modelToUse;
lastConfig = config;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`ReadFileTool > getSchema > should return the base schema when no modelId is provided 1`] = `"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."`;

exports[`ReadFileTool > getSchema > should return the schema from the resolver when modelId is provided 1`] = `"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."`;
34 changes: 34 additions & 0 deletions packages/core/src/tools/__snapshots__/shell.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,37 @@ exports[`ShellTool > getDescription > should return the windows description when
Background PIDs: Only included if background processes were started.
Process Group PGID: Only included if available."
`;

exports[`ShellTool > getSchema > should return the base schema when no modelId is provided 1`] = `
"This tool executes a given shell command as \`bash -c <command>\`. Command can start background processes using \`&\`. Command is executed as a subprocess that leads its own process group. Command process group can be terminated as \`kill -- -PGID\` or signaled as \`kill -s SIGNAL -- -PGID\`.

Efficiency Guidelines:
- Quiet Flags: Always prefer silent or quiet flags (e.g., \`npm install --silent\`, \`git --no-pager\`) to reduce output volume while still capturing necessary information.
- Pagination: Always disable terminal pagination to ensure commands terminate (e.g., use \`git --no-pager\`, \`systemctl --no-pager\`, or set \`PAGER=cat\`).

The following information is returned:

Output: Combined stdout/stderr. Can be \`(empty)\` or partial on error and for any unwaited background processes.
Exit Code: Only included if non-zero (command failed).
Error: Only included if a process-level error occurred (e.g., spawn failure).
Signal: Only included if process was terminated by a signal.
Background PIDs: Only included if background processes were started.
Process Group PGID: Only included if available."
`;

exports[`ShellTool > getSchema > should return the schema from the resolver when modelId is provided 1`] = `
"This tool executes a given shell command as \`bash -c <command>\`. Command can start background processes using \`&\`. Command is executed as a subprocess that leads its own process group. Command process group can be terminated as \`kill -- -PGID\` or signaled as \`kill -s SIGNAL -- -PGID\`.

Efficiency Guidelines:
- Quiet Flags: Always prefer silent or quiet flags (e.g., \`npm install --silent\`, \`git --no-pager\`) to reduce output volume while still capturing necessary information.
- Pagination: Always disable terminal pagination to ensure commands terminate (e.g., use \`git --no-pager\`, \`systemctl --no-pager\`, or set \`PAGER=cat\`).

The following information is returned:

Output: Combined stdout/stderr. Can be \`(empty)\` or partial on error and for any unwaited background processes.
Exit Code: Only included if non-zero (command failed).
Error: Only included if a process-level error occurred (e.g., spawn failure).
Signal: Only included if process was terminated by a signal.
Background PIDs: Only included if background processes were started.
Process Group PGID: Only included if available."
`;
Loading
Loading