Agent SDK

在 Node 应用中嵌入 Downcity Agent 运行时

@downcity/agent 用于在同一进程中创建 Agent、管理 Session 并执行模型调用。完整 API 参考在 Agent SDK 文档。

import { Agent } from "@downcity/agent";
import { City } from "@downcity/city";
import { create_builtin_power_registrations } from "@downcity/powers";
import { Workspace } from "@downcity/city";
import { Shell } from "@downcity/city";
import { MicrosandboxProvider } from "@downcity/sandbox-microsandbox";

const workspace = new Workspace({
  id: "project",
  path: process.cwd(),
  shell: new Shell({
    sandbox_provider: new MicrosandboxProvider(),
  }),
  env: { API_KEY: process.env.API_KEY ?? "" },
});

const agent = new Agent({
  id: "repo-helper",
  model,
  tools: { search: search_tool },
});
const city = new City({
  workspaces: [workspace],
  powers: create_builtin_power_registrations(),
});
city.agents.add(agent);

// Session 在创建时选择 Workspace。
const session = await agent.sessions.create({ workspace });
const turn = await session.prompt({ query: "检查当前项目" });
const result = await turn.finished;

await agent.dispose();

Workspace 来自 @downcity/city,拥有项目文件、搜索工具、Env 与可选 Shell,但不拥有 Agent 身份、存储、Power 或 Session 语义。agent.sessions.create({ workspace }) 在 City 提供的 Agent Storage 作用域内创建 SessionStore。

Agent 配置不绑定 Workspace。每个 Session 可以使用一个 Workspace;多个 Agent 也可以通过各自的 Workspace 实例操作同一物理目录。agent.dispose() 会释放 Agent 资源和 Session 使用的 Workspace 资源。

本地运行状态集中保存在项目目录之外:

~/.downcity/agents/<agent_id>/
├── sessions/<origin_type>/<session_id>/
└── archived-sessions/<origin_type>/<session_id>/

默认来源分区是 chat。创建方可以指定任意非空 origin.type,恢复时通过 get(session_id, origin_type) 明确读取同一个来源分区。

City 在添加 Power 时启动唯一实例与后台任务,Session 执行会自动等待初始化完成。关闭整个宿主 使用 await city.close();只移除 Agent 使用 city.agents.remove()。HTTP 与 RPC 也由 City 统一提供。

流式消息

subscribe() 返回统一的实时 Session Mutation:

const unsubscribe = session.subscribe((mutation) => {
  if (mutation.variant === "delta" && mutation.type === "text") {
    process.stdout.write(mutation.delta);
  }
});

Tool 生命周期使用 type: "tool",用户参与使用 type: "interaction"。pending Interaction 携带完整请求,关联 Tool 同时进入 waiting-user;响应通过独立 Session 命令提交:

同一 Assistant step 内的 Part 顺序始终与模型流一致。即使 Tool 已提前进入执行准备,它也会等待对应的 Tool Part 出现在流中,不会越过尚未写入的前置文本。等待按 tool_call_id 相互隔离,多个 Tool 仍可并发执行;客户端应按 Mutation 与历史快照中的顺序直接渲染,无需自行重排。

const unsubscribe = session.subscribe((mutation) => {
  if (mutation.variant !== "part" || mutation.part.type !== "tool") return;
  const interaction = (mutation.part.interactions ?? []).find(
    (item) => item.status === "pending",
  );
  if (interaction) render_interaction(interaction);
});

async function decide_approval(interaction_id: string, decision: "approved" | "denied") {
  await session.respond({
    interaction_id,
    response: { type: "approval", outcome: decision === "approved" ? "resolved" : "denied", payload: { decision } },
  });
}

历史快照使用 session.messages()。它按 Message 边界分页,并在存在更早历史时返回 next_before_sequence 游标。连接断开后重新读取快照并建立新的订阅。

需要渲染平铺活动时间线的宿主可以直接使用 SDK 投影,不需要依赖 .downcity 的物理文件名:

import { to_session_message_timeline_events } from "@downcity/agent";

const page = await session.messages();
const timeline = page.items.flatMap(to_session_message_timeline_events);

Session JSONL、日志和调度任务使用 City 注入的 StorageProvider 作用域;没有 City 时,Agent 使用进程内存储。项目 File/Search 工具无法访问这些私有状态;应用应通过 Agent.sessions 和 Session API 访问结构化状态。

继续阅读: