第4章:query() —— Agent 的启动按钮
一句话:掌握 SDK 的核心函数
query()的所有用法,从此你就能让 Agent 听你指挥干活了。
本章目标
- 深入理解
query()函数的工作原理 - 掌握所有配置选项(Options)
- 理解消息类型:SystemMessage、AssistantMessage、ResultMessage
- 能灵活配置 Agent 的行为
- 写出自己的第一个实用 Agent 程序
前置知识
- 第3章(环境已搭建好,SDK 已安装)
4.1 query() 是什么?
整个 Claude Agent SDK 就围绕一个函数 —— query()。
没错,就一个函数。你学会了它,就学会了整个 SDK 的 80%。
打个比方
query() 就像洗衣机上的"启动"按钮:
- 你先把衣服放进去(设置
prompt—— 告诉 Agent 要干什么) - 选好洗衣程序(设置
options—— 用什么模型、给什么工具、权限怎么配) - 按下启动按钮(调用
query()) - 洗衣机开始自动运转(Agent 进入 Agentic Loop —— 思考、行动、检查、再思考...)
- 你可以随时看进度(通过
for await接收消息) - 洗完了会通知你(收到 ResultMessage)
函数签名
import { query } from "@anthropic-ai/claude-code";
function query({
prompt,
options
}: {
prompt: string | AsyncIterable<SDKUserMessage>;
options?: Options;
}): Query
返回值 Query 是一个 AsyncGenerator<SDKMessage, void>,说人话就是:它会一条一条地"吐出"消息,你用 for await 去接就行。
它的工作流程
你调用 query()
│
▼
SDK 创建一个 Agent 循环
│
▼
┌──────────────────────────────┐
│ Agent 循环开始运转: │
│ │
│ 1. 发 SystemMessage(init) │ ← 告诉你"我准备好了"
│ 2. Claude 思考,给回复 │ ← AssistantMessage
│ 3. 需要用工具吗? │
│ ├── 是 → 执行工具 │
│ │ → 把结果交给 Claude │
│ │ → 回到第2步 │
│ └── 否 → 任务完成 │
│ 4. 发 ResultMessage │ ← 告诉你"我做完了"
│ │
└──────────────────────────────┘
整个过程全自动,你只需要坐等消息就行。
4.2 最小可运行示例
示例1:最简单的 —— 让 Agent 说句话
// 文件:hello-agent.ts
import { query } from "@anthropic-ai/claude-code";
async function main() {
// 调用 query(),启动 Agent
for await (const message of query({
prompt: "用一句话介绍你自己",
options: {
allowedTools: [], // 不给任何工具,纯聊天
maxTurns: 1, // 只跑一轮
permissionMode: "default" // 默认权限模式
}
})) {
// 只关心 Agent 的回复
if (message.type === "assistant") {
// message.message.content 是一个数组,里面有 text 块
for (const block of message.message.content) {
if (block.type === "text") {
console.log("Agent 说:", block.text);
}
}
}
}
}
main().catch(console.error);
逐行解释:
| 行 | 干什么 |
|---|---|
import { query } |
从 SDK 导入唯一的核心函数 |
for await (const message of query({...})) |
启动 Agent,并逐条接收消息 |
prompt: "用一句话介绍你自己" |
告诉 Agent 要做什么 |
allowedTools: [] |
不给工具,Agent 只能"说话"不能"动手" |
maxTurns: 1 |
只允许一个回合 |
message.type === "assistant" |
过滤出 Agent 的回复消息 |
block.type === "text" |
过滤出文本内容(不是工具调用) |
运行它:
npx ts-node hello-agent.ts
你会看到 Agent 回复一句自我介绍,然后程序结束。
示例2:给 Agent 一些工具
// 文件:agent-with-tools.ts
import { query } from "@anthropic-ai/claude-code";
async function main() {
for await (const message of query({
prompt: "请查看当前目录下有哪些文件,然后告诉我",
options: {
allowedTools: ["Bash", "Glob", "Read"], // 给三个工具
permissionMode: "bypassPermissions", // 自动放行(测试用)
maxTurns: 5 // 最多跑5轮
}
})) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "text") {
console.log(block.text);
}
if (block.type === "tool_use") {
console.log(`[调用工具] ${block.name}(${JSON.stringify(block.input)})`);
}
}
}
if (message.type === "result") {
console.log("\n--- 任务完成 ---");
if (message.subtype === "success") {
console.log("花费:", message.total_cost_usd, "美元");
console.log("用了", message.num_turns, "个回合");
}
}
}
}
main().catch(console.error);
这次 Agent 有工具了!它可能会:
- 先用
Bash执行ls命令 - 看到文件列表后,用自然语言告诉你结果
你能在终端里实时看到它的思考和行动过程。
4.3 理解消息类型
query() 会吐出好几种消息。最重要的就三种,我们一个一个来看。
消息流的整体顺序
SystemMessage (type: "system", subtype: "init")
↓
AssistantMessage (type: "assistant") ← 可能有多条
↓
AssistantMessage (type: "assistant") ← Agent 继续思考和行动
↓
ResultMessage (type: "result") ← 最后一条
4.3.1 SystemMessage —— "我准备好了"
这是你收到的第一条消息,Agent 用它告诉你:"我启动了,这是我的基本信息。"
TypeScript 类型定义:
type SDKSystemMessage = {
type: "system";
subtype: "init";
uuid: string;
session_id: string; // 会话 ID,很重要!后面恢复会话要用
apiKeySource: string; // API Key 来源
cwd: string; // 工作目录
tools: string[]; // 可用工具列表
mcp_servers: { // MCP 服务器状态
name: string;
status: string;
}[];
model: string; // 使用的模型
permissionMode: string; // 权限模式
slash_commands: string[]; // 可用的斜杠命令
output_style: string; // 输出风格
}
实际拿到的数据长什么样?
// 打印 SystemMessage
if (message.type === "system" && message.subtype === "init") {
console.log("会话 ID:", message.session_id);
console.log("工作目录:", message.cwd);
console.log("可用工具:", message.tools);
console.log("使用模型:", message.model);
console.log("权限模式:", message.permissionMode);
console.log("MCP 服务器:", message.mcp_servers);
}
输出示例:
会话 ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
工作目录: /home/user/my-project
可用工具: ["Bash", "Read", "Write", "Edit", "Glob", "Grep"]
使用模型: claude-sonnet-4-20250514
权限模式: bypassPermissions
MCP 服务器: []
这条消息有什么用?
- 拿到
session_id,保存起来,以后可以恢复这个会话 - 确认工具和模型是否符合预期
- 检查 MCP 服务器是否连接成功
4.3.2 AssistantMessage —— Agent 的"实时汇报"
这是最常见的消息,每次 Claude 思考完或者要调用工具时,你都会收到一条。
TypeScript 类型定义:
type SDKAssistantMessage = {
type: "assistant";
uuid: string;
session_id: string;
message: { // Anthropic API 的原始消息格式
role: "assistant";
content: ContentBlock[]; // 内容块数组
stop_reason: string; // 停止原因
usage: Usage; // Token 用量
};
parent_tool_use_id: string | null; // 如果是子 Agent,这里有值
}
重点来了:content 数组
content 里面可以有两种东西:
1. 文本块(TextBlock)—— Agent 在"说话":
{
type: "text",
text: "让我来看看当前目录有哪些文件..."
}
2. 工具调用块(ToolUseBlock)—— Agent 在"动手":
{
type: "tool_use",
id: "toolu_abc123",
name: "Bash",
input: {
command: "ls -la"
}
}
处理 AssistantMessage 的完整代码:
if (message.type === "assistant") {
for (const block of message.message.content) {
switch (block.type) {
case "text":
// Agent 在说话
console.log("[思考]", block.text);
break;
case "tool_use":
// Agent 在调用工具
console.log(`[行动] 调用 ${block.name}`);
console.log(` 参数: ${JSON.stringify(block.input, null, 2)}`);
break;
}
}
// 还可以看看为什么停下来
const stopReason = message.message.stop_reason;
if (stopReason === "end_turn") {
console.log("(Agent 说完了)");
} else if (stopReason === "tool_use") {
console.log("(Agent 要用工具,循环继续)");
}
}
4.3.3 ResultMessage —— "我做完了"
这是你收到的最后一条消息。Agent 用它告诉你:任务完成了(或者出错了)。
ResultMessage 有两种变体:
变体1:成功
{
type: "result",
subtype: "success",
uuid: string,
session_id: string,
duration_ms: number, // 总耗时(毫秒)
duration_api_ms: number, // API 调用耗时
is_error: false,
num_turns: number, // 跑了几个回合
result: string, // 最终结果文本
total_cost_usd: number, // 总花费(美元)
usage: { // Token 用量统计
input_tokens: number,
output_tokens: number,
cache_creation_input_tokens: number,
cache_read_input_tokens: number
},
modelUsage: { ... }, // 按模型分的用量
permission_denials: [], // 被拒绝的权限列表
structured_output?: unknown // 结构化输出(如果配了的话)
}
变体2:失败
{
type: "result",
subtype: "error_max_turns" // 超过最大回合数
| "error_during_execution" // 执行过程中出错
| "error_max_budget_usd" // 超过预算
| "error_max_structured_output_retries", // 结构化输出重试次数超限
uuid: string,
session_id: string,
duration_ms: number,
duration_api_ms: number,
is_error: true,
num_turns: number,
total_cost_usd: number,
usage: { ... },
modelUsage: { ... },
permission_denials: [],
errors: string[] // 错误信息列表
}
处理 ResultMessage 的代码:
if (message.type === "result") {
if (message.subtype === "success") {
console.log("任务成功完成!");
console.log("结果:", message.result);
console.log("花费:", message.total_cost_usd.toFixed(4), "美元");
console.log("耗时:", (message.duration_ms / 1000).toFixed(1), "秒");
console.log("回合数:", message.num_turns);
console.log("Token 用量:", JSON.stringify(message.usage));
// 如果有结构化输出
if (message.structured_output) {
console.log("结构化数据:", message.structured_output);
}
} else {
console.error("任务失败!");
console.error("失败类型:", message.subtype);
if ("errors" in message) {
console.error("错误详情:", message.errors);
}
}
}
消息类型速查表
| 类型 | type 值 | 什么时候出现 | 你要关注什么 |
|---|---|---|---|
| SystemMessage | "system" |
最开始 | session_id、tools、model |
| AssistantMessage | "assistant" |
中间(多次) | content 数组中的 text 和 tool_use |
| ResultMessage | "result" |
最后 | subtype、total_cost_usd、result |
4.4 配置选项大全(Options)
query() 的第二个参数 options 是一个对象,里面有很多配置项。下面我按"使用频率"从高到低来讲。
4.4.1 allowedTools —— 给 Agent 什么工具
allowedTools: string[]
大白话: 决定 Agent 能用哪些工具。不给工具,Agent 就只能说话不能干活。
常用工具列表:
| 工具名 | 干什么的 |
|---|---|
"Read" |
读文件 |
"Write" |
写文件 |
"Edit" |
编辑文件 |
"Bash" |
执行 Shell 命令 |
"Glob" |
按模式搜索文件名 |
"Grep" |
搜索文件内容 |
"WebSearch" |
搜索网页 |
"WebFetch" |
抓取网页内容 |
"Task" |
启动子 Agent |
"TodoRead" |
读取任务清单 |
"TodoWrite" |
写入任务清单 |
代码示例:
// 纯聊天 Agent —— 没有工具
const chatAgent = query({
prompt: "讲个笑话",
options: { allowedTools: [] }
});
// 只读 Agent —— 只能看不能改
const readOnlyAgent = query({
prompt: "分析这个项目的代码结构",
options: { allowedTools: ["Read", "Glob", "Grep"] }
});
// 全能 Agent —— 能读能写能执行
const fullAgent = query({
prompt: "帮我重构这个函数",
options: { allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] }
});
// 带 MCP 工具 —— 通配符写法
const mcpAgent = query({
prompt: "列出我的 GitHub Issues",
options: { allowedTools: ["mcp__github__*"] } // 允许 github MCP 的所有工具
});
什么时候用: 每次调用 query() 都应该设置这个,精确控制 Agent 的能力边界。
4.4.2 permissionMode —— Agent 的"胆子大小"
permissionMode: "default" | "acceptEdits" | "bypassPermissions" | "plan"
大白话: 决定 Agent 遇到敏感操作时怎么办。
| 模式 | 行为 | 适用场景 |
|---|---|---|
"default" |
遇到敏感操作就停下来问你 | 日常使用(推荐) |
"acceptEdits" |
文件编辑自动放行,其他的问你 | 已信任 Agent 改文件 |
"bypassPermissions" |
全部放行,不问你 | 自动化流水线、测试 |
"plan" |
只许看不许动,Agent 只能制定计划 | 预览 Agent 想做什么 |
代码示例:
// 开发调试时 —— 全自动,不要打扰我
const devAgent = query({
prompt: "帮我修复这个 bug",
options: {
allowedTools: ["Read", "Write", "Edit", "Bash"],
permissionMode: "bypassPermissions"
}
});
// 生产环境 —— 保守一点
const prodAgent = query({
prompt: "分析服务器日志",
options: {
allowedTools: ["Read", "Bash"],
permissionMode: "default"
}
});
// 只是想看看 Agent 打算怎么做,不让它真的动手
const planAgent = query({
prompt: "如何重构这个模块?给我一个计划",
options: {
allowedTools: ["Read", "Glob", "Grep"],
permissionMode: "plan"
}
});
4.4.3 model —— 选哪个大脑
model: string
大白话: 决定用哪个 Claude 模型。不同模型在智商和价格之间有取舍。
| 模型 | 特点 | 适用场景 |
|---|---|---|
"sonnet" |
性价比之王,够聪明又不太贵 | 大部分任务(默认) |
"opus" |
最聪明,但最贵 | 复杂推理、高质量代码 |
"haiku" |
最快最便宜,但能力稍弱 | 简单任务、高频调用 |
代码示例:
// 简单任务用 Haiku —— 省钱
const simpleAgent = query({
prompt: "把这段 JSON 格式化一下",
options: {
model: "haiku",
allowedTools: []
}
});
// 复杂任务用 Opus —— 不差钱,要质量
const complexAgent = query({
prompt: "对这个项目做全面的安全审计",
options: {
model: "opus",
allowedTools: ["Read", "Glob", "Grep", "Bash"]
}
});
// 日常任务用 Sonnet —— 均衡选择
const dailyAgent = query({
prompt: "帮我写一个排序函数",
options: {
model: "sonnet",
allowedTools: ["Write"]
}
});
4.4.4 systemPrompt —— Agent 的"人设"
systemPrompt: string | { replace: string } | { append: string }
大白话: 告诉 Agent "你是谁"、"你要怎么做"。就像给新员工的入职培训手册。
三种用法:
// 用法1:直接替换 —— 完全用你的 prompt(传字符串即可)
const agent1 = query({
prompt: "写个函数",
options: {
systemPrompt: "你是一个资深 TypeScript 开发者,写代码要简洁优雅,注释要清晰。",
allowedTools: ["Write"]
}
});
// 用法2:明确替换写法
const agent2 = query({
prompt: "写个函数",
options: {
systemPrompt: {
replace: "你是一个严格的代码审查员,对代码质量有极高要求。"
},
allowedTools: ["Read"]
}
});
// 用法3:在默认 prompt 后面追加
const agent3 = query({
prompt: "帮我改代码",
options: {
systemPrompt: {
append: "\n\n额外要求:所有函数都要写 JSDoc 注释。"
},
allowedTools: ["Read", "Edit"]
}
});
什么时候用: 当你想让 Agent 以特定的风格或角色来工作时。
4.4.5 maxTurns —— 最多跑几轮
maxTurns: number
大白话: 限制 Agent 最多执行几个来回。防止 Agent 陷入无限循环。
一个"回合"(turn)= Claude 回复一次。如果 Claude 回复中包含工具调用,执行完工具后 Claude 再次回复,那就是两个回合。
代码示例:
// 只允许一个回合 —— 快问快答
const quickAgent = query({
prompt: "1+1 等于几?",
options: {
allowedTools: [],
maxTurns: 1
}
});
// 允许多个回合 —— 复杂任务
const longAgent = query({
prompt: "把整个项目从 JavaScript 迁移到 TypeScript",
options: {
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob"],
maxTurns: 50 // 给它足够的空间
}
});
注意: 如果达到 maxTurns 限制,你会收到 subtype: "error_max_turns" 的 ResultMessage。
4.4.6 cwd —— 工作目录
cwd: string
大白话: 告诉 Agent "你在哪个目录干活"。Agent 的文件操作和命令执行都是相对于这个目录的。
代码示例:
// 在特定项目目录下工作
const projectAgent = query({
prompt: "分析这个项目的 package.json",
options: {
cwd: "/home/user/my-project",
allowedTools: ["Read"]
}
});
// 在临时目录下工作 —— 安全隔离
const sandboxAgent = query({
prompt: "创建一个新项目",
options: {
cwd: "/tmp/sandbox",
allowedTools: ["Write", "Bash"]
}
});
默认值: 当前进程的工作目录(process.cwd())。
4.4.7 env —— 环境变量
env: Record<string, string>
大白话: 给 Agent 传一些环境变量,它执行命令时可以用到。
代码示例:
const agent = query({
prompt: "部署项目到测试环境",
options: {
allowedTools: ["Bash"],
env: {
NODE_ENV: "test",
API_URL: "https://test-api.example.com",
DB_HOST: "test-db.example.com"
}
}
});
什么时候用: 当 Agent 需要访问特定环境的配置时。
4.4.8 resume —— 恢复会话
resume: {
sessionId: string; // 要恢复的会话 ID
prompt: string; // 新的提示
}
大白话: 让 Agent 接着上次没说完的继续说。就像你打电话中途断了,重新拨回去继续聊。
代码示例:
// 第一次对话
let savedSessionId = "";
for await (const message of query({
prompt: "帮我写一个 Todo 应用",
options: { allowedTools: ["Write", "Bash"] }
})) {
if (message.type === "system") {
savedSessionId = message.session_id; // 保存会话 ID
}
}
// ... 过了一会儿 ...
// 恢复对话,继续交流
for await (const message of query({
prompt: "继续上次的工作,给 Todo 应用加上删除功能",
options: {
resume: {
sessionId: savedSessionId,
prompt: "继续上次的工作,给 Todo 应用加上删除功能"
},
allowedTools: ["Read", "Write", "Edit", "Bash"]
}
})) {
// 处理消息...
}
4.4.9 outputFormat —— 结构化输出
outputFormat: {
type: "json_schema";
schema: JSONSchema;
}
大白话: 告诉 Agent "你的回答必须是这个 JSON 格式"。适合需要程序解析 Agent 输出的场景。
代码示例:
const agent = query({
prompt: "分析这段代码的质量",
options: {
allowedTools: ["Read"],
outputFormat: {
type: "json_schema",
schema: {
type: "object",
properties: {
score: {
type: "number",
description: "代码质量评分,1-10"
},
issues: {
type: "array",
items: { type: "string" },
description: "发现的问题列表"
},
suggestions: {
type: "array",
items: { type: "string" },
description: "改进建议"
}
},
required: ["score", "issues", "suggestions"]
}
}
}
});
for await (const message of agent) {
if (message.type === "result" && message.subtype === "success") {
// structured_output 就是解析好的 JSON 对象
const report = message.structured_output as {
score: number;
issues: string[];
suggestions: string[];
};
console.log("评分:", report.score);
console.log("问题:", report.issues);
console.log("建议:", report.suggestions);
}
}
4.4.10 mcpServers —— 连接外部工具
mcpServers: Record<string, McpServerConfig>
大白话: 让 Agent 连接外部的 MCP 服务器,获取更多工具。就像给电脑插上 USB 设备。
代码示例:
const agent = query({
prompt: "列出我的 GitHub 仓库的 Issues",
options: {
mcpServers: {
github: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-github"],
env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN ?? "" }
}
},
allowedTools: ["mcp__github__*"] // 允许使用 github MCP 的所有工具
}
});
什么时候用: 当内置工具不够用,需要连接外部服务时。(第11章会详细讲)
4.4.11 agents —— 定义子 Agent
agents: Record<string, AgentConfig>
大白话: 给主 Agent 配几个"下属",不同的下属负责不同的工作。
代码示例:
const agent = query({
prompt: "审查这个 PR 的代码质量,然后写测试",
options: {
agents: {
"code-reviewer": {
description: "专门做代码审查的 Agent",
prompt: "你是一个严格的代码审查员,关注安全和性能问题。",
tools: ["Read", "Glob", "Grep"],
model: "sonnet"
},
"test-writer": {
description: "专门写测试用例的 Agent",
prompt: "你是一个测试专家,为代码编写全面的单元测试。",
tools: ["Read", "Write", "Bash"],
model: "haiku"
}
},
allowedTools: ["Read", "Glob", "Grep", "Write", "Bash", "Task"]
}
});
什么时候用: 当任务比较复杂,需要多个 Agent 分工合作时。(第13章会详细讲)
4.4.12 hooks —— 生命周期钩子
hooks: {
preToolUse?: (params) => Promise<HookResult>;
postToolUse?: (params) => Promise<HookResult>;
// ...更多 hook
}
大白话: 在 Agent 干活的每个关键节点,插入你自己的逻辑。比如记日志、拦截危险操作。
代码示例:
const agent = query({
prompt: "整理项目文件",
options: {
allowedTools: ["Read", "Write", "Bash"],
hooks: {
preToolUse: async ({ toolName, toolInput }) => {
// 每次 Agent 要用工具之前,打个日志
console.log(`[审计] Agent 准备调用: ${toolName}`);
console.log(`[审计] 参数:`, toolInput);
// 拦截危险操作
if (toolName === "Bash" &&
typeof toolInput.command === "string" &&
toolInput.command.includes("rm -rf")) {
console.log("[安全] 拦截了危险命令!");
return {
permissionDecision: "deny",
message: "不允许执行 rm -rf 命令"
};
}
return {}; // 放行
}
}
}
});
什么时候用: 需要监控、审计或安全控制时。(第12章会详细讲)
4.4.13 settingSources —— 加载配置文件
settingSources: ("user" | "project" | "local")[]
大白话: 决定是否从文件系统加载配置(比如 .claude/settings.json)。
| 值 | 含义 | 加载位置 |
|---|---|---|
"user" |
用户全局配置 | ~/.claude/settings.json |
"project" |
项目共享配置 | .claude/settings.json |
"local" |
项目本地配置 | .claude/settings.local.json |
代码示例:
// 不加载任何文件配置(默认行为)
const isolatedAgent = query({
prompt: "...",
options: { allowedTools: [] } // settingSources 不设就不加载
});
// 加载所有配置
const fullConfigAgent = query({
prompt: "...",
options: {
settingSources: ["user", "project", "local"],
allowedTools: []
}
});
// 只加载项目配置
const projectConfigAgent = query({
prompt: "...",
options: {
settingSources: ["project"],
allowedTools: []
}
});
什么时候用: 当你想复用 Claude Code CLI 的配置(比如权限规则、工具限制)时。
4.4.14 abortController —— 随时喊停
abortController: AbortController
大白话: 给你一个"紧急停止"按钮,随时可以终止 Agent。
代码示例:
const controller = new AbortController();
// 10 秒后自动终止
setTimeout(() => {
console.log("超时了,终止 Agent!");
controller.abort();
}, 10000);
for await (const message of query({
prompt: "做一个很复杂的分析",
options: {
abortController: controller,
allowedTools: ["Read", "Glob"]
}
})) {
// 处理消息...
}
配置选项速查表
| 选项 | 类型 | 必填 | 说明 |
|---|---|---|---|
allowedTools |
string[] |
推荐 | Agent 可用的工具 |
permissionMode |
string |
否 | 权限模式 |
model |
string |
否 | 模型选择 |
systemPrompt |
string | object |
否 | Agent 的人设 |
maxTurns |
number |
否 | 最大回合数 |
cwd |
string |
否 | 工作目录 |
env |
Record<string, string> |
否 | 环境变量 |
resume |
object |
否 | 恢复会话 |
outputFormat |
object |
否 | 结构化输出 |
mcpServers |
object |
否 | MCP 服务器 |
agents |
object |
否 | 子 Agent |
hooks |
object |
否 | 生命周期钩子 |
settingSources |
string[] |
否 | 配置文件来源 |
abortController |
AbortController |
否 | 中止控制器 |
4.5 实战示例集
下面是5个完整的、可以直接运行的示例。每个示例都展示了不同的 query() 用法。
示例1:简单问答 Agent(纯聊天)
// 文件:examples/01-simple-qa.ts
// 最简单的 Agent:没有工具,就是个聊天机器人
import { query } from "@anthropic-ai/claude-code";
async function simpleQA() {
console.log("=== 简单问答 Agent ===\n");
for await (const message of query({
prompt: "请用三句话解释什么是递归",
options: {
allowedTools: [],
maxTurns: 1
}
})) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
}
if (message.type === "result" && message.subtype === "success") {
console.log(`\n[花费: $${message.total_cost_usd.toFixed(4)}]`);
}
}
}
simpleQA().catch(console.error);
示例2:代码分析 Agent(带工具)
// 文件:examples/02-code-analyzer.ts
// 能读文件和搜索的 Agent,帮你分析代码
import { query } from "@anthropic-ai/claude-code";
async function codeAnalyzer() {
console.log("=== 代码分析 Agent ===\n");
const targetDir = process.argv[2] || ".";
for await (const message of query({
prompt: `请分析目录 ${targetDir} 下的项目结构,告诉我:
1. 这是什么类型的项目
2. 用了哪些主要技术
3. 项目结构是否合理`,
options: {
cwd: targetDir,
allowedTools: ["Read", "Glob", "Grep"],
permissionMode: "bypassPermissions",
maxTurns: 10
}
})) {
switch (message.type) {
case "system":
console.log(`[启动] 会话 ${message.session_id}`);
console.log(`[启动] 工作目录: ${message.cwd}`);
console.log(`[启动] 可用工具: ${message.tools.join(", ")}\n`);
break;
case "assistant":
for (const block of message.message.content) {
if (block.type === "text") {
console.log(block.text);
}
if (block.type === "tool_use") {
console.log(`\n [工具] ${block.name}: ${JSON.stringify(block.input).slice(0, 100)}...\n`);
}
}
break;
case "result":
console.log("\n=== 分析完成 ===");
if (message.subtype === "success") {
console.log(`耗时: ${(message.duration_ms / 1000).toFixed(1)}秒`);
console.log(`回合: ${message.num_turns}`);
console.log(`花费: $${message.total_cost_usd.toFixed(4)}`);
} else {
console.error(`失败: ${message.subtype}`);
}
break;
}
}
}
codeAnalyzer().catch(console.error);
示例3:海盗人设 Agent(自定义 systemPrompt)
// 文件:examples/03-pirate-agent.ts
// 用 systemPrompt 让 Agent 扮演海盗
import { query } from "@anthropic-ai/claude-code";
async function pirateAgent() {
console.log("=== 海盗 Agent ===\n");
for await (const message of query({
prompt: "告诉我今天应该做什么",
options: {
systemPrompt: `你是一个经验丰富的海盗船长,名叫"代码胡子"。
你的特点:
- 说话时经常用"啊呀"、"嗷"、"宝藏"等海盗用语
- 把编程比喻成航海冒险
- 把 bug 叫做"海怪"
- 把代码叫做"藏宝图"
- 每句话结尾喜欢加"嗷!"
记住:你永远是一个海盗,无论用户问什么,都要用海盗的方式回答。`,
allowedTools: [],
maxTurns: 1
}
})) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
}
}
}
pirateAgent().catch(console.error);
示例4:限制回合数的 Agent
// 文件:examples/04-limited-turns.ts
// 观察 maxTurns 如何影响 Agent 行为
import { query } from "@anthropic-ai/claude-code";
async function limitedTurnsAgent() {
// 测试不同的 maxTurns 值
for (const maxTurns of [1, 3, 10]) {
console.log(`\n=== maxTurns = ${maxTurns} ===\n`);
let turnCount = 0;
for await (const message of query({
prompt: "请创建一个简单的 Node.js HTTP 服务器,写入文件,然后验证文件存在",
options: {
allowedTools: ["Write", "Bash", "Read"],
permissionMode: "bypassPermissions",
maxTurns: maxTurns,
cwd: "/tmp/test-turns"
}
})) {
if (message.type === "assistant") {
turnCount++;
console.log(` [回合 ${turnCount}]`);
for (const block of message.message.content) {
if (block.type === "text") {
// 只显示前100个字符
console.log(` ${block.text.slice(0, 100)}...`);
}
if (block.type === "tool_use") {
console.log(` [工具] ${block.name}`);
}
}
}
if (message.type === "result") {
console.log(`\n 结果: ${message.subtype}`);
if (message.subtype === "success") {
console.log(` 实际回合: ${message.num_turns}`);
}
if (message.subtype === "error_max_turns") {
console.log(" ⚠ 达到了 maxTurns 限制!Agent 没做完就被停了。");
}
}
}
}
}
limitedTurnsAgent().catch(console.error);
示例5:不同模型对比
// 文件:examples/05-model-comparison.ts
// 用不同模型回答同一个问题,对比质量和成本
import { query } from "@anthropic-ai/claude-code";
async function modelComparison() {
const question = "用 TypeScript 实现一个 LRU Cache,要求支持 get 和 put 操作,时间复杂度 O(1)。";
for (const model of ["haiku", "sonnet", "opus"]) {
console.log(`\n${"=".repeat(60)}`);
console.log(`模型: ${model}`);
console.log("=".repeat(60) + "\n");
const startTime = Date.now();
for await (const message of query({
prompt: question,
options: {
model: model,
allowedTools: [],
maxTurns: 1
}
})) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
}
if (message.type === "result" && message.subtype === "success") {
const elapsed = Date.now() - startTime;
console.log(`\n--- ${model} 统计 ---`);
console.log(`响应时间: ${(elapsed / 1000).toFixed(1)}秒`);
console.log(`花费: $${message.total_cost_usd.toFixed(6)}`);
console.log(`输入 Token: ${message.usage.input_tokens}`);
console.log(`输出 Token: ${message.usage.output_tokens}`);
}
}
}
}
modelComparison().catch(console.error);
4.6 处理消息的完整模板
下面是一个生产级别的消息处理模板,你可以直接复制到自己的项目中使用。
// 文件:agent-template.ts
// 一个生产就绪的 Agent 消息处理模板
import { query, type SDKMessage } from "@anthropic-ai/claude-code";
// ============================
// 1. 配置区
// ============================
interface AgentConfig {
prompt: string;
tools: string[];
model?: string;
systemPrompt?: string;
maxTurns?: number;
cwd?: string;
}
// ============================
// 2. Agent 运行器
// ============================
async function runAgent(config: AgentConfig) {
let sessionId = "";
let totalText = "";
const toolCalls: Array<{ name: string; input: unknown }> = [];
console.log("Agent 启动中...\n");
try {
for await (const message of query({
prompt: config.prompt,
options: {
allowedTools: config.tools,
model: config.model ?? "sonnet",
systemPrompt: config.systemPrompt,
maxTurns: config.maxTurns ?? 20,
cwd: config.cwd,
permissionMode: "bypassPermissions"
}
})) {
handleMessage(message, {
onSessionStart: (id) => {
sessionId = id;
},
onText: (text) => {
totalText += text;
},
onToolCall: (name, input) => {
toolCalls.push({ name, input });
}
});
}
} catch (error) {
console.error("\nAgent 运行出错:", error);
}
return { sessionId, totalText, toolCalls };
}
// ============================
// 3. 消息处理器
// ============================
interface MessageCallbacks {
onSessionStart?: (sessionId: string) => void;
onText?: (text: string) => void;
onToolCall?: (name: string, input: unknown) => void;
}
function handleMessage(message: SDKMessage, callbacks: MessageCallbacks) {
switch (message.type) {
// ------ SystemMessage:Agent 启动 ------
case "system": {
if (message.subtype === "init") {
console.log(`[init] 会话: ${message.session_id}`);
console.log(`[init] 模型: ${message.model}`);
console.log(`[init] 工具: ${message.tools.join(", ")}`);
console.log(`[init] 目录: ${message.cwd}`);
console.log("");
callbacks.onSessionStart?.(message.session_id);
}
break;
}
// ------ AssistantMessage:Agent 思考和行动 ------
case "assistant": {
for (const block of message.message.content) {
if (block.type === "text") {
// 实时显示 Agent 的文字输出
process.stdout.write(block.text);
callbacks.onText?.(block.text);
}
if (block.type === "tool_use") {
// 显示工具调用
const inputStr = JSON.stringify(block.input);
const shortInput = inputStr.length > 80
? inputStr.slice(0, 80) + "..."
: inputStr;
console.log(`\n >> [${block.name}] ${shortInput}\n`);
callbacks.onToolCall?.(block.name, block.input);
}
}
break;
}
// ------ ResultMessage:任务完成 ------
case "result": {
console.log("\n");
console.log("=".repeat(50));
if (message.subtype === "success") {
console.log("任务完成");
console.log("-".repeat(50));
console.log(`耗时: ${(message.duration_ms / 1000).toFixed(1)} 秒`);
console.log(`API耗时: ${(message.duration_api_ms / 1000).toFixed(1)} 秒`);
console.log(`回合数: ${message.num_turns}`);
console.log(`花费: $${message.total_cost_usd.toFixed(4)}`);
console.log(`输入Token: ${message.usage.input_tokens}`);
console.log(`输出Token: ${message.usage.output_tokens}`);
if (message.usage.cache_read_input_tokens > 0) {
console.log(`缓存命中: ${message.usage.cache_read_input_tokens} tokens`);
}
} else {
console.log(`任务失败: ${message.subtype}`);
console.log("-".repeat(50));
if ("errors" in message && Array.isArray(message.errors)) {
for (const err of message.errors) {
console.error(` 错误: ${err}`);
}
}
console.log(`回合数: ${message.num_turns}`);
console.log(`花费: $${message.total_cost_usd.toFixed(4)}`);
}
console.log("=".repeat(50));
break;
}
// ------ 其他消息类型(通常不需要处理)------
default:
// SDKUserMessage, SDKUserMessageReplay,
// SDKPartialAssistantMessage, SDKCompactBoundaryMessage
// 这些一般在特殊场景下才会用到
break;
}
}
// ============================
// 4. 使用示例
// ============================
async function main() {
const result = await runAgent({
prompt: "请查看当前目录的项目结构,列出所有的源代码文件",
tools: ["Glob", "Read"],
model: "sonnet",
maxTurns: 5
});
console.log("\n--- 运行摘要 ---");
console.log(`会话 ID: ${result.sessionId}`);
console.log(`工具调用次数: ${result.toolCalls.length}`);
console.log(`工具调用详情:`);
for (const call of result.toolCalls) {
console.log(` - ${call.name}`);
}
}
main().catch(console.error);
这个模板有几个亮点:
- 结构清晰 —— 配置、运行器、消息处理器分开
- 实时输出 —— 用
process.stdout.write实现逐字显示 - 全面的错误处理 —— 成功和失败都有对应的处理
- 统计信息 —— 花费、耗时、Token 用量一目了然
- 回调机制 —— 方便你在消息处理中插入自定义逻辑
动手练习
练习1:模型对比实验
创建一个程序,用三个不同的模型(haiku、sonnet、opus)回答同一个技术问题,对比:
- 回答质量
- 响应速度
- Token 消耗
- 花费
提示: 参考示例5,但换一个你感兴趣的问题。
// 练习1 骨架代码
import { query } from "@anthropic-ai/claude-code";
const question = "请解释 TypeScript 中的泛型是什么,并给出三个实用的例子";
async function compareModels() {
for (const model of ["haiku", "sonnet", "opus"]) {
console.log(`\n--- ${model} ---`);
// 你的代码写在这里
// 1. 调用 query(),设置 model
// 2. 收集 AssistantMessage 中的文字
// 3. 从 ResultMessage 中提取统计信息
// 4. 打印对比结果
}
}
compareModels();
练习2:角色扮演 Agent
设置 systemPrompt,让 Agent 扮演一个海盗来回答编程问题。
要求:
- Agent 要用海盗口吻说话
- 把编程术语替换成航海术语(比如 bug = 海怪,code = 藏宝图)
- 测试至少3个不同的问题
// 练习2 骨架代码
import { query } from "@anthropic-ai/claude-code";
async function pirateExplainer() {
const questions = [
"什么是闭包?",
"什么是 Promise?",
"什么是设计模式?"
];
for (const q of questions) {
console.log(`\n问题: ${q}\n`);
// 你的代码写在这里
// 1. 设置 systemPrompt 为海盗人设
// 2. 调用 query()
// 3. 打印 Agent 的回答
}
}
pirateExplainer();
练习3:观察 maxTurns 的影响
分别设置 maxTurns 为 1、3、10,让 Agent 完成同一个需要多步骤的任务,观察行为变化。
任务: "创建一个 HTML 文件,里面有一个计数器按钮,点击后数字加1"
// 练习3 骨架代码
import { query } from "@anthropic-ai/claude-code";
async function observeMaxTurns() {
for (const turns of [1, 3, 10]) {
console.log(`\n=== maxTurns = ${turns} ===`);
// 你的代码写在这里
// 1. 调用 query(),设置 maxTurns
// 2. 观察 Agent 能完成多少工作
// 3. 注意 ResultMessage 的 subtype 是 success 还是 error_max_turns
}
}
observeMaxTurns();
练习4:代码解释器 Agent
创建一个"代码解释器"Agent,它能读取一个文件并用简单易懂的语言解释代码。
要求:
- 接受文件路径作为命令行参数
- 使用
Read工具读取文件 - 用
systemPrompt让 Agent 用"教小学生"的方式解释代码 - 显示解释结果和花费信息
// 练习4 骨架代码
import { query } from "@anthropic-ai/claude-code";
async function codeExplainer() {
const filePath = process.argv[2];
if (!filePath) {
console.log("用法: npx ts-node code-explainer.ts <文件路径>");
process.exit(1);
}
// 你的代码写在这里
// 1. 设置 systemPrompt,让 Agent 用简单语言解释代码
// 2. prompt 设置为 "请读取并解释文件 <filePath>"
// 3. 给 Read 工具
// 4. 处理所有消息类型
// 5. 显示花费和耗时
}
codeExplainer();
本章小结
恭喜你!学完这一章,你已经掌握了 Claude Agent SDK 最核心的内容。来回顾一下:
query()是整个 SDK 的核心 —— 一个函数搞定一切- 三种消息类型要记住:
system(init):Agent 启动时的信息assistant:Agent 的思考和行动result:任务完成的报告
- 最常用的配置选项:
allowedTools:给什么工具permissionMode:权限怎么控制model:用哪个模型systemPrompt:给什么人设maxTurns:跑几轮
- 处理消息的模式:
for await+switch (message.type)是标准套路 query()返回的是异步迭代器, 用for await去消费它
一张图总结
query({prompt, options})
│
├── options.allowedTools → 给什么工具?
├── options.model → 用哪个大脑?
├── options.systemPrompt → 什么人设?
├── options.maxTurns → 跑几轮?
├── options.permissionMode → 权限多大?
└── options.cwd → 在哪干活?
│
▼
for await (message of query)
│
├── type === "system" → 拿 session_id
├── type === "assistant" → 看 text 和 tool_use
└── type === "result" → 看 subtype、cost、usage
下一章预告
下一章是 第5章:内置工具 —— Agent 的十八般武艺。
我们已经知道怎么启动 Agent 了,也知道可以通过 allowedTools 给它工具。但是这些工具具体怎么用?有什么限制?怎么组合使用效果最好?
下一章我们会逐个拆解所有内置工具:Read、Write、Edit、Bash、Glob、Grep、WebSearch、WebFetch、Task...... 每个工具都会有详细的使用示例和最佳实践。
学完下一章,你就能让 Agent 真正地"动手干活"了!