输入与 Turn

使用 session.prompt() 提交用户输入、理解 steering、排队和最终结果

session.prompt() 是唯一的用户输入入口。它接受一条新的输入,并在 SDK 已确认该输入归属哪个 Turn 后返回 TurnHandle。

const turn = await session.prompt({
  query: "分析这个仓库",
});

const result = await turn.finished;
if (result.success) {
  console.log(result.text);
}

prompt() 不等于“等待模型最终回答”。实时过程由 subscribe() 接收;最终结果由 turn.finished 接收。

输入格式

query 可以是文本,也可以是 Downcity Session User Part 数组:

const turn = await session.prompt({
  query: [
    {
      type: "context",
      tag: "reference",
      context: "此前引用的消息",
    },
    { type: "text", text: "说明图片里的架构" },
    {
      type: "file",
      media_type: "image/png",
      url: "/absolute/path/architecture.png",
      filename: "architecture.png",
    },
  ],
});

输入与持久化消息都使用 Downcity 自有的 snake_case 字段。prompt() 接收的 Part 尚未分配 part_id;进入 Session 后才成为 canonical User Part。

context Part 会在 canonical Message 中保留结构化的 tag 与 context,进入模型上下文时才转换为 <reference>此前引用的消息</reference> 这样的 Text Content。标签名必须以小写字母开头,并且只能包含小写字母、数字、下划线和连字符;正文中的 XML 结构字符会被安全转义。它不属于普通用户正文,通常应和一个 text Part 一起使用。

Data URL 附件不会直接写入 Session Message。Session 会先把 Data URL 解码并保存到 Agent 存储作用域的 sessions/<origin_type>/<session_id>/attachments/,Message 保存该私有文件的绝对路径。模型执行前,Session 再从该路径 读取附件并转换成模型可消费的数据格式。

创建 Session fork 时,源 Session 自己持有的本地附件会复制到新 Session 的附件目录,fork 不会继续引用源目录。因此源 Session 后续被归档或删除时,新 Session 的历史附件仍然可用。

远程 Session 的 Data URL 会在服务端保存;远程客户端传入的本地路径必须能被服务端访问。远程客户端优先使用 Data URL 或服务端可访问的 URL。

Turn Handle

const turn = await session.prompt({ query: "继续" });

console.log(turn.id);
console.log(turn.result); // 完成前为 null

const result = await turn.finished;

finished 不会因模型或工具失败而 reject,而是总会 resolve 为:

{
  turn_id: string;
  success: boolean;
  text: string;
  error?: string;
}

因此应用代码必须检查 success,而不是只等待 Promise 成功兑现。

结构化 Agent Message 通过 messages() 与 subscribe() 读取,不在 Turn Result 中复制。模型或运行时失败时,Session 会收口 Agent Message,并使用用户可见的 Error Part 持久化失败原因。

Tool 抛出异常、返回 { success: false },或者模型生成的 Tool 参数不是完整 JSON 时,SDK 会把同一个 Tool Part 标记为 failed,并把 failed Tool Result 交回下一次模型 Step。它们不会直接使 Turn 失败。只有模型 Provider 流、canonical Message 写入或其他执行基础设施失败才会结束 Turn;无任何可见输出的临时 Provider 流错误会自动重试 5 次,连同首次调用最多请求 6 次。已经产生部分输出时则保留草稿并明确返回失败,避免重试造成正文重复。

每次模型请求失败都会实时发布 warning / model_request Mutation。request_kind 区分 Turn、标题和历史压缩请求;attempt 和 max_attempts 表示当前与最大调用次数,will_retry 表示失败后是否继续重试。主 Turn Warning 带 turn_id,后台 Session 请求允许不带。最终一次失败的 will_retry 为 false;用户主动停止产生的 cancelled 不会作为 Warning 发布。

OpenAI-compatible Provider 的 SSE 连接如果在事件中间结束,会返回可重试的 transport_error;完整但不是合法 JSON 的 SSE 事件会返回不可重试的 provider_error。对外错误只包含稳定说明,底层 JSON 解析异常不会作为用户文案暴露。

运行中继续输入

运行中的 Session 仍可再次调用 prompt():

const first_turn = await session.prompt({ query: "先分析目录结构" });
const follow_up = await session.prompt({ query: "只看 packages/agent" });

Session 会自行决定第二条输入:

情况结果
当前 Turn 仍可吸收输入写入一条普通 canonical User Message,并入当前 Turn
当前 Turn 已越过可吸收边界写入普通 User Message,排到下一 Turn
Session 空闲立即创建新 Turn

两个 prompt() 调用可以返回同一个 turn.id,这表示它们被并入同一轮执行,不是重复执行。不要在客户端另建队列,也不要假设每次调用都会创建新 Turn。

实时显示与最终结果

典型界面同时做两件事:

const unsubscribe = session.subscribe(render_mutation);
const turn = await session.prompt({ query: "生成实现方案" });
const result = await turn.finished;
unsubscribe();
  • delta:立即追加可见文本、推理文本或 Tool 输入原文。
  • part:更新同一个工具或文本 Part 的完整状态。
  • turn.finish:更新加载状态并与 turn.finished 结果对齐。

不要用 turn.result 轮询流式内容。它仅在 Turn 结束后变成最终快照。

空输入与错误处理

空字符串、空数组和只含空文本的 Part 数组会被拒绝。调用前应禁用空输入提交,调用后仍应处理错误,例如远程连接失败、模型未配置或 Session 已归档。

停止运行中的 Turn 请使用 session.stop()。