mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-12 23:21:27 -07:00
189 lines
5.2 KiB
TypeScript
189 lines
5.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {
|
|
BaseDeclarativeTool,
|
|
BaseToolInvocation,
|
|
Kind,
|
|
type ToolInvocation,
|
|
type ToolResult,
|
|
type ToolCallConfirmationDetails,
|
|
} from './tools.js';
|
|
import { GET_INTERNAL_DOCS_TOOL_NAME } from './tool-names.js';
|
|
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { glob } from 'glob';
|
|
import { ToolErrorType } from './tool-error.js';
|
|
import { GET_INTERNAL_DOCS_DEFINITION } from './definitions/coreTools.js';
|
|
import { resolveToolDeclaration } from './definitions/resolver.js';
|
|
|
|
/**
|
|
* Parameters for the GetInternalDocs tool.
|
|
*/
|
|
export interface GetInternalDocsParams {
|
|
/**
|
|
* The relative path to a specific documentation file (e.g., 'cli/commands.md').
|
|
* If omitted, the tool will return a list of all available documentation paths.
|
|
*/
|
|
path?: string;
|
|
}
|
|
|
|
/**
|
|
* Helper to find the absolute path to the documentation directory.
|
|
*/
|
|
async function getDocsRoot(): Promise<string> {
|
|
const currentFile = fileURLToPath(import.meta.url);
|
|
let searchDir = path.dirname(currentFile);
|
|
|
|
const isDocsDir = async (dir: string) => {
|
|
try {
|
|
const stats = await fs.stat(dir);
|
|
if (stats.isDirectory()) {
|
|
const marker = path.join(dir, 'sidebar.json');
|
|
await fs.access(marker);
|
|
return true;
|
|
}
|
|
} catch {
|
|
// Not a valid docs directory
|
|
}
|
|
return false;
|
|
};
|
|
|
|
while (true) {
|
|
const candidate = path.join(searchDir, 'docs');
|
|
if (await isDocsDir(candidate)) {
|
|
return candidate;
|
|
}
|
|
|
|
const parent = path.dirname(searchDir);
|
|
if (parent === searchDir) {
|
|
break;
|
|
}
|
|
searchDir = parent;
|
|
}
|
|
|
|
throw new Error('Could not find Gemini CLI documentation directory.');
|
|
}
|
|
|
|
class GetInternalDocsInvocation extends BaseToolInvocation<
|
|
GetInternalDocsParams,
|
|
ToolResult
|
|
> {
|
|
constructor(
|
|
params: GetInternalDocsParams,
|
|
messageBus: MessageBus,
|
|
_toolName?: string,
|
|
_toolDisplayName?: string,
|
|
) {
|
|
super(params, messageBus, _toolName, _toolDisplayName);
|
|
}
|
|
|
|
override async shouldConfirmExecute(
|
|
_abortSignal: AbortSignal,
|
|
): Promise<ToolCallConfirmationDetails | false> {
|
|
return false;
|
|
}
|
|
|
|
getDescription(): string {
|
|
if (this.params.path) {
|
|
return `Reading internal documentation: ${this.params.path}`;
|
|
}
|
|
return 'Listing all available internal documentation.';
|
|
}
|
|
|
|
async execute(_signal: AbortSignal): Promise<ToolResult> {
|
|
try {
|
|
const docsRoot = await getDocsRoot();
|
|
|
|
if (!this.params.path) {
|
|
// List all .md files recursively
|
|
const files = await glob('**/*.md', { cwd: docsRoot, posix: true });
|
|
files.sort();
|
|
|
|
const fileList = files.map((f) => `- ${f}`).join('\n');
|
|
const resultContent = `Available Gemini CLI documentation files:\n\n${fileList}`;
|
|
|
|
return {
|
|
llmContent: resultContent,
|
|
returnDisplay: `Found ${files.length} documentation files.`,
|
|
};
|
|
}
|
|
|
|
// Read a specific file
|
|
// Security: Prevent path traversal by resolving and verifying it stays within docsRoot
|
|
const resolvedPath = path.resolve(docsRoot, this.params.path);
|
|
if (!resolvedPath.startsWith(docsRoot)) {
|
|
throw new Error(
|
|
'Access denied: Requested path is outside the documentation directory.',
|
|
);
|
|
}
|
|
|
|
const content = await fs.readFile(resolvedPath, 'utf8');
|
|
|
|
return {
|
|
llmContent: content,
|
|
returnDisplay: `Successfully read documentation: ${this.params.path}`,
|
|
};
|
|
} catch (error) {
|
|
const errorMessage =
|
|
error instanceof Error ? error.message : String(error);
|
|
return {
|
|
llmContent: `Error accessing internal documentation: ${errorMessage}`,
|
|
returnDisplay: `Failed to access documentation: ${errorMessage}`,
|
|
error: {
|
|
message: errorMessage,
|
|
type: ToolErrorType.EXECUTION_FAILED,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A tool that provides access to Gemini CLI's internal documentation.
|
|
* If no path is provided, it returns a list of all available documentation files.
|
|
* If a path is provided, it returns the content of that specific file.
|
|
*/
|
|
export class GetInternalDocsTool extends BaseDeclarativeTool<
|
|
GetInternalDocsParams,
|
|
ToolResult
|
|
> {
|
|
static readonly Name = GET_INTERNAL_DOCS_TOOL_NAME;
|
|
|
|
constructor(messageBus: MessageBus) {
|
|
super(
|
|
GetInternalDocsTool.Name,
|
|
'GetInternalDocs',
|
|
GET_INTERNAL_DOCS_DEFINITION.base.description!,
|
|
Kind.Think,
|
|
GET_INTERNAL_DOCS_DEFINITION.base.parametersJsonSchema,
|
|
messageBus,
|
|
/* isOutputMarkdown */ true,
|
|
/* canUpdateOutput */ false,
|
|
);
|
|
}
|
|
|
|
protected createInvocation(
|
|
params: GetInternalDocsParams,
|
|
messageBus: MessageBus,
|
|
_toolName?: string,
|
|
_toolDisplayName?: string,
|
|
): ToolInvocation<GetInternalDocsParams, ToolResult> {
|
|
return new GetInternalDocsInvocation(
|
|
params,
|
|
messageBus,
|
|
_toolName ?? GetInternalDocsTool.Name,
|
|
_toolDisplayName,
|
|
);
|
|
}
|
|
|
|
override getSchema(modelId?: string) {
|
|
return resolveToolDeclaration(GET_INTERNAL_DOCS_DEFINITION, modelId);
|
|
}
|
|
}
|