Gemini CLI SDK 实战指南:用 @google/gemini-cli-sdk 在 Node.js 中构建可编程的 Gemini Agent
【免费下载链接】gemini-cliAn open-source AI agent that brings the power of Gemini directly into your terminal.项目地址: https://gitcode.com/GitHub_Trending/gemi/gemini-cli
本篇指南围绕 packages/sdk/README.md 展开,系统讲解 Gemini CLI 官方 SDK 的安装、Agent 创建、流式会话、自定义工具、会话上下文与技能加载等核心能力,并结合仓库内packages/sdk包的源码(agent.ts、session.ts、tool.ts、skills.ts、types.ts)剖析其底层 Agent 循环、工具注册与错误处理机制,帮助读者在自己的 Node.js 项目中把 Gemini 的终端智能体能力以编程方式嵌入自动化脚本、CI 流水线或服务端应用。
一、SDK 是什么:定位与实现状态
Gemini CLI SDK 为 Gemini 模型与工具提供编程式接口(programmatic interface),让你无需启动交互式终端,就能在代码中驱动一个具备完整 Agent 循环能力的 Gemini 会话。它在仓库中是packages/sdk这个独立 npm 包,包名为@google/gemini-cli-sdk。
从 packages/sdk/package.json 可以确认其关键元信息:
- 包名
@google/gemini-cli-sdk,License 为 Apache-2.0; "type": "module":纯 ESM 包,使用import语法引入;"engines": { "node": ">=20" }:要求 Node.js 20 及以上;- 运行时依赖
@google/gemini-cli-core(以file:../core方式指向仓库内packages/core包,即 CLI 本体共用的核心引擎)、zod与zod-to-json-schema(用于工具参数的声明式 schema 定义与向模型侧的 JSON Schema 转换)。
SDK 的公开 API 从 packages/sdk/src/index.ts 统一导出,共五个模块:
export * from './agent.js'; // GeminiCliAgent export * from './session.js'; // GeminiCliSession export * from './tool.js'; // tool、Tool、SdkTool、ModelVisibleError、z export * from './skills.js'; // skillDir、SkillReference export * from './types.js'; // GeminiCliAgentOptions、SystemInstructions、SessionContext 等需要说明的是,SDK 目前仍处于快速演进阶段。仓库中的设计文档 packages/sdk/SDK_DESIGN.md 明确标注了各功能的实现状态:
| 能力 | 状态(以 SDK_DESIGN.md 标注为准) |
|---|---|
会话创建 / 会话恢复(session()/resumeSession()) | 已实现 |
| 系统指令(静态字符串 + 动态函数) | 已实现 |
自定义工具(tool()+ Zod schema) | 已实现 |
自定义技能(skillDir) | 已实现 |
| SessionContext(fs / shell 接口) | 已实现 |
| 自定义 Hooks / Subagents / Extensions / ACP 模式 / 审批策略 | 未实现 |
下文讲解的所有内容均以“已实现”部分为准,未实现部分会在最后单独说明边界。
二、安装
按 packages/sdk/README.md 的说明,安装方式只有一条命令:
npm install @google/gemini-cli-sdk前提条件:
- Node.js >= 20(见 package.json 中
engines字段); - 代码中使用 ESM 语法(
import),因为包声明为"type": "module"; - 运行时需要具备 Gemini 的认证配置——从源码看,会话初始化时通过
getAuthTypeFromEnv()读取环境变量来确定认证方式,未设置时回退到AuthType.COMPUTE_ADC(Google Cloud 工作负载身份),详见 packages/sdk/src/session.ts 的initialize()方法。
三、快速上手:README 最简示例
README 给出的最小可用示例如下(原文继承自 packages/sdk/README.md):
import { GeminiCliAgent } from '@google/gemini-cli-sdk'; async function main() { const agent = new GeminiCliAgent({ instructions: 'You are a helpful assistant.', }); const controller = new AbortController(); const signal = controller.signal; // Stream responses from the agent const stream = agent.sendStream('Why is the sky blue?', signal); for await (const chunk of stream) { if (chunk.type === 'content') { process.stdout.write(chunk.value.text || ''); } } } main().catch(console.error);示例传达的三件核心事:创建一个 Agent、用AbortController的 signal 控制取消、用for await消费流式响应。
不过从当前源码结构看,需要做一个重要补充:packages/sdk/src/agent.ts 中的GeminiCliAgent类目前只暴露session()与resumeSession()两个方法,流式发送的实现在GeminiCliSession.sendStream(prompt, signal?)上(见 packages/sdk/src/session.ts)。SDK_DESIGN.md 与仓库内示例采用的是“Agent → Session → sendStream”的两级结构,这也是当前可运行的调用路径:
import { GeminiCliAgent } from '@google/gemini-cli-sdk'; const agent = new GeminiCliAgent({ instructions: 'You are a helpful assistant.', }); // 创建新会话 const session = agent.session(); // 也可以恢复既有会话 // const session = await agent.resumeSession('some-session-id'); const controller = new AbortController(); for await (const event of session.sendStream('Why is the sky blue?', controller.signal)) { console.log(event); // JSON 流式事件 }sendStream是一个AsyncGenerator<ServerGeminiStreamEvent>,事件类型沿用@google/gemini-cli-core定义的GeminiEventType。从 session.ts 的实现可以看到,源码至少处理了GeminiEventType.ToolCallRequest(模型请求调用工具时产生)这类事件,模型响应文本等事件则直接透传给调用方。消费事件时,建议按event.type做分支处理,而不是假定每个 chunk 都有text字段。
四、GeminiCliAgentOptions:完整参数说明
Agent 的全部可配置项定义在 packages/sdk/src/types.ts 的GeminiCliAgentOptions接口中。结合接口内的 JSDoc 注释与 session.ts 构造函数的实际使用方式,完整参数表如下:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
instructions | string \| ((ctx: SessionContext) => string \| Promise<string>) | 是 | 无 | 系统指令。可以是静态字符串,也可以是接收SessionContext的动态函数(下一节详述) |
tools | Array<Tool<any>> | 否 | [] | 自定义工具列表,每个工具由tool()辅助函数创建 |
skills | SkillReference[] | 否 | [] | 技能目录引用,由skillDir(path)生成 |
model | string | 否 | PREVIEW_GEMINI_MODEL_AUTO(自动选择) | 指定 Gemini 模型名称 |
cwd | string | 否 | process.cwd() | Agent 工作目录,等价于gemini -p运行时加载工作区配置的目录 |
debug | boolean | 否 | false | 调试模式,输出详细日志(映射到 core 的debugMode) |
recordResponses | string | 否 | 无 | 将 Agent 响应记录到指定文件路径,用于调试与回放 |
fakeResponses | string | 否 | 无 | 从指定文件加载预录制(re-simulated)响应,用于确定性测试 |
cwd参数值得展开:session.ts 中它同时被写入ConfigParameters的targetDir与cwd字段,resumeSession时也用它定位Storage(见 agent.ts 中new Storage(cwd))。这意味着会话历史与项目级配置都锚定在cwd上,多租户或多项目场景下务必显式指定。
recordResponses/fakeResponses这对参数是 SDK 面向测试能力的关键设计:仓库中 packages/sdk/test-data/ 目录下就有大量配套数据文件,例如agent-static-instructions.json、agent-async-instructions.json、agent-resume-session.json、tool-success.json、tool-error-recovery.json等,供集成测试回放确定性的模型响应(见下文“测试与可回放性”一节)。
五、系统指令:静态字符串与动态函数
SDK 支持两种指令形式,类型定义为 types.ts 中的SystemInstructions:
export type SystemInstructions = | string | ((context: SessionContext) => string | Promise<string>);静态字符串会在GeminiCliSession构造时写入 core 的Config的userMemory字段,出现在模型调用中 GEMINI.md 内容通常所在的位置;动态函数则更强——从 session.ts 的sendStream实现可以看到:
- 每轮 Agent 循环开始时都会重新求值:
while (true)循环顶部,若instructions是函数,SDK 会组装当前SessionContext(含最新 transcript、时间戳、fs、shell 等)并await调用它; - 求值结果通过
this.config.setUserMemory(newInstructions)更新,再调用client.updateSystemInstruction()让新指令在下一轮模型请求中生效。
这意味着你可以实现“随对话状态演化”的系统指令,例如在 SDK_DESIGN.md 中给出的例子:
const agent = new GeminiCliAgent({ instructions: (ctx) => `The current time is ${new Date().toISOString()} in session ${ctx.sessionId}.`, });安全提示来自 types.ts 的官方注释:动态指令函数会把SessionContext数据拼进提示词,必须自行做消毒处理(例如去除换行、],转义<、>),防止会话内容反向注入系统指令。
六、自定义工具:tool() 辅助函数与错误处理
自定义工具是 SDK 的核心卖点。tool()辅助函数、Tool接口与z(Zod 的再导出)均定义在 packages/sdk/src/tool.ts。
6.1 基本用法
README 与 SDK_DESIGN.md 中给出的示例(仓库内 examples/simple.ts 也有可运行的等价实现):
import { GeminiCliAgent, tool, z } from '@google/gemini-cli-sdk'; const addTool = tool( { name: 'add', description: 'add two numbers', inputSchema: z.object({ a: z.number().describe('first number to add'), b: z.number().describe('second number to add'), }), }, ({ a, b }) => ({ result: a + b }), ); const agent = new GeminiCliAgent({ tools: [addTool], instructions: '...' }); const session = agent.session(); for await (const chunk of session.sendStream('what is 23 + 79?')) { console.log(chunk); }ToolDefinition的四个字段(见 tool.ts):
name:模型用来调用工具的唯一名称;description:发送给模型的工具说明,直接影响模型“何时选用该工具”;inputSchema:Zod schema,用于参数校验,并且会被zodToJsonSchema转换后作为 JSON Schema 提供给模型(SdkTool构造函数中的zodToJsonSchema(definition.inputSchema));sendErrorsToModel?:默认false。为true时,action 抛出的错误会作为Error: <message>文本送回模型,供其自行纠正重试。
action 的签名是(params: z.infer<T>, context?: SessionContext) => Promise<unknown>:
params的类型由 Zod schema 静态推断,拿到即是类型安全的;- 返回值若不是字符串,会被
JSON.stringify(result, null, 2)序列化后作为llmContent回传给模型; - 第二个参数
context即下文详述的SessionContext,让工具可以访问沙箱文件系统与 shell。
6.2 面向模型的错误:ModelVisibleError
tool.ts 中定义了一个专门的错误类:
export class ModelVisibleError extends Error { constructor(message: string | Error) { super(message instanceof Error ? message.message : message); this.name = 'ModelVisibleError'; } }SdkToolInvocation.execute()的 catch 分支处理逻辑清晰:
- 若抛出的错误是
ModelVisibleError,或工具定义声明了sendErrorsToModel: true,则错误信息以Error: <message>形式作为工具响应回传模型(同时带error元数据),模型可以据此调整策略; - 否则错误原样向外抛出,中断本轮工具执行。
这是“把可控反馈给模型、把真正的故障留给宿主程序”的分工设计,写工具时应尽量抛出ModelVisibleError来表达可恢复的业务性失败。
七、SessionContext:工具内的文件系统与 Shell 能力
SessionContext是 SDK 传给工具与动态指令函数的“环境句柄”,接口定义在 types.ts,与 SDK_DESIGN.md 中设计稿一致:
export interface SessionContext { sessionId: string; // 会话唯一标识 transcript: readonly Content[]; // 只读对话历史 cwd: string; // 会话工作目录 timestamp: string; // ISO 8601 时间戳 fs: AgentFilesystem; // 沙箱文件系统 shell: AgentShell; // 沙箱 shell agent: GeminiCliAgent; // 所属 Agent 实例 session: GeminiCliSession; // 当前会话实例 }两个关键子接口:
AgentFilesystem:readFile(path)返回Promise<string | null>(不存在或无权限返回null),writeFile(path, content)返回Promise<void>(策略拒绝时抛错)。JSDoc 强调实现内部必须校验路径、防止..与空字节造成的路径穿越;AgentShell:exec(cmd, options?)返回AgentShellResult,包含exitCode(进程被杀时为null)、output(stdout+stderr 合并)、stdout、stderr与可选的error;AgentShellOptions支持env(与环境合并)、timeoutSeconds、cwd。
SessionContext的实际构造发生在 session.ts 的sendStream中:每一轮循环 SDK 都会new SdkAgentFilesystem(this.config)与new SdkAgentShell(this.config)(见 fs.ts 与 shell.ts),并把工具注册表克隆一份作用域副本(scopedRegistry),在其中把SdkTool替换为bindContext(context)后的实例——即每个工具调用绑定的都是本轮最新的上下文。
仓库内 examples/session-context.ts 给出了完整可运行的用法:定义一个无参数的get_context工具,action 内读取context.sessionId、context.cwd、context.timestamp,调用context.fs.readFile('package.json')与context.shell.exec('echo "Hello from SDK Shell"'),最终把探测结果回传给模型。该示例同时展示了cwd参数如何让 Agent“知道”项目根目录(cwd: process.cwd())。
八、技能(Skills):用目录扩展 Agent 能力
技能系统通过 packages/sdk/src/skills.ts 暴露,API 极其精简:
export type SkillReference = { type: 'dir'; path: string }; export function skillDir(path: string): SkillReference { return { type: 'dir', path }; }一个技能是一个目录,至少包含SKILL.md(元数据与指令),可选tools/子目录存放工具脚本,目录结构如 SDK_DESIGN.md 所述:
skill-dir/ SKILL.md (Metadata and instructions) tools/ (Optional directory for tools) my-tool.js在 Agent 上加载:
import { GeminiCliAgent, skillDir } from '@google/gemini-cli-sdk'; const agent = new GeminiCliAgent({ instructions: 'You are a helpful assistant.', skills: [ skillDir('./my-skill'), // 加载单个技能目录 skillDir('./skills-collection'), // 加载根目录下所有子技能 ], });加载时机在 session.ts 的initialize()中:对每个type === 'dir'的引用调用 core 的loadSkillsFromDir(ref.path),结果通过skillManager.addSkills()注入;只要技能非空,就会(先卸载再)注册 core 的ActivateSkillTool,使模型能通过激活技能工具按需使用技能内容。仓库中 packages/sdk/test-data/skills/pirate-skill/SKILL.md 就是一个真实的技能目录样例,examples/simple.ts 中“always talk like a pirate”的指令正是围绕这类技能机制展开的演示风格。加载失败不会抛异常,而是console.error后跳过该目录(源码中有 TODO 标注未来改用正式 logger)。
九、会话生命周期:initialize、sendStream 与 resumeSession
把前面各节串起来,一个完整的会话生命周期在源码中的路径如下(均在 session.ts 与 agent.ts):
1. 初始化initialize()(幂等)
getAuthTypeFromEnv() || AuthType.COMPUTE_ADC决定认证方式,依次执行config.refreshAuth(authType)与config.initialize();- 加载技能、注册
ActivateSkillTool; - 把每个 SDK 工具包装成
SdkTool注册进toolRegistry; - 若会话是恢复的(携带
resumedData),则把ConversationRecord.messages逐条映射为{ role: 'model' | 'user', parts }形式的Content[],调用client.resumeChat(history, resumedData)重放历史。
注意sendStream内部也会检查this.initialized,未初始化时自动补一次initialize(),调用方不必手动管理。
2. Agent 循环sendStream(prompt, signal?)
这是 SDK 最有含金量的部分,一个典型的 while 循环:
- (可选)重新求值动态指令并更新
userMemory; client.sendMessageStream(request, abortSignal, sessionId)发起流式请求,逐事件yield给调用方;- 同时收集
GeminiEventType.ToolCallRequest事件(若args是字符串会JSON.parse反序列化); - 若本轮没有工具调用则
break结束;否则克隆注册表并绑定本轮SessionContext,调用 core 的scheduleAgentTools(this.config, toolCallsToSchedule, { schedulerId: sessionId, toolRegistry, signal })执行全部工具; - 把各工具响应的
responseParts平铺为functionResponses,作为下一轮request发回模型——如此往复,直到模型不再请求工具,产出最终回答。
AbortSignal会一路透传到模型流与工具调度器,取消是贯穿整个循环的。
3. 恢复会话resumeSession(sessionId)
agent.ts 中实现了与 CLI 共享的会话恢复逻辑:基于Storage(cwd)列出项目 chat 文件(listProjectChatFiles()),先用sessionId前 8 位匹配文件名(源码注释说明这是对文件命名约定的优化,无候选时回退全量),再逐个loadConversationRecord()精确比对完整sessionId。找不到时分别抛出No sessions found in <chats 目录>或Session with ID <id> not found。恢复后的会话继续走resumeChat重放历史,与 CLI 的--resume体验对齐。
十、会话内配置细节:源码里的默认值
session.ts 构造函数中写入ConfigParameters的一组默认值,直接决定了 SDK 会话的行为边界,值得逐一列出:
const configParams: ConfigParameters = { sessionId: this.sessionId, targetDir: cwd, cwd, debugMode: options.debug ?? false, model: options.model || PREVIEW_GEMINI_MODEL_AUTO, userMemory: initialMemory, // 静态 instructions enableHooks: false, // 与 SDK_DESIGN.md 的 “Hooks: Not Implemented” 一致 mcpEnabled: false, extensionsEnabled: false, recordResponses: options.recordResponses, fakeResponses: options.fakeResponses, skillsSupport: true, adminSkillsEnabled: true, policyEngineConfig: { // TODO: Revisit this default when we have a mechanism for wiring up approvals defaultDecision: PolicyDecision.ALLOW, }, };可以推断:当前 SDK 会话是策略默认放行(PolicyDecision.ALLOW)且明确关闭了 hooks / MCP / extensions 入口的轻量形态;源码中的 TODO 注释也表明审批(approvals)机制尚未接入,这与 SDK_DESIGN.md 的 “Approvals / Policies: Not Implemented” 互相印证。使用 SDK 驱动文件与 shell 操作时,应自行在工具实现层做权限约束。
十一、测试与可回放性
SDK 自带了完整的测试分层,可作为工程实践参考:
- 单元测试:packages/sdk/src/tool.test.ts、packages/sdk/src/session.test.ts;
- 集成测试:packages/sdk/src/agent.integration.test.ts、packages/sdk/src/tool.integration.test.ts、packages/sdk/src/skills.integration.test.ts,配合 test-data/ 下的预录响应文件(如
tool-success.json、tool-error-recovery.json、agent-resume-session.json、skill-dir-success.json)实现确定性的端到端验证; - 配置:packages/sdk/vitest.config.ts,包级脚本见 package.json(
test/typecheck/lint)。
recordResponses+fakeResponses这对参数正是 SDK_DESIGN.md “Notes” 中提到的“用 mock 的模型 API 让测试接近端到端且保持确定性”思路的落地:先录制真实响应,再用fakeResponses回放,从而让工具执行、错误恢复、会话恢复等逻辑都能离线验证。
十二、当前边界与后续演进
基于 packages/sdk/SDK_DESIGN.md 的状态标注与源码交叉验证,当前版本的明确边界是:
- 未实现:自定义 Hooks、Subagents、Extensions、ACP 模式、显式审批/策略 API(源码中
enableHooks: false、mcpEnabled: false、extensionsEnabled: false与之对应); - 策略引擎默认
ALLOW,审批机制处于 TODO 状态; - README 的流式示例(
agent.sendStream)与当前源码中GeminiCliAgent仅暴露session()/resumeSession()的结构存在出入,实际开发请以“Agent → Session →sendStream”路径及 src/agent.ts、src/session.ts 为准; - 事件消费按
ServerGeminiStreamEvent/GeminiEventType分支处理更稳妥,不同事件类型的value结构并不统一。
设计文档同时给出了演进路线:Hook 接口需从字符串事件名到请求/响应类型的强类型映射、审批流需同时兼容 CLI 触发的确认与开发者发起的用户提示(HITL)、子代理需明确消息上下文继承方式(例如是否共享 sessionId)。这些方向可作为跟踪该包后续版本的依据。
小结
Gemini CLI SDK 以极小的 API 面(GeminiCliAgent、GeminiCliSession、tool、skillDir、SessionContext)把 Gemini CLI 的核心 Agent 循环开放给了 Node.js 开发者:用 Zod 声明类型安全的自定义工具,用动态指令函数让系统提示随会话演化,用SessionContext.fs/shell在沙箱边界内扩展工具能力,用recordResponses/fakeResponses保证行为可回放、可测试。结合上文给出的参数表、调用链与源码路径(packages/sdk/src/下各文件),你既可以按 README 的三步式示例快速起步,也可以下钻到session.ts的 while 循环理解每一轮“请求—工具—回填”的完整机制。
【免费下载链接】gemini-cliAn open-source AI agent that brings the power of Gemini directly into your terminal.项目地址: https://gitcode.com/GitHub_Trending/gemi/gemini-cli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考