🚀

出先でもGemini-CLI使いたい!WebSocketでラップしてDiscord連携してみた

に公開

はじめに

現在、Claude CodeやCodex CLI、そしてGemini-CLIのようなCLI型のコーディングエージェントが非常に人気ですね。

これらのツールはとても良いと思うのですが、個人的には出先や自宅で別作業をしているときにDevinにSlack経由で依頼をするのも非同期で好きです。がっつり自分がAIと伴奏しながらコードを書きたい時もありますが、そうじゃないこともありますからね。

というわけで、以下でclaude-codeでされているみたいに、Gemini-CLIも簡単にできるかと思ったのですが、良い感じのI/Fが提供されていませんでした。

https://zenn.dev/mizchi/articles/discord-claude-code-interface

最終的にgemini-cliをラップしたWebSocketサーバーを作ったので、そちらへ至る道を含めて今回はまとめてみます。

TL;DR

  • Gemini CLIをそのままDiscordやSlackから呼べるようにするのは困難
  • 内部実装のgemini-cli-coreを使うことで自前のラッパーが容易に作成可能
  • 今回WebSocketベースのgemini-cli-proxyを作ったので簡単に自作アプリとGemini-CLIを連携できる
  • 実際にDiscordから呼ぶアプリのDoppelを開発中

そもそもGemini-CLI GitHub Actionsじゃダメなの?

ちょうど先日、Gemini-CLI Actionsが出ました。正式なローンチは先日なのですが、前からGitHubには存在していたので最初にこちらが自分の用途に合うかを調査しました。

https://cloud.google.com/blog/ja/topics/developers-practitioners/introducing-gemini-cli-github-actions?hl=ja

結論としてはこれはこれで良いのですが、自分の使い道としては少し微妙です。gemini-cli-github-actionsの主な機能は下記の3つです。

  • インテリジェントなイシューの振り分け
  • プルリクエスト レビューの迅速化
  • IssueやPRでのオンデマンドでの共同作業

今回の要件に近いのは1番最後のものですね。ただ、実際に触ってみて思ったのは 「自分はIssueを切る所からAIと相談して進めたい」 ということでした。すでにIssueに切ったものではなく、ふわっとした内容から要件を詰めるところまで対話しながらやりたかったので、Issueに既になっている状態だとちょっと要件が合わなかったわけですね。もっとDevin的なのが欲しかったので作ることにしました。

というわけで、公式のActionsは微妙に目的に合わないし、開発向けのI/Fも残念ながら整備されていなかったので、良い感じのプログラマブルな接合点を作る旅を始めました。

自前実装案1: Gemini CLI GitHub Actionsを真似する

Gemini CLI GitHub Actions自体は私のやりたいことと少し違いましたが、これを参考にすればやりたいツールは比較的簡単にできそう、と思い、まずはコードを解析しました。OSSなのがありがたいですね。

しかも今だと、以下のようにGeminiでGitHubリポジトリを読み込ませて対話的にコード分析を進めることができますコードを分析する速度を大幅アップですね!

分析はGemini CLIを使ったり、DevinのDeepWikiとかでも良いのですが、コード分析だけなら普通にGeminiアプリをGitHubに繋ぐ方がサブスクの範囲内でできてコスパも良くて助かりますね。

で、色々深堀して聞いていたのですが、以下を最終的に実行しているようです。

gemini --yolo --prompt "${PROMPT}"

普通に、geminiのプロンプト実行モードですね。これを踏襲しても良かったのですが、このモードでは記憶が保持されません

そのため、Gemini CLI GitHub Actionsでは基本的に必要なコンテキストを全てgeminiに渡すようになっており、別途セッションの概念を作って記憶を保持しています。

この方式もお手軽かつgemini-cliをそのまま使えてメンテナンス性も良いので、良かったのですがエージェントの思考過程とかは残りませんし、ちょっと正しく作るにはひと手間掛かりそうだったのでいったん別な方法にしました。

自前実装案2: Gemini CLIの対話モードをラッピングする

次に考えたのが通常使うGemini CLIの対話モードをWebSocketか何かでラッピングするという方法です。
これができれば一番目的に叶いますよね。

これは最終的にはほぼコードを書かずに断念しました。もちろんGeminiをバックグラウンドで起動させて、コードから管理すること自体は難しくありません。

問題は、ユーザーインターフェースです。Geminiは**GUIならぬTUI(Text User Interface)**を使っているので、簡単にはアプリにパラメータを渡したり実行結果を取得することができず、TUIの解析をしっかりやる必要があります。

これはUIの構造に依存するので、RPAなみに不安定な動作を導き、絶賛開発中のGeminiにやるのは得策ではありません。苦労の割には実りが少なそうなので。

自前実装案3: gemini-cli-coreを呼ぶ

対話モードをラッピングする調査の副産物としてGemini CLI自体のコードを解析していたのですが、コアコンポーネントは@google/gemini-cli-coreというライブラリに分離されていることに気づきました。

なので、TUIのコードを参考にgemini-cli-coreを扱うコードを書いてやれば行けそう! ということでGeminiと一緒にGemini CLIを分析して以下のような書き方をしました。

コードの全文はこちら

session.history.push({
  role: 'user',
  content: userMessage,
  timestamp: new Date()
});

