跳转到内容

Tool Call — 读用户本地文件

让 Agent 调用客户端的 “filesystem:read” 工具读用户本地文件。展示 Capability Manifest 声明 + tool_call + sensitivity 提示的完整闭环。

前提

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 registry
const 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_response
agent.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");

行为

  1. 用户:“读取 ~/notes.md”
  2. Agent → 客户端发 tool_call(卡片,sensitivity: medium)
  3. 客户端首次 → 弹窗”Agent 想读取 ~/notes.md”
  4. 用户点”允许”
  5. 客户端执行 read → 回 tool_response
  6. Agent 收到 result → 回复用户内容

24 小时内同设备同会话,再次调用 → 静默执行(sensitivity: medium 的滑动窗口)。

进阶