mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-16 09:01:17 -07:00
feat: support structured tool results and preserve terminal state context
Establishes core data contracts and state management for enhanced tool outputs. - Introduces GrepResult, ListDirectoryResult, and ReadManyFilesResult types. - Updates tools to return structured data instead of flat strings. - Preserves confirmation details and diff stats in terminal tool states. - Ensures backward compatibility in standard CLI views via safe type guards.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
import { type FunctionCall } from '@google/genai';
|
||||
import type {
|
||||
DiffStat,
|
||||
ToolConfirmationOutcome,
|
||||
ToolConfirmationPayload,
|
||||
} from '../tools/tools.js';
|
||||
@@ -79,6 +80,7 @@ export type SerializableConfirmationDetails =
|
||||
fileDiff: string;
|
||||
originalContent: string | null;
|
||||
newContent: string;
|
||||
diffStat?: DiffStat;
|
||||
isModifying?: boolean;
|
||||
}
|
||||
| {
|
||||
|
||||
@@ -346,6 +346,8 @@ export class SchedulerStateManager {
|
||||
response: ToolCallResponseInfo,
|
||||
): ErroredToolCall {
|
||||
const startTime = 'startTime' in call ? call.startTime : undefined;
|
||||
const confirmationDetails =
|
||||
'confirmationDetails' in call ? call.confirmationDetails : undefined;
|
||||
return {
|
||||
request: call.request,
|
||||
status: 'error',
|
||||
@@ -353,6 +355,7 @@ export class SchedulerStateManager {
|
||||
response,
|
||||
durationMs: startTime ? Date.now() - startTime : undefined,
|
||||
outcome: call.outcome,
|
||||
confirmationDetails,
|
||||
schedulerId: call.schedulerId,
|
||||
};
|
||||
}
|
||||
@@ -415,6 +418,8 @@ export class SchedulerStateManager {
|
||||
private toCancelled(call: ToolCall, reason: string): CancelledToolCall {
|
||||
this.validateHasToolAndInvocation(call, 'cancelled');
|
||||
const startTime = 'startTime' in call ? call.startTime : undefined;
|
||||
const confirmationDetails =
|
||||
'confirmationDetails' in call ? call.confirmationDetails : undefined;
|
||||
|
||||
// TODO: Refactor this tool-specific logic into the confirmation details payload.
|
||||
// See: https://github.com/google-gemini/gemini-cli/issues/16716
|
||||
@@ -463,6 +468,7 @@ export class SchedulerStateManager {
|
||||
},
|
||||
durationMs: startTime ? Date.now() - startTime : undefined,
|
||||
outcome: call.outcome,
|
||||
confirmationDetails,
|
||||
schedulerId: call.schedulerId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,6 +71,9 @@ export type ErroredToolCall = {
|
||||
tool?: AnyDeclarativeTool;
|
||||
durationMs?: number;
|
||||
outcome?: ToolConfirmationOutcome;
|
||||
confirmationDetails?:
|
||||
| ToolCallConfirmationDetails
|
||||
| SerializableConfirmationDetails;
|
||||
schedulerId?: string;
|
||||
};
|
||||
|
||||
@@ -105,6 +108,9 @@ export type CancelledToolCall = {
|
||||
invocation: AnyToolInvocation;
|
||||
durationMs?: number;
|
||||
outcome?: ToolConfirmationOutcome;
|
||||
confirmationDetails?:
|
||||
| ToolCallConfirmationDetails
|
||||
| SerializableConfirmationDetails;
|
||||
schedulerId?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -687,6 +687,14 @@ class EditToolInvocation
|
||||
'Proposed',
|
||||
DEFAULT_DIFF_OPTIONS,
|
||||
);
|
||||
|
||||
const diffStat = getDiffStat(
|
||||
fileName,
|
||||
editData.currentContent ?? '',
|
||||
editData.newContent,
|
||||
this.params.new_string,
|
||||
);
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const ideConfirmation =
|
||||
this.config.getIdeMode() && ideClient.isDiffingEnabled()
|
||||
@@ -701,6 +709,7 @@ class EditToolInvocation
|
||||
fileDiff,
|
||||
originalContent: editData.currentContent,
|
||||
newContent: editData.newContent,
|
||||
diffStat,
|
||||
onConfirm: async (outcome: ToolConfirmationOutcome) => {
|
||||
if (outcome === ToolConfirmationOutcome.ProceedAlways) {
|
||||
// No need to publish a policy update as the default policy for
|
||||
|
||||
@@ -286,7 +286,14 @@ class GrepToolInvocation extends BaseToolInvocation<
|
||||
|
||||
return {
|
||||
llmContent: llmContent.trim(),
|
||||
returnDisplay: `Found ${matchCount} ${matchTerm}${wasTruncated ? ' (limited)' : ''}`,
|
||||
returnDisplay: {
|
||||
summary: `Found ${matchCount} ${matchTerm}${wasTruncated ? ' (limited)' : ''}`,
|
||||
matches: allMatches.map((m) => ({
|
||||
filePath: m.filePath,
|
||||
lineNumber: m.lineNumber,
|
||||
line: m.line,
|
||||
})),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
debugLogger.warn(`Error during GrepLogic execution: ${error}`);
|
||||
|
||||
@@ -254,7 +254,12 @@ class LSToolInvocation extends BaseToolInvocation<LSToolParams, ToolResult> {
|
||||
|
||||
return {
|
||||
llmContent: resultMessage,
|
||||
returnDisplay: displayMessage,
|
||||
returnDisplay: {
|
||||
summary: displayMessage,
|
||||
files: entries.map(
|
||||
(entry) => `${entry.isDirectory ? '[DIR] ' : ''}${entry.name}`,
|
||||
),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMsg = `Error listing directory: ${error instanceof Error ? error.message : String(error)}`;
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
*/
|
||||
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import type { ToolInvocation, ToolResult } from './tools.js';
|
||||
import type {
|
||||
ToolInvocation,
|
||||
ToolResult,
|
||||
ReadManyFilesResult,
|
||||
} from './tools.js';
|
||||
import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js';
|
||||
import { getErrorMessage } from '../utils/errors.js';
|
||||
import * as fsPromises from 'node:fs/promises';
|
||||
@@ -441,9 +445,22 @@ ${finalExclusionPatternsForDescription
|
||||
'No files matching the criteria were found or all were skipped.',
|
||||
);
|
||||
}
|
||||
|
||||
const returnDisplay: ReadManyFilesResult = {
|
||||
summary:
|
||||
processedFilesRelativePaths.length > 0
|
||||
? `Read ${processedFilesRelativePaths.length} file(s)`
|
||||
: 'No files read',
|
||||
files: processedFilesRelativePaths,
|
||||
skipped: skippedFiles,
|
||||
include: this.params.include,
|
||||
excludes: effectiveExcludes,
|
||||
targetDir: this.config.getTargetDir(),
|
||||
};
|
||||
|
||||
return {
|
||||
llmContent: contentParts,
|
||||
returnDisplay: displayMessage.trim(),
|
||||
returnDisplay,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,7 +664,40 @@ export interface TodoList {
|
||||
todos: Todo[];
|
||||
}
|
||||
|
||||
export type ToolResultDisplay = string | FileDiff | AnsiOutput | TodoList;
|
||||
export interface GrepResult {
|
||||
summary: string;
|
||||
matches: Array<{
|
||||
filePath: string;
|
||||
lineNumber: number;
|
||||
line: string;
|
||||
}>;
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
export interface ListDirectoryResult {
|
||||
summary: string;
|
||||
files: string[];
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
export interface ReadManyFilesResult {
|
||||
summary: string;
|
||||
files: string[];
|
||||
skipped?: Array<{ path: string; reason: string }>;
|
||||
include?: string[];
|
||||
excludes?: string[];
|
||||
targetDir?: string;
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
export type ToolResultDisplay =
|
||||
| string
|
||||
| FileDiff
|
||||
| AnsiOutput
|
||||
| TodoList
|
||||
| GrepResult
|
||||
| ListDirectoryResult
|
||||
| ReadManyFilesResult;
|
||||
|
||||
export type TodoStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled';
|
||||
|
||||
@@ -706,6 +739,7 @@ export interface ToolEditConfirmationDetails {
|
||||
fileDiff: string;
|
||||
originalContent: string | null;
|
||||
newContent: string;
|
||||
diffStat?: DiffStat;
|
||||
isModifying?: boolean;
|
||||
ideConfirmation?: Promise<DiffUpdateResult>;
|
||||
}
|
||||
|
||||
@@ -212,6 +212,15 @@ class WriteFileToolInvocation extends BaseToolInvocation<
|
||||
DEFAULT_DIFF_OPTIONS,
|
||||
);
|
||||
|
||||
const originallyProposedContent =
|
||||
this.params.ai_proposed_content || this.params.content;
|
||||
const diffStat = getDiffStat(
|
||||
fileName,
|
||||
originalContent,
|
||||
originallyProposedContent,
|
||||
this.params.content,
|
||||
);
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const ideConfirmation =
|
||||
this.config.getIdeMode() && ideClient.isDiffingEnabled()
|
||||
@@ -226,6 +235,7 @@ class WriteFileToolInvocation extends BaseToolInvocation<
|
||||
fileDiff,
|
||||
originalContent,
|
||||
newContent: correctedContent,
|
||||
diffStat,
|
||||
onConfirm: async (outcome: ToolConfirmationOutcome) => {
|
||||
if (outcome === ToolConfirmationOutcome.ProceedAlways) {
|
||||
// No need to publish a policy update as the default policy for
|
||||
|
||||
Reference in New Issue
Block a user