Skip to content

线程与运行

Thread 是持久化的对话。Run 在该 thread 上启动一个 turn,并返回最终结果或持续输出执行事件。下面的示例都从一个已连接的 client 继续,连接步骤见 SDK 快速开始

管理 thread

启动新 thread、按 ID 恢复 thread,或列出某个 identity 的 thread。

ts
const thread = await dotcraft.threads.start({ userId: "me" });
const resumed = await dotcraft.threads.resume(threadId);
const threads = await dotcraft.threads.list({ userId: "me" });
const snapshot = await dotcraft.threads.read(threadId);
csharp
var identity = new SessionIdentity { ChannelName = "my-app", UserId = Environment.UserName };
var thread = await client.Threads.StartAsync(new ThreadStartParams { Identity = identity });
var resumed = await client.Threads.ResumeAsync(new ThreadResumeParams { ThreadId = threadId });
var threads = await client.Threads.ListAsync(new ThreadListParams { Identity = identity });
var snapshot = await client.Threads.ReadAsync(threadId);

TypeScript 还提供 getOrCreate。它返回该 identity 下第一个 active 或 paused 的 thread(paused 的会先恢复),两者都不存在时才启动新 thread。

read 返回当前 Thread 头部和 runtime 状态,不包含对话历史。通过有界的 Turn 和 Item 分页读取历史:

ts
const turns = await dotcraft.threads.listTurns(threadId, {
  limit: 20,
  sortDirection: "descending",
});
const items = await dotcraft.threads.listItems(threadId, {
  turnId: turns.data[0]?.id,
  limit: 100,
  sortDirection: "ascending",
});
csharp
var turns = await client.Threads.ListTurnsAsync(new ThreadTurnsListParams
{
    ThreadId = threadId,
    Limit = 20,
    SortDirection = "descending"
});
var items = await client.Threads.ListItemsAsync(new ThreadItemsListParams
{
    ThreadId = threadId,
    TurnId = turns.Data.FirstOrDefault()?.Id,
    Limit = 100,
    SortDirection = "ascending"
});

Turn 页只包含元数据,不包含 Item。Item 页会带上每个 Item 所属的 Turn ID,并可跨整个 Thread 或限定到一个 Turn。使用相同的 Thread、scope、可选 Turn 和方向继续传入 nextCursor / NextCursor 读取下一页。请把 cursor 视为 opaque token。

选择模型

在展示模型选择器或验证已保存配置前,先发现模型目录。

ts
const models = await dotcraft.models.list();
for (const model of models) console.log(model.id);
const configuration = (await dotcraft.threads.read(thread.id)).configuration;
csharp
var catalog = await client.Models.GetCatalogAsync();
foreach (var model in catalog.Models.Value ?? [])
    Console.WriteLine(model.Id.Value);
var currentConfiguration = await client.Threads.ReadModelConfigurationAsync(thread.Id);

两种高层 client 都会通过 thread read 返回当前 ThreadConfiguration。.NET client 另外提供针对模型字段的 read-modify-write helper,它会保留无关和未知的配置字段:

csharp
var configuration = await client.Threads.UpdateModelConfigurationAsync(
    thread.Id,
    providerId: "<provider-id>",
    model: "<model-id>",
    reasoning: new ReasoningConfig { Enabled = true, Effort = "high" },
    speed: null,
    contextWindow: null);

TypeScript 在高层接口提供模型发现,但没有这个配置 helper。使用类型化 Wire 层的应用必须更新完整 ThreadConfiguration,并保留不归自己所有的字段。不要跨 provider 推断模型 ID 或 reasoning 选项,只使用所连接 AppServer 返回的目录。

构造输入

纯文本直接传入字符串。文件、图片、Skill 或 Command 使用 input part。

ts
import { fileRefPart, textPart } from "@dotcraft/sdk";

const result = await thread.run([
  textPart("Review this file."),
  fileRefPart("src/app.ts"),
]);
csharp
using DotCraft.Protocol.AppServer;

var result = await thread.RunAsync([
    new InputPart { Type = "text", Text = "Review this file." },
    new InputPart { Type = "fileRef", Path = "src/App.cs" },
]);
Part用途TypeScript helper
text原样用户文本textPart
fileRef工作区或本地文件引用fileRefPart
imageBase64 data:image/... URLimageDataUrlPart
localImageAppServer 可读取的图片路径localImagePart
skillRefSkill 引用skillRefPart
commandRef自定义 Command 引用commandRefPart

.NET 直接构造生成的 InputPart contract。高层 client 不会把以 /command$skill@file 开头的文本自动转换为结构化 part。

image part 不接受远程图片 URL。先下载图片,再发送 data URL,或发送 AppServer 可读取的 localImage 路径。

