Gemini CLI SDK 实战指南:用 @google/gemini-cli-sdk 在 Node.js 中构建可编程的 Gemini Agent
2026/9/7 23:02:24 网站建设 项目流程

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.tssession.tstool.tsskills.tstypes.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 本体共用的核心引擎)、zodzod-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);

示例传达的三件核心事:创建一个 AgentAbortController的 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 构造函数的实际使用方式,完整参数表如下:

参数类型必填默认值说明
instructionsstring \| ((ctx: SessionContext) => string \| Promise<string>)系统指令。可以是静态字符串,也可以是接收SessionContext的动态函数(下一节详述)
toolsArray<Tool<any>>[]自定义工具列表,每个工具由tool()辅助函数创建
skillsSkillReference[][]技能目录引用,由skillDir(path)生成
modelstringPREVIEW_GEMINI_MODEL_AUTO(自动选择)指定 Gemini 模型名称
cwdstringprocess.cwd()Agent 工作目录,等价于gemini -p运行时加载工作区配置的目录
debugbooleanfalse调试模式,输出详细日志(映射到 core 的debugMode
recordResponsesstring将 Agent 响应记录到指定文件路径,用于调试与回放
fakeResponsesstring从指定文件加载预录制(re-simulated)响应,用于确定性测试

cwd参数值得展开:session.ts 中它同时被写入ConfigParameterstargetDircwd字段,resumeSession时也用它定位Storage(见 agent.ts 中new Storage(cwd))。这意味着会话历史与项目级配置都锚定在cwd,多租户或多项目场景下务必显式指定。

recordResponses/fakeResponses这对参数是 SDK 面向测试能力的关键设计:仓库中 packages/sdk/test-data/ 目录下就有大量配套数据文件,例如agent-static-instructions.jsonagent-async-instructions.jsonagent-resume-session.jsontool-success.jsontool-error-recovery.json等,供集成测试回放确定性的模型响应(见下文“测试与可回放性”一节)。

五、系统指令:静态字符串与动态函数

SDK 支持两种指令形式,类型定义为 types.ts 中的SystemInstructions

export type SystemInstructions = | string | ((context: SessionContext) => string | Promise<string>);

静态字符串会在GeminiCliSession构造时写入 core 的ConfiguserMemory字段,出现在模型调用中 GEMINI.md 内容通常所在的位置;动态函数则更强——从 session.ts 的sendStream实现可以看到:

  1. 每轮 Agent 循环开始时都会重新求值while (true)循环顶部,若instructions是函数,SDK 会组装当前SessionContext(含最新 transcript、时间戳、fs、shell 等)并await调用它;
  2. 求值结果通过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; // 当前会话实例 }

两个关键子接口:

  • AgentFilesystemreadFile(path)返回Promise<string | null>(不存在或无权限返回null),writeFile(path, content)返回Promise<void>(策略拒绝时抛错)。JSDoc 强调实现内部必须校验路径、防止..与空字节造成的路径穿越;
  • AgentShellexec(cmd, options?)返回AgentShellResult,包含exitCode(进程被杀时为null)、output(stdout+stderr 合并)、stdoutstderr与可选的errorAgentShellOptions支持env(与环境合并)、timeoutSecondscwd

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.sessionIdcontext.cwdcontext.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 循环:

  1. (可选)重新求值动态指令并更新userMemory
  2. client.sendMessageStream(request, abortSignal, sessionId)发起流式请求,逐事件yield给调用方;
  3. 同时收集GeminiEventType.ToolCallRequest事件(若args是字符串会JSON.parse反序列化);
  4. 若本轮没有工具调用则break结束;否则克隆注册表并绑定本轮SessionContext,调用 core 的scheduleAgentTools(this.config, toolCallsToSchedule, { schedulerId: sessionId, toolRegistry, signal })执行全部工具;
  5. 把各工具响应的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.jsontool-error-recovery.jsonagent-resume-session.jsonskill-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: falsemcpEnabled: falseextensionsEnabled: 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 面(GeminiCliAgentGeminiCliSessiontoolskillDirSessionContext)把 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询