Gemini CLI プロジェクトアーキテクチャ分析
プロジェクト概要
Gemini CLI は、Google Gemini AI をベースとしたコマンドラインツールで、ユーザーの自然言語入力を理解し、ツール呼び出しを通じて様々な開発タスクを完了することができます。このプロジェクトは、豊富なツールエコシステムを持つモジュラーアーキテクチャを採用しています。
プロジェクトアーキテクチャ分析
主要コンポーネント
-
CLI エントリレイヤー (
packages/cli/
)- ユーザーインターフェースとインタラクションレイヤー
- React/Ink ベースのターミナル UI
- ユーザー入力の処理と結果の表示
-
コアエンジン (
packages/core/
)- AI インタラクションと会話管理
- ツール実行スケジューリング
- 設定と認証管理
-
ツールシステム
- ファイル操作ツール
- システムコマンド実行
- ネットワークリクエストツール
- 拡張ツールサポート
-
設定管理
- 認証設定
- ユーザー設定
- 拡張機能管理
利用可能なツールリスト
ファイル操作ツール
-
write-file
- ファイル内容の書き込み -
read-file
- ファイル内容の読み取り -
edit
- 既存ファイルの編集 -
read-many-files
- 複数ファイルの一括読み取り
検索・ブラウズツール
-
grep
- ファイル内のテキスト内容検索 -
glob
- パターンマッチングによるファイル検索 -
ls
- ディレクトリ内容のリスト表示
ネットワークツール
-
web-fetch
- ウェブコンテンツの取得 -
web-search
- ウェブ検索
システムツール
-
shell
- シェルコマンドの実行 -
memoryTool
- 会話メモリの管理
拡張ツール
-
mcp-client
- MCP プロトコルサポート -
mcp-tool
- サードパーティツール統合
ユーザー入力から結果出力までの完全フロー
「ウェブページを作成」というユーザーリクエストを例として:
1. 起動フェーズ
// packages/cli/index.ts
main().catch((error) => {
console.error('An unexpected critical error occurred:');
process.exit(1);
});
- 設定ファイルとユーザー設定の読み込み
- 認証情報の検証
- ツールレジストリの初期化
- Gemini API との接続確立
2. ユーザー入力処理
-
インタラクティブモード: ターミナル UI (
InputPrompt.tsx
) を通じた入力受信 - 非インタラクティブモード: stdin からの入力読み取り
- 自動補完、履歴、ファイルパス参照 (
@path/to/file
) のサポート
3. AI 理解と処理
// packages/core/src/core/geminiChat.ts
async sendMessage(params: SendMessageParameters): Promise<GenerateContentResponse> {
const inputContent = createUserContent(params.message);
const apiCall = () => this.contentGenerator.generateContent({...});
}
- ユーザー入力を Gemini API に送信
- AI がユーザーの意図を分析
- 呼び出すツールを決定
- ツール呼び出しパラメータを生成
4. ツールスケジューリングと実行
// packages/core/src/core/coreToolScheduler.ts
async schedule(request: ToolCallRequestInfo[]): Promise<void> {
for (const req of requests) {
const tool = toolRegistry.getTool(req.name);
// パラメータ検証、確認要求、ツール実行
}
}
- ツールパラメータの妥当性検証
- ユーザー確認の要求(必要に応じて)
- ツールの実行と結果収集
- エラーと例外の処理
5. 結果表示
- AI 応答内容のリアルタイム表示
- ツール実行結果の表示
- ユーザーインタラクションフィードバックの提供
シーケンス図
詳細プロセス説明
コア実行フロー
1. プログラム起動
-
packages/cli/index.ts
から実行開始 -
main()
関数を呼び出してシステム全体を初期化 - ユーザー設定、認証情報、ツールレジストリの読み込み
2. ユーザーインタラクションインターフェース
- React/Ink を使用したモダンなターミナル UI の構築
- リアルタイム入力、自動補完、コマンド履歴のサポート
- 特殊構文の処理:
-
@path/to/file
- ファイルパス参照 -
/command
- スラッシュコマンド -
!
- シェルモード切り替え
-
3. AI 会話管理
// GeminiChat コアメソッド
async sendMessage(params: SendMessageParameters): Promise<GenerateContentResponse> {
await this.sendPromise;
return (this.sendPromise = this._sendMessage(params));
}
- Gemini API との会話セッション管理
- 会話履歴とコンテキストの維持
- ストリーミング応答とツール呼び出しの処理
4. ツールシステムアーキテクチャ
ツールシステムは gemini-cli のコア機能です:
// ツールベースクラス定義
export abstract class BaseTool<TParams = unknown, TResult extends ToolResult = ToolResult> {
abstract execute(params: TParams, signal: AbortSignal): Promise<TResult>;
shouldConfirmExecute(params: TParams): Promise<ToolCallConfirmationDetails | false>;
validateToolParams(params: TParams): string | null;
}
5. ツール実行フロー
// CoreToolScheduler スケジューリングロジック
async schedule(request: ToolCallRequestInfo[]): Promise<void> {
for (const req of requests) {
const tool = toolRegistry.getTool(req.name);
if (!tool) {
// ツールが見つからないエラーの処理
}
// パラメータ検証 -> 確認要求 -> ツール実行
}
}
実例:ウェブページの作成
ユーザーが「シンプルなウェブページを作成」と入力した場合の完全実行フロー:
ステップ 1: AI 分析
- Gemini がユーザーの HTML ファイル作成ニーズを理解
- 技術要件(HTML/CSS/JavaScript)を分析
- ファイル構造と内容を計画
ステップ 2: ツール選択
-
write_file
ツールの使用を決定 - ファイルパスを生成:
./index.html
- 基本的な HTML コード内容を生成
ステップ 3: ユーザー確認
// WriteFileTool 確認ロジック
async shouldConfirmExecute(params: WriteFileToolParams): Promise<ToolCallConfirmationDetails | false> {
const fileDiff = Diff.createPatch(fileName, originalContent, correctedContent);
return {
type: 'edit',
title: `書き込み確認: ${shortenPath(relativePath)}`,
fileDiff,
onConfirm: async (outcome) => { /* 確認結果の処理 */ }
};
}
- 作成されるファイル内容の表示
- ファイル差分比較の表示
- ユーザーの確認またはキャンセルを待機
ステップ 4: ファイル作成
- ファイルパスのセキュリティ検証
- 書き込み操作の実行
- 実行結果の返却
ステップ 5: 後続提案
AI は以下を続けて提案する可能性があります:
- CSS スタイルファイルの作成
- npm プロジェクトの初期化
- ローカル開発サーバーの起動
インタラクティブシナリオ詳細操作ステップ
ユーザーテキスト入力後の完全処理フロー
ユーザーがインタラクティブインターフェースでテキストを入力し、Enter を押すと、システムは以下の詳細ステップを実行します:
フェーズ 1: 入力キャプチャと前処理 (InputPrompt.tsx)
ステップ 1.1: キーイベントキャプチャ
// InputPrompt.tsx - handleInput 関数
if (key.name === 'return') {
if (query.trim()) {
handleSubmitAndClear(query);
}
}
- ユーザーの Enter キー押下を検出
- 入力が空でないことを検証
- 送信処理をトリガー
ステップ 1.2: テキストバッファクリーンアップ
const handleSubmitAndClear = useCallback((submittedValue: string) => {
// onSubmit を呼び出す *前に* バッファをクリア
buffer.setText('');
onSubmit(submittedValue);
resetCompletionState();
}, [onSubmit, buffer, resetCompletionState]);
- 入力バッファを即座にクリア
- 自動補完状態をリセット
- 親コンポーネントの送信ハンドラーを呼び出し
フェーズ 2: アプリケーションレイヤー処理 (App.tsx)
ステップ 2.1: 最終送信検証
// App.tsx - handleFinalSubmit
const handleFinalSubmit = useCallback((submittedValue: string) => {
const trimmedValue = submittedValue.trim();
if (trimmedValue.length > 0) {
submitQuery(trimmedValue);
}
}, [submitQuery]);
- 入力が空でないことを再検証
- useGeminiStream の submitQuery 関数を呼び出し
フェーズ 3: クエリ前処理 (useGeminiStream.ts)
ステップ 3.1: ストリーム状態チェック
// useGeminiStream.ts - submitQuery
if ((streamingState === StreamingState.Responding ||
streamingState === StreamingState.WaitingForConfirmation) &&
!options?.isContinuation) {
return; // 応答中または確認待ちの場合、新しい入力を無視
}
- 現在他のリクエストを処理中かチェック
- 同時処理の競合を回避
ステップ 3.2: アボートコントローラー作成
const userMessageTimestamp = Date.now();
abortControllerRef.current = new AbortController();
const abortSignal = abortControllerRef.current.signal;
turnCancelledRef.current = false;
- メッセージタイムスタンプを生成
- キャンセル用の新しいアボートコントローラーを作成
- キャンセルフラグをリセット
ステップ 3.3: クエリ準備と前処理
// prepareQueryForGemini 関数
const { queryToSend, shouldProceed } = await prepareQueryForGemini(
query, userMessageTimestamp, abortSignal
);
詳細前処理ステップ:
a) ユーザー入力ログ
logUserPrompt(config, new UserPromptEvent(trimmedQuery.length, trimmedQuery));
await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
b) 特殊コマンド処理
// スラッシュコマンド処理 (/help, /theme など)
const slashCommandResult = await handleSlashCommand(trimmedQuery);
if (typeof slashCommandResult === 'boolean' && slashCommandResult) {
return { queryToSend: null, shouldProceed: false };
}
// シェルモード処理
if (shellModeActive && handleShellCommand(trimmedQuery, abortSignal)) {
return { queryToSend: null, shouldProceed: false };
}
// @コマンド処理 (@file/path)
if (isAtCommand(trimmedQuery)) {
const atCommandResult = await handleAtCommand({...});
if (!atCommandResult.shouldProceed) {
return { queryToSend: null, shouldProceed: false };
}
localQueryToSendToGemini = atCommandResult.processedQuery;
}
c) 履歴への追加
// 通常のクエリをユーザー履歴に追加
addItem({ type: MessageType.USER, text: trimmedQuery }, userMessageTimestamp);
フェーズ 4: AI インタラクション処理
ステップ 4.1: 状態更新
startNewTurn(); // 新しい会話ターンを開始
setIsResponding(true); // 応答状態を設定
setInitError(null); // エラー状態をクリア
ステップ 4.2: Gemini API への送信
const stream = geminiClient.sendMessageStream(queryToSend, abortSignal);
const processingStatus = await processGeminiStreamEvents(
stream, userMessageTimestamp, abortSignal
);
フェーズ 5: ストリームイベント処理 (processGeminiStreamEvents)
ステップ 5.1: イベントループ処理
for await (const event of stream) {
switch (event.type) {
case ServerGeminiEventType.Thought:
setThought(event.value); // AI の思考プロセスを表示
break;
case ServerGeminiEventType.Content:
geminiMessageBuffer = handleContentEvent(event.value, geminiMessageBuffer, userMessageTimestamp);
break;
case ServerGeminiEventType.ToolCallRequest:
toolCallRequests.push(event.value); // ツール呼び出しリクエストを収集
break;
// ... その他のイベントタイプ
}
}
ステップ 5.2: コンテンツイベント処理
// handleContentEvent - AI 応答コンテンツの処理
let newGeminiMessageBuffer = currentGeminiMessageBuffer + eventValue;
// 保留中の履歴アイテムを作成または更新
if (pendingHistoryItemRef.current?.type !== 'gemini') {
setPendingHistoryItem({ type: 'gemini', text: '' });
newGeminiMessageBuffer = eventValue;
}
// パフォーマンス最適化:大きなメッセージを分割
const splitPoint = findLastSafeSplitPoint(newGeminiMessageBuffer);
if (splitPoint === newGeminiMessageBuffer.length) {
// 既存メッセージを更新
setPendingHistoryItem((item) => ({
type: 'gemini',
text: newGeminiMessageBuffer,
}));
} else {
// レンダリングパフォーマンス向上のためメッセージを分割
addItem({ type: 'gemini', text: beforeText }, userMessageTimestamp);
setPendingHistoryItem({ type: 'gemini_content', text: afterText });
}
フェーズ 6: ツール呼び出し処理
ステップ 6.1: ツール呼び出しスケジューリング
if (toolCallRequests.length > 0) {
scheduleToolCalls(toolCallRequests, signal);
}
ステップ 6.2: ツール検証と確認
- ツールパラメータの妥当性検証
- 設定に基づく確認ダイアログの表示
- ユーザー確認の待機または自動実行
ステップ 6.3: ツール実行
- 特定のツール操作の実行(ファイル書き込み、コマンド実行など)
- 実行ステータスのリアルタイム更新
- 実行結果の収集
フェーズ 7: 結果処理と表示
ステップ 7.1: 保留アイテムの完了
if (pendingHistoryItemRef.current) {
addItem(pendingHistoryItemRef.current, userMessageTimestamp);
setPendingHistoryItem(null);
}
ステップ 7.2: ツール結果送信
// handleCompletedTools - 完了したツール呼び出しの処理
const responsesToSend = geminiTools.map(toolCall => toolCall.response.responseParts);
submitQuery(mergePartListUnions(responsesToSend), { isContinuation: true });
ステップ 7.3: 状態リセット
setIsResponding(false); // 応答状態をリセット
// 次のユーザー入力を受信する準備
エラー処理と中断メカニズム
ユーザーキャンセル処理
useInput((_input, key) => {
if (streamingState === StreamingState.Responding && key.escape) {
turnCancelledRef.current = true;
abortControllerRef.current?.abort();
addItem({ type: MessageType.INFO, text: 'リクエストがキャンセルされました。' }, Date.now());
}
});
エラーイベント処理
case ServerGeminiEventType.Error:
addItem({
type: MessageType.ERROR,
text: parseAndFormatApiError(eventValue.error, authType)
}, userMessageTimestamp);
パフォーマンス最適化機能
- メッセージ分割: 大きな AI 応答はレンダリングパフォーマンス向上のため分割
- 静的レンダリング: Ink の Static コンポーネントを使用して履歴コンテンツの再レンダリングを回避
- アボートシグナル: 長時間実行される操作のキャンセルをサポート
- ストリーミング処理: AI 応答コンテンツのリアルタイム表示
- 状態管理: 競合状態を防ぐための精密な UI 状態制御
この詳細フローは、Gemini CLI が各ユーザー入力を慎重に処理し、応答性の高いフィードバック、スムーズなユーザーエクスペリエンスを確保しながら、システムの安定性と信頼性を維持する方法を示しています。
主要機能
1. セキュリティ
- パス検証: すべてのファイル操作はプロジェクトルートディレクトリ内に制限
- パラメータ検証: ツールパラメータの厳密な検証
- ユーザー確認: 重要な操作には明示的なユーザー確認が必要
2. ユーザーエクスペリエンス
- リアルタイムフィードバック: ストリーミング出力と進捗更新のサポート
- スマート補完: ファイルパスとコマンドの自動補完
- エラー処理: フレンドリーなエラーメッセージと提案
3. 拡張性
- MCP プロトコル: サードパーティツール統合のサポート
- プラグインシステム: 拡張可能なツールアーキテクチャ
- 設定管理: 柔軟な設定とテーマシステム
4. インテリジェンス
- コンテキスト理解: プロジェクト構造と履歴に基づくスマート提案
- コード修正: AI による自動的なコード修正と最適化
- マルチステップ計画: 複雑なタスクの自動分解と実行
5. 開発効率
- マルチファイル操作: 複数ファイルの一括処理
- シェル統合: システムコマンドのシームレスな実行
- メモリ管理: インテリジェントな会話コンテキスト管理
まとめ
Gemini CLI は、慎重に設計されたアーキテクチャを通じて AI 理解能力と実用的な開発ツールを成功的に組み合わせ、開発者に強力で安全な AI プログラミングアシスタントを提供しています。そのモジュラー設計により、システムは安定性と信頼性を保ちながら、進化する開発ニーズに適応できる優れた拡張性を持っています。
シンプルなファイル操作から複雑なプロジェクトセットアップまで、Gemini CLI はユーザーの意図を理解し、適切なツール呼び出しを通じてタスクを完了することができ、開発効率とエクスペリエンスを大幅に向上させます。
Discussion