mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-08 16:11:58 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1cbd28497a | |||
| b4df1183ba | |||
| ce84b3cb5f |
@@ -103,6 +103,7 @@ they appear in the UI.
|
||||
| -------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
|
||||
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
|
||||
| Enable Tool Discovery | `tools.enableToolDiscovery` | Enables the tool discovery feature using `tools.discoveryCommand`. | `false` |
|
||||
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
|
||||
| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Maximum characters to show when truncating large tool outputs. Set to 0 or negative to disable truncation. | `40000` |
|
||||
| Disable LLM Correction | `tools.disableLLMCorrection` | Disable LLM-based error correction for edit tools. When enabled, tools will fail immediately if exact string matches are not found, instead of attempting to self-correct. | `true` |
|
||||
|
||||
@@ -753,6 +753,12 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.enableToolDiscovery`** (boolean):
|
||||
- **Description:** Enables the tool discovery feature using
|
||||
`tools.discoveryCommand`.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.callCommand`** (string):
|
||||
- **Description:** Defines a custom shell command for invoking discovered
|
||||
tools. The command must take the tool name as the first argument, read JSON
|
||||
|
||||
+1
-1
@@ -93,4 +93,4 @@ backend, these Terms of Service and Privacy Notice documents apply:
|
||||
|
||||
You may opt-out from sending Gemini CLI Usage Statistics to Google by following
|
||||
the instructions available here:
|
||||
[Usage Statistics Configuration](https://github.com/google-gemini/gemini-cli/blob/main/docs/reference/configuration.md#usage-statistics).
|
||||
[Usage Statistics Configuration](https://github.com/google-gemini/gemini-cli/blob/main/docs/get-started/configuration.md#usage-statistics).
|
||||
|
||||
@@ -75,10 +75,6 @@ const baseConfig = {
|
||||
write: true,
|
||||
};
|
||||
|
||||
const commonAliases = {
|
||||
punycode: 'punycode/',
|
||||
};
|
||||
|
||||
const cliConfig = {
|
||||
...baseConfig,
|
||||
banner: {
|
||||
@@ -92,7 +88,6 @@ const cliConfig = {
|
||||
plugins: createWasmPlugins(),
|
||||
alias: {
|
||||
'is-in-ci': path.resolve(__dirname, 'packages/cli/src/patches/is-in-ci.ts'),
|
||||
...commonAliases,
|
||||
},
|
||||
metafile: true,
|
||||
};
|
||||
@@ -108,7 +103,6 @@ const a2aServerConfig = {
|
||||
'process.env.CLI_VERSION': JSON.stringify(pkg.version),
|
||||
},
|
||||
plugins: createWasmPlugins(),
|
||||
alias: commonAliases,
|
||||
};
|
||||
|
||||
Promise.allSettled([
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, expect } from 'vitest';
|
||||
import { evalTest } from './test-helper.js';
|
||||
import { READ_FILE_TOOL_NAME, EDIT_TOOL_NAME } from '@google/gemini-cli-core';
|
||||
|
||||
describe('Frugal reads eval', () => {
|
||||
/**
|
||||
* Ensures that the agent is frugal in its use of context by relying
|
||||
* primarily on ranged reads when the line number is known, and combining
|
||||
* nearby ranges into a single contiguous read to save tool calls.
|
||||
*/
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'should use ranged read when nearby lines are targeted',
|
||||
files: {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'test-project',
|
||||
version: '1.0.0',
|
||||
type: 'module',
|
||||
}),
|
||||
'eslint.config.mjs': `export default [
|
||||
{
|
||||
files: ["**/*.ts"],
|
||||
rules: {
|
||||
"no-var": "error"
|
||||
}
|
||||
}
|
||||
];`,
|
||||
'linter_mess.ts': (() => {
|
||||
const lines = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
if (i === 500 || i === 510 || i === 520) {
|
||||
lines.push(`var oldVar${i} = "needs fix";`);
|
||||
} else {
|
||||
lines.push(`const goodVar${i} = "clean";`);
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
})(),
|
||||
},
|
||||
prompt:
|
||||
'Fix all linter errors in linter_mess.ts manually by editing the file. Run eslint directly (using "npx --yes eslint") to find them. Do not run the file.',
|
||||
assert: async (rig) => {
|
||||
const logs = rig.readToolLogs();
|
||||
|
||||
// Check if the agent read the whole file
|
||||
const readCalls = logs.filter(
|
||||
(log) => log.toolRequest?.name === READ_FILE_TOOL_NAME,
|
||||
);
|
||||
|
||||
const targetFileReads = readCalls.filter((call) => {
|
||||
const args = JSON.parse(call.toolRequest.args);
|
||||
return args.file_path.includes('linter_mess.ts');
|
||||
});
|
||||
|
||||
expect(
|
||||
targetFileReads.length,
|
||||
'Agent should have used read_file to check context',
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
// We expect 1-3 ranges in a single turn.
|
||||
expect(
|
||||
targetFileReads.length,
|
||||
'Agent should have used 1-3 ranged reads for near errors',
|
||||
).toBeLessThanOrEqual(3);
|
||||
|
||||
const firstPromptId = targetFileReads[0].toolRequest.prompt_id;
|
||||
expect(firstPromptId, 'Prompt ID should be defined').toBeDefined();
|
||||
expect(
|
||||
targetFileReads.every(
|
||||
(call) => call.toolRequest.prompt_id === firstPromptId,
|
||||
),
|
||||
'All reads should have happened in the same turn',
|
||||
).toBe(true);
|
||||
|
||||
let totalLinesRead = 0;
|
||||
const readRanges: { offset: number; limit: number }[] = [];
|
||||
|
||||
for (const call of targetFileReads) {
|
||||
const args = JSON.parse(call.toolRequest.args);
|
||||
|
||||
expect(
|
||||
args.limit,
|
||||
'Agent read the entire file (missing limit) instead of using ranged read',
|
||||
).toBeDefined();
|
||||
|
||||
const limit = args.limit;
|
||||
const offset = args.offset ?? 0;
|
||||
totalLinesRead += limit;
|
||||
readRanges.push({ offset, limit });
|
||||
|
||||
expect(args.limit, 'Agent read too many lines at once').toBeLessThan(
|
||||
1001,
|
||||
);
|
||||
}
|
||||
|
||||
// Ranged read shoud be frugal and just enough to satisfy the task at hand.
|
||||
expect(
|
||||
totalLinesRead,
|
||||
'Agent read more of the file than expected',
|
||||
).toBeLessThan(1000);
|
||||
|
||||
// Check that we read around the error lines
|
||||
const errorLines = [500, 510, 520];
|
||||
for (const line of errorLines) {
|
||||
const covered = readRanges.some(
|
||||
(range) => line >= range.offset && line < range.offset + range.limit,
|
||||
);
|
||||
expect(covered, `Agent should have read around line ${line}`).toBe(
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
const editCalls = logs.filter(
|
||||
(log) => log.toolRequest?.name === EDIT_TOOL_NAME,
|
||||
);
|
||||
const targetEditCalls = editCalls.filter((call) => {
|
||||
const args = JSON.parse(call.toolRequest.args);
|
||||
return args.file_path.includes('linter_mess.ts');
|
||||
});
|
||||
expect(
|
||||
targetEditCalls.length,
|
||||
'Agent should have made replacement calls on the target file',
|
||||
).toBeGreaterThanOrEqual(3);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensures the agent uses multiple ranged reads when the targets are far
|
||||
* apart to avoid the need to read the whole file.
|
||||
*/
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'should use ranged read when targets are far apart',
|
||||
files: {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'test-project',
|
||||
version: '1.0.0',
|
||||
type: 'module',
|
||||
}),
|
||||
'eslint.config.mjs': `export default [
|
||||
{
|
||||
files: ["**/*.ts"],
|
||||
rules: {
|
||||
"no-var": "error"
|
||||
}
|
||||
}
|
||||
];`,
|
||||
'far_mess.ts': (() => {
|
||||
const lines = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
if (i === 100 || i === 900) {
|
||||
lines.push(`var oldVar${i} = "needs fix";`);
|
||||
} else {
|
||||
lines.push(`const goodVar${i} = "clean";`);
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
})(),
|
||||
},
|
||||
prompt:
|
||||
'Fix all linter errors in far_mess.ts manually by editing the file. Run eslint directly (using "npx --yes eslint") to find them. Do not run the file.',
|
||||
assert: async (rig) => {
|
||||
const logs = rig.readToolLogs();
|
||||
|
||||
const readCalls = logs.filter(
|
||||
(log) => log.toolRequest?.name === READ_FILE_TOOL_NAME,
|
||||
);
|
||||
|
||||
const targetFileReads = readCalls.filter((call) => {
|
||||
const args = JSON.parse(call.toolRequest.args);
|
||||
return args.file_path.includes('far_mess.ts');
|
||||
});
|
||||
|
||||
// The agent should use ranged reads to be frugal with context tokens,
|
||||
// even if it requires multiple calls for far-apart errors.
|
||||
expect(
|
||||
targetFileReads.length,
|
||||
'Agent should have used read_file to check context',
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
// We allow multiple calls since the errors are far apart.
|
||||
expect(
|
||||
targetFileReads.length,
|
||||
'Agent should have used separate reads for far apart errors',
|
||||
).toBeLessThanOrEqual(4);
|
||||
|
||||
for (const call of targetFileReads) {
|
||||
const args = JSON.parse(call.toolRequest.args);
|
||||
expect(
|
||||
args.limit,
|
||||
'Agent should have used ranged read (limit) to save tokens',
|
||||
).toBeDefined();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Validates that the agent reads the entire file if there are lots of matches
|
||||
* (e.g.: 10), as it's more efficient than many small ranged reads.
|
||||
*/
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'should read the entire file when there are many matches',
|
||||
files: {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'test-project',
|
||||
version: '1.0.0',
|
||||
type: 'module',
|
||||
}),
|
||||
'eslint.config.mjs': `export default [
|
||||
{
|
||||
files: ["**/*.ts"],
|
||||
rules: {
|
||||
"no-var": "error"
|
||||
}
|
||||
}
|
||||
];`,
|
||||
'many_mess.ts': (() => {
|
||||
const lines = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
if (i % 100 === 0) {
|
||||
lines.push(`var oldVar${i} = "needs fix";`);
|
||||
} else {
|
||||
lines.push(`const goodVar${i} = "clean";`);
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
})(),
|
||||
},
|
||||
prompt:
|
||||
'Fix all linter errors in many_mess.ts manually by editing the file. Run eslint directly (using "npx --yes eslint") to find them. Do not run the file.',
|
||||
assert: async (rig) => {
|
||||
const logs = rig.readToolLogs();
|
||||
|
||||
const readCalls = logs.filter(
|
||||
(log) => log.toolRequest?.name === READ_FILE_TOOL_NAME,
|
||||
);
|
||||
|
||||
const targetFileReads = readCalls.filter((call) => {
|
||||
const args = JSON.parse(call.toolRequest.args);
|
||||
return args.file_path.includes('many_mess.ts');
|
||||
});
|
||||
|
||||
expect(
|
||||
targetFileReads.length,
|
||||
'Agent should have used read_file to check context',
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
// In this case, we expect the agent to realize there are many scattered errors
|
||||
// and just read the whole file to be efficient with tool calls.
|
||||
const readEntireFile = targetFileReads.some((call) => {
|
||||
const args = JSON.parse(call.toolRequest.args);
|
||||
return args.limit === undefined;
|
||||
});
|
||||
|
||||
expect(
|
||||
readEntireFile,
|
||||
'Agent should have read the entire file because of the high number of scattered matches',
|
||||
).toBe(true);
|
||||
|
||||
// Check that the agent actually fixed the errors
|
||||
const editCalls = logs.filter(
|
||||
(log) => log.toolRequest?.name === EDIT_TOOL_NAME,
|
||||
);
|
||||
const targetEditCalls = editCalls.filter((call) => {
|
||||
const args = JSON.parse(call.toolRequest.args);
|
||||
return args.file_path.includes('many_mess.ts');
|
||||
});
|
||||
expect(
|
||||
targetEditCalls.length,
|
||||
'Agent should have made replacement calls on the target file',
|
||||
).toBeGreaterThanOrEqual(1);
|
||||
},
|
||||
});
|
||||
});
|
||||
+62
-106
@@ -9,7 +9,7 @@ import { evalTest } from './test-helper.js';
|
||||
|
||||
/**
|
||||
* Evals to verify that the agent uses search tools efficiently (frugally)
|
||||
* by utilizing limiting parameters like `total_max_matches` and `max_matches_per_file`.
|
||||
* by utilizing limiting parameters like `limit` and `max_matches_per_file`.
|
||||
* This ensures the agent doesn't flood the context window with unnecessary search results.
|
||||
*/
|
||||
describe('Frugal Search', () => {
|
||||
@@ -25,120 +25,76 @@ describe('Frugal Search', () => {
|
||||
return args;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that the agent makes use of either grep or ranged reads in fulfilling this task.
|
||||
* The task is specifically phrased to not evoke "view" or "search" specifically because
|
||||
* the model implicitly understands that such tasks are searches. This covers the case of
|
||||
* an unexpectedly large file benefitting from frugal approaches to viewing, like grep, or
|
||||
* ranged reads.
|
||||
*/
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'should use targeted search with limit',
|
||||
prompt: 'find me a sample usage of path.resolve() in the codebase',
|
||||
name: 'should use grep or ranged read for large files',
|
||||
prompt: 'What year was legacy_processor.ts written?',
|
||||
files: {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'test-project',
|
||||
version: '1.0.0',
|
||||
main: 'dist/index.js',
|
||||
scripts: {
|
||||
build: 'tsc',
|
||||
test: 'vitest',
|
||||
},
|
||||
dependencies: {
|
||||
typescript: '^5.0.0',
|
||||
'@types/node': '^20.0.0',
|
||||
vitest: '^1.0.0',
|
||||
},
|
||||
}),
|
||||
'src/index.ts': `
|
||||
import { App } from './app.ts';
|
||||
|
||||
const app = new App();
|
||||
app.start();
|
||||
`,
|
||||
'src/app.ts': `
|
||||
import * as path from 'path';
|
||||
import { UserController } from './controllers/user.ts';
|
||||
|
||||
export class App {
|
||||
constructor() {
|
||||
console.log('App initialized');
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
const userController = new UserController();
|
||||
console.log('Static path:', path.resolve(__dirname, '../public'));
|
||||
}
|
||||
}
|
||||
`,
|
||||
'src/utils.ts': `
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export function resolvePath(p: string): string {
|
||||
return path.resolve(process.cwd(), p);
|
||||
}
|
||||
|
||||
export function ensureDir(dirPath: string): void {
|
||||
const absolutePath = path.resolve(dirPath);
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
fs.mkdirSync(absolutePath, { recursive: true });
|
||||
}
|
||||
}
|
||||
`,
|
||||
'src/config.ts': `
|
||||
import * as path from 'path';
|
||||
|
||||
export const config = {
|
||||
dbPath: path.resolve(process.cwd(), 'data/db.sqlite'),
|
||||
logLevel: 'info',
|
||||
};
|
||||
`,
|
||||
'src/controllers/user.ts': `
|
||||
import * as path from 'path';
|
||||
|
||||
export class UserController {
|
||||
public getUsers(): any[] {
|
||||
console.log('Loading users from:', path.resolve('data/users.json'));
|
||||
return [{ id: 1, name: 'Alice' }];
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tests/app.test.ts': `
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import * as path from 'path';
|
||||
|
||||
describe('App', () => {
|
||||
it('should resolve paths', () => {
|
||||
const p = path.resolve('test');
|
||||
expect(p).toBeDefined();
|
||||
});
|
||||
});
|
||||
`,
|
||||
'src/utils.ts': 'export const add = (a, b) => a + b;',
|
||||
'src/types.ts': 'export type ID = string;',
|
||||
'src/legacy_processor.ts': [
|
||||
'// Copyright 2005 Legacy Systems Inc.',
|
||||
...Array.from(
|
||||
{ length: 5000 },
|
||||
(_, i) =>
|
||||
`// Legacy code block ${i} - strictly preserved for backward compatibility`,
|
||||
),
|
||||
].join('\n'),
|
||||
'README.md': '# Project documentation',
|
||||
},
|
||||
assert: async (rig) => {
|
||||
const toolCalls = rig.readToolLogs();
|
||||
const grepCalls = toolCalls.filter(
|
||||
(call) => call.toolRequest.name === 'grep_search',
|
||||
);
|
||||
const getParams = (call: any) => {
|
||||
let args = call.toolRequest.args;
|
||||
if (typeof args === 'string') {
|
||||
try {
|
||||
args = JSON.parse(args);
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
return args;
|
||||
};
|
||||
|
||||
expect(grepCalls.length).toBeGreaterThan(0);
|
||||
// Check for wasteful full file reads
|
||||
const fullReads = toolCalls.filter((call) => {
|
||||
if (call.toolRequest.name !== 'read_file') return false;
|
||||
const args = getParams(call);
|
||||
return (
|
||||
args.file_path === 'src/legacy_processor.ts' &&
|
||||
(args.limit === undefined || args.limit === null)
|
||||
);
|
||||
});
|
||||
|
||||
const grepParams = grepCalls.map(getGrepParams);
|
||||
|
||||
const hasTotalMaxLimit = grepParams.some(
|
||||
(p) => p.total_max_matches !== undefined && p.total_max_matches <= 100,
|
||||
);
|
||||
expect(
|
||||
hasTotalMaxLimit,
|
||||
`Expected agent to use a small total_max_matches (<= 100) for a sample usage request. Actual values: ${JSON.stringify(
|
||||
grepParams.map((p) => p.total_max_matches),
|
||||
)}`,
|
||||
).toBe(true);
|
||||
fullReads.length,
|
||||
'Agent should not attempt to read the entire large file at once',
|
||||
).toBe(0);
|
||||
|
||||
const hasMaxMatchesPerFileLimit = grepParams.some(
|
||||
(p) =>
|
||||
p.max_matches_per_file !== undefined && p.max_matches_per_file <= 5,
|
||||
);
|
||||
expect(
|
||||
hasMaxMatchesPerFileLimit,
|
||||
`Expected agent to use a small max_matches_per_file (<= 5) for a sample usage request. Actual values: ${JSON.stringify(
|
||||
grepParams.map((p) => p.max_matches_per_file),
|
||||
)}`,
|
||||
).toBe(true);
|
||||
// Check that it actually tried to find it using appropriate tools
|
||||
const validAttempts = toolCalls.filter((call) => {
|
||||
const args = getParams(call);
|
||||
if (call.toolRequest.name === 'grep_search') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
call.toolRequest.name === 'read_file' &&
|
||||
args.file_path === 'src/legacy_processor.ts' &&
|
||||
args.limit !== undefined
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
expect(validAttempts.length).toBeGreaterThan(0);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+9
-9
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -14,7 +14,6 @@
|
||||
"ink": "npm:@jrichman/ink@6.4.10",
|
||||
"latest-version": "^9.0.0",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"punycode": "^2.3.1",
|
||||
"simple-git": "^3.28.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -13548,6 +13547,7 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -17254,7 +17254,7 @@
|
||||
},
|
||||
"packages/a2a-server": {
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
"@google-cloud/storage": "^7.16.0",
|
||||
@@ -17312,7 +17312,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
@@ -17409,7 +17409,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@google/gemini-cli-core",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
@@ -17575,7 +17575,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@google/gemini-cli-sdk",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.29.0-nightly.20260203.71f46f116",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -17592,7 +17592,7 @@
|
||||
},
|
||||
"packages/test-utils": {
|
||||
"name": "@google/gemini-cli-test-utils",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -17609,7 +17609,7 @@
|
||||
},
|
||||
"packages/vscode-ide-companion": {
|
||||
"name": "gemini-cli-vscode-ide-companion",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
|
||||
+2
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "git+https://github.com/google-gemini/gemini-cli.git"
|
||||
},
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.30.0-preview.6"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.30.0-nightly.20260210.a2174751d"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development node scripts/start.js",
|
||||
@@ -129,7 +129,6 @@
|
||||
"ink": "npm:@jrichman/ink@6.4.10",
|
||||
"latest-version": "^9.0.0",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"punycode": "^2.3.1",
|
||||
"simple-git": "^3.28.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"description": "Gemini CLI A2A Server",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"description": "Gemini CLI",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
@@ -26,7 +26,7 @@
|
||||
"dist"
|
||||
],
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.30.0-preview.6"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.30.0-nightly.20260210.a2174751d"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
|
||||
@@ -1190,6 +1190,17 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Command to run for tool discovery.',
|
||||
showInDialog: false,
|
||||
},
|
||||
enableToolDiscovery: {
|
||||
type: 'boolean',
|
||||
label: 'Enable Tool Discovery',
|
||||
category: 'Tools',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: oneLine`
|
||||
Enables the tool discovery feature using \`tools.discoveryCommand\`.
|
||||
`,
|
||||
showInDialog: true,
|
||||
},
|
||||
callCommand: {
|
||||
type: 'string',
|
||||
label: 'Tool Call Command',
|
||||
|
||||
@@ -694,7 +694,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
settings.setValue(scope, 'security.auth.selectedType', authType);
|
||||
|
||||
try {
|
||||
config.setRemoteAdminSettings(undefined);
|
||||
await config.refreshAuth(authType);
|
||||
setAuthState(AuthState.Authenticated);
|
||||
} catch (e) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import { getDisplayString } from '@google/gemini-cli-core';
|
||||
|
||||
interface AboutBoxProps {
|
||||
cliVersion: string;
|
||||
@@ -80,9 +79,7 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text color={theme.text.primary}>
|
||||
{getDisplayString(modelVersion)}
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>{modelVersion}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flexDirection="row">
|
||||
|
||||
@@ -42,14 +42,12 @@ describe('<ModelDialog />', () => {
|
||||
const mockGetModel = vi.fn();
|
||||
const mockOnClose = vi.fn();
|
||||
const mockGetHasAccessToPreviewModel = vi.fn();
|
||||
const mockGetGemini31LaunchedSync = vi.fn();
|
||||
|
||||
interface MockConfig extends Partial<Config> {
|
||||
setModel: (model: string, isTemporary?: boolean) => void;
|
||||
getModel: () => string;
|
||||
getHasAccessToPreviewModel: () => boolean;
|
||||
getIdeMode: () => boolean;
|
||||
getGemini31LaunchedSync: () => boolean;
|
||||
}
|
||||
|
||||
const mockConfig: MockConfig = {
|
||||
@@ -57,14 +55,12 @@ describe('<ModelDialog />', () => {
|
||||
getModel: mockGetModel,
|
||||
getHasAccessToPreviewModel: mockGetHasAccessToPreviewModel,
|
||||
getIdeMode: () => false,
|
||||
getGemini31LaunchedSync: mockGetGemini31LaunchedSync,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
mockGetModel.mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO);
|
||||
mockGetHasAccessToPreviewModel.mockReturnValue(false);
|
||||
mockGetGemini31LaunchedSync.mockReturnValue(false);
|
||||
|
||||
// Default implementation for getDisplayString
|
||||
mockGetDisplayString.mockImplementation((val: string) => {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import {
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
@@ -19,14 +18,11 @@ import {
|
||||
ModelSlashCommandEvent,
|
||||
logModelSlashCommand,
|
||||
getDisplayString,
|
||||
AuthType,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { DescriptiveRadioButtonSelect } from './shared/DescriptiveRadioButtonSelect.js';
|
||||
import { ConfigContext } from '../contexts/ConfigContext.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
|
||||
interface ModelDialogProps {
|
||||
onClose: () => void;
|
||||
@@ -34,7 +30,6 @@ interface ModelDialogProps {
|
||||
|
||||
export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
const config = useContext(ConfigContext);
|
||||
const settings = useSettings();
|
||||
const [view, setView] = useState<'main' | 'manual'>('main');
|
||||
const [persistMode, setPersistMode] = useState(false);
|
||||
|
||||
@@ -42,10 +37,6 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
const preferredModel = config?.getModel() || DEFAULT_GEMINI_MODEL_AUTO;
|
||||
|
||||
const shouldShowPreviewModels = config?.getHasAccessToPreviewModel();
|
||||
const useGemini31 = config?.getGemini31LaunchedSync?.() ?? false;
|
||||
const selectedAuthType = settings.merged.security.auth.selectedType;
|
||||
const useCustomToolModel =
|
||||
useGemini31 && selectedAuthType === AuthType.USE_GEMINI;
|
||||
|
||||
const manualModelSelected = useMemo(() => {
|
||||
const manualModels = [
|
||||
@@ -53,8 +44,6 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
];
|
||||
if (manualModels.includes(preferredModel)) {
|
||||
@@ -105,14 +94,13 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
list.unshift({
|
||||
value: PREVIEW_GEMINI_MODEL_AUTO,
|
||||
title: getDisplayString(PREVIEW_GEMINI_MODEL_AUTO),
|
||||
description: useGemini31
|
||||
? 'Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash'
|
||||
: 'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
|
||||
description:
|
||||
'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
|
||||
key: PREVIEW_GEMINI_MODEL_AUTO,
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}, [shouldShowPreviewModels, manualModelSelected, useGemini31]);
|
||||
}, [shouldShowPreviewModels, manualModelSelected]);
|
||||
|
||||
const manualOptions = useMemo(() => {
|
||||
const list = [
|
||||
@@ -134,19 +122,11 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
];
|
||||
|
||||
if (shouldShowPreviewModels) {
|
||||
const previewProModel = useGemini31
|
||||
? PREVIEW_GEMINI_3_1_MODEL
|
||||
: PREVIEW_GEMINI_MODEL;
|
||||
|
||||
const previewProValue = useCustomToolModel
|
||||
? PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL
|
||||
: previewProModel;
|
||||
|
||||
list.unshift(
|
||||
{
|
||||
value: previewProValue,
|
||||
title: previewProModel,
|
||||
key: previewProModel,
|
||||
value: PREVIEW_GEMINI_MODEL,
|
||||
title: PREVIEW_GEMINI_MODEL,
|
||||
key: PREVIEW_GEMINI_MODEL,
|
||||
},
|
||||
{
|
||||
value: PREVIEW_GEMINI_FLASH_MODEL,
|
||||
@@ -156,7 +136,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
return list;
|
||||
}, [shouldShowPreviewModels, useGemini31, useCustomToolModel]);
|
||||
}, [shouldShowPreviewModels]);
|
||||
|
||||
const options = view === 'main' ? mainOptions : manualOptions;
|
||||
|
||||
|
||||
@@ -23,13 +23,11 @@ import {
|
||||
import { computeSessionStats } from '../utils/computeStats.js';
|
||||
import {
|
||||
type RetrieveUserQuotaResponse,
|
||||
isActiveModel,
|
||||
VALID_GEMINI_MODELS,
|
||||
getDisplayString,
|
||||
isAutoModel,
|
||||
AuthType,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import type { QuotaStats } from '../types.js';
|
||||
import { QuotaStatsInfo } from './QuotaStatsInfo.js';
|
||||
|
||||
@@ -84,13 +82,9 @@ const Section: React.FC<SectionProps> = ({ title, children }) => (
|
||||
const buildModelRows = (
|
||||
models: Record<string, ModelMetrics>,
|
||||
quotas?: RetrieveUserQuotaResponse,
|
||||
useGemini3_1 = false,
|
||||
useCustomToolModel = false,
|
||||
) => {
|
||||
const getBaseModelName = (name: string) => name.replace('-001', '');
|
||||
const usedModelNames = new Set(
|
||||
Object.keys(models).map(getBaseModelName).map(getDisplayString),
|
||||
);
|
||||
const usedModelNames = new Set(Object.keys(models).map(getBaseModelName));
|
||||
|
||||
// 1. Models with active usage
|
||||
const activeRows = Object.entries(models).map(([name, metrics]) => {
|
||||
@@ -99,7 +93,7 @@ const buildModelRows = (
|
||||
const inputTokens = metrics.tokens.input;
|
||||
return {
|
||||
key: name,
|
||||
modelName: getDisplayString(modelName),
|
||||
modelName,
|
||||
requests: metrics.api.totalRequests,
|
||||
cachedTokens: cachedTokens.toLocaleString(),
|
||||
inputTokens: inputTokens.toLocaleString(),
|
||||
@@ -115,12 +109,12 @@ const buildModelRows = (
|
||||
?.filter(
|
||||
(b) =>
|
||||
b.modelId &&
|
||||
isActiveModel(b.modelId, useGemini3_1, useCustomToolModel) &&
|
||||
!usedModelNames.has(getDisplayString(b.modelId)),
|
||||
VALID_GEMINI_MODELS.has(b.modelId) &&
|
||||
!usedModelNames.has(b.modelId),
|
||||
)
|
||||
.map((bucket) => ({
|
||||
key: bucket.modelId!,
|
||||
modelName: getDisplayString(bucket.modelId!),
|
||||
modelName: bucket.modelId!,
|
||||
requests: '-',
|
||||
cachedTokens: '-',
|
||||
inputTokens: '-',
|
||||
@@ -141,8 +135,6 @@ const ModelUsageTable: React.FC<{
|
||||
pooledRemaining?: number;
|
||||
pooledLimit?: number;
|
||||
pooledResetTime?: string;
|
||||
useGemini3_1?: boolean;
|
||||
useCustomToolModel?: boolean;
|
||||
}> = ({
|
||||
models,
|
||||
quotas,
|
||||
@@ -152,10 +144,8 @@ const ModelUsageTable: React.FC<{
|
||||
pooledRemaining,
|
||||
pooledLimit,
|
||||
pooledResetTime,
|
||||
useGemini3_1,
|
||||
useCustomToolModel,
|
||||
}) => {
|
||||
const rows = buildModelRows(models, quotas, useGemini3_1, useCustomToolModel);
|
||||
const rows = buildModelRows(models, quotas);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return null;
|
||||
@@ -411,11 +401,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
const { models, tools, files } = metrics;
|
||||
const computed = computeSessionStats(metrics);
|
||||
const settings = useSettings();
|
||||
const config = useConfig();
|
||||
const useGemini3_1 = config.getGemini31LaunchedSync?.() ?? false;
|
||||
const useCustomToolModel =
|
||||
useGemini3_1 &&
|
||||
config.getContentGeneratorConfig().authType === AuthType.USE_GEMINI;
|
||||
|
||||
const pooledRemaining = quotaStats?.remaining;
|
||||
const pooledLimit = quotaStats?.limit;
|
||||
const pooledResetTime = quotaStats?.resetTime;
|
||||
@@ -549,8 +535,6 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
pooledRemaining={pooledRemaining}
|
||||
pooledLimit={pooledLimit}
|
||||
pooledResetTime={pooledResetTime}
|
||||
useGemini3_1={useGemini3_1}
|
||||
useCustomToolModel={useCustomToolModel}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import type React from 'react';
|
||||
import { Text, Box } from 'ink';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import { getDisplayString } from '@google/gemini-cli-core';
|
||||
|
||||
interface ModelMessageProps {
|
||||
model: string;
|
||||
@@ -16,7 +15,7 @@ interface ModelMessageProps {
|
||||
export const ModelMessage: React.FC<ModelMessageProps> = ({ model }) => (
|
||||
<Box marginLeft={2}>
|
||||
<Text color={theme.ui.comment} italic>
|
||||
Responding with {getDisplayString(model)}
|
||||
Responding with {model}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
MessageBusType,
|
||||
IdeClient,
|
||||
CoreToolCallStatus,
|
||||
type SerializableConfirmationDetails,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { type IndividualToolCallDisplay } from '../types.js';
|
||||
|
||||
@@ -183,44 +182,4 @@ describe('ToolActionsContext', () => {
|
||||
|
||||
expect(result.current.isDiffingEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it('calls local onConfirm for tools without correlationId', async () => {
|
||||
const mockOnConfirm = vi.fn().mockResolvedValue(undefined);
|
||||
const legacyTool: IndividualToolCallDisplay = {
|
||||
callId: 'legacy-call',
|
||||
name: 'legacy-tool',
|
||||
description: 'desc',
|
||||
status: CoreToolCallStatus.AwaitingApproval,
|
||||
resultDisplay: undefined,
|
||||
confirmationDetails: {
|
||||
type: 'exec',
|
||||
title: 'exec',
|
||||
command: 'ls',
|
||||
rootCommand: 'ls',
|
||||
rootCommands: ['ls'],
|
||||
onConfirm: mockOnConfirm,
|
||||
} as unknown as SerializableConfirmationDetails,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useToolActions(), {
|
||||
wrapper: ({ children }) => (
|
||||
<ToolActionsProvider config={mockConfig} toolCalls={[legacyTool]}>
|
||||
{children}
|
||||
</ToolActionsProvider>
|
||||
),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.confirm(
|
||||
'legacy-call',
|
||||
ToolConfirmationOutcome.ProceedOnce,
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockOnConfirm).toHaveBeenCalledWith(
|
||||
ToolConfirmationOutcome.ProceedOnce,
|
||||
undefined,
|
||||
);
|
||||
expect(mockMessageBus.publish).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,28 +18,10 @@ import {
|
||||
MessageBusType,
|
||||
type Config,
|
||||
type ToolConfirmationPayload,
|
||||
type SerializableConfirmationDetails,
|
||||
debugLogger,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { IndividualToolCallDisplay } from '../types.js';
|
||||
|
||||
type LegacyConfirmationDetails = SerializableConfirmationDetails & {
|
||||
onConfirm: (
|
||||
outcome: ToolConfirmationOutcome,
|
||||
payload?: ToolConfirmationPayload,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
function hasLegacyCallback(
|
||||
details: SerializableConfirmationDetails | undefined,
|
||||
): details is LegacyConfirmationDetails {
|
||||
return (
|
||||
!!details &&
|
||||
'onConfirm' in details &&
|
||||
typeof details.onConfirm === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
interface ToolActionsContextValue {
|
||||
confirm: (
|
||||
callId: string,
|
||||
@@ -143,15 +125,7 @@ export const ToolActionsProvider: React.FC<ToolActionsProviderProps> = (
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Fallback: Legacy Callback
|
||||
if (hasLegacyCallback(details)) {
|
||||
await details.onConfirm(outcome, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
debugLogger.warn(
|
||||
`ToolActions: No correlationId or callback for ${callId}`,
|
||||
);
|
||||
debugLogger.warn(`ToolActions: No correlationId for ${callId}`);
|
||||
},
|
||||
[config, ideClient, toolCalls, isDiffingEnabled],
|
||||
);
|
||||
|
||||
@@ -155,10 +155,9 @@ describe('useQuotaAndFallback', () => {
|
||||
expect(request?.isTerminalQuotaError).toBe(true);
|
||||
|
||||
const message = request!.message;
|
||||
expect(message).toContain('Usage limit reached for all Pro models.');
|
||||
expect(message).toContain('Usage limit reached for gemini-pro.');
|
||||
expect(message).toContain('Access resets at'); // From getResetTimeMessage
|
||||
expect(message).toContain('/stats model for usage details');
|
||||
expect(message).toContain('/model to switch models.');
|
||||
expect(message).toContain('/auth to switch to API key.');
|
||||
|
||||
expect(mockHistoryManager.addItem).not.toHaveBeenCalled();
|
||||
@@ -177,77 +176,6 @@ describe('useQuotaAndFallback', () => {
|
||||
expect(mockHistoryManager.addItem).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should show the model name for a terminal quota error on a non-pro model', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQuotaAndFallback({
|
||||
config: mockConfig,
|
||||
historyManager: mockHistoryManager,
|
||||
userTier: UserTierId.FREE,
|
||||
setModelSwitchedFromQuotaError: mockSetModelSwitchedFromQuotaError,
|
||||
onShowAuthSelection: mockOnShowAuthSelection,
|
||||
}),
|
||||
);
|
||||
|
||||
const handler = setFallbackHandlerSpy.mock
|
||||
.calls[0][0] as FallbackModelHandler;
|
||||
|
||||
let promise: Promise<FallbackIntent | null>;
|
||||
const error = new TerminalQuotaError(
|
||||
'flash quota',
|
||||
mockGoogleApiError,
|
||||
1000 * 60 * 5,
|
||||
);
|
||||
act(() => {
|
||||
promise = handler('gemini-flash', 'gemini-pro', error);
|
||||
});
|
||||
|
||||
const request = result.current.proQuotaRequest;
|
||||
expect(request).not.toBeNull();
|
||||
expect(request?.failedModel).toBe('gemini-flash');
|
||||
|
||||
const message = request!.message;
|
||||
expect(message).toContain('Usage limit reached for gemini-flash.');
|
||||
expect(message).not.toContain('all Pro models');
|
||||
|
||||
act(() => {
|
||||
result.current.handleProQuotaChoice('retry_later');
|
||||
});
|
||||
|
||||
await promise!;
|
||||
});
|
||||
|
||||
it('should handle terminal quota error without retry delay', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQuotaAndFallback({
|
||||
config: mockConfig,
|
||||
historyManager: mockHistoryManager,
|
||||
userTier: UserTierId.FREE,
|
||||
setModelSwitchedFromQuotaError: mockSetModelSwitchedFromQuotaError,
|
||||
onShowAuthSelection: mockOnShowAuthSelection,
|
||||
}),
|
||||
);
|
||||
|
||||
const handler = setFallbackHandlerSpy.mock
|
||||
.calls[0][0] as FallbackModelHandler;
|
||||
|
||||
let promise: Promise<FallbackIntent | null>;
|
||||
const error = new TerminalQuotaError('no delay', mockGoogleApiError);
|
||||
act(() => {
|
||||
promise = handler('gemini-pro', 'gemini-flash', error);
|
||||
});
|
||||
|
||||
const request = result.current.proQuotaRequest;
|
||||
const message = request!.message;
|
||||
expect(message).not.toContain('Access resets at');
|
||||
expect(message).toContain('Usage limit reached for all Pro models.');
|
||||
|
||||
act(() => {
|
||||
result.current.handleProQuotaChoice('retry_later');
|
||||
});
|
||||
|
||||
await promise!;
|
||||
});
|
||||
|
||||
it('should handle race conditions by stopping subsequent requests', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQuotaAndFallback({
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
TerminalQuotaError,
|
||||
ModelNotFoundError,
|
||||
type UserTierId,
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
VALID_GEMINI_MODELS,
|
||||
isProModel,
|
||||
getDisplayString,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { type UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
@@ -67,9 +67,11 @@ export function useQuotaAndFallback({
|
||||
let message: string;
|
||||
let isTerminalQuotaError = false;
|
||||
let isModelNotFoundError = false;
|
||||
const usageLimitReachedModel = isProModel(failedModel)
|
||||
? 'all Pro models'
|
||||
: failedModel;
|
||||
const usageLimitReachedModel =
|
||||
failedModel === DEFAULT_GEMINI_MODEL ||
|
||||
failedModel === PREVIEW_GEMINI_MODEL
|
||||
? 'all Pro models'
|
||||
: failedModel;
|
||||
if (error instanceof TerminalQuotaError) {
|
||||
isTerminalQuotaError = true;
|
||||
// Common part of the message for both tiers
|
||||
@@ -85,7 +87,7 @@ export function useQuotaAndFallback({
|
||||
isModelNotFoundError = true;
|
||||
if (VALID_GEMINI_MODELS.has(failedModel)) {
|
||||
const messageLines = [
|
||||
`It seems like you don't have access to ${getDisplayString(failedModel)}.`,
|
||||
`It seems like you don't have access to ${failedModel}.`,
|
||||
`Your admin might have disabled the access. Contact them to enable the Preview Release Channel.`,
|
||||
];
|
||||
message = messageLines.join('\n');
|
||||
|
||||
@@ -488,10 +488,7 @@ export class Session {
|
||||
const functionCalls: FunctionCall[] = [];
|
||||
|
||||
try {
|
||||
const model = resolveModel(
|
||||
this.config.getModel(),
|
||||
(await this.config.getGemini31Launched?.()) ?? false,
|
||||
);
|
||||
const model = resolveModel(this.config.getModel());
|
||||
const responseStream = await chat.sendMessageStream(
|
||||
{ model },
|
||||
nextMessage?.parts ?? [],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-core",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"description": "Gemini CLI Core",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
} from './policyCatalog.js';
|
||||
import {
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
} from '../config/models.js';
|
||||
|
||||
@@ -24,27 +22,6 @@ describe('policyCatalog', () => {
|
||||
expect(chain).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('returns Gemini 3.1 chain when useGemini31 is true', () => {
|
||||
const chain = getModelPolicyChain({
|
||||
previewEnabled: true,
|
||||
useGemini31: true,
|
||||
});
|
||||
expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_MODEL);
|
||||
expect(chain).toHaveLength(2);
|
||||
expect(chain[1]?.model).toBe('gemini-3-flash-preview');
|
||||
});
|
||||
|
||||
it('returns Gemini 3.1 Custom Tools chain when useGemini31 and useCustomToolModel are true', () => {
|
||||
const chain = getModelPolicyChain({
|
||||
previewEnabled: true,
|
||||
useGemini31: true,
|
||||
useCustomToolModel: true,
|
||||
});
|
||||
expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
|
||||
expect(chain).toHaveLength(2);
|
||||
expect(chain[1]?.model).toBe('gemini-3-flash-preview');
|
||||
});
|
||||
|
||||
it('returns default chain when preview disabled', () => {
|
||||
const chain = getModelPolicyChain({ previewEnabled: false });
|
||||
expect(chain[0]?.model).toBe(DEFAULT_GEMINI_MODEL);
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
resolveModel,
|
||||
} from '../config/models.js';
|
||||
import type { UserTierId } from '../code_assist/types.js';
|
||||
|
||||
@@ -29,8 +28,6 @@ type PolicyConfig = Omit<ModelPolicy, 'actions' | 'stateTransitions'> & {
|
||||
export interface ModelPolicyOptions {
|
||||
previewEnabled: boolean;
|
||||
userTier?: UserTierId;
|
||||
useGemini31?: boolean;
|
||||
useCustomToolModel?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_ACTIONS: ModelPolicyActionMap = {
|
||||
@@ -59,6 +56,11 @@ const DEFAULT_CHAIN: ModelPolicyChain = [
|
||||
definePolicy({ model: DEFAULT_GEMINI_FLASH_MODEL, isLastResort: true }),
|
||||
];
|
||||
|
||||
const PREVIEW_CHAIN: ModelPolicyChain = [
|
||||
definePolicy({ model: PREVIEW_GEMINI_MODEL }),
|
||||
definePolicy({ model: PREVIEW_GEMINI_FLASH_MODEL, isLastResort: true }),
|
||||
];
|
||||
|
||||
const FLASH_LITE_CHAIN: ModelPolicyChain = [
|
||||
definePolicy({
|
||||
model: DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||
@@ -82,15 +84,7 @@ export function getModelPolicyChain(
|
||||
options: ModelPolicyOptions,
|
||||
): ModelPolicyChain {
|
||||
if (options.previewEnabled) {
|
||||
const previewModel = resolveModel(
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
options.useGemini31,
|
||||
options.useCustomToolModel,
|
||||
);
|
||||
return [
|
||||
definePolicy({ model: previewModel }),
|
||||
definePolicy({ model: PREVIEW_GEMINI_FLASH_MODEL, isLastResort: true }),
|
||||
];
|
||||
return cloneChain(PREVIEW_CHAIN);
|
||||
}
|
||||
|
||||
return cloneChain(DEFAULT_CHAIN);
|
||||
|
||||
@@ -15,17 +15,12 @@ import type { Config } from '../config/config.js';
|
||||
import {
|
||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||
DEFAULT_GEMINI_MODEL_AUTO,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
} from '../config/models.js';
|
||||
import { AuthType } from '../core/contentGenerator.js';
|
||||
|
||||
const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
||||
({
|
||||
getUserTier: () => undefined,
|
||||
getModel: () => 'gemini-2.5-pro',
|
||||
getGemini31LaunchedSync: () => false,
|
||||
getContentGeneratorConfig: () => ({ authType: undefined }),
|
||||
...overrides,
|
||||
}) as unknown as Config;
|
||||
|
||||
@@ -133,27 +128,6 @@ describe('policyHelpers', () => {
|
||||
expect(chain[0]?.model).toBe('gemini-2.5-pro');
|
||||
expect(chain[1]?.model).toBe('gemini-2.5-flash');
|
||||
});
|
||||
|
||||
it('returns Gemini 3.1 Pro chain when launched and auto-gemini-3 requested', () => {
|
||||
const config = createMockConfig({
|
||||
getModel: () => 'auto-gemini-3',
|
||||
getGemini31LaunchedSync: () => true,
|
||||
});
|
||||
const chain = resolvePolicyChain(config);
|
||||
expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_MODEL);
|
||||
expect(chain[1]?.model).toBe('gemini-3-flash-preview');
|
||||
});
|
||||
|
||||
it('returns Gemini 3.1 Pro Custom Tools chain when launched, auth is Gemini, and auto-gemini-3 requested', () => {
|
||||
const config = createMockConfig({
|
||||
getModel: () => 'auto-gemini-3',
|
||||
getGemini31LaunchedSync: () => true,
|
||||
getContentGeneratorConfig: () => ({ authType: AuthType.USE_GEMINI }),
|
||||
});
|
||||
const chain = resolvePolicyChain(config);
|
||||
expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
|
||||
expect(chain[1]?.model).toBe('gemini-3-flash-preview');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildFallbackPolicyContext', () => {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import type { GenerateContentConfig } from '@google/genai';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { AuthType } from '../core/contentGenerator.js';
|
||||
import type {
|
||||
FailureKind,
|
||||
FallbackAction,
|
||||
@@ -45,16 +44,7 @@ export function resolvePolicyChain(
|
||||
const configuredModel = config.getModel();
|
||||
|
||||
let chain;
|
||||
const useGemini31 = config.getGemini31LaunchedSync?.() ?? false;
|
||||
const useCustomToolModel =
|
||||
useGemini31 &&
|
||||
config.getContentGeneratorConfig?.()?.authType === AuthType.USE_GEMINI;
|
||||
|
||||
const resolvedModel = resolveModel(
|
||||
modelFromConfig,
|
||||
useGemini31,
|
||||
useCustomToolModel,
|
||||
);
|
||||
const resolvedModel = resolveModel(modelFromConfig);
|
||||
const isAutoPreferred = preferredModel ? isAutoModel(preferredModel) : false;
|
||||
const isAutoConfigured = isAutoModel(configuredModel);
|
||||
const hasAccessToPreview = config.getHasAccessToPreviewModel?.() ?? true;
|
||||
@@ -74,8 +64,6 @@ export function resolvePolicyChain(
|
||||
chain = getModelPolicyChain({
|
||||
previewEnabled,
|
||||
userTier: config.getUserTier(),
|
||||
useGemini31,
|
||||
useCustomToolModel,
|
||||
});
|
||||
} else {
|
||||
// User requested Gemini 3 but has no access. Proactively downgrade
|
||||
@@ -83,8 +71,6 @@ export function resolvePolicyChain(
|
||||
return getModelPolicyChain({
|
||||
previewEnabled: false,
|
||||
userTier: config.getUserTier(),
|
||||
useGemini31,
|
||||
useCustomToolModel,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -345,7 +345,6 @@ describe('Admin Controls', () => {
|
||||
// Should still start polling
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
strictModeDisabled: true,
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(5 * 60 * 1000);
|
||||
|
||||
@@ -364,10 +363,7 @@ describe('Admin Controls', () => {
|
||||
});
|
||||
|
||||
it('should fetch from server if no cachedSettings provided', async () => {
|
||||
const serverResponse = {
|
||||
strictModeDisabled: false,
|
||||
adminControlsApplicable: true,
|
||||
};
|
||||
const serverResponse = { strictModeDisabled: false };
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue(serverResponse);
|
||||
|
||||
const result = await fetchAdminControls(
|
||||
@@ -390,24 +386,31 @@ describe('Admin Controls', () => {
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should throw error on fetch error and NOT start polling', async () => {
|
||||
const error = new Error('Network error');
|
||||
(mockServer.fetchAdminControls as Mock).mockRejectedValue(error);
|
||||
it('should return empty object on fetch error and still start polling', async () => {
|
||||
(mockServer.fetchAdminControls as Mock).mockRejectedValue(
|
||||
new Error('Network error'),
|
||||
);
|
||||
const result = await fetchAdminControls(
|
||||
mockServer,
|
||||
undefined,
|
||||
true,
|
||||
mockOnSettingsChanged,
|
||||
);
|
||||
|
||||
await expect(
|
||||
fetchAdminControls(mockServer, undefined, true, mockOnSettingsChanged),
|
||||
).rejects.toThrow(error);
|
||||
expect(result).toEqual({});
|
||||
|
||||
// Polling should NOT have been started
|
||||
// Advance timers just to be absolutely sure
|
||||
// Polling should have been started and should retry
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
strictModeDisabled: false,
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(5 * 60 * 1000);
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1); // Only initial fetch
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(2); // Initial + poll
|
||||
});
|
||||
|
||||
it('should return empty object on adminControlsApplicable false and STOP polling', async () => {
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
adminControlsApplicable: false,
|
||||
});
|
||||
it('should return empty object on 403 fetch error and STOP polling', async () => {
|
||||
const error403 = new Error('Forbidden');
|
||||
Object.assign(error403, { status: 403 });
|
||||
(mockServer.fetchAdminControls as Mock).mockRejectedValue(error403);
|
||||
|
||||
const result = await fetchAdminControls(
|
||||
mockServer,
|
||||
@@ -418,7 +421,7 @@ describe('Admin Controls', () => {
|
||||
|
||||
expect(result).toEqual({});
|
||||
|
||||
// Advance time - should NOT poll because of adminControlsApplicable: false
|
||||
// Advance time - should NOT poll because of 403
|
||||
await vi.advanceTimersByTimeAsync(5 * 60 * 1000);
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1); // Only the initial call
|
||||
});
|
||||
@@ -427,7 +430,6 @@ describe('Admin Controls', () => {
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
strictModeDisabled: false,
|
||||
unknownField: 'bad',
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
|
||||
const result = await fetchAdminControls(
|
||||
@@ -453,9 +455,7 @@ describe('Admin Controls', () => {
|
||||
});
|
||||
|
||||
it('should reset polling interval if called again', async () => {
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({});
|
||||
|
||||
// First call
|
||||
await fetchAdminControls(
|
||||
@@ -514,7 +514,6 @@ describe('Admin Controls', () => {
|
||||
const serverResponse = {
|
||||
strictModeDisabled: true,
|
||||
unknownField: 'should be removed',
|
||||
adminControlsApplicable: true,
|
||||
};
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue(serverResponse);
|
||||
|
||||
@@ -533,22 +532,22 @@ describe('Admin Controls', () => {
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return empty object on adminControlsApplicable false', async () => {
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
adminControlsApplicable: false,
|
||||
});
|
||||
it('should return empty object on 403 fetch error', async () => {
|
||||
const error403 = new Error('Forbidden');
|
||||
Object.assign(error403, { status: 403 });
|
||||
(mockServer.fetchAdminControls as Mock).mockRejectedValue(error403);
|
||||
|
||||
const result = await fetchAdminControlsOnce(mockServer, true);
|
||||
expect(result).toEqual({});
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should throw error on any other fetch error', async () => {
|
||||
const error = new Error('Network error');
|
||||
(mockServer.fetchAdminControls as Mock).mockRejectedValue(error);
|
||||
await expect(fetchAdminControlsOnce(mockServer, true)).rejects.toThrow(
|
||||
error,
|
||||
it('should return empty object on any other fetch error', async () => {
|
||||
(mockServer.fetchAdminControls as Mock).mockRejectedValue(
|
||||
new Error('Network error'),
|
||||
);
|
||||
const result = await fetchAdminControlsOnce(mockServer, true);
|
||||
expect(result).toEqual({});
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -556,9 +555,7 @@ describe('Admin Controls', () => {
|
||||
const setIntervalSpy = vi.spyOn(global, 'setInterval');
|
||||
const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
|
||||
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({});
|
||||
await fetchAdminControlsOnce(mockServer, true);
|
||||
|
||||
expect(setIntervalSpy).not.toHaveBeenCalled();
|
||||
@@ -571,7 +568,6 @@ describe('Admin Controls', () => {
|
||||
// Initial fetch
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
strictModeDisabled: true,
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
await fetchAdminControls(
|
||||
mockServer,
|
||||
@@ -583,7 +579,6 @@ describe('Admin Controls', () => {
|
||||
// Update for next poll
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
strictModeDisabled: false,
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
|
||||
// Fast forward
|
||||
@@ -603,10 +598,7 @@ describe('Admin Controls', () => {
|
||||
});
|
||||
|
||||
it('should NOT emit if settings are deeply equal but not the same instance', async () => {
|
||||
const settings = {
|
||||
strictModeDisabled: false,
|
||||
adminControlsApplicable: true,
|
||||
};
|
||||
const settings = { strictModeDisabled: false };
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue(settings);
|
||||
|
||||
await fetchAdminControls(
|
||||
@@ -621,7 +613,6 @@ describe('Admin Controls', () => {
|
||||
// Next poll returns a different object with the same values
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
strictModeDisabled: false,
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(5 * 60 * 1000);
|
||||
|
||||
@@ -632,7 +623,6 @@ describe('Admin Controls', () => {
|
||||
// Initial fetch is successful
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
strictModeDisabled: true,
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
await fetchAdminControls(
|
||||
mockServer,
|
||||
@@ -653,7 +643,6 @@ describe('Admin Controls', () => {
|
||||
// Subsequent poll succeeds with new data
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
strictModeDisabled: false,
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(5 * 60 * 1000);
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(3);
|
||||
@@ -670,11 +659,10 @@ describe('Admin Controls', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should STOP polling if server returns adminControlsApplicable false', async () => {
|
||||
it('should STOP polling if server returns 403', async () => {
|
||||
// Initial fetch is successful
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
strictModeDisabled: true,
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
await fetchAdminControls(
|
||||
mockServer,
|
||||
@@ -684,10 +672,10 @@ describe('Admin Controls', () => {
|
||||
);
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Next poll returns adminControlsApplicable: false
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
adminControlsApplicable: false,
|
||||
});
|
||||
// Next poll returns 403
|
||||
const error403 = new Error('Forbidden');
|
||||
Object.assign(error403, { status: 403 });
|
||||
(mockServer.fetchAdminControls as Mock).mockRejectedValue(error403);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(5 * 60 * 1000);
|
||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(2);
|
||||
@@ -700,9 +688,7 @@ describe('Admin Controls', () => {
|
||||
|
||||
describe('stopAdminControlsPolling', () => {
|
||||
it('should stop polling after it has started', async () => {
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({
|
||||
adminControlsApplicable: true,
|
||||
});
|
||||
(mockServer.fetchAdminControls as Mock).mockResolvedValue({});
|
||||
|
||||
// Start polling
|
||||
await fetchAdminControls(
|
||||
|
||||
@@ -80,6 +80,15 @@ export function sanitizeAdminSettings(
|
||||
};
|
||||
}
|
||||
|
||||
function isGaxiosError(error: unknown): error is { status: number } {
|
||||
return (
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
'status' in error &&
|
||||
typeof (error as { status: unknown }).status === 'number'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the admin controls from the server if enabled by experiment flag.
|
||||
* Safely handles polling start/stop based on the flag and server availability.
|
||||
@@ -104,7 +113,7 @@ export async function fetchAdminControls(
|
||||
|
||||
// If we already have settings (e.g. from IPC during relaunch), use them
|
||||
// to avoid blocking startup with another fetch. We'll still start polling.
|
||||
if (cachedSettings && Object.keys(cachedSettings).length !== 0) {
|
||||
if (cachedSettings) {
|
||||
currentSettings = cachedSettings;
|
||||
startAdminControlsPolling(server, server.projectId, onSettingsChanged);
|
||||
return cachedSettings;
|
||||
@@ -114,20 +123,22 @@ export async function fetchAdminControls(
|
||||
const rawSettings = await server.fetchAdminControls({
|
||||
project: server.projectId,
|
||||
});
|
||||
|
||||
if (rawSettings.adminControlsApplicable !== true) {
|
||||
stopAdminControlsPolling();
|
||||
currentSettings = undefined;
|
||||
return {};
|
||||
}
|
||||
|
||||
const sanitizedSettings = sanitizeAdminSettings(rawSettings);
|
||||
currentSettings = sanitizedSettings;
|
||||
startAdminControlsPolling(server, server.projectId, onSettingsChanged);
|
||||
return sanitizedSettings;
|
||||
} catch (e) {
|
||||
// Non-enterprise users don't have access to fetch settings.
|
||||
if (isGaxiosError(e) && e.status === 403) {
|
||||
stopAdminControlsPolling();
|
||||
currentSettings = undefined;
|
||||
return {};
|
||||
}
|
||||
debugLogger.error('Failed to fetch admin controls: ', e);
|
||||
throw e;
|
||||
// If initial fetch fails, start polling to retry.
|
||||
currentSettings = {};
|
||||
startAdminControlsPolling(server, server.projectId, onSettingsChanged);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,18 +162,17 @@ export async function fetchAdminControlsOnce(
|
||||
const rawSettings = await server.fetchAdminControls({
|
||||
project: server.projectId,
|
||||
});
|
||||
|
||||
if (rawSettings.adminControlsApplicable !== true) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return sanitizeAdminSettings(rawSettings);
|
||||
} catch (e) {
|
||||
// Non-enterprise users don't have access to fetch settings.
|
||||
if (isGaxiosError(e) && e.status === 403) {
|
||||
return {};
|
||||
}
|
||||
debugLogger.error(
|
||||
'Failed to fetch admin controls: ',
|
||||
e instanceof Error ? e.message : e,
|
||||
);
|
||||
throw e;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,13 +192,6 @@ function startAdminControlsPolling(
|
||||
const rawSettings = await server.fetchAdminControls({
|
||||
project,
|
||||
});
|
||||
|
||||
if (rawSettings.adminControlsApplicable !== true) {
|
||||
stopAdminControlsPolling();
|
||||
currentSettings = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const newSettings = sanitizeAdminSettings(rawSettings);
|
||||
|
||||
if (!isDeepStrictEqual(newSettings, currentSettings)) {
|
||||
@@ -196,6 +199,12 @@ function startAdminControlsPolling(
|
||||
onSettingsChanged(newSettings);
|
||||
}
|
||||
} catch (e) {
|
||||
// Non-enterprise users don't have access to fetch settings.
|
||||
if (isGaxiosError(e) && e.status === 403) {
|
||||
stopAdminControlsPolling();
|
||||
currentSettings = undefined;
|
||||
return;
|
||||
}
|
||||
debugLogger.error('Failed to poll admin controls: ', e);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,7 +16,6 @@ export const ExperimentFlags = {
|
||||
MASKING_PROTECTION_THRESHOLD: 45758817,
|
||||
MASKING_PRUNABLE_THRESHOLD: 45758818,
|
||||
MASKING_PROTECT_LATEST_TURN: 45758819,
|
||||
GEMINI_3_1_PRO_LAUNCHED: 45760185,
|
||||
} as const;
|
||||
|
||||
export type ExperimentFlagName =
|
||||
|
||||
@@ -355,5 +355,4 @@ export const FetchAdminControlsResponseSchema = z.object({
|
||||
strictModeDisabled: z.boolean().optional(),
|
||||
mcpSetting: McpSettingSchema.optional(),
|
||||
cliFeatureSetting: CliFeatureSettingSchema.optional(),
|
||||
adminControlsApplicable: z.boolean().optional(),
|
||||
});
|
||||
|
||||
@@ -2063,21 +2063,6 @@ describe('Config Quota & Preview Model Access', () => {
|
||||
expect(config.getHasAccessToPreviewModel()).toBe(true);
|
||||
});
|
||||
|
||||
it('should update hasAccessToPreviewModel to true if quota includes Gemini 3.1 preview model', async () => {
|
||||
mockCodeAssistServer.retrieveUserQuota.mockResolvedValue({
|
||||
buckets: [
|
||||
{
|
||||
modelId: 'gemini-3.1-pro-preview',
|
||||
remainingAmount: '100',
|
||||
remainingFraction: 1.0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await config.refreshUserQuota();
|
||||
expect(config.getHasAccessToPreviewModel()).toBe(true);
|
||||
});
|
||||
|
||||
it('should update hasAccessToPreviewModel to false if quota does not include preview model', async () => {
|
||||
mockCodeAssistServer.retrieveUserQuota.mockResolvedValue({
|
||||
buckets: [
|
||||
|
||||
@@ -388,6 +388,7 @@ export interface ConfigParameters {
|
||||
/** @deprecated Use Policy Engine instead */
|
||||
excludeTools?: string[];
|
||||
toolDiscoveryCommand?: string;
|
||||
enableToolDiscovery?: boolean;
|
||||
toolCallCommand?: string;
|
||||
mcpServerCommand?: string;
|
||||
mcpServers?: Record<string, MCPServerConfig>;
|
||||
@@ -524,6 +525,7 @@ export class Config {
|
||||
/** @deprecated Use Policy Engine instead */
|
||||
private readonly excludeTools: string[] | undefined;
|
||||
private readonly toolDiscoveryCommand: string | undefined;
|
||||
private readonly enableToolDiscovery: boolean;
|
||||
private readonly toolCallCommand: string | undefined;
|
||||
private readonly mcpServerCommand: string | undefined;
|
||||
private readonly mcpEnabled: boolean;
|
||||
@@ -696,6 +698,7 @@ export class Config {
|
||||
this.allowedTools = params.allowedTools;
|
||||
this.excludeTools = params.excludeTools;
|
||||
this.toolDiscoveryCommand = params.toolDiscoveryCommand;
|
||||
this.enableToolDiscovery = params.enableToolDiscovery ?? false;
|
||||
this.toolCallCommand = params.toolCallCommand;
|
||||
this.mcpServerCommand = params.mcpServerCommand;
|
||||
this.mcpServers = params.mcpServers;
|
||||
@@ -1041,12 +1044,6 @@ export class Config {
|
||||
// Reset availability status when switching auth (e.g. from limited key to OAuth)
|
||||
this.modelAvailabilityService.reset();
|
||||
|
||||
// Clear stale authType to ensure getGemini31LaunchedSync doesn't return stale results
|
||||
// during the transition.
|
||||
if (this.contentGeneratorConfig) {
|
||||
this.contentGeneratorConfig.authType = undefined;
|
||||
}
|
||||
|
||||
const newContentGeneratorConfig = await createContentGeneratorConfig(
|
||||
this,
|
||||
authMethod,
|
||||
@@ -1169,7 +1166,7 @@ export class Config {
|
||||
return this.remoteAdminSettings;
|
||||
}
|
||||
|
||||
setRemoteAdminSettings(settings: AdminControlsSettings | undefined): void {
|
||||
setRemoteAdminSettings(settings: AdminControlsSettings): void {
|
||||
this.remoteAdminSettings = settings;
|
||||
}
|
||||
|
||||
@@ -1326,10 +1323,7 @@ export class Config {
|
||||
if (pooled.remaining !== undefined) {
|
||||
return pooled.remaining;
|
||||
}
|
||||
const primaryModel = resolveModel(
|
||||
this.getModel(),
|
||||
this.getGemini31LaunchedSync(),
|
||||
);
|
||||
const primaryModel = resolveModel(this.getModel());
|
||||
return this.modelQuotas.get(primaryModel)?.remaining;
|
||||
}
|
||||
|
||||
@@ -1338,10 +1332,7 @@ export class Config {
|
||||
if (pooled.limit !== undefined) {
|
||||
return pooled.limit;
|
||||
}
|
||||
const primaryModel = resolveModel(
|
||||
this.getModel(),
|
||||
this.getGemini31LaunchedSync(),
|
||||
);
|
||||
const primaryModel = resolveModel(this.getModel());
|
||||
return this.modelQuotas.get(primaryModel)?.limit;
|
||||
}
|
||||
|
||||
@@ -1350,10 +1341,7 @@ export class Config {
|
||||
if (pooled.resetTime !== undefined) {
|
||||
return pooled.resetTime;
|
||||
}
|
||||
const primaryModel = resolveModel(
|
||||
this.getModel(),
|
||||
this.getGemini31LaunchedSync(),
|
||||
);
|
||||
const primaryModel = resolveModel(this.getModel());
|
||||
return this.modelQuotas.get(primaryModel)?.resetTime;
|
||||
}
|
||||
|
||||
@@ -1467,8 +1455,7 @@ export class Config {
|
||||
}
|
||||
|
||||
const hasAccess =
|
||||
quota.buckets?.some((b) => b.modelId && isPreviewModel(b.modelId)) ??
|
||||
false;
|
||||
quota.buckets?.some((b) => b.modelId === PREVIEW_GEMINI_MODEL) ?? false;
|
||||
this.setHasAccessToPreviewModel(hasAccess);
|
||||
return quota;
|
||||
} catch (e) {
|
||||
@@ -1550,6 +1537,10 @@ export class Config {
|
||||
return this.toolDiscoveryCommand;
|
||||
}
|
||||
|
||||
getEnableToolDiscovery(): boolean {
|
||||
return this.enableToolDiscovery;
|
||||
}
|
||||
|
||||
getToolCallCommand(): string | undefined {
|
||||
return this.toolCallCommand;
|
||||
}
|
||||
@@ -2191,36 +2182,6 @@ export class Config {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether Gemini 3.1 has been launched.
|
||||
* This method is async and ensures that experiments are loaded before returning the result.
|
||||
*/
|
||||
async getGemini31Launched(): Promise<boolean> {
|
||||
await this.ensureExperimentsLoaded();
|
||||
return this.getGemini31LaunchedSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether Gemini 3.1 has been launched.
|
||||
*
|
||||
* Note: This method should only be called after startup, once experiments have been loaded.
|
||||
* If you need to call this during startup or from an async context, use
|
||||
* getGemini31Launched instead.
|
||||
*/
|
||||
getGemini31LaunchedSync(): boolean {
|
||||
const authType = this.contentGeneratorConfig?.authType;
|
||||
if (
|
||||
authType === AuthType.USE_GEMINI ||
|
||||
authType === AuthType.USE_VERTEX_AI
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
this.experiments?.flags[ExperimentFlags.GEMINI_3_1_PRO_LAUNCHED]
|
||||
?.boolValue ?? false
|
||||
);
|
||||
}
|
||||
|
||||
private async ensureExperimentsLoaded(): Promise<void> {
|
||||
if (!this.experimentsPromise) {
|
||||
return;
|
||||
|
||||
@@ -25,42 +25,8 @@ import {
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
DEFAULT_GEMINI_MODEL_AUTO,
|
||||
isActiveModel,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
isPreviewModel,
|
||||
isProModel,
|
||||
} from './models.js';
|
||||
|
||||
describe('isPreviewModel', () => {
|
||||
it('should return true for preview models', () => {
|
||||
expect(isPreviewModel(PREVIEW_GEMINI_MODEL)).toBe(true);
|
||||
expect(isPreviewModel(PREVIEW_GEMINI_3_1_MODEL)).toBe(true);
|
||||
expect(isPreviewModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL)).toBe(true);
|
||||
expect(isPreviewModel(PREVIEW_GEMINI_FLASH_MODEL)).toBe(true);
|
||||
expect(isPreviewModel(PREVIEW_GEMINI_MODEL_AUTO)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-preview models', () => {
|
||||
expect(isPreviewModel(DEFAULT_GEMINI_MODEL)).toBe(false);
|
||||
expect(isPreviewModel('gemini-1.5-pro')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isProModel', () => {
|
||||
it('should return true for models containing "pro"', () => {
|
||||
expect(isProModel('gemini-3-pro-preview')).toBe(true);
|
||||
expect(isProModel('gemini-2.5-pro')).toBe(true);
|
||||
expect(isProModel('pro')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for models without "pro"', () => {
|
||||
expect(isProModel('gemini-3-flash-preview')).toBe(false);
|
||||
expect(isProModel('gemini-2.5-flash')).toBe(false);
|
||||
expect(isProModel('auto')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isCustomModel', () => {
|
||||
it('should return true for models not starting with gemini-', () => {
|
||||
expect(isCustomModel('testing')).toBe(true);
|
||||
@@ -149,12 +115,6 @@ describe('getDisplayString', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should return PREVIEW_GEMINI_3_1_MODEL for PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL', () => {
|
||||
expect(getDisplayString(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL)).toBe(
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the model name as is for other models', () => {
|
||||
expect(getDisplayString('custom-model')).toBe('custom-model');
|
||||
expect(getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL)).toBe(
|
||||
@@ -186,16 +146,6 @@ describe('resolveModel', () => {
|
||||
expect(model).toBe(PREVIEW_GEMINI_MODEL);
|
||||
});
|
||||
|
||||
it('should return Gemini 3.1 Pro when auto-gemini-3 is requested and useGemini3_1 is true', () => {
|
||||
const model = resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true);
|
||||
expect(model).toBe(PREVIEW_GEMINI_3_1_MODEL);
|
||||
});
|
||||
|
||||
it('should return Gemini 3.1 Pro Custom Tools when auto-gemini-3 is requested, useGemini3_1 is true, and useCustomToolModel is true', () => {
|
||||
const model = resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, true);
|
||||
expect(model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
|
||||
});
|
||||
|
||||
it('should return the Default Pro model when auto-gemini-2.5 is requested', () => {
|
||||
const model = resolveModel(DEFAULT_GEMINI_MODEL_AUTO);
|
||||
expect(model).toBe(DEFAULT_GEMINI_MODEL);
|
||||
@@ -289,71 +239,4 @@ describe('resolveClassifierModel', () => {
|
||||
resolveClassifierModel(PREVIEW_GEMINI_MODEL_AUTO, GEMINI_MODEL_ALIAS_PRO),
|
||||
).toBe(PREVIEW_GEMINI_MODEL);
|
||||
});
|
||||
|
||||
it('should return Gemini 3.1 Pro when alias is pro and useGemini3_1 is true', () => {
|
||||
expect(
|
||||
resolveClassifierModel(
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
GEMINI_MODEL_ALIAS_PRO,
|
||||
true,
|
||||
),
|
||||
).toBe(PREVIEW_GEMINI_3_1_MODEL);
|
||||
});
|
||||
|
||||
it('should return Gemini 3.1 Pro Custom Tools when alias is pro, useGemini3_1 is true, and useCustomToolModel is true', () => {
|
||||
expect(
|
||||
resolveClassifierModel(
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
GEMINI_MODEL_ALIAS_PRO,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isActiveModel', () => {
|
||||
it('should return true for valid models when useGemini3_1 is false', () => {
|
||||
expect(isActiveModel(DEFAULT_GEMINI_MODEL)).toBe(true);
|
||||
expect(isActiveModel(PREVIEW_GEMINI_MODEL)).toBe(true);
|
||||
expect(isActiveModel(DEFAULT_GEMINI_FLASH_MODEL)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for unknown models and aliases', () => {
|
||||
expect(isActiveModel('invalid-model')).toBe(false);
|
||||
expect(isActiveModel(GEMINI_MODEL_ALIAS_AUTO)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for PREVIEW_GEMINI_MODEL when useGemini3_1 is true', () => {
|
||||
expect(isActiveModel(PREVIEW_GEMINI_MODEL, true)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for other valid models when useGemini3_1 is true', () => {
|
||||
expect(isActiveModel(DEFAULT_GEMINI_MODEL, true)).toBe(true);
|
||||
});
|
||||
|
||||
it('should correctly filter Gemini 3.1 models based on useCustomToolModel when useGemini3_1 is true', () => {
|
||||
// When custom tools are preferred, standard 3.1 should be inactive
|
||||
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, true)).toBe(false);
|
||||
expect(
|
||||
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, true),
|
||||
).toBe(true);
|
||||
|
||||
// When custom tools are NOT preferred, custom tools 3.1 should be inactive
|
||||
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, false)).toBe(true);
|
||||
expect(
|
||||
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, false),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for both Gemini 3.1 models when useGemini3_1 is false', () => {
|
||||
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, false, true)).toBe(false);
|
||||
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, false, false)).toBe(false);
|
||||
expect(
|
||||
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, false, true),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, false, false),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
*/
|
||||
|
||||
export const PREVIEW_GEMINI_MODEL = 'gemini-3-pro-preview';
|
||||
export const PREVIEW_GEMINI_3_1_MODEL = 'gemini-3.1-pro-preview';
|
||||
export const PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL =
|
||||
'gemini-3.1-pro-preview-customtools';
|
||||
export const PREVIEW_GEMINI_FLASH_MODEL = 'gemini-3-flash-preview';
|
||||
export const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro';
|
||||
export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash';
|
||||
@@ -15,8 +12,6 @@ export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite';
|
||||
|
||||
export const VALID_GEMINI_MODELS = new Set([
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
@@ -42,29 +37,20 @@ export const DEFAULT_THINKING_MODE = 8192;
|
||||
* to a concrete model name.
|
||||
*
|
||||
* @param requestedModel The model alias or concrete model name requested by the user.
|
||||
* @param useGemini3_1 Whether to use Gemini 3.1 Pro Preview for auto/pro aliases.
|
||||
* @returns The resolved concrete model name.
|
||||
*/
|
||||
export function resolveModel(
|
||||
requestedModel: string,
|
||||
useGemini3_1: boolean = false,
|
||||
useCustomToolModel: boolean = false,
|
||||
): string {
|
||||
export function resolveModel(requestedModel: string): string {
|
||||
switch (requestedModel) {
|
||||
case PREVIEW_GEMINI_MODEL:
|
||||
case PREVIEW_GEMINI_MODEL_AUTO:
|
||||
case GEMINI_MODEL_ALIAS_AUTO:
|
||||
case GEMINI_MODEL_ALIAS_PRO: {
|
||||
if (useGemini3_1) {
|
||||
return useCustomToolModel
|
||||
? PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL
|
||||
: PREVIEW_GEMINI_3_1_MODEL;
|
||||
}
|
||||
case PREVIEW_GEMINI_MODEL_AUTO: {
|
||||
return PREVIEW_GEMINI_MODEL;
|
||||
}
|
||||
case DEFAULT_GEMINI_MODEL_AUTO: {
|
||||
return DEFAULT_GEMINI_MODEL;
|
||||
}
|
||||
case GEMINI_MODEL_ALIAS_AUTO:
|
||||
case GEMINI_MODEL_ALIAS_PRO: {
|
||||
return PREVIEW_GEMINI_MODEL;
|
||||
}
|
||||
case GEMINI_MODEL_ALIAS_FLASH: {
|
||||
return PREVIEW_GEMINI_FLASH_MODEL;
|
||||
}
|
||||
@@ -87,8 +73,6 @@ export function resolveModel(
|
||||
export function resolveClassifierModel(
|
||||
requestedModel: string,
|
||||
modelAlias: string,
|
||||
useGemini3_1: boolean = false,
|
||||
useCustomToolModel: boolean = false,
|
||||
): string {
|
||||
if (modelAlias === GEMINI_MODEL_ALIAS_FLASH) {
|
||||
if (
|
||||
@@ -105,7 +89,7 @@ export function resolveClassifierModel(
|
||||
}
|
||||
return resolveModel(GEMINI_MODEL_ALIAS_FLASH);
|
||||
}
|
||||
return resolveModel(requestedModel, useGemini3_1, useCustomToolModel);
|
||||
return resolveModel(requestedModel);
|
||||
}
|
||||
export function getDisplayString(model: string) {
|
||||
switch (model) {
|
||||
@@ -117,8 +101,6 @@ export function getDisplayString(model: string) {
|
||||
return PREVIEW_GEMINI_MODEL;
|
||||
case GEMINI_MODEL_ALIAS_FLASH:
|
||||
return PREVIEW_GEMINI_FLASH_MODEL;
|
||||
case PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL:
|
||||
return PREVIEW_GEMINI_3_1_MODEL;
|
||||
default:
|
||||
return model;
|
||||
}
|
||||
@@ -133,23 +115,11 @@ export function getDisplayString(model: string) {
|
||||
export function isPreviewModel(model: string): boolean {
|
||||
return (
|
||||
model === PREVIEW_GEMINI_MODEL ||
|
||||
model === PREVIEW_GEMINI_3_1_MODEL ||
|
||||
model === PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL ||
|
||||
model === PREVIEW_GEMINI_FLASH_MODEL ||
|
||||
model === PREVIEW_GEMINI_MODEL_AUTO
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the model is a Pro model.
|
||||
*
|
||||
* @param model The model name to check.
|
||||
* @returns True if the model is a Pro model.
|
||||
*/
|
||||
export function isProModel(model: string): boolean {
|
||||
return model.toLowerCase().includes('pro');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the model is a Gemini 3 model.
|
||||
*
|
||||
@@ -218,35 +188,3 @@ export function isAutoModel(model: string): boolean {
|
||||
export function supportsMultimodalFunctionResponse(model: string): boolean {
|
||||
return model.startsWith('gemini-3-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given model is considered active based on the current configuration.
|
||||
*
|
||||
* @param model The model name to check.
|
||||
* @param useGemini3_1 Whether Gemini 3.1 Pro Preview is enabled.
|
||||
* @returns True if the model is active.
|
||||
*/
|
||||
export function isActiveModel(
|
||||
model: string,
|
||||
useGemini3_1: boolean = false,
|
||||
useCustomToolModel: boolean = false,
|
||||
): boolean {
|
||||
if (!VALID_GEMINI_MODELS.has(model)) {
|
||||
return false;
|
||||
}
|
||||
if (useGemini3_1) {
|
||||
if (model === PREVIEW_GEMINI_MODEL) {
|
||||
return false;
|
||||
}
|
||||
if (useCustomToolModel) {
|
||||
return model !== PREVIEW_GEMINI_3_1_MODEL;
|
||||
} else {
|
||||
return model !== PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL;
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
model !== PREVIEW_GEMINI_3_1_MODEL &&
|
||||
model !== PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,8 +520,34 @@ exports[`Core System Prompt (prompts.ts) > should append userMemory with separat
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -649,8 +675,34 @@ exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator wi
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -744,8 +796,34 @@ exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator wi
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -1308,8 +1386,34 @@ exports[`Core System Prompt (prompts.ts) > should include available_skills with
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -1433,8 +1537,34 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -1549,8 +1679,34 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -1665,8 +1821,34 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -1777,8 +1959,34 @@ exports[`Core System Prompt (prompts.ts) > should include mandate to distinguish
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -1889,8 +2097,34 @@ exports[`Core System Prompt (prompts.ts) > should include planning phase suggest
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -2000,8 +2234,34 @@ exports[`Core System Prompt (prompts.ts) > should include sub-agents in XML for
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -2351,8 +2611,34 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -2463,8 +2749,34 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -2686,8 +2998,34 @@ exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for p
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
@@ -2798,8 +3136,34 @@ exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for p
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to grep_search, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like grep_search to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like read_file and grep_search.
|
||||
- read_file fails if old_string is ambiguous, causing extra turns. Take care to read enough with read_file and grep_search to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like grep_search and glob with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like grep_search with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like grep_search and/or read_file called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
|
||||
@@ -542,10 +542,7 @@ export class GeminiClient {
|
||||
|
||||
// Availability logic: The configured model is the source of truth,
|
||||
// including any permanent fallbacks (config.setModel) or manual overrides.
|
||||
return resolveModel(
|
||||
this.config.getActiveModel(),
|
||||
this.config.getGemini31LaunchedSync?.() ?? false,
|
||||
);
|
||||
return resolveModel(this.config.getActiveModel());
|
||||
}
|
||||
|
||||
private async *processTurn(
|
||||
|
||||
@@ -146,12 +146,7 @@ export async function createContentGenerator(
|
||||
return new LoggingContentGenerator(fakeGenerator, gcConfig);
|
||||
}
|
||||
const version = await getVersion();
|
||||
const model = resolveModel(
|
||||
gcConfig.getModel(),
|
||||
config.authType === AuthType.USE_GEMINI ||
|
||||
config.authType === AuthType.USE_VERTEX_AI ||
|
||||
((await gcConfig.getGemini31Launched?.()) ?? false),
|
||||
);
|
||||
const model = resolveModel(gcConfig.getModel());
|
||||
const customHeadersEnv =
|
||||
process.env['GEMINI_CLI_CUSTOM_HEADERS'] || undefined;
|
||||
const userAgent = `GeminiCLI/${version}/${model} (${process.platform}; ${process.arch})`;
|
||||
|
||||
@@ -496,14 +496,13 @@ export class GeminiChat {
|
||||
const initialActiveModel = this.config.getActiveModel();
|
||||
|
||||
const apiCall = async () => {
|
||||
const useGemini3_1 = (await this.config.getGemini31Launched?.()) ?? false;
|
||||
// Default to the last used model (which respects arguments/availability selection)
|
||||
let modelToUse = resolveModel(lastModelToUse, useGemini3_1);
|
||||
let modelToUse = resolveModel(lastModelToUse);
|
||||
|
||||
// If the active model has changed (e.g. due to a fallback updating the config),
|
||||
// we switch to the new active model.
|
||||
if (this.config.getActiveModel() !== initialActiveModel) {
|
||||
modelToUse = resolveModel(this.config.getActiveModel(), useGemini3_1);
|
||||
modelToUse = resolveModel(this.config.getActiveModel());
|
||||
}
|
||||
|
||||
if (modelToUse !== lastModelToUse) {
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
// But other directories (user, default) should be there
|
||||
expect(calledDirs).toContain('/non/existent/user/policies');
|
||||
expect(calledDirs).toContain('/tmp/mock/default/policies');
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
it('should return ASK_USER for write tools and ALLOW for read-only tools by default', async () => {
|
||||
const actualFs =
|
||||
|
||||
@@ -58,10 +58,7 @@ export class PromptProvider {
|
||||
const enabledToolNames = new Set(toolNames);
|
||||
const approvedPlanPath = config.getApprovedPlanPath();
|
||||
|
||||
const desiredModel = resolveModel(
|
||||
config.getActiveModel(),
|
||||
config.getGemini31LaunchedSync?.() ?? false,
|
||||
);
|
||||
const desiredModel = resolveModel(config.getActiveModel());
|
||||
const isModernModel = supportsModernFeatures(desiredModel);
|
||||
const activeSnippets = isModernModel ? snippets : legacySnippets;
|
||||
const contextFilenames = getAllGeminiMdFilenames();
|
||||
@@ -234,10 +231,7 @@ export class PromptProvider {
|
||||
}
|
||||
|
||||
getCompressionPrompt(config: Config): string {
|
||||
const desiredModel = resolveModel(
|
||||
config.getActiveModel(),
|
||||
config.getGemini31LaunchedSync?.() ?? false,
|
||||
);
|
||||
const desiredModel = resolveModel(config.getActiveModel());
|
||||
const isModernModel = supportsModernFeatures(desiredModel);
|
||||
const activeSnippets = isModernModel ? snippets : legacySnippets;
|
||||
return activeSnippets.getCompressionPrompt();
|
||||
|
||||
@@ -155,6 +155,10 @@ export function renderCoreMandates(options?: CoreMandatesOptions): string {
|
||||
.join(', ') + ` or \`${filenames[filenames.length - 1]}\``
|
||||
: `\`${filenames[0]}\``;
|
||||
|
||||
// ⚠️ IMPORTANT: the Context Efficiency changes strike a delicate balance that encourages
|
||||
// the agent to minimize response sizes while also taking care to avoid extra turns. You
|
||||
// must run the major benchmarks, such as SWEBench, prior to committing any changes to
|
||||
// the Context Efficiency section to avoid regressing this behavior.
|
||||
return `
|
||||
# Core Mandates
|
||||
|
||||
@@ -163,8 +167,34 @@ export function renderCoreMandates(options?: CoreMandatesOptions): string {
|
||||
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
|
||||
|
||||
## Context Efficiency:
|
||||
- Always scope and limit your searches to avoid context window exhaustion and ensure high-signal results. Use include to target relevant files and strictly limit results using total_max_matches and max_matches_per_file, especially during the research phase.
|
||||
- For broad discovery, use names_only=true or max_matches_per_file=1 to identify files without retrieving their context.
|
||||
Be strategic in your use of the available tools to minimize unnecessary context usage while still
|
||||
providing the best answer that you can.
|
||||
|
||||
Consider the following when estimating the cost of your approach:
|
||||
<estimating_context_usage>
|
||||
- The agent passes the full history with each subsequent message. The larger context is early in the session, the more expensive each subsequent turn is.
|
||||
- Unnecessary turns are generally more expensive than other types of wasted context.
|
||||
- You can reduce context usage by limiting the outputs of tools but take care not to cause more token consumption via additional turns required to recover from a tool failure or compensate for a misapplied optimization strategy.
|
||||
</estimating_context_usage>
|
||||
|
||||
Use the following guidelines to optimize your search and read patterns.
|
||||
<guidelines>
|
||||
- Combine turns whenever possible by utilizing parallel searching and reading and by requesting enough context by passing context, before, or after to ${GREP_TOOL_NAME}, to enable you to skip using an extra turn reading the file.
|
||||
- Prefer using tools like ${GREP_TOOL_NAME} to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like ${READ_FILE_TOOL_NAME} and ${GREP_TOOL_NAME}.
|
||||
- ${READ_FILE_TOOL_NAME} fails if old_string is ambiguous, causing extra turns. Take care to read enough with ${READ_FILE_TOOL_NAME} and ${GREP_TOOL_NAME} to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like ${GREP_TOOL_NAME} and ${GLOB_TOOL_NAME} with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
|
||||
- **Searching and editing:** utilize search tools like ${GREP_TOOL_NAME} with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like ${GREP_TOOL_NAME} and/or ${READ_FILE_TOOL_NAME} called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
## Engineering Standards
|
||||
- **Contextual Precedence:** Instructions found in ${formattedFilenames} files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
|
||||
|
||||
@@ -18,14 +18,11 @@ import {
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
DEFAULT_GEMINI_MODEL_AUTO,
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
} from '../../config/models.js';
|
||||
import { promptIdContext } from '../../utils/promptIdContext.js';
|
||||
import type { Content } from '@google/genai';
|
||||
import type { ResolvedModelConfig } from '../../services/modelConfigService.js';
|
||||
import { debugLogger } from '../../utils/debugLogger.js';
|
||||
import { AuthType } from '../../core/contentGenerator.js';
|
||||
|
||||
vi.mock('../../core/baseLlmClient.js');
|
||||
|
||||
@@ -56,10 +53,6 @@ describe('ClassifierStrategy', () => {
|
||||
},
|
||||
getModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO),
|
||||
getNumericalRoutingEnabled: vi.fn().mockResolvedValue(false),
|
||||
getGemini31Launched: vi.fn().mockResolvedValue(false),
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
}),
|
||||
} as unknown as Config;
|
||||
mockBaseLlmClient = {
|
||||
generateJson: vi.fn(),
|
||||
@@ -346,49 +339,4 @@ describe('ClassifierStrategy', () => {
|
||||
// Since requestedModel is Pro, and choice is flash, it should resolve to Flash
|
||||
expect(decision?.model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
|
||||
});
|
||||
|
||||
describe('Gemini 3.1 and Custom Tools Routing', () => {
|
||||
it('should route to PREVIEW_GEMINI_3_1_MODEL when Gemini 3.1 is launched', async () => {
|
||||
vi.mocked(mockConfig.getGemini31Launched).mockResolvedValue(true);
|
||||
vi.mocked(mockConfig.getModel).mockReturnValue(PREVIEW_GEMINI_MODEL_AUTO);
|
||||
const mockApiResponse = {
|
||||
reasoning: 'Complex task',
|
||||
model_choice: 'pro',
|
||||
};
|
||||
vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue(
|
||||
mockApiResponse,
|
||||
);
|
||||
|
||||
const decision = await strategy.route(
|
||||
mockContext,
|
||||
mockConfig,
|
||||
mockBaseLlmClient,
|
||||
);
|
||||
|
||||
expect(decision?.model).toBe(PREVIEW_GEMINI_3_1_MODEL);
|
||||
});
|
||||
|
||||
it('should route to PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL when Gemini 3.1 is launched and auth is USE_GEMINI', async () => {
|
||||
vi.mocked(mockConfig.getGemini31Launched).mockResolvedValue(true);
|
||||
vi.mocked(mockConfig.getModel).mockReturnValue(PREVIEW_GEMINI_MODEL_AUTO);
|
||||
vi.mocked(mockConfig.getContentGeneratorConfig).mockReturnValue({
|
||||
authType: AuthType.USE_GEMINI,
|
||||
});
|
||||
const mockApiResponse = {
|
||||
reasoning: 'Complex task',
|
||||
model_choice: 'pro',
|
||||
};
|
||||
vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue(
|
||||
mockApiResponse,
|
||||
);
|
||||
|
||||
const decision = await strategy.route(
|
||||
mockContext,
|
||||
mockConfig,
|
||||
mockBaseLlmClient,
|
||||
);
|
||||
|
||||
expect(decision?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
} from '../../utils/messageInspectors.js';
|
||||
import { debugLogger } from '../../utils/debugLogger.js';
|
||||
import { LlmRole } from '../../telemetry/types.js';
|
||||
import { AuthType } from '../../core/contentGenerator.js';
|
||||
|
||||
// The number of recent history turns to provide to the router for context.
|
||||
const HISTORY_TURNS_FOR_CONTEXT = 4;
|
||||
@@ -170,15 +169,9 @@ export class ClassifierStrategy implements RoutingStrategy {
|
||||
|
||||
const reasoning = routerResponse.reasoning;
|
||||
const latencyMs = Date.now() - startTime;
|
||||
const useGemini3_1 = (await config.getGemini31Launched?.()) ?? false;
|
||||
const useCustomToolModel =
|
||||
useGemini3_1 &&
|
||||
config.getContentGeneratorConfig().authType === AuthType.USE_GEMINI;
|
||||
const selectedModel = resolveClassifierModel(
|
||||
model,
|
||||
routerResponse.model_choice,
|
||||
useGemini3_1,
|
||||
useCustomToolModel,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -21,10 +21,7 @@ export class DefaultStrategy implements TerminalStrategy {
|
||||
config: Config,
|
||||
_baseLlmClient: BaseLlmClient,
|
||||
): Promise<RoutingDecision> {
|
||||
const defaultModel = resolveModel(
|
||||
config.getModel(),
|
||||
config.getGemini31LaunchedSync?.() ?? false,
|
||||
);
|
||||
const defaultModel = resolveModel(config.getModel());
|
||||
return {
|
||||
model: defaultModel,
|
||||
metadata: {
|
||||
|
||||
@@ -23,10 +23,7 @@ export class FallbackStrategy implements RoutingStrategy {
|
||||
_baseLlmClient: BaseLlmClient,
|
||||
): Promise<RoutingDecision | null> {
|
||||
const requestedModel = context.requestedModel ?? config.getModel();
|
||||
const resolvedModel = resolveModel(
|
||||
requestedModel,
|
||||
config.getGemini31LaunchedSync?.() ?? false,
|
||||
);
|
||||
const resolvedModel = resolveModel(requestedModel);
|
||||
const service = config.getModelAvailabilityService();
|
||||
const snapshot = service.snapshot(resolvedModel);
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ import type { BaseLlmClient } from '../../core/baseLlmClient.js';
|
||||
import {
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
DEFAULT_GEMINI_MODEL_AUTO,
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
@@ -22,7 +20,6 @@ import { promptIdContext } from '../../utils/promptIdContext.js';
|
||||
import type { Content } from '@google/genai';
|
||||
import type { ResolvedModelConfig } from '../../services/modelConfigService.js';
|
||||
import { debugLogger } from '../../utils/debugLogger.js';
|
||||
import { AuthType } from '../../core/contentGenerator.js';
|
||||
|
||||
vi.mock('../../core/baseLlmClient.js');
|
||||
|
||||
@@ -55,10 +52,6 @@ describe('NumericalClassifierStrategy', () => {
|
||||
getSessionId: vi.fn().mockReturnValue('control-group-id'), // Default to Control Group (Hash 71 >= 50)
|
||||
getNumericalRoutingEnabled: vi.fn().mockResolvedValue(true),
|
||||
getClassifierThreshold: vi.fn().mockResolvedValue(undefined),
|
||||
getGemini31Launched: vi.fn().mockResolvedValue(false),
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
}),
|
||||
} as unknown as Config;
|
||||
mockBaseLlmClient = {
|
||||
generateJson: vi.fn(),
|
||||
@@ -542,68 +535,4 @@ describe('NumericalClassifierStrategy', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
describe('Gemini 3.1 and Custom Tools Routing', () => {
|
||||
it('should route to PREVIEW_GEMINI_3_1_MODEL when Gemini 3.1 is launched', async () => {
|
||||
vi.mocked(mockConfig.getGemini31Launched).mockResolvedValue(true);
|
||||
const mockApiResponse = {
|
||||
complexity_reasoning: 'Complex task',
|
||||
complexity_score: 80,
|
||||
};
|
||||
vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue(
|
||||
mockApiResponse,
|
||||
);
|
||||
|
||||
const decision = await strategy.route(
|
||||
mockContext,
|
||||
mockConfig,
|
||||
mockBaseLlmClient,
|
||||
);
|
||||
|
||||
expect(decision?.model).toBe(PREVIEW_GEMINI_3_1_MODEL);
|
||||
});
|
||||
it('should route to PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL when Gemini 3.1 is launched and auth is USE_GEMINI', async () => {
|
||||
vi.mocked(mockConfig.getGemini31Launched).mockResolvedValue(true);
|
||||
vi.mocked(mockConfig.getContentGeneratorConfig).mockReturnValue({
|
||||
authType: AuthType.USE_GEMINI,
|
||||
});
|
||||
const mockApiResponse = {
|
||||
complexity_reasoning: 'Complex task',
|
||||
complexity_score: 80,
|
||||
};
|
||||
vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue(
|
||||
mockApiResponse,
|
||||
);
|
||||
|
||||
const decision = await strategy.route(
|
||||
mockContext,
|
||||
mockConfig,
|
||||
mockBaseLlmClient,
|
||||
);
|
||||
|
||||
expect(decision?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
|
||||
});
|
||||
|
||||
it('should NOT route to custom tools model when auth is USE_VERTEX_AI', async () => {
|
||||
vi.mocked(mockConfig.getGemini31Launched).mockResolvedValue(true);
|
||||
vi.mocked(mockConfig.getContentGeneratorConfig).mockReturnValue({
|
||||
authType: AuthType.USE_VERTEX_AI,
|
||||
});
|
||||
const mockApiResponse = {
|
||||
complexity_reasoning: 'Complex task',
|
||||
complexity_score: 80,
|
||||
};
|
||||
vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue(
|
||||
mockApiResponse,
|
||||
);
|
||||
|
||||
const decision = await strategy.route(
|
||||
mockContext,
|
||||
mockConfig,
|
||||
mockBaseLlmClient,
|
||||
);
|
||||
|
||||
expect(decision?.model).toBe(PREVIEW_GEMINI_3_1_MODEL);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,6 @@ import { createUserContent, Type } from '@google/genai';
|
||||
import type { Config } from '../../config/config.js';
|
||||
import { debugLogger } from '../../utils/debugLogger.js';
|
||||
import { LlmRole } from '../../telemetry/types.js';
|
||||
import { AuthType } from '../../core/contentGenerator.js';
|
||||
|
||||
// The number of recent history turns to provide to the router for context.
|
||||
const HISTORY_TURNS_FOR_CONTEXT = 8;
|
||||
@@ -183,16 +182,8 @@ export class NumericalClassifierStrategy implements RoutingStrategy {
|
||||
config,
|
||||
config.getSessionId() || 'unknown-session',
|
||||
);
|
||||
const useGemini3_1 = (await config.getGemini31Launched?.()) ?? false;
|
||||
const useCustomToolModel =
|
||||
useGemini3_1 &&
|
||||
config.getContentGeneratorConfig().authType === AuthType.USE_GEMINI;
|
||||
const selectedModel = resolveClassifierModel(
|
||||
model,
|
||||
modelAlias,
|
||||
useGemini3_1,
|
||||
useCustomToolModel,
|
||||
);
|
||||
|
||||
const selectedModel = resolveClassifierModel(model, modelAlias);
|
||||
|
||||
const latencyMs = Date.now() - startTime;
|
||||
|
||||
|
||||
@@ -33,10 +33,7 @@ export class OverrideStrategy implements RoutingStrategy {
|
||||
|
||||
// Return the overridden model name.
|
||||
return {
|
||||
model: resolveModel(
|
||||
overrideModel,
|
||||
config.getGemini31LaunchedSync?.() ?? false,
|
||||
),
|
||||
model: resolveModel(overrideModel),
|
||||
metadata: {
|
||||
source: this.name,
|
||||
latencyMs: 0,
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
} from '../config/models.js';
|
||||
import { PreCompressTrigger } from '../hooks/types.js';
|
||||
import { LlmRole } from '../telemetry/types.js';
|
||||
@@ -102,7 +101,6 @@ export function findCompressSplitPoint(
|
||||
export function modelStringToModelConfigAlias(model: string): string {
|
||||
switch (model) {
|
||||
case PREVIEW_GEMINI_MODEL:
|
||||
case PREVIEW_GEMINI_3_1_MODEL:
|
||||
return 'chat-compression-3-pro';
|
||||
case PREVIEW_GEMINI_FLASH_MODEL:
|
||||
return 'chat-compression-3-flash';
|
||||
|
||||
@@ -373,6 +373,182 @@ describe('EditTool', () => {
|
||||
expect(result.occurrences).toBe(1);
|
||||
});
|
||||
|
||||
it('should perform a fuzzy replacement when exact match fails but similarity is high', async () => {
|
||||
const content =
|
||||
'const myConfig = {\n enableFeature: true,\n retries: 3\n};';
|
||||
// Typo: missing comma after true
|
||||
const oldString =
|
||||
'const myConfig = {\n enableFeature: true\n retries: 3\n};';
|
||||
const newString =
|
||||
'const myConfig = {\n enableFeature: false,\n retries: 5\n};';
|
||||
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'config.ts',
|
||||
instruction: 'update config',
|
||||
old_string: oldString,
|
||||
new_string: newString,
|
||||
},
|
||||
currentContent: content,
|
||||
abortSignal,
|
||||
});
|
||||
|
||||
expect(result.occurrences).toBe(1);
|
||||
expect(result.newContent).toBe(newString);
|
||||
});
|
||||
|
||||
it('should NOT perform a fuzzy replacement when similarity is below threshold', async () => {
|
||||
const content =
|
||||
'const myConfig = {\n enableFeature: true,\n retries: 3\n};';
|
||||
// Completely different string
|
||||
const oldString = 'function somethingElse() {\n return false;\n}';
|
||||
const newString =
|
||||
'const myConfig = {\n enableFeature: false,\n retries: 5\n};';
|
||||
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'config.ts',
|
||||
instruction: 'update config',
|
||||
old_string: oldString,
|
||||
new_string: newString,
|
||||
},
|
||||
currentContent: content,
|
||||
abortSignal,
|
||||
});
|
||||
|
||||
expect(result.occurrences).toBe(0);
|
||||
expect(result.newContent).toBe(content);
|
||||
});
|
||||
|
||||
it('should NOT perform a fuzzy replacement when the complexity (length * size) is too high', async () => {
|
||||
// 2000 chars
|
||||
const longString = 'a'.repeat(2000);
|
||||
|
||||
// Create a file with enough lines to trigger the complexity limit
|
||||
// Complexity = Lines * Length^2
|
||||
// Threshold = 500,000,000
|
||||
// 2000^2 = 4,000,000.
|
||||
// Need > 125 lines. Let's use 200 lines.
|
||||
const lines = Array(200).fill(longString);
|
||||
const content = lines.join('\n');
|
||||
|
||||
// Mismatch at the end (making it a fuzzy match candidate)
|
||||
const oldString = longString + 'c';
|
||||
const newString = 'replacement';
|
||||
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'test.ts',
|
||||
instruction: 'update',
|
||||
old_string: oldString,
|
||||
new_string: newString,
|
||||
},
|
||||
currentContent: content,
|
||||
abortSignal,
|
||||
});
|
||||
|
||||
// Should return 0 occurrences because fuzzy match is skipped
|
||||
expect(result.occurrences).toBe(0);
|
||||
expect(result.newContent).toBe(content);
|
||||
});
|
||||
|
||||
it('should perform multiple fuzzy replacements if multiple valid matches are found', async () => {
|
||||
const content = `
|
||||
function doIt() {
|
||||
console.log("hello");
|
||||
}
|
||||
|
||||
function doIt() {
|
||||
console.log("hello");
|
||||
}
|
||||
`;
|
||||
// old_string uses single quotes, file uses double.
|
||||
// This is a fuzzy match (quote difference).
|
||||
const oldString = `
|
||||
function doIt() {
|
||||
console.log('hello');
|
||||
}
|
||||
`.trim();
|
||||
|
||||
const newString = `
|
||||
function doIt() {
|
||||
console.log("bye");
|
||||
}
|
||||
`.trim();
|
||||
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'test.ts',
|
||||
instruction: 'update',
|
||||
old_string: oldString,
|
||||
new_string: newString,
|
||||
},
|
||||
currentContent: content,
|
||||
abortSignal,
|
||||
});
|
||||
|
||||
expect(result.occurrences).toBe(2);
|
||||
const expectedContent = `
|
||||
function doIt() {
|
||||
console.log("bye");
|
||||
}
|
||||
|
||||
function doIt() {
|
||||
console.log("bye");
|
||||
}
|
||||
`;
|
||||
expect(result.newContent).toBe(expectedContent);
|
||||
});
|
||||
|
||||
it('should correctly rebase indentation in flexible replacement without double-indenting', async () => {
|
||||
const content = ' if (a) {\n foo();\n }\n';
|
||||
// old_string and new_string are unindented. They should be rebased to 4-space.
|
||||
const oldString = 'if (a) {\n foo();\n}';
|
||||
const newString = 'if (a) {\n bar();\n}';
|
||||
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'test.ts',
|
||||
old_string: oldString,
|
||||
new_string: newString,
|
||||
},
|
||||
currentContent: content,
|
||||
abortSignal,
|
||||
});
|
||||
|
||||
expect(result.occurrences).toBe(1);
|
||||
// foo() was at 8 spaces (4 base + 4 indent).
|
||||
// newString has bar() at 4 spaces (0 base + 4 indent).
|
||||
// Rebased to 4 base, it should be 4 + 4 = 8 spaces.
|
||||
const expectedContent = ' if (a) {\n bar();\n }\n';
|
||||
expect(result.newContent).toBe(expectedContent);
|
||||
});
|
||||
|
||||
it('should correctly rebase indentation in fuzzy replacement without double-indenting', async () => {
|
||||
const content =
|
||||
' const myConfig = {\n enableFeature: true,\n retries: 3\n };';
|
||||
// Typo: missing comma. old_string/new_string are unindented.
|
||||
const fuzzyOld =
|
||||
'const myConfig = {\n enableFeature: true\n retries: 3\n};';
|
||||
const fuzzyNew =
|
||||
'const myConfig = {\n enableFeature: false,\n retries: 5\n};';
|
||||
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'test.ts',
|
||||
old_string: fuzzyOld,
|
||||
new_string: fuzzyNew,
|
||||
},
|
||||
currentContent: content,
|
||||
abortSignal,
|
||||
});
|
||||
|
||||
expect(result.strategy).toBe('fuzzy');
|
||||
const expectedContent =
|
||||
' const myConfig = {\n enableFeature: false,\n retries: 5\n };';
|
||||
expect(result.newContent).toBe(expectedContent);
|
||||
});
|
||||
|
||||
it('should NOT insert extra newlines when replacing a block preceded by a blank line (regression)', async () => {
|
||||
const content = '\n function oldFunc() {\n // some code\n }';
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
|
||||
@@ -49,8 +49,13 @@ import {
|
||||
EDIT_DISPLAY_NAME,
|
||||
} from './tool-names.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import levenshtein from 'fast-levenshtein';
|
||||
import { EDIT_DEFINITION } from './definitions/coreTools.js';
|
||||
import { resolveToolDeclaration } from './definitions/resolver.js';
|
||||
|
||||
const ENABLE_FUZZY_MATCH_RECOVERY = true;
|
||||
const FUZZY_MATCH_THRESHOLD = 0.1; // Allow up to 10% weighted difference
|
||||
const WHITESPACE_PENALTY_FACTOR = 0.1; // Whitespace differences cost 10% of a character difference
|
||||
interface ReplacementContext {
|
||||
params: EditToolParams;
|
||||
currentContent: string;
|
||||
@@ -62,6 +67,8 @@ interface ReplacementResult {
|
||||
occurrences: number;
|
||||
finalOldString: string;
|
||||
finalNewString: string;
|
||||
strategy?: 'exact' | 'flexible' | 'regex' | 'fuzzy';
|
||||
matchRanges?: Array<{ start: number; end: number }>;
|
||||
}
|
||||
|
||||
export function applyReplacement(
|
||||
@@ -176,9 +183,7 @@ async function calculateFlexibleReplacement(
|
||||
const firstLineInMatch = window[0];
|
||||
const indentationMatch = firstLineInMatch.match(/^([ \t]*)/);
|
||||
const indentation = indentationMatch ? indentationMatch[1] : '';
|
||||
const newBlockWithIndent = replaceLines.map(
|
||||
(line: string) => `${indentation}${line}`,
|
||||
);
|
||||
const newBlockWithIndent = applyIndentation(replaceLines, indentation);
|
||||
sourceLines.splice(
|
||||
i,
|
||||
searchLinesStripped.length,
|
||||
@@ -247,9 +252,7 @@ async function calculateRegexReplacement(
|
||||
|
||||
const indentation = match[1] || '';
|
||||
const newLines = normalizedReplace.split('\n');
|
||||
const newBlockWithIndent = newLines
|
||||
.map((line) => `${indentation}${line}`)
|
||||
.join('\n');
|
||||
const newBlockWithIndent = applyIndentation(newLines, indentation).join('\n');
|
||||
|
||||
// Use replace with the regex to substitute the matched content.
|
||||
// Since the regex doesn't have the 'g' flag, it will only replace the first occurrence.
|
||||
@@ -305,6 +308,14 @@ export async function calculateReplacement(
|
||||
return regexResult;
|
||||
}
|
||||
|
||||
let fuzzyResult;
|
||||
if (
|
||||
ENABLE_FUZZY_MATCH_RECOVERY &&
|
||||
(fuzzyResult = await calculateFuzzyReplacement(config, context))
|
||||
) {
|
||||
return fuzzyResult;
|
||||
}
|
||||
|
||||
return {
|
||||
newContent: currentContent,
|
||||
occurrences: 0,
|
||||
@@ -395,6 +406,8 @@ interface CalculatedEdit {
|
||||
error?: { display: string; raw: string; type: ToolErrorType };
|
||||
isNewFile: boolean;
|
||||
originalLineEnding: '\r\n' | '\n';
|
||||
strategy?: 'exact' | 'flexible' | 'regex' | 'fuzzy';
|
||||
matchRanges?: Array<{ start: number; end: number }>;
|
||||
}
|
||||
|
||||
class EditToolInvocation
|
||||
@@ -520,6 +533,8 @@ class EditToolInvocation
|
||||
isNewFile: false,
|
||||
error: undefined,
|
||||
originalLineEnding,
|
||||
strategy: secondAttemptResult.strategy,
|
||||
matchRanges: secondAttemptResult.matchRanges,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -633,6 +648,8 @@ class EditToolInvocation
|
||||
isNewFile: false,
|
||||
error: undefined,
|
||||
originalLineEnding,
|
||||
strategy: replacementResult.strategy,
|
||||
matchRanges: replacementResult.matchRanges,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -859,6 +876,10 @@ class EditToolInvocation
|
||||
? `Created new file: ${this.params.file_path} with provided content.`
|
||||
: `Successfully modified file: ${this.params.file_path} (${editData.occurrences} replacements).`,
|
||||
];
|
||||
const fuzzyFeedback = getFuzzyMatchFeedback(editData);
|
||||
if (fuzzyFeedback) {
|
||||
llmSuccessMessageParts.push(fuzzyFeedback);
|
||||
}
|
||||
if (this.params.modified_by_user) {
|
||||
llmSuccessMessageParts.push(
|
||||
`User modified the \`new_string\` content to be: ${this.params.new_string}.`,
|
||||
@@ -1011,3 +1032,188 @@ export class EditTool
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function stripWhitespace(str: string): string {
|
||||
return str.replace(/\s/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the target indentation to the lines, while preserving relative indentation.
|
||||
* It identifies the common indentation of the provided lines and replaces it with the target indentation.
|
||||
*/
|
||||
function applyIndentation(
|
||||
lines: string[],
|
||||
targetIndentation: string,
|
||||
): string[] {
|
||||
if (lines.length === 0) return [];
|
||||
|
||||
// Use the first line as the reference for indentation, even if it's empty/whitespace.
|
||||
// This is because flexible/fuzzy matching identifies the indentation of the START of the match.
|
||||
const referenceLine = lines[0];
|
||||
const refIndentMatch = referenceLine.match(/^([ \t]*)/);
|
||||
const refIndent = refIndentMatch ? refIndentMatch[1] : '';
|
||||
|
||||
return lines.map((line) => {
|
||||
if (line.trim() === '') {
|
||||
return '';
|
||||
}
|
||||
if (line.startsWith(refIndent)) {
|
||||
return targetIndentation + line.slice(refIndent.length);
|
||||
}
|
||||
return targetIndentation + line.trimStart();
|
||||
});
|
||||
}
|
||||
|
||||
function getFuzzyMatchFeedback(editData: CalculatedEdit): string | null {
|
||||
if (
|
||||
editData.strategy === 'fuzzy' &&
|
||||
editData.matchRanges &&
|
||||
editData.matchRanges.length > 0
|
||||
) {
|
||||
const ranges = editData.matchRanges
|
||||
.map((r) => (r.start === r.end ? `${r.start}` : `${r.start}-${r.end}`))
|
||||
.join(', ');
|
||||
return `Applied fuzzy match at line${editData.matchRanges.length > 1 ? 's' : ''} ${ranges}.`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function calculateFuzzyReplacement(
|
||||
config: Config,
|
||||
context: ReplacementContext,
|
||||
): Promise<ReplacementResult | null> {
|
||||
const { currentContent, params } = context;
|
||||
const { old_string, new_string } = params;
|
||||
|
||||
// Pre-check: Don't fuzzy match very short strings to avoid false positives
|
||||
if (old_string.length < 10) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedCode = currentContent.replace(/\r\n/g, '\n');
|
||||
const normalizedSearch = old_string.replace(/\r\n/g, '\n');
|
||||
const normalizedReplace = new_string.replace(/\r\n/g, '\n');
|
||||
|
||||
const sourceLines = normalizedCode.match(/.*(?:\n|$)/g)?.slice(0, -1) ?? [];
|
||||
const searchLines = normalizedSearch
|
||||
.match(/.*(?:\n|$)/g)
|
||||
?.slice(0, -1)
|
||||
.map((l) => l.trimEnd()); // Trim end of search lines to be more robust
|
||||
|
||||
// Limit the scope of the fuzzy match to reduce impact on responsivesness.
|
||||
// Each comparison takes roughly O(L^2) time.
|
||||
// We perform sourceLines.length comparisons (sliding window).
|
||||
// Total complexity proxy: sourceLines.length * old_string.length^2
|
||||
// Limit to 4e8 for < 1 second.
|
||||
if (sourceLines.length * Math.pow(old_string.length, 2) > 400_000_000) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!searchLines || searchLines.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const N = searchLines.length;
|
||||
const candidates: Array<{ index: number; score: number }> = [];
|
||||
const searchBlock = searchLines.join('\n');
|
||||
|
||||
// Sliding window
|
||||
for (let i = 0; i <= sourceLines.length - N; i++) {
|
||||
const windowLines = sourceLines.slice(i, i + N);
|
||||
const windowText = windowLines.map((l) => l.trimEnd()).join('\n'); // Normalized join for comparison
|
||||
|
||||
// Length Heuristic Optimization
|
||||
const lengthDiff = Math.abs(windowText.length - searchBlock.length);
|
||||
if (
|
||||
lengthDiff / searchBlock.length >
|
||||
FUZZY_MATCH_THRESHOLD / WHITESPACE_PENALTY_FACTOR
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tiered Scoring
|
||||
const d_raw = levenshtein.get(windowText, searchBlock);
|
||||
const d_norm = levenshtein.get(
|
||||
stripWhitespace(windowText),
|
||||
stripWhitespace(searchBlock),
|
||||
);
|
||||
|
||||
const weightedDist = d_norm + (d_raw - d_norm) * WHITESPACE_PENALTY_FACTOR;
|
||||
const score = weightedDist / searchBlock.length;
|
||||
|
||||
if (score <= FUZZY_MATCH_THRESHOLD) {
|
||||
candidates.push({ index: i, score });
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Select best non-overlapping matches
|
||||
// Sort by score ascending. If scores equal, prefer earlier index (stable sort).
|
||||
candidates.sort((a, b) => a.score - b.score || a.index - b.index);
|
||||
|
||||
const selectedMatches: Array<{ index: number; score: number }> = [];
|
||||
for (const candidate of candidates) {
|
||||
// Check for overlap with already selected matches
|
||||
// Two windows overlap if their start indices are within N lines of each other
|
||||
// (Assuming window size N. Actually overlap is |i - j| < N)
|
||||
const overlaps = selectedMatches.some(
|
||||
(m) => Math.abs(m.index - candidate.index) < N,
|
||||
);
|
||||
if (!overlaps) {
|
||||
selectedMatches.push(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
// If we found matches, apply them
|
||||
if (selectedMatches.length > 0) {
|
||||
const event = new EditStrategyEvent('fuzzy');
|
||||
logEditStrategy(config, event);
|
||||
|
||||
// Calculate match ranges before sorting for replacement
|
||||
// Indices in selectedMatches are 0-based line indices
|
||||
const matchRanges = selectedMatches
|
||||
.map((m) => ({ start: m.index + 1, end: m.index + N }))
|
||||
.sort((a, b) => a.start - b.start);
|
||||
|
||||
// Sort matches by index descending to apply replacements from bottom to top
|
||||
// so that indices remain valid
|
||||
selectedMatches.sort((a, b) => b.index - a.index);
|
||||
|
||||
const newLines = normalizedReplace.split('\n');
|
||||
|
||||
for (const match of selectedMatches) {
|
||||
// If we want to preserve the indentation of the first line of the match:
|
||||
const firstLineMatch = sourceLines[match.index];
|
||||
const indentationMatch = firstLineMatch.match(/^([ \t]*)/);
|
||||
const indentation = indentationMatch ? indentationMatch[1] : '';
|
||||
|
||||
const indentedReplaceLines = applyIndentation(newLines, indentation);
|
||||
|
||||
let replacementText = indentedReplaceLines.join('\n');
|
||||
// If the last line of the match had a newline, preserve it in the replacement
|
||||
// to avoid merging with the next line or losing a blank line separator.
|
||||
if (sourceLines[match.index + N - 1].endsWith('\n')) {
|
||||
replacementText += '\n';
|
||||
}
|
||||
|
||||
sourceLines.splice(match.index, N, replacementText);
|
||||
}
|
||||
|
||||
let modifiedCode = sourceLines.join('');
|
||||
modifiedCode = restoreTrailingNewline(currentContent, modifiedCode);
|
||||
|
||||
return {
|
||||
newContent: modifiedCode,
|
||||
occurrences: selectedMatches.length,
|
||||
finalOldString: normalizedSearch,
|
||||
finalNewString: normalizedReplace,
|
||||
strategy: 'fuzzy',
|
||||
matchRanges,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -235,8 +235,8 @@ describe('LSTool', () => {
|
||||
|
||||
expect(entries[0]).toBe('[DIR] x-dir');
|
||||
expect(entries[1]).toBe('[DIR] y-dir');
|
||||
expect(entries[2]).toBe('a-file.txt');
|
||||
expect(entries[3]).toBe('b-file.txt');
|
||||
expect(entries[2]).toBe('a-file.txt (8 bytes)');
|
||||
expect(entries[3]).toBe('b-file.txt (8 bytes)');
|
||||
});
|
||||
|
||||
it('should handle permission errors gracefully', async () => {
|
||||
|
||||
@@ -241,7 +241,12 @@ class LSToolInvocation extends BaseToolInvocation<LSToolParams, ToolResult> {
|
||||
|
||||
// Create formatted content for LLM
|
||||
const directoryContent = entries
|
||||
.map((entry) => `${entry.isDirectory ? '[DIR] ' : ''}${entry.name}`)
|
||||
.map((entry) => {
|
||||
if (entry.isDirectory) {
|
||||
return `[DIR] ${entry.name}`;
|
||||
}
|
||||
return `${entry.name} (${entry.size} bytes)`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
let resultMessage = `Directory listing for ${resolvedDirPath}:\n${directoryContent}`;
|
||||
|
||||
@@ -22,6 +22,7 @@ import fs from 'node:fs';
|
||||
import { MockTool } from '../test-utils/mock-tool.js';
|
||||
import { ToolErrorType } from './tool-error.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
|
||||
vi.mock('node:fs');
|
||||
|
||||
@@ -216,6 +217,7 @@ describe('ToolRegistry', () => {
|
||||
unsubscribe: vi.fn(),
|
||||
} as unknown as MessageBus;
|
||||
let mockConfigGetToolDiscoveryCommand: ReturnType<typeof vi.spyOn>;
|
||||
let mockConfigGetEnableToolDiscovery: ReturnType<typeof vi.spyOn>;
|
||||
let mockConfigGetExcludedTools: MockInstance<
|
||||
typeof Config.prototype.getExcludeTools
|
||||
>;
|
||||
@@ -242,6 +244,12 @@ describe('ToolRegistry', () => {
|
||||
config,
|
||||
'getToolDiscoveryCommand',
|
||||
);
|
||||
mockConfigGetEnableToolDiscovery = vi.spyOn(
|
||||
config,
|
||||
'getEnableToolDiscovery',
|
||||
);
|
||||
mockConfigGetEnableToolDiscovery.mockReturnValue(true);
|
||||
|
||||
mockConfigGetExcludedTools = vi.spyOn(config, 'getExcludeTools');
|
||||
vi.spyOn(config, 'getMcpServers');
|
||||
vi.spyOn(config, 'getMcpServerCommand');
|
||||
@@ -472,6 +480,22 @@ describe('ToolRegistry', () => {
|
||||
});
|
||||
|
||||
describe('discoverTools', () => {
|
||||
it('should warn and skip discovery if enableToolDiscovery is false', async () => {
|
||||
const discoveryCommand = 'my-discovery-command';
|
||||
mockConfigGetToolDiscoveryCommand.mockReturnValue(discoveryCommand);
|
||||
mockConfigGetEnableToolDiscovery.mockReturnValue(false);
|
||||
|
||||
const warnSpy = vi.spyOn(debugLogger, 'warn');
|
||||
const mockSpawn = vi.mocked(spawn);
|
||||
|
||||
await toolRegistry.discoverAllTools();
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Tool discovery is disabled by default'),
|
||||
);
|
||||
expect(mockSpawn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should will preserve tool parametersJsonSchema during discovery from command', async () => {
|
||||
const discoveryCommand = 'my-discovery-command';
|
||||
mockConfigGetToolDiscoveryCommand.mockReturnValue(discoveryCommand);
|
||||
|
||||
@@ -314,6 +314,13 @@ export class ToolRegistry {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.config.getEnableToolDiscovery()) {
|
||||
debugLogger.warn(
|
||||
'Tool discovery is disabled by default. Please enable it in settings to use discovered tools.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const cmdParts = parse(discoveryCmd);
|
||||
if (cmdParts.length === 0) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-sdk",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.29.0-nightly.20260203.71f46f116",
|
||||
"description": "Gemini CLI SDK",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-test-utils",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -208,6 +208,7 @@ export interface ParsedLog {
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
error?: string;
|
||||
prompt_id?: string;
|
||||
};
|
||||
scopeMetrics?: {
|
||||
metrics: {
|
||||
@@ -1051,6 +1052,7 @@ export class TestRig {
|
||||
args: string;
|
||||
success: boolean;
|
||||
duration_ms: number;
|
||||
prompt_id?: string;
|
||||
};
|
||||
}[] = [];
|
||||
|
||||
@@ -1079,6 +1081,13 @@ export class TestRig {
|
||||
args = argsMatch[1];
|
||||
}
|
||||
|
||||
// Look for prompt_id in the context
|
||||
let promptId = undefined;
|
||||
const promptIdMatch = context.match(/prompt_id:\s*'([^']+)'/);
|
||||
if (promptIdMatch) {
|
||||
promptId = promptIdMatch[1];
|
||||
}
|
||||
|
||||
// Also try to find function_name to double-check
|
||||
// Updated regex to handle tool names with hyphens and underscores
|
||||
const nameMatch = context.match(/function_name:\s*'([\w-]+)'/);
|
||||
@@ -1091,6 +1100,7 @@ export class TestRig {
|
||||
args: args,
|
||||
success: success,
|
||||
duration_ms: duration,
|
||||
prompt_id: promptId,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1138,6 +1148,7 @@ export class TestRig {
|
||||
args: obj.attributes.function_args || '{}',
|
||||
success: obj.attributes.success !== false,
|
||||
duration_ms: obj.attributes.duration_ms || 0,
|
||||
prompt_id: obj.attributes.prompt_id,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1152,6 +1163,7 @@ export class TestRig {
|
||||
args: obj.attributes.function_args,
|
||||
success: obj.attributes.success,
|
||||
duration_ms: obj.attributes.duration_ms,
|
||||
prompt_id: obj.attributes.prompt_id,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1242,6 +1254,7 @@ export class TestRig {
|
||||
args: string;
|
||||
success: boolean;
|
||||
duration_ms: number;
|
||||
prompt_id?: string;
|
||||
};
|
||||
}[] = [];
|
||||
|
||||
@@ -1258,6 +1271,7 @@ export class TestRig {
|
||||
args: logData.attributes.function_args ?? '{}',
|
||||
success: logData.attributes.success ?? false,
|
||||
duration_ms: logData.attributes.duration_ms ?? 0,
|
||||
prompt_id: logData.attributes.prompt_id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,9 +49,6 @@ async function main() {
|
||||
define: {
|
||||
'import.meta.url': 'import_meta.url',
|
||||
},
|
||||
alias: {
|
||||
punycode: 'punycode/',
|
||||
},
|
||||
plugins: [
|
||||
/* add to the end of plugins array */
|
||||
esbuildProblemMatcherPlugin,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "gemini-cli-vscode-ide-companion",
|
||||
"displayName": "Gemini CLI Companion",
|
||||
"description": "Enable Gemini CLI with direct access to your IDE workspace.",
|
||||
"version": "0.30.0-preview.6",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"publisher": "google",
|
||||
"icon": "assets/icon.png",
|
||||
"repository": {
|
||||
|
||||
@@ -1251,6 +1251,13 @@
|
||||
"markdownDescription": "Command to run for tool discovery.\n\n- Category: `Tools`\n- Requires restart: `yes`",
|
||||
"type": "string"
|
||||
},
|
||||
"enableToolDiscovery": {
|
||||
"title": "Enable Tool Discovery",
|
||||
"description": "Enables the tool discovery feature using `tools.discoveryCommand`.",
|
||||
"markdownDescription": "Enables the tool discovery feature using `tools.discoveryCommand`.\n\n- Category: `Tools`\n- Requires restart: `yes`\n- Default: `false`",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"callCommand": {
|
||||
"title": "Tool Call Command",
|
||||
"description": "Defines a custom shell command for invoking discovered tools. The command must take the tool name as the first argument, read JSON arguments from stdin, and emit JSON results on stdout.",
|
||||
|
||||
Reference in New Issue
Block a user