import { type SegmentEndFields } from "./project.js"; /** * 本帧是不是模型文本的段界。**四条消费腿共用这一只**:账本投影 switch(`trace/ledger-sink.ts`)、 * 顶层 live 流与转发子代腿(`http/routes/tasks.ts`)、子代 tail 面(`fleet/subagent-tail-bus.ts`)。 * 未知/未来词一律 `false` —— 不是兜底,是上面三道钉保证「未知词不存在」之后剩下的诚实答案 * (第三方 Runner 塞进来的野帧不该切走宿主的权威段)。 */ export declare function isModelSegmentBoundary(type: string): boolean; /** live 面的段末帧(`type` 即事件名;其余键由 `reasoningEndEventData` 白名单铸出)。 * 形是**声明**出来的、不是 `Record`(S-334 门①):`eventId` 是本帧的续流游标, * 型必须说得出口 —— live 腿的 `id:` 行取的正是它。 */ export type ReasoningEndFrame = SegmentEndFields & { type: "reasoning_end"; }; /** 登记册**读到的全部字段**,逐个写出来(不是 `Record`):`TaskEvent` 与转发腿的原始帧 * 都结构性满足它,于是调用点一个宽断言都不必写;将来有人想读第七个键,得先在这里加一行。 */ export interface ReasoningStreamEvent { type?: string; delta?: unknown; eventId?: unknown; parentToolCallId?: unknown; sourceTaskId?: unknown; bgAgentId?: unknown; } /** 一条**流**的身份(不含 `eventId`:core 给每个内容事件铸各自的 eventId,拿它当流身份会把每块 delta * 切成一段)。三键都缺席 = 宿主自己的流。 */ export interface ReasoningStreamIdent { parentToolCallId?: string; sourceTaskId?: string; bgAgentId?: string; } /** * 一条 live 腿上**按流身份分账**的 `reasoning` 段登记册。 * * 🔴 **为什么是登记册而不是一只累积器**(codex 对抗复审 R1 两根 [high],亲跑复现后按类修):一条腿上 * 并发子代的 `reasoning_delta` 是**交错**到达的。一只累积器只有两种活法,两条都错: * · 「换身份就收段」⇒ A 说一半、B 插一句、A 说另一半 ⇒ A 被切成两枚权威段,**跨块的凭据正好落在切口上** * ——本帧存在的唯一理由当场失效(实测:两枚 `reasoning_end` 拼起来仍是明文密钥); * · 「不换就不收」⇒ 两个子代的思考拼成一段,再盖上其中一个的身份键(串味 + 冤枉归属)。 * 分账之后规则反而**更少**:一条流的段,只被**它自己**的段界或**它自己**的终态收走;别人说什么都与它无关。 * * 状态的界:每条流的条目在它的首块 delta 上建、在收段时删;腿终局 {@link ReasoningSegments.cut} 不带 * 匹配器 = 全收全删。子代终态那一次带匹配器的收段(转发腿的 `onTaskNotification`)是让**活着的子代数** * 而不是「这条 run 一共派过多少子代」成为上界的那一手。 */ export interface ReasoningSegments { /** 喂一帧 → 本帧收走的段末帧(0 或 1 枚,且**只可能是本帧自己那条流**的段)。返回数组只为让调用点 * 与 {@link cut} 同形(`for (const f of …) send(f)`),不是「一帧可能收多段」。 */ onEvent(ev: ReasoningStreamEvent): ReasoningEndFrame[]; /** 收段:`match` 缺省 = 本腿**全部**流(腿终局 / 句柄终态);给了就只收匹配的那几条(某个子代终态)。 * 与账本腿 `LedgerSink.onDone → flush()` 同位同义 —— 段界表里没有终局词,终局走这扇门。 */ cut(match?: (ident: ReasoningStreamIdent) => boolean): ReasoningEndFrame[]; } /** * 一段推理的**段末事件 id** —— 唯一铸点,live 段末帧与账本 `reasoning` 行共用这一只。 * * 🔴 **为什么段末帧不能沿用任何一块 delta 的 `eventId`**(S-334):SSE 的 `id:` 行是**续流游标**, * 「每帧唯一」就是它的定义(消费端据它做**整帧幂等**:见过的 id 整帧丢);而本仓 live 腿的 `id:` 行 * 恰恰取自载荷的 `eventId`(`http/routes/tasks.ts` 的 `sseData`)。沿用段内某一块的锚 ⇒ 段末帧与那一块 * delta 顶着同一枚游标,消费端把**权威段**当重放帧丢弃 —— 本帧存在的唯一理由(整段脱敏后的权威副本) * 当场结构性失效。「这段是从哪一帧开始攒的」不是游标要回答的问题,而「整段替换」这条消费律本来就 * 不需要段首锚:重连从**帧**续,不从段续。 * * 形 = 那一枚锚 + 一个派生记号,**确定性**是它的全部要点:live 腿与账本腿各自看着同一条流、在同一个 * 段界上算出**同一枚**字符串,于是「同一份权威副本」在身份面也成立,而两条腿之间不需要任何传值通道 * ——bg / resume 两条账本腿上根本没有 live 登记册可问,靠传值就只能做成「detach 车道有、别的腿没有」 * 的两种账本行形。 * * 输入 = 本段**第一块带锚**的 delta 的 `eventId`(不是「第一块」的:首块无锚不等于整段无锚,三条腿 * 统一用 `??=` 闩第一枚**有定义**的锚 —— 与 E18 的 `textEventId ??=` 逐字同律)。整段一块带锚的都没有 * ⇒ 段末 id 缺席(诚实的「无锚」,绝不合成占位 —— 与 `sseData` 的既有纪律同源:不可锚的位置不许 * 伪装成可锚)。 */ export declare function reasoningSegmentEventId(anchor: unknown): string | undefined; /** 一块 `reasoning_delta` 对段的**贡献字节** —— 非空串才算。账本腿与三条 live 腿共用这一只:空/坏形 * 增量一个字节都不贡献,既不开段也不参与闩锚 ⇒ 两面结构上不可能把锚闩在不同的帧上(`reasoning_end` * 与账本 `reasoning` 行的正文早就逐字节相等,身份面随 S-334 收进同一条规则)。 */ export declare function reasoningDeltaBytes(ev: { delta?: unknown; }): string | undefined; /** 「这条流属于这些子代 id 之一」—— 子代终态收段的匹配器(`task_id` 与 `sessionId` 是同一个子代的两个 * id 域,取并集;`undefined` 成员自动忽略)。腿上共用这一只,免得各写一遍 id 域的并集。 */ export declare function reasoningStreamOfTask(...ids: ReadonlyArray): (ident: ReasoningStreamIdent) => boolean; export declare function createReasoningSegments(): ReasoningSegments; //# sourceMappingURL=model-segment.d.ts.map