Gemini CLI接入IntelliJ IDEA:用ACP桥接统一AI助手工作流
很多用 IntelliJ IDEA 写代码的人和我一样有个共同的别扭点终端里 Gemini CLI 用得顺手代码洞察、文件修改、命令行执行一条龙但一回到 IDE 里AI 助手却是另一套模型、另一套配置两边像是互不相认的两个系统。我之前试过直接在 IDEA 里重新配置 Gemini 的 API Key倒是能用但总觉得少了点什么——CLI 里沉淀下来的会话偏好、工具链、自定义指令编辑器这边完全不知道。后来我折腾出一条比较顺的路子把 Gemini CLI 作为 IDEA 后端真正的“干活引擎”通过一条本地 ACP 桥接把两边串起来。这篇文章就是完整过程不绕弯子从原理到落地我踩过的坑也一并写出来。适合已经装过 Gemini CLI、想省掉重复配置的人也适合刚接触 CLI 但对 IDEA 很熟的朋友。1. 集成思路与 ACP 协议背景1.1 为什么要把 CLI 塞进 IDE先说说我为什么非要折腾这件事。Gemini CLI 这种命令行智能体和 IDE 自带 AI 助手的定位其实不一样。IDE 的 AI 助手更侧重“在你写代码的上下文里给建议、补全、解释”而 Gemini CLI 更强的是独立完成一个任务链条读仓库、分析报错、改多个文件、跑测试甚至调用外部工具。两者不是替代关系是互补关系。问题是互补的前提是“能互通”。大多数人的工作流是在 IDEA 里写着写着遇到问题切到终端敲 Gemini CLICLI 分析完给出修改意见再切回 IDE 手动改。来回切换非常打断心流。如果能让 IDEA 直接调用 CLI 的能力编辑器只负责展示上下文和接收结果代码修改、任务拆解这些交给 CLI体验会顺很多。这就是这篇博客的动机不是给 IDEA 再配一个 API而是让 IDEA “用上”你已经装好的 Gemini CLI复用它的鉴权、模型配置和指令体系。1.2 ACP 是什么为什么选它标题里写了 ACP这里得说清楚。ACP 全称 Agent Communication Protocol最早是围绕编码 Agent 之间互操作提出的一套通信约定。说人话就是不同工具链里的 AI 代理通过一套标准化的 HTTP 请求互相调用不用每家公司自成一派。它的思路和 LSP 有点像——LSP 统一了编辑器与语言服务器之间的通信ACP 想统一的是 IDE 与编码 Agent 之间的通信。Gemini CLI 自带 ACP 服务端能力也就是说它不只是埋头在终端干活还能对外提供接口接收外部系统的请求执行完再返回结果。IDEA 这边需要做的是成为一个 ACP 客户端把编辑器里的提示词、文件上下文、对话历史组装成标准请求发给 Gemini CLI再渲染拿到的结果。但这里有个实际问题IDEA 的 AI Assistant 插件对“自定义 AI 服务”的支持并不是直接识别 ACP 协议它更认基于 OpenAI 兼容接口的交互。OpenAI 兼容接口在今天的 AI 工具圈子里几乎成了事实标准差不多所有 IDE 插件、中间层都认识/v1/chat/completions这种格式。所以集成的最佳路径不是让 IDEA 去理解 Gemini CLI 的私有协议而是中间加一层桥接左边接 IDEA 的 OpenAI 兼容请求右边调 Gemini CLI两边各说各话桥接负责翻译。1.3 整体架构分三层整个方案可以拆成三个部分客户端IntelliJ IDEA 的 AI Assistant 插件负责采集代码上下文、对话记录并渲染生成结果。桥接服务一个跑在本机的 Python 服务暴露 OpenAI 兼容的 HTTP 接口收到 IDEA 的请求后转成一条 Gemini CLI 可执行的指令。执行端Gemini CLI 本身负责调用 Gemini 模型、执行工具、返回结果文本。桥接服务是整个链路里我花费时间最多的地方。IDE 发来的请求是 OpenAI 格式的 JSON包含 messages 数组、模型名、温度等参数Gemini CLI 在-p模式下更愿意接收一段纯文本提示。所以桥接的核心工作就是把 messages 数组拼成一段结构化的文本提示丢给gemini -p再把 CLI 输出的文本封装成 OpenAI 格式的响应返回给 IDEA。这个架构的好处是隔离。以后如果 IDEA 换了别的 AI 助手版本请求格式变了只需改桥接层如果 Gemini CLI 升级了也只需在桥接层适配新的命令行参数。改一处不影响另一端。2. 环境准备先让 Gemini CLI 在终端里跑通2.1 安装方式怎么选接入 IDEA 之前Gemini CLI 必须在终端里已经被验证过能用不然桥接层再折腾也是白搭。安装方式主要有两条路通过 Node.js 包管理器全局安装。前提是机器上有 Node.js 20 或更高版本。装完直接有gemini命令升级也方便。下载独立二进制。适合不想装 Node.js 的人解压后放到 PATH 目录下就行但升级要手动替换文件。我建议用包管理器方式原因不只是升级方便还在于后续桥接脚本里调用gemini命令时PATH 环境变量的处理更省心。安装完先验证一下版本gemini --version能看到版本号说明命令本身可用。然后随便问它一个问题echo 你好用一句话介绍你自己 | gemini -p这一步很重要。如果这里报错多半是鉴权没配好后面所有工作都建不起来。2.2 API Key 与登录态配置Gemini CLI 的鉴权有两种主流方式。一种是直接使用 API Key适合简单场景另一种是 OAuth 登录适合需要持久登录态、不想把 Key 写进环境变量的场景。API Key 的方式比较直接先去 AI Studio 之类的官方入口生成 Key然后在环境变量里设置export GEMINI_API_KEY你的Key为了不让 Key 每次开终端都要重设建议写进 shell 的配置文件中。不过要注意环境变量作用于所有子进程桥接脚本运行时也会继承这个变量这是好事我们后面要利用这一点。OAuth 登录则简单粗暴运行gemini init或者直接第一次执行gemini -p时CLI 会给出登录链接浏览器授权完成后鉴权信息会存在本机的配置目录里。两种方式选一种就行。我日常用的是 API Key因为脚本化调用更可控出了问题也好排查。2.3 先跑通一条完整命令配置完鉴权把最基础的那条命令再跑一遍确认输出稳定echo 给出一段Python快速排序代码 | gemini -p如果能看到代码输出说明 Gemini CLI 已经可以正常使用。这还不够我建议再试一条稍微复杂的指令比如带文件读取的echo 请查看当前目录下的README.md总结其中的关键信息 | gemini -p这一步是验证 CLI 的工具调用能力。桥接层接进 IDEA 后我们会把代码上下文直接拼进 prompt 里如果 CLI 本身工具执行有问题就得提前暴露不然等进了 IDEA 再排查会非常痛苦。3. 桥接服务实现本地 ACP 翻译官3.1 OpenAI 兼容接口到底长什么样在写桥接代码之前得先搞清楚 IDEA 发过来的请求长什么样。以 OpenAI 兼容的/v1/chat/completions为例一个典型的 POST 请求体大致是{ model: gemini-cli, messages: [ {role: system, content: 你是一个代码助手}, {role: user, content: 帮我解释下面这段代码的逻辑} ], temperature: 0.7 }对应地IDEA 期望的响应格式是{ id: chatcmpl-local-bridge, object: chat.completion, model: gemini-cli, choices: [ { index: 0, message: {role: assistant, content: 这段代码的逻辑是……}, finish_reason: stop } ] }桥接服务要做的就是把这套入参格式翻译成gemini -p能理解的纯文本再把 CLI 的输出翻译回上面这套 JSON 结构。IDEA 不关心背后的模型是谁只要接口契约对得上就行。3.2 Python 桥接脚本实战桥接层我用 Python 写配合 Flask 这个轻量 Web 框架。选 Flask 的原因很直接代码量小依赖少跑在本机足够。安装依赖和执行脚本的完整过程在下面这段代码里都包含你不需要额外安装什么重型框架。先安装 Flaskpip install flask然后创建一个gemini_bridge.py内容如下import os import subprocess import time from flask import Flask, request, jsonify app Flask(__name__) MODEL_NAME gemini-cli def build_prompt(messages): parts [] for message in messages: role message.get(role, user) content message.get(content, ) # 避免 IDEA 发送多模态内容导致 content 变成数组 if isinstance(content, list): text .join( part.get(text, ) for part in content if isinstance(part, dict) and text in part ) else: text str(content) parts.append(f{role}: {text}) return \n.join(parts) app.route(/v1/models, methods[GET]) def list_models(): return jsonify({ object: list, data: [ {id: MODEL_NAME, object: model, created: 0, owned_by: local} ] }) app.route(/v1/chat/completions, methods[POST]) def chat_completions(): data request.get_json(silentTrue) or {} messages data.get(messages, []) prompt build_prompt(messages) if not prompt.strip(): return jsonify({error: {message: empty prompt, code: 400}}), 400 env dict(os.environ) env[NO_COLOR] 1 try: proc subprocess.run( [gemini, -p, prompt], capture_outputTrue, textTrue, encodingutf-8, timeout180, envenv, ) except subprocess.TimeoutExpired: return jsonify({error: {message: gemini cli timeout, code: 504}}), 504 if proc.returncode ! 0: error_msg proc.stderr.strip() or gemini cli failed return jsonify({error: {message: error_msg, code: 502}}), 502 output proc.stdout.strip() return jsonify({ id: chatcmpl-local-bridge, object: chat.completion, created: int(time.time()), model: MODEL_NAME, choices: [ { index: 0, message: {role: assistant, content: output}, finish_reason: stop, } ], usage: {prompt_tokens: 0, completion_tokens: 0, total_tokens: 0}, }) if __name__ __main__: app.run(host127.0.0.1, port8000, debugFalse)代码逻辑谈不上复杂但有三个点值得单独说明。第一content有可能不是字符串。IDEA 在较新的版本里发送跨模态内容时content字段可能是数组里面是不同片段对象。代码里做了兼容处理避免解析异常。第二env[NO_COLOR] 1非常关键。Gemini CLI 在终端输出时默认带颜色控制符如果直接返回给 IDEA渲染出来会夹着一堆\x1b[31m之类的转义序列。设置这个环境变量后CLI 会以纯文本输出响应干净很多。第三timeout180决定了单次请求的最长等待时间。IDEA 的 AI 助手一般也有自己的超时机制桥接层的超时最好比 IDEA 的短或接近这样出问题时能更快拿到明确错误信息而不是两边互相干等。3.3 启动桥接服务与连通性测试脚本写好后启动它python gemini_bridge.py看到 Flask 的输出说明服务已经在 8000 端口监听。先别急着去配 IDEA先用 curl 把两个关键接口测一遍。测试模型列表接口curl http://127.0.0.1:8000/v1/models正常情况下应该返回一个包含gemini-cli模型对象的 JSON。再测对话接口curl -X POST http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d {messages:[{role:user,content:用一句话解释什么是ACP}]}如果桥接正常你会收到一个包含choices[0].message.content的 JSON内容是 Gemini CLI 的回复。看到这里桥接一趟已经通了剩下的就是让 IDEA 连上这个本地服务。注意桥接服务不要用host0.0.0.0启动这会把服务暴露到局域网。本机集成只需要监听 127.0.0.1 就够了。4. IntelliJ IDEA 侧配置与联调4.1 找到自定义 AI 服务入口IDEA 版本更新换代很快AI Assistant 插件的界面也在变但核心配置项基本稳定。在设置里搜索 “AI Assistant” 或者 “Custom URL”、“OpenAI compatible” 这些关键词能找到自定义模型提供方的入口。我用的版本里路径大致是Settings → Tools → AI Assistant → 模型/自定义服务 → 自定义端点不同小版本名字略有差异有一版叫 “Custom URL”有一版叫 “Bring Your Own Key”本质是一样的允许你填一个自定义服务的 Base URL 和 API Key。填写的参数如下参数值说明Base URLhttp://127.0.0.1:8000/v1指向本地桥接服务API Keysk-local-bridge随便填桥接层不校验模型名称gemini-cli必须和桥接代码里的MODEL_NAME一致启用流式响应关闭当前桥接实现返回的是非流式 JSON其中“启用流式响应”这个开关值得展开说一下。IDEA 的 AI 助手默认会尝试 SSE 流式响应也就是边生成边输出。而我们这个桥接实现是一次性等gemini -p跑完再返回属于非流式。如果 IDEA 开启了流式请求可能表现成“一直在转圈但迟迟不出字”但实际上 CLI 已经在后台运行了。所以要么在 IDEA 里强制关闭流式要么后续把桥接层升级成 SSE 流式输出。4.2 模型名与请求参数调优配置界面里的模型名必须和桥接代码里返回的MODEL_NAME一致这个已经有说明。另一个容易踩的坑是温度、最大 token 这类参数。IDEA 在界面里可能给了温度滑杆但这些参数只是 IDEA 往请求体里塞的一个字段桥接层并没有把它传给 Gemini CLI 的-p模式。这其实是当前方案的一个妥协gemini -p只接收提示文本无法精细控制温度和采样参数。但实测下来编码场景里这影响不大Gemini 模型默认的采样策略已经够用。如果你对某些任务的温度特别敏感可以在桥接层把temperature字段读出来作为环境变量传给 CLI但 Gemini CLI 对这类参数的支持因版本而异需要自己试不保证所有版本都认。4.3 验证完整链路配置完成后在 IDEA 的 AI 助手面板随便问一个问题比如让它解释当前打开文件里的某个方法。正常情况下IDE 会把当前文件的上下文、光标位置、你的问题一起封装成请求发到桥接服务桥接层转给 Gemini CLICLI 处理完返回结果IDEA 渲染到面板里。我第一次联通时看到回复出现在 IDEA 面板里说实话有点兴奋。但随即发现输出里偶尔夹杂一些奇怪的字符排查下来是 ANSI 颜色码。后来把NO_COLOR设进环境变量这个问题就消失了。这个细节如果不说你自己遇到可能要折腾好一阵。完整链路验证通过后还可以再测两种典型场景选中一段代码让 IDEA 的 AI 助手“解释选中代码”。在对话里让它“重构当前方法”看是否返回可操作的方案。这两种场景覆盖了 IDE 里最高频的 AI 使用方式。如果都能正常工作说明集成已经具备日常实用价值。5. 常见问题与排查技巧实录5.1 五个典型报错及解决办法集成过程中难免出问题。下面这几个是我实际遇到过、或者朋友集成时问过最多的整理成速查表现象可能原因解决办法IDEA 一直转圈几十秒后报超时桥接层timeout太短或 CLI 本身响应慢调大timeout参数先在 curl 里测一遍耗时回复内容出现\x1b[开头的乱码Gemini CLI 输出了 ANSI 颜色码确认桥接脚本里设置了NO_COLOR1提示gemini: command not foundIDEA 启动时没继承终端的 PATH在桥接脚本里把gemini换成绝对路径或用shutil.which找路径curl 测试正常但 IDEA 连不上IDEA 配置的 Base URL 末尾多打了/Base URL 填http://127.0.0.1:8000/v1不要带多余的斜杠回复内容为空只有空白-p模式输出到了 stderr或 CLI 版本差异检查脚本里proc.stderr临时改成只回显 stderr 看内容这个表格是经验提炼前三条尤其常见。特别是 PATH 问题IDEA 图形界面启动的进程默认不会加载 shell 的完整环境变量如果你平时用的是nvm、homebrew这类工具gemini命令的路径很可能不在 IDEA 继承的 PATH 里。最省事的办法就是在脚本里写死绝对路径。5.2 利用 CLI 日志定位问题桥接层的一个天然优势是问题链路短排查起来比直接调 API 要直观得多。Gemini CLI 自己会输出运行时日志默认在用户的配置目录下。遇到“IDEA 发消息但没反应”的情况我建议按这个顺序排查看桥接服务终端有没有收到请求。如果 Flask 没有任何访问日志说明问题出在 IDEA 到桥接这一段重点检查 Base URL、API Key 配置。看 CLI 日志。如果桥接收到请求但 CLI 没执行可能是 prompt 拼接出错或消息体解析有问题。看 CLI 的退出码。proc.returncode不等于 0 时把proc.stderr打出来错误信息会直接提示是鉴权失败、模型不存在还是工具执行异常。这个顺序是从外到内一层层逼近比盲目改代码高效得多。我通常在脚本里加一个print把收到的 messages 摘要打出来调试完再注释掉这样每次请求的上下文一目了然。5.3 让桥接用起来更顺手的小经验几个加分项虽然不影响基本功能但会让日常用起来舒服很多。给桥接服务加一个日志文件。把 Flask 的日志重定向到文件后出问题不用守着终端看python gemini_bridge.py /tmp/gemini_bridge.log 21如果有需求让多个项目共享这个桥接服务可以把端口改成不常用的高位端口比如 8231避免和本地开发服务冲突。还有一个值得做的小优化处理content数组时把图片、文件引用类的非文本内容忽略掉只保留文本片段。这样 IDEA 发来多模态内容时桥接层不至于因为 JSON 解析失败直接报错而是优雅地把可用的文本部分交给 CLI。最后如果每次重启电脑都要手动启动桥接服务会有点烦。我目前的习惯是把它做成开机自启的任务或者平时不关终端放在前端服务里跑着。你要是也经常遇到“打开 IDEA 想起来还没启动桥接”可以试试把它注册成系统服务只是写法会因操作系统而异这里就不展开了。6. 安全边界与后续还能怎么玩6.1 本地桥接的安全边界先给所有准备照做的人提个醒这个桥接服务只适合跑在本机不要用公网地址启动更不要把它挂到云服务器上。原因有两个层面。一是功能层面它没有做任何鉴权和访问控制任何能连到你机器 8000 端口的进程都可以免费调用你的 Gemini CLI消耗你的配额。二是数据层面IDEA 发过来的请求通常包含完整代码上下文这些数据都应当在本地闭环流转不应该经过任何第三方路径。如果你确实需要远程访问 IDEA应该用 IDEA 自身的安全远程开发方案而不是靠裸奔的桥接服务。这一点算不上技术难题属于使用边界意识。6.2 从非流式到流式的升级方向当前桥接实现是非流式用户体验上有一点落差IDEA 面板要等 Gemini CLI 跑完才会一次性输出遇到长回答时等待感明显。升级思路也不复杂用 Flask 实现 SSE 时把gemini -p的输出改成逐行读取每读到一行就作为 SSE 的data:字段推给 IDEA。前提是 Gemini CLI 的输出是逐行的并且流式返回时不被缓冲。这个升级花不了太久效果却是实打实的提升。IDEA 面板上能像原生 AI 助手一样逐个字冒出来沉浸在编辑器里的用户才不会觉得这是个半成品集成。6.3 还能把谁接进来这个桥接架构不只适用于 Gemini CLI。你手头如果有其他命令行 AI 工具只要它支持“非交互式输入一段提示并输出文本”理论上都能用近乎一样的思路接入 IDEA改一下子进程调用的命令保持 OpenAI 兼容接口不变IDEA 侧完全不需要动。这就是把接口契约和实际执行解耦带来的好处。想更进一步的话可以在桥接层里写一个简单的路由规则根据 IDEA 请求里的模型名判断是走 Gemini CLI 还是走其他工具。这相当于在自己电脑上做了一个小小的 AI 网关日常用起来会非常灵活。把 Gemini CLI 接进 IntelliJ IDEA 之后我个人的日常开发基本不再来回切换窗口了。遇到需要深度分析的任务直接在 IDEA 里唤起 AI 助手底层自动跑到 Gemini CLI 去完成编辑器的补全和跳转仍然由 IDEA 负责。可以明显感受到两边各自做擅长的事整体效率反而比单一工具单打独斗更高。最后再分享一个小技巧桥接脚本里那个build_prompt函数你可以按自己的偏好调整格式比如在每轮 user 消息前加一行“当前工作目录内容如下”之类的背景信息Gemini CLI 的上下文感知会更贴合项目。集成工具的乐趣就在这它不是终点而是你顺手就能改造的起点。