运行 turn

需要最终结果时使用 buffered 形式,需要实时进度时使用 streamed 形式。

ts
const result = await thread.run("Run the tests and summarize failures.");
console.log(result.text);

for await (const event of thread.runStreamed("Now fix them.")) {
  if (event.type === "agent_message_delta") process.stdout.write(event.delta ?? "");
}
csharp
var result = await thread.RunAsync("Run the tests and summarize failures.");
Console.WriteLine(result.Text);

await foreach (var runEvent in thread.RunStreamedAsync("Now fix them."))
{
    if (runEvent is DotCraftRunEvent<ItemDeltaNotification> delta &&
        runEvent.Type == DotCraftRunEventTypes.AgentMessageDelta)
        Console.Write(delta.Params.Delta);
}

读取结果

TypeScript.NET
合并回复result.textresult.Text
Thread IDresult.thread.idresult.ThreadId
Turn IDresult.turn?.idresult.TurnId
终止 turnresult.turnresult.Turn
Item 和 usageresult.itemsresult.usageresult.Turn?.Itemsresult.Turn?.TokenUsage
Raw eventresult.rawEventsresult.RawEvents

只有启用各语言的 collectRawEvents / CollectRawEvents 选项时,SDK 才收集 raw event。

Run 选项

行为TypeScript.NET
Sender contextsenderRunOptions.Sender
Busy 时排队enqueueIfBusyRunOptions.EnqueueIfBusy
收集 raw eventcollectRawEventsRunOptions.CollectRawEvents
返回失败终态不支持RunOptions.ThrowOnFailure = false
通过取消中断AbortSignalCancellationToken

未启用 busy 选项时,启动第二个 turn 会抛出 TurnInProgressErrorTurnInProgressException。启用后,SDK 会把输入排队,并返回不含 turn ID 的 queued result。

控制 thread

任务TypeScript.NET
最新 snapshotsnapshot()Snapshot
重新读取状态refresh()RefreshAsync()
订阅subscribe()SubscribeAsync()
取消订阅unsubscribe()UnsubscribeAsync()
排队输入enqueue()EnqueueAsync()
中断 turninterrupt()InterruptAsync()
切换模式setMode()SetModeAsync()
归档archive()ArchiveAsync()
删除delete()DeleteAsync()

subscribe({ replayRecent: true }) 及各语言对应形式只回放近期事件,不返回完整的当前状态。调用 refreshread 获取权威头部状态,通过历史分页方法获取持久化的 Turn 和 Item。

读取流式事件

TypeScript 会规范化事件名称。.NET 在 DotCraftRunEvent.Type 中使用 Wire 方法名,并通过 DotCraftRunEvent<TParams>.Params 暴露已知参数。

TypeScript 类型Wire 方法
turn_startedturn/started
item_started / item_completeditem/started / item/completed
agent_message_deltaitem/agentMessage/delta
reasoning_deltaitem/reasoning/delta
tool_arguments_deltaitem/toolCall/argumentsDelta
approval_resolveditem/approval/resolved
usage_deltaitem/usage/delta
plan_updated / subagent_progress / system_eventplan/updated / subagent/progress / system/event
completed / failed / cancelledturn/completed / turn/failed / turn/cancelled
raw未知的已订阅通知

每个事件都保留原始通知。请及时消费事件流:client 跟不上时缓冲只到一个上限,超出后 AppServer 会断开连接。

停止迭代不一定会中断服务端工作。要真正停下这个 turn,TypeScript 中止传入的 AbortSignal,.NET 取消 CancellationToken

断线后恢复

重连会恢复 Wire 传输、重新初始化并保留本地 handler 注册。它不会重放进行中的请求或 turn/start,也不会重建 thread subscription 或运行时工具绑定。

重连后:

  1. 应用需要 thread 事件时重新订阅。
  2. 读取或刷新 Thread 头部。
  3. 如果应用展示历史,重新读取最新的 Turn 和 Item 页,不复用上一个连接的 cursor。
  4. 恢复 thread 时重新绑定运行时工具。
  5. 从服务端状态启动下一项操作。

活动的 .NET run 会以 RunDisconnectedException 失败。不要因为请求响应丢失就假定 AppServer 从未收到请求。

处理 Run 错误

情形TypeScript.NET
Turn 失败TurnFailedErrorTurnFailedException
Turn 取消TurnCancelledErrorTurnCancelledException
已有 turn 在运行TurnInProgressErrorTurnInProgressException

按错误类型或稳定的 code 分支,message 只用于诊断。初始化、transport、timeout、JSON-RPC 和协议错误见各语言参考。

相关文档