DSH plugin 怎么自定义会话标题?SessionTitleProvider 注册与生成实现
写一个会话标题提供方,核心不是“调模型写一句话”,而是遵守一个窄契约:注册唯一实现、在正确的节奏被调用、返回精确的消息归因、接受服务侧的规范化与字节上限。 服务负责持久化与钉住语义,provider 只负责“提出一个标题修订”。把边界分清,才不会出现“标题生成了但没写入”或“用户改名又被覆盖”。
DeepSeek Harness:register 的唯一实现与生命周期
每个上下文只允许一个可选标题 provider,注册是带生命周期的 effect。 要点:(来源)
register(provider)— 传入 id、automatic 与 generate。预期:返回 effect disposer,卸载时清理。- 唯一性 — 重复注册会被替换;旧实现进入 disposal。预期:不会同时有两个 provider 抢写。
- 晚到结果不提交 — 旧实现已经发出的 generate 即使返回,也不会写入标题。预期:不会出现“新旧标题来回跳”。
- disposer 等待静止 — 返回的 disposer 会在活跃调用结束后才 settle。预期:卸载不会留下半截写入。
provider 的 id 会记录在标题来源里;取一个稳定、可识别的名字,后续排查能直接看出是谁生成的。
DeepSeek Harness:generate 的输入
provider 拿到的是快照,不是活的会话读写句柄。 输入字段:(来源)
| 字段 | 含义 |
|---|---|
session | 正在生成标题的会话 |
messages | 截至当前修订的合格人类文本消息,含 seq 与 text |
route | 当前已记录的主请求 route,可能不存在 |
currentTitle | 最新已接纳标题快照,可能来自 fallback |
signal | 取消信号,覆盖取代、释放、超时与调用方取消 |
messages 只包含人类文本消息,不包含工具结果或系统提示。provider 不要自己去读 transcript;用这份快照即可。
currentTitle 是“最新已接纳”,不是“上次 provider 的输出”;它可能是一个 fallback。provider 可以选择忽略它,也可以用它做增量改写。
DeepSeek Harness:generate 的输出
返回标题、精确归因与可选模型路由;服务负责规范化、字节上限与持久化。 三个字段:(来源)
title— 提议的标题文本。预期:服务会规范化并强制字节上限。messageSeqs— 实际使用的用户消息 seq,必须来自本次request.messages。预期:服务校验顺序,不接受越界。model— 如果用了模型,返回 provider 与 model 身份。预期:记录到标题来源与辅助请求。
provider 不要自己写 session/title 事件;写入是服务拥有的动作。provider 也不要做“空标题就删标题”的处理,规范化失败会让服务拒绝接纳。
DeepSeek Harness:三种生成策略
first-prompt、all-prompts、fallback-only,按成本与体验选。 三种:(来源)
- first-prompt — 只在新会话首条人类提示后生成一次。预期:调用少,标题稳定;长对话后可能不贴主题。
- all-prompts — 每次新人类提示都可能生成修订。预期:更贴主题,但辅助请求更多,需要处理取代。
- fallback-only — 不注册 provider,使用内置 fallback。预期:零成本,标题来自首条消息或内置规则。
如果选 all-prompts,要尊重 signal:新提示到来时旧调用会被取消,provider 不应把取消当成错误日志刷屏。
DeepSeek Harness:失败、替换与钉住
失败不应让会话不可用;用户重命名优先于任何自动结果。 三条:(来源)
- 生成失败 — 标题可停留在 fallback 或上一个已接纳版本。预期:会话照常可用。
- provider 替换 — 旧实现晚到结果不提交。预期:不会覆盖新实现。
- 用户重命名 — source 为 user,自动生成停止调度。预期:只有显式
refresh才重新生成。
如果 provider 内部有缓存,替换或卸载时要一起清理;否则新实现可能读到旧实现的中间状态。
DeepSeek Harness:请求记录与字节上限
标题生成是一次辅助模型请求,服务会在调用前记录模型可见输入。 记录包括 titleProvider、messageSeqs、route、system、messages、maxTokens 与 reasoningEffort。即使后续失败,也能复现模型看到了什么。(来源)
标题写入前会规范化并强制执行字节上限;超限不会被原样保存。provider 不要假设“返回多长就存多长”。
一个最小 provider 骨架
provider 只实现 generate,写入与规范化交给服务。 骨架:
const provider = {
id: 'my-title-provider',
automatic: 'first-prompt',
async generate(request) {
const text = request.messages.map((m) => m.text).join('\n')
const title = await makeTitle(text, request.signal)
return {
title,
messageSeqs: request.messages.map((m) => m.seq),
}
},
}
const dispose = ctx.sessionTitle.register(provider)
generate 必须尊重 request.signal;返回的 messageSeqs 只能来自本次 request.messages,不要用会话里其他消息凑数。
DeepSeek Harness 常见问题
Q:provider 能不能直接改 currentTitle?
不能。它是快照;provider 只能返回新标题,写入由服务完成。
Q:为什么 generate 被调用了,标题却没变?
看返回的 messageSeqs 是否来自本次请求、标题是否规范化后为空、是否超过字节上限;这三种都会被服务拒绝。
Q:用户重命名后还能通过 provider 改回来吗?
不能自动改;只有显式 refresh 才是刻意解除钉住。
一句话收束: 标题 provider 只负责“提出修订 + 精确归因”,其余规范化、持久化、钉住与替换都交给服务;越界写事件只会让结果不可预测。
常见问题
DeepSeek Harness 里,只能注册一个可选 provider。register 返回 effect disposer;注册新实现时,旧实现会在 disposal 开始后被替换,旧实现晚到的结果永远不会提交。
DeepSeek Harness 里,拿到 session、截至当前修订的合格人类文本消息(含 seq 与 text)、可选的主请求 route、最新已接纳标题快照 currentTitle,以及取消 signal。
DeepSeek Harness 里,必须精确来自本次 request.messages。服务会校验顺序;越界或凭空构造的 seq 不会被接纳,标题也不会写入。
DeepSeek Harness 里,first-prompt 只在新会话首条人类提示后生成一次,成本低、标题稳定;all-prompts 会在后续人类提示时继续生成修订,更贴主题但调用更多。按产品体验与额度权衡。
DeepSeek Harness 里,不能。用户重命名会写入 source 为 user 的标题并钉住,自动生成停止调度;只有显式 refresh 才是刻意的解除钉住与重试。
相关术语
- SessionTitleProvider
- SessionTitleProvider 是 DeepSeek Harness 可选的异步标题提供方,含稳定 id、automatic 生成节奏与 generate 方法;每个上下文只允许注册一个实现。— DeepSeek Harness 官方参考 - 会话标题
- automatic
- automatic 是标题提供方声明的自动生成节奏,取值为 first-prompt 或 all-prompts,决定服务在哪些人类提示后调度 generate。— DeepSeek Harness 官方参考 - 会话标题
- currentTitle
- currentTitle 是 provider 调用时最新已接纳的标题快照,可能来自 fallback 或之前的 provider;provider 可自行决定是否利用它。— DeepSeek Harness 官方参考 - 会话标题
- messageSeqs
- messageSeqs 是标题生成实际使用的用户消息 seq 列表,必须来自本次 request.messages;服务据此记录标题与源消息的归因。— DeepSeek Harness 官方参考 - 会话标题
来源
- DeepSeek Harness 官方参考 - 会话标题· deepseek-ai
- DeepSeek Harness 官方参考 - 会话· deepseek-ai