Tool Call — 读用户本地文件
让 Agent 调用客户端的 “filesystem:read” 工具读用户本地文件。展示 Capability Manifest 声明 + tool_call + sensitivity 提示的完整闭环。
前提
- 同 发送文本
- 创建 Agent 时声明了 Capability Manifest 含
filesystem:readscope(详见 Capability Manifest 声明)
Manifest(在 POST /agents 时提交)
{ "schema_version": "1.0", "agent_version": "1.0.0", "tools": [ { "name": "read_file", "description_i18n_key": "agent.cap.read_file.description", "input_schema": { "type": "object", "properties": { "path": { "type": "string" } }, "required": ["path"], "additionalProperties": false }, "permission_scope": "filesystem:read", "timeout_ms": 5000 } ], "permission_scopes": [ { "id": "filesystem:read", "label_i18n_key": "agent.scope.filesystem_read.label", "sensitivity": "medium" } ]}代码
import { HasheeAgent } from "@hasheeai/agent-sdk-ts";import { ulid } from "ulid";
const agent = await HasheeAgent.init({ agentId: process.env.HASHEE_AGENT_ID!, token: process.env.HASHEE_AGENT_TOKEN!, baseUrl: "https://api.hashee.ai", connectionMode: "websocket",});
// 等待 tool_response 的 promise registryconst pending = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
async function readFile(convId: string, path: string): Promise<string> { const callId = ulid(); const promise = new Promise<string>((resolve, reject) => { pending.set(callId, { resolve: resolve as any, reject }); setTimeout(() => { pending.delete(callId); reject(new Error("tool call total timeout")); }, 30_000); });
await agent.sendArtifact(convId, { artifact: { artifact_id: callId, subtype: "tool_call", payload: { call_id: callId, tool_name: "read_file", arguments: { path }, permission_scope: "filesystem:read", timeout_ms: 5000, }, }, }); return promise;}
// 收 tool_responseagent.addEventHandler(async (event) => { if (event.type !== "artifact_response") return; if (event.payload.ref_artifact_subtype !== "tool_response") return; const { call_id, status, result, reason } = event.payload; const handler = pending.get(call_id); if (!handler) return; pending.delete(call_id); if (status === "ok") handler.resolve(result?.content ?? ""); else handler.reject(new Error(`tool ${status}: ${reason}`));});
// 用户消息 → 解析路径 → 调 read_file → 回复内容agent.addMessageHandler(async (msg) => { if (msg.payload?.type !== "text") return; const m = msg.payload.text.match(/^读取\s+(.+)$/); if (!m) return;
try { const content = await readFile(msg.conversation_id, m[1]); await agent.send(msg.conversation_id, { type: "text", text: `📄 ${m[1]} 内容(前 500 字符):\n\n\`\`\`\n${content.slice(0, 500)}\n\`\`\``, }); } catch (err) { await agent.send(msg.conversation_id, { type: "text", text: `读取失败:${(err as Error).message}`, }); }});
console.log("[tool-bot] up");行为
- 用户:“读取 ~/notes.md”
- Agent → 客户端发 tool_call(卡片,sensitivity: medium)
- 客户端首次 → 弹窗”Agent 想读取 ~/notes.md”
- 用户点”允许”
- 客户端执行 read → 回 tool_response
- Agent 收到 result → 回复用户内容
24 小时内同设备同会话,再次调用 → 静默执行(sensitivity: medium 的滑动窗口)。
进阶
- Tool Call 完整文档
- Capability Manifest 规范 — sensitivity 三级语义
- LLM 集成 OpenAI 函数调用