const geminiClient = session.config.getGeminiClient();
if (!geminiClient) {
  ws.send(JSON.stringify({ type: 'error', error: 'Gemini client not initialized' }));
  return;
}

const chat = await geminiClient.getChat();
if (!chat) {
  ws.send(JSON.stringify({ type: 'error', error: 'Failed to get chat instance' }));
  return;
}

この一連の処理をWebSocketとして接続先に返します。

gemini-cli-coreのアーキテクチャ

gemini-cli-core主要なコンポーネントは以下です。なお、外部向けに公開されているAPIではなく、内部実装なので今後の互換性は期待できないことには注意をしてください。

  • Config クラス
  • GeminiClient クラス
  • ToolRegistry クラス

ConfigAPI通信、ツール、認証を統合的に管理します。必要なパラメータは以下のように設定します。

const config = new Config({
  sessionId: sessionId,
  targetDir: workingDir,
  debugMode: false,
  cwd: workingDir,
  model: 'gemini-2.5-pro',  
  embeddingModel: 'gemini-embedding-001',  
  fullContext: false,
  checkpointing: false,
  coreTools: undefined, // undefinedは全てのtoolsの利用を許可
  excludeTools: [],
});

GeminiClientGemini APIとの通信を管理し、以下の主要機能を提供します。

  • ストリーミング通信: sendMessageStream()によるリアルタイム応答。
  • チャット履歴管理: 会話のコンテキストを自動で管理します。
  • トークン制限対応: 長い会話履歴を自動的に要約・圧縮します。
  • 認証管理: AuthType.USE_GEMINIによるAPIキー認証を管理します。

ToolRegistry組み込みツールや動的ツール(MCP)の発見と管理を行います。組み込みツールは以下が提供されています。

  • ファイルシステムツール: read_file, write_file, list_directory
  • 実行ツール: run_shell_command
  • Webツール: web_fetch, google_web_search
  • メモリツール: save_memory

これら、特に run_shell_commandを使うことでGitHubへのアクセスやファイルの作成、ビルドなど様々な操作を実施 Sすることができます。

ghコマンドを入れることでcloneやpushはもちろん、PR等も作成したり内容をチェックできるのでかなり良い感じに動かすことができました。

gemini-cli-proxyの使い方

使い方はシンプルで、コンテナにしてあるのでローカルでもどこでも動かすことができます。今は、自分はGCEで基本的に動作させていますね。

セッション管理などは複数プロセスからのアクセスを現時点では考慮した実装になっていないのでそのまま利用する場合は、個人用として扱ってください。

https://github.com/koduki/gemini-cli-proxy

まずは、docker-compose等でProxyを立ち上げます。

docker compose up -d --build

http://localhost:3000にアクセスすると以下のようなデモプログラムにアクセスすることができます。

実際に使われているAPIは以下の通り。

POST /api/chatでセッションを初期化します。

Response:

{
  "sessionId": "unique-session-id"
}

その後、WebSocketを使って、先ほど作成したセッションIDを使ってコミュニケーションを開始します。

```json
{
  "type": "init",
  "sessionId": "unique-session-id"
}
```

リクエストは以下のフォーマットで渡すだけです。

```json
{
  "type": "message",
  "content": "Your message here"
}
```

レスポンスとしては、gemini-cliは思考中の内容を段階的に返してくるのでストリームチャンクとして受け取ります。また、data.typeでそれがツールなのか否かなども取得できます。

```json
{
  "type": "stream_chunk",
  "data": {
    "type": "content", // or "tool_code", "tool_result", etc.
    "data": "AI response text"
  }
}
```

また、定義しないといけない環境変数は以下です。

GEMINI_API_KEY
GITHUB_APP_ID
GITHUB_APP_INSTALLATION_ID
GITHUB_APP_PRIVATE_KEY

GEMINI_API_KEYは通常通り、Google AI Studioからの取得ですが、今回はghコマンドに認証情報を渡してやる必要があります。

そのため、GitHub Apps を作成し、そちらでリポジトリ操作やPRの権限を取得できるようにしています。
環境変数でGitHub Appsの情報を渡し、そこから認証情報を取り出して、ghコマンドに連携する流れです。やはり、GitHubが触れないと始まらないですからね。

このProxyを使って、実際に、Discordと連携 させているのが以下のDoppelです。最終的にはDevinみたいなUXをGemini-CLIに被せられると良いな、と思っています。

https://github.com/koduki/doppel

まとめ

Gemini-CLIは便利だけど出先でDevinみたいに使いたい、ということでDoppel の開発を思い立つもDiscordとの連携が思ったより骨が折れたので作ったのが今回のgemini-cli-proxyとなります。

正直、そこまで高度なものではないですし、遠からず公式が良い感じのSDKも提供してくれると信じていますが、いったんあると便利なので公開してみました。

また、今回はgemini-cliやgemini-cli-actionsの解析にAIを使ったわけですが、やはり解析がだいぶ楽になりますね。

全部自力でやろうとしたら、重い腰が上がらなかった部分もあると思うので、大いに助かりました。コードの解析だけならGeminiアプリでやるのが費用的にお得、というのも個人的には良い気付きでした。

それでは、Happy Hacking!

Discussion