berry-agent-sdk 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -0
- package/dist/packages/berry-agent-sdk/src/client.d.ts +6 -0
- package/dist/packages/berry-agent-sdk/src/client.js +59 -0
- package/dist/packages/berry-agent-sdk/src/client.js.map +1 -0
- package/dist/packages/berry-agent-sdk/src/http.d.ts +24 -0
- package/dist/packages/berry-agent-sdk/src/http.js +232 -0
- package/dist/packages/berry-agent-sdk/src/http.js.map +1 -0
- package/dist/packages/berry-agent-sdk/src/index.d.ts +25 -0
- package/dist/packages/berry-agent-sdk/src/index.js +22 -0
- package/dist/packages/berry-agent-sdk/src/index.js.map +1 -0
- package/dist/packages/berry-agent-sdk/src/stdio.d.ts +23 -0
- package/dist/packages/berry-agent-sdk/src/stdio.js +238 -0
- package/dist/packages/berry-agent-sdk/src/stdio.js.map +1 -0
- package/dist/packages/berry-agent-sdk/src/types.d.ts +117 -0
- package/dist/packages/berry-agent-sdk/src/types.js +19 -0
- package/dist/packages/berry-agent-sdk/src/types.js.map +1 -0
- package/dist/src/agent/events.d.ts +11 -0
- package/dist/src/agent/events.js +2 -0
- package/dist/src/agent/events.js.map +1 -0
- package/dist/src/agent/index.d.ts +13 -0
- package/dist/src/agent/index.js +3 -0
- package/dist/src/agent/index.js.map +1 -0
- package/dist/src/agent/loop.d.ts +19 -0
- package/dist/src/agent/loop.js +140 -0
- package/dist/src/agent/loop.js.map +1 -0
- package/dist/src/agent/queue.d.ts +105 -0
- package/dist/src/agent/queue.js +120 -0
- package/dist/src/agent/queue.js.map +1 -0
- package/dist/src/agent/stream.d.ts +21 -0
- package/dist/src/agent/stream.js +80 -0
- package/dist/src/agent/stream.js.map +1 -0
- package/dist/src/agent/tools-batch.d.ts +49 -0
- package/dist/src/agent/tools-batch.js +222 -0
- package/dist/src/agent/tools-batch.js.map +1 -0
- package/dist/src/agent/types.d.ts +104 -0
- package/dist/src/agent/types.js +9 -0
- package/dist/src/agent/types.js.map +1 -0
- package/dist/src/channels/ask-queue.d.ts +44 -0
- package/dist/src/channels/ask-queue.js +83 -0
- package/dist/src/channels/ask-queue.js.map +1 -0
- package/dist/src/channels/commands.d.ts +56 -0
- package/dist/src/channels/commands.js +130 -0
- package/dist/src/channels/commands.js.map +1 -0
- package/dist/src/channels/engine/cell.d.ts +55 -0
- package/dist/src/channels/engine/cell.js +144 -0
- package/dist/src/channels/engine/cell.js.map +1 -0
- package/dist/src/channels/engine/diff.d.ts +3 -0
- package/dist/src/channels/engine/diff.js +144 -0
- package/dist/src/channels/engine/diff.js.map +1 -0
- package/dist/src/channels/engine/engine.d.ts +151 -0
- package/dist/src/channels/engine/engine.js +417 -0
- package/dist/src/channels/engine/engine.js.map +1 -0
- package/dist/src/channels/engine/index.d.ts +22 -0
- package/dist/src/channels/engine/index.js +9 -0
- package/dist/src/channels/engine/index.js.map +1 -0
- package/dist/src/channels/engine/input-keys.d.ts +39 -0
- package/dist/src/channels/engine/input-keys.js +87 -0
- package/dist/src/channels/engine/input-keys.js.map +1 -0
- package/dist/src/channels/engine/input.d.ts +174 -0
- package/dist/src/channels/engine/input.js +597 -0
- package/dist/src/channels/engine/input.js.map +1 -0
- package/dist/src/channels/engine/memory-io.d.ts +46 -0
- package/dist/src/channels/engine/memory-io.js +72 -0
- package/dist/src/channels/engine/memory-io.js.map +1 -0
- package/dist/src/channels/engine/process-io.d.ts +23 -0
- package/dist/src/channels/engine/process-io.js +39 -0
- package/dist/src/channels/engine/process-io.js.map +1 -0
- package/dist/src/channels/engine/types.d.ts +202 -0
- package/dist/src/channels/engine/types.js +18 -0
- package/dist/src/channels/engine/types.js.map +1 -0
- package/dist/src/channels/engine/width.d.ts +23 -0
- package/dist/src/channels/engine/width.js +123 -0
- package/dist/src/channels/engine/width.js.map +1 -0
- package/dist/src/channels/index.d.ts +25 -0
- package/dist/src/channels/index.js +16 -0
- package/dist/src/channels/index.js.map +1 -0
- package/dist/src/channels/registry.d.ts +33 -0
- package/dist/src/channels/registry.js +57 -0
- package/dist/src/channels/registry.js.map +1 -0
- package/dist/src/channels/sdk/admit.d.ts +32 -0
- package/dist/src/channels/sdk/admit.js +30 -0
- package/dist/src/channels/sdk/admit.js.map +1 -0
- package/dist/src/channels/sdk/backend.d.ts +19 -0
- package/dist/src/channels/sdk/backend.js +103 -0
- package/dist/src/channels/sdk/backend.js.map +1 -0
- package/dist/src/channels/sdk/cursor.d.ts +51 -0
- package/dist/src/channels/sdk/cursor.js +57 -0
- package/dist/src/channels/sdk/cursor.js.map +1 -0
- package/dist/src/channels/sdk/index.d.ts +20 -0
- package/dist/src/channels/sdk/index.js +21 -0
- package/dist/src/channels/sdk/index.js.map +1 -0
- package/dist/src/channels/sdk/jsonl.d.ts +47 -0
- package/dist/src/channels/sdk/jsonl.js +201 -0
- package/dist/src/channels/sdk/jsonl.js.map +1 -0
- package/dist/src/channels/sdk/protocol.d.ts +297 -0
- package/dist/src/channels/sdk/protocol.js +25 -0
- package/dist/src/channels/sdk/protocol.js.map +1 -0
- package/dist/src/channels/sdk/schema.d.ts +12 -0
- package/dist/src/channels/sdk/schema.js +92 -0
- package/dist/src/channels/sdk/schema.js.map +1 -0
- package/dist/src/channels/sdk/wire-core.d.ts +199 -0
- package/dist/src/channels/sdk/wire-core.js +447 -0
- package/dist/src/channels/sdk/wire-core.js.map +1 -0
- package/dist/src/channels/service.d.ts +66 -0
- package/dist/src/channels/service.js +170 -0
- package/dist/src/channels/service.js.map +1 -0
- package/dist/src/channels/tui/autocomplete/autocomplete.d.ts +32 -0
- package/dist/src/channels/tui/autocomplete/autocomplete.js +43 -0
- package/dist/src/channels/tui/autocomplete/autocomplete.js.map +1 -0
- package/dist/src/channels/tui/autocomplete/file-mentions.d.ts +18 -0
- package/dist/src/channels/tui/autocomplete/file-mentions.js +107 -0
- package/dist/src/channels/tui/autocomplete/file-mentions.js.map +1 -0
- package/dist/src/channels/tui/autocomplete/popup.d.ts +47 -0
- package/dist/src/channels/tui/autocomplete/popup.js +149 -0
- package/dist/src/channels/tui/autocomplete/popup.js.map +1 -0
- package/dist/src/channels/tui/autocomplete/provider.d.ts +38 -0
- package/dist/src/channels/tui/autocomplete/provider.js +8 -0
- package/dist/src/channels/tui/autocomplete/provider.js.map +1 -0
- package/dist/src/channels/tui/autocomplete/token.d.ts +22 -0
- package/dist/src/channels/tui/autocomplete/token.js +69 -0
- package/dist/src/channels/tui/autocomplete/token.js.map +1 -0
- package/dist/src/channels/tui/backend/ansi-rows.d.ts +86 -0
- package/dist/src/channels/tui/backend/ansi-rows.js +209 -0
- package/dist/src/channels/tui/backend/ansi-rows.js.map +1 -0
- package/dist/src/channels/tui/backend/main-screen.d.ts +90 -0
- package/dist/src/channels/tui/backend/main-screen.js +208 -0
- package/dist/src/channels/tui/backend/main-screen.js.map +1 -0
- package/dist/src/channels/tui/backend/osc.d.ts +79 -0
- package/dist/src/channels/tui/backend/osc.js +92 -0
- package/dist/src/channels/tui/backend/osc.js.map +1 -0
- package/dist/src/channels/tui/backend/transcript.d.ts +98 -0
- package/dist/src/channels/tui/backend/transcript.js +231 -0
- package/dist/src/channels/tui/backend/transcript.js.map +1 -0
- package/dist/src/channels/tui/backend/tui-backend.d.ts +324 -0
- package/dist/src/channels/tui/backend/tui-backend.js +916 -0
- package/dist/src/channels/tui/backend/tui-backend.js.map +1 -0
- package/dist/src/channels/tui/editor/editor-model.d.ts +141 -0
- package/dist/src/channels/tui/editor/editor-model.js +544 -0
- package/dist/src/channels/tui/editor/editor-model.js.map +1 -0
- package/dist/src/channels/tui/editor/editor-view.d.ts +36 -0
- package/dist/src/channels/tui/editor/editor-view.js +141 -0
- package/dist/src/channels/tui/editor/editor-view.js.map +1 -0
- package/dist/src/channels/tui/editor/editor.d.ts +50 -0
- package/dist/src/channels/tui/editor/editor.js +276 -0
- package/dist/src/channels/tui/editor/editor.js.map +1 -0
- package/dist/src/channels/tui/editor/undo-stack.d.ts +22 -0
- package/dist/src/channels/tui/editor/undo-stack.js +36 -0
- package/dist/src/channels/tui/editor/undo-stack.js.map +1 -0
- package/dist/src/channels/tui/editor/visual-lines.d.ts +39 -0
- package/dist/src/channels/tui/editor/visual-lines.js +127 -0
- package/dist/src/channels/tui/editor/visual-lines.js.map +1 -0
- package/dist/src/channels/tui/editor/word-nav.d.ts +10 -0
- package/dist/src/channels/tui/editor/word-nav.js +107 -0
- package/dist/src/channels/tui/editor/word-nav.js.map +1 -0
- package/dist/src/channels/tui/history/history-viewer.d.ts +133 -0
- package/dist/src/channels/tui/history/history-viewer.js +404 -0
- package/dist/src/channels/tui/history/history-viewer.js.map +1 -0
- package/dist/src/channels/tui/index.d.ts +16 -0
- package/dist/src/channels/tui/index.js +4 -0
- package/dist/src/channels/tui/index.js.map +1 -0
- package/dist/src/channels/tui/markdown/blocks.d.ts +42 -0
- package/dist/src/channels/tui/markdown/blocks.js +112 -0
- package/dist/src/channels/tui/markdown/blocks.js.map +1 -0
- package/dist/src/channels/tui/markdown/inline.d.ts +24 -0
- package/dist/src/channels/tui/markdown/inline.js +76 -0
- package/dist/src/channels/tui/markdown/inline.js.map +1 -0
- package/dist/src/channels/tui/markdown/markdown.d.ts +34 -0
- package/dist/src/channels/tui/markdown/markdown.js +183 -0
- package/dist/src/channels/tui/markdown/markdown.js.map +1 -0
- package/dist/src/channels/tui/memory/memory-viewer.d.ts +235 -0
- package/dist/src/channels/tui/memory/memory-viewer.js +541 -0
- package/dist/src/channels/tui/memory/memory-viewer.js.map +1 -0
- package/dist/src/channels/tui/overlay/alt-screen.d.ts +79 -0
- package/dist/src/channels/tui/overlay/alt-screen.js +106 -0
- package/dist/src/channels/tui/overlay/alt-screen.js.map +1 -0
- package/dist/src/channels/tui/overlay/overlay.d.ts +53 -0
- package/dist/src/channels/tui/overlay/overlay.js +58 -0
- package/dist/src/channels/tui/overlay/overlay.js.map +1 -0
- package/dist/src/channels/tui/overlay/select-confirm.d.ts +73 -0
- package/dist/src/channels/tui/overlay/select-confirm.js +157 -0
- package/dist/src/channels/tui/overlay/select-confirm.js.map +1 -0
- package/dist/src/channels/tui/panels/todo-panel.d.ts +24 -0
- package/dist/src/channels/tui/panels/todo-panel.js +55 -0
- package/dist/src/channels/tui/panels/todo-panel.js.map +1 -0
- package/dist/src/channels/tui/panels/tool-progress-panel.d.ts +37 -0
- package/dist/src/channels/tui/panels/tool-progress-panel.js +94 -0
- package/dist/src/channels/tui/panels/tool-progress-panel.js.map +1 -0
- package/dist/src/channels/tui/scroll/scroll-view.d.ts +96 -0
- package/dist/src/channels/tui/scroll/scroll-view.js +255 -0
- package/dist/src/channels/tui/scroll/scroll-view.js.map +1 -0
- package/dist/src/channels/tui/status/status-line.d.ts +46 -0
- package/dist/src/channels/tui/status/status-line.js +77 -0
- package/dist/src/channels/tui/status/status-line.js.map +1 -0
- package/dist/src/channels/tui/theme.d.ts +27 -0
- package/dist/src/channels/tui/theme.js +42 -0
- package/dist/src/channels/tui/theme.js.map +1 -0
- package/dist/src/channels/types.d.ts +84 -0
- package/dist/src/channels/types.js +12 -0
- package/dist/src/channels/types.js.map +1 -0
- package/dist/src/channels/ui-core.d.ts +80 -0
- package/dist/src/channels/ui-core.js +210 -0
- package/dist/src/channels/ui-core.js.map +1 -0
- package/dist/src/context/codes.d.ts +1 -0
- package/dist/src/context/codes.js +41 -0
- package/dist/src/context/codes.js.map +1 -0
- package/dist/src/context/events.d.ts +62 -0
- package/dist/src/context/events.js +161 -0
- package/dist/src/context/events.js.map +1 -0
- package/dist/src/context/index.d.ts +16 -0
- package/dist/src/context/index.js +14 -0
- package/dist/src/context/index.js.map +1 -0
- package/dist/src/context/logger.d.ts +67 -0
- package/dist/src/context/logger.js +137 -0
- package/dist/src/context/logger.js.map +1 -0
- package/dist/src/context/scope.d.ts +78 -0
- package/dist/src/context/scope.js +183 -0
- package/dist/src/context/scope.js.map +1 -0
- package/dist/src/context/workspace.d.ts +8 -0
- package/dist/src/context/workspace.js +105 -0
- package/dist/src/context/workspace.js.map +1 -0
- package/dist/src/contracts/agent-events.d.ts +73 -0
- package/dist/src/contracts/agent-events.js +17 -0
- package/dist/src/contracts/agent-events.js.map +1 -0
- package/dist/src/contracts/api.d.ts +249 -0
- package/dist/src/contracts/api.js +334 -0
- package/dist/src/contracts/api.js.map +1 -0
- package/dist/src/contracts/approval.d.ts +37 -0
- package/dist/src/contracts/approval.js +10 -0
- package/dist/src/contracts/approval.js.map +1 -0
- package/dist/src/contracts/env-ref.d.ts +41 -0
- package/dist/src/contracts/env-ref.js +33 -0
- package/dist/src/contracts/env-ref.js.map +1 -0
- package/dist/src/contracts/errors.d.ts +56 -0
- package/dist/src/contracts/errors.js +267 -0
- package/dist/src/contracts/errors.js.map +1 -0
- package/dist/src/contracts/events.d.ts +70 -0
- package/dist/src/contracts/events.js +330 -0
- package/dist/src/contracts/events.js.map +1 -0
- package/dist/src/contracts/index.d.ts +32 -0
- package/dist/src/contracts/index.js +27 -0
- package/dist/src/contracts/index.js.map +1 -0
- package/dist/src/contracts/llm.d.ts +278 -0
- package/dist/src/contracts/llm.js +13 -0
- package/dist/src/contracts/llm.js.map +1 -0
- package/dist/src/contracts/messages.d.ts +63 -0
- package/dist/src/contracts/messages.js +66 -0
- package/dist/src/contracts/messages.js.map +1 -0
- package/dist/src/contracts/redact.d.ts +46 -0
- package/dist/src/contracts/redact.js +224 -0
- package/dist/src/contracts/redact.js.map +1 -0
- package/dist/src/contracts/tools.d.ts +198 -0
- package/dist/src/contracts/tools.js +35 -0
- package/dist/src/contracts/tools.js.map +1 -0
- package/dist/src/contracts/types.d.ts +304 -0
- package/dist/src/contracts/types.js +51 -0
- package/dist/src/contracts/types.js.map +1 -0
- package/dist/src/contracts/ui.d.ts +122 -0
- package/dist/src/contracts/ui.js +16 -0
- package/dist/src/contracts/ui.js.map +1 -0
- package/dist/src/sdk/types.d.ts +144 -0
- package/dist/src/sdk/types.js +33 -0
- package/dist/src/sdk/types.js.map +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK 线协议核心(03 篇 §10.6 线协议七原则的运转件——批 13b-2)。
|
|
3
|
+
*
|
|
4
|
+
* 架构位:单连接状态机。请求面六动词受理(hello 版本握手/游标重放、prompt
|
|
5
|
+
* admit+受理、interrupt、decide、getEntries、sessions)+ 活体事件外推腿
|
|
6
|
+
* (delta 剥快照的订阅侧过滤 + durable 水位 seq 指派)+ 心跳装配面(静默
|
|
7
|
+
* 填充式——tick 由宿主定时驱动,件内零自驱时钟)+ 出站有界队列背压(⑦)。
|
|
8
|
+
*
|
|
9
|
+
* **依赖全注入**(channels 边表律——conversation/session/persist 面一律经
|
|
10
|
+
* 装配桥注入回调,FetchProjection 先例同款):本件只吃契约类型与注入面,
|
|
11
|
+
* 传输归宿主 serve(13c stdio)/core:sdk 件(13e HTTP+SSE)——`sink` 抽象
|
|
12
|
+
* 单点承接,两传输同律吃本核,零第二套。
|
|
13
|
+
*
|
|
14
|
+
* **衔接序与同步读面**(05 §3.5 衔接序①②③):注入读面全同步 ⇒ 订阅 =
|
|
15
|
+
* 「先快照高水位→重放至快照→replay-end→挂活体监听」在单同步事务内原子
|
|
16
|
+
* 完成——活体缓冲窗不存在(快照后事件只走直播段,零跨段重复零丢失——
|
|
17
|
+
* 双轨律按构造成立)。13e 已收官仍为同步桥读面(衔接序①②③按构造零
|
|
18
|
+
* 丢失——终形);活体缓冲随异步 durable 读面真实需求再启窗,届时仅扩
|
|
19
|
+
* 本件订阅相位机不改协议词汇。
|
|
20
|
+
*/
|
|
21
|
+
import type { AgentEvent } from '../../agent/index.js';
|
|
22
|
+
import type { ApprovalAskAnswer, RetryProbe } from '../../contracts/index.js';
|
|
23
|
+
import { type SdkDurableEntry, type SdkRequest, type SdkSessionSummary, type SdkWireFrame } from './protocol.js';
|
|
24
|
+
/** 会话状态三档(prompt 受理门 + hello 订阅门共用判据——装配桥映射真源) */
|
|
25
|
+
export type SdkSessionState = 'open' | 'closed' | 'missing';
|
|
26
|
+
/** durable 读面分页(queryEntries 注入面载荷——窗口 (since, 高水位] 投影) */
|
|
27
|
+
export interface SdkDurablePage {
|
|
28
|
+
entries: SdkDurableEntry[];
|
|
29
|
+
/** 分页续读游标(05 §3.4——缺席 = 已跟尽) */
|
|
30
|
+
nextCursor?: string;
|
|
31
|
+
}
|
|
32
|
+
/** prompt 受理注入面载荷(fresh 档唯一写路径——admit 判定在核内先决) */
|
|
33
|
+
export interface SdkSubmitInput {
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
content: string;
|
|
36
|
+
messageId: string;
|
|
37
|
+
}
|
|
38
|
+
/** prompt 受理结果(sessionId 缺席新建时由桥落定回示——调用方以此获会话句柄) */
|
|
39
|
+
export interface SdkSubmitOutcome {
|
|
40
|
+
sessionId: string;
|
|
41
|
+
/** 驱动侧单源路由观察(在飞 run 时的并发路由结果——缺席 = 新开轮) */
|
|
42
|
+
routedChannel?: 'steer' | 'followUp';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 出站传输 sink 抽象(单点承接两传输)。`write` 返回 false = 传输面暂不可写
|
|
46
|
+
* (背压)——帧入有界队列待 {@link SdkWireCore.drain}(宿主 writable 时调用)。
|
|
47
|
+
*/
|
|
48
|
+
export interface SdkOutboundSink {
|
|
49
|
+
write(frame: SdkWireFrame): boolean;
|
|
50
|
+
}
|
|
51
|
+
/** 注入面全集(装配桥 13c/13e 实装——channels 边表律的执法位) */
|
|
52
|
+
export interface SdkWireDeps {
|
|
53
|
+
sink: SdkOutboundSink;
|
|
54
|
+
/** prompt 受理(fresh 档唯一写路径) */
|
|
55
|
+
submitPrompt(input: SdkSubmitInput): SdkSubmitOutcome;
|
|
56
|
+
/** durable dedupeKey 查面(跨重启幂等腿——durable 即真相,05 §3.5 第二腿) */
|
|
57
|
+
lookupDedupeKey(sessionId: string, messageId: string): string | undefined;
|
|
58
|
+
/** 打断在飞 run(受理经事件流可观察——无应答帧) */
|
|
59
|
+
interruptSession(sessionId: string): void;
|
|
60
|
+
/** durable 平铺投影读面((since, 高水位] 窗口——与重放同源) */
|
|
61
|
+
queryEntries(sessionId: string, since: number, cursor?: string): SdkDurablePage;
|
|
62
|
+
/** 会话清单 */
|
|
63
|
+
listSessions(): SdkSessionSummary[];
|
|
64
|
+
/** 会话高水位(= 内存日志长度;missing 会话 = undefined) */
|
|
65
|
+
highWaterOf(sessionId: string): number | undefined;
|
|
66
|
+
/** 会话状态三档 */
|
|
67
|
+
sessionStateOf(sessionId: string): SdkSessionState;
|
|
68
|
+
/** 驱动重试只读小面(心跳 retry 阶段唯一线面出口——04 §2 零新事件型) */
|
|
69
|
+
retryProbeOf(sessionId: string): RetryProbe | null;
|
|
70
|
+
/** 审批应答(跨入口竞速回执——unknown 归 superseded,由 13b-3 后端实装) */
|
|
71
|
+
decideApproval(approvalId: string, answer: ApprovalAskAnswer, note?: string): 'applied' | 'superseded';
|
|
72
|
+
/** 订阅受理钩(未决 ask 全量重推位——13b-3 挂;重连补推语义) */
|
|
73
|
+
onSubscribed?(sessionId: string): void;
|
|
74
|
+
/** 过载断连钩(SDK_OVERLOADED 已尽力直写后调用——宿主关传输) */
|
|
75
|
+
onOverload?(retryAfterMs: number): void;
|
|
76
|
+
}
|
|
77
|
+
/** 构造选项(时钟/节拍/背压参全注入——测试确定性) */
|
|
78
|
+
export interface SdkWireOptions {
|
|
79
|
+
/** 假钟注入位(心跳 elapsedMs/stageElapsedMs 与静默判据的时源) */
|
|
80
|
+
now?: () => number;
|
|
81
|
+
/** 心跳静默阈值(毫秒——会话级最近一帧距今超阈即 tick 补拍) */
|
|
82
|
+
heartbeatIntervalMs?: number;
|
|
83
|
+
/** 出站有界队列容量(⑦ 背压——溢出先丢纯活体帧、线控帧不可丢则断连) */
|
|
84
|
+
queueCap?: number;
|
|
85
|
+
/** SDK_OVERLOADED 载荷 retryAfterMs */
|
|
86
|
+
overloadRetryAfterMs?: number;
|
|
87
|
+
/**
|
|
88
|
+
* 连接级缺省 noDelta 初值(07 §5 --no-delta:serve 收旗标为宿主侧立场
|
|
89
|
+
* 缺省——连接级 hello 显式携值胜出、缺席承本缺省)
|
|
90
|
+
*/
|
|
91
|
+
initialConnectionNoDelta?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** 出站帧去向(⑦ 背压三态 + 过载态) */
|
|
94
|
+
export type EnqueueStatus = 'sent' | 'queued' | 'dropped' | 'overload';
|
|
95
|
+
/**
|
|
96
|
+
* 判帧是否纯活体可丢(⑦ 背压 shedding 面——03 §10.6「纯活体帧可丢」):
|
|
97
|
+
* message_update delta 与 tool_execution_update 进度帧——丢帧无害,durable
|
|
98
|
+
* 定稿/锚定帧兜底(双轨律掉帧语义)。其余 event 帧与全部线控/回执帧不可丢。
|
|
99
|
+
*/
|
|
100
|
+
export declare function isDroppableFrame(frame: SdkWireFrame): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* 出站有界队列(⑦):sink 可写即直写;背压期入队;溢出先丢队内最旧可丢帧
|
|
103
|
+
* (shedding——丢纯活体保线控),无可丢可让则过载(线控帧不可丢 ⇒ 断连档)。
|
|
104
|
+
*/
|
|
105
|
+
export declare class SdkOutboundQueue {
|
|
106
|
+
private readonly sink;
|
|
107
|
+
private readonly cap;
|
|
108
|
+
private readonly queue;
|
|
109
|
+
private overloaded;
|
|
110
|
+
constructor(sink: SdkOutboundSink, cap: number);
|
|
111
|
+
/** 累计丢弃纯活体帧数(观测面——宿主日志/指标可读) */
|
|
112
|
+
droppedCount: number;
|
|
113
|
+
/** 容量观测面(过载错误帧文案与宿主指标用) */
|
|
114
|
+
get capacity(): number;
|
|
115
|
+
enqueue(frame: SdkWireFrame): EnqueueStatus;
|
|
116
|
+
/**
|
|
117
|
+
* 宿主 writable 时冲刷(按序 FIFO;sink 再拒即停——余量留下轮)。
|
|
118
|
+
* @returns 本轮冲出帧数
|
|
119
|
+
*/
|
|
120
|
+
drain(): number;
|
|
121
|
+
get isOverloaded(): boolean;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* SDK 线协议核心(单连接状态机)。
|
|
125
|
+
*
|
|
126
|
+
* 生命周期间隙:宿主建核 → 行解码(./jsonl.ts)→ handleRequest 逐请求;
|
|
127
|
+
* 13b-3 后端把活体事件推入 {@link pushEvent};宿主定时调
|
|
128
|
+
* {@link heartbeatTick}(心跳义务的装配驱动位)与 {@link drain}(writable)。
|
|
129
|
+
*/
|
|
130
|
+
export declare class SdkWireCore {
|
|
131
|
+
private readonly deps;
|
|
132
|
+
private readonly subs;
|
|
133
|
+
/** admit 已知键账(连接生命周期内快速档——跨重启档走 lookupDedupeKey) */
|
|
134
|
+
private readonly known;
|
|
135
|
+
/** 连接级 messageId → sessionId 反查账(sessionId 缺席重发的快速档定位面) */
|
|
136
|
+
private readonly messageIndex;
|
|
137
|
+
private readonly queue;
|
|
138
|
+
private readonly now;
|
|
139
|
+
private readonly heartbeatIntervalMs;
|
|
140
|
+
private readonly overloadRetryAfterMs;
|
|
141
|
+
/** 连接级缺省 noDelta(连接级 hello 落定;prompt 自动订阅继承) */
|
|
142
|
+
private connectionNoDelta;
|
|
143
|
+
private closed;
|
|
144
|
+
constructor(deps: SdkWireDeps, options?: SdkWireOptions);
|
|
145
|
+
/** 请求受理总入口(行解码后逐请求路由——本方法即 NDJSON 请求面的全语义) */
|
|
146
|
+
handleRequest(req: SdkRequest): void;
|
|
147
|
+
/** hello:版本握手(⑤)→ 会话订阅 + 游标重放(③)→ 衔接界标 */
|
|
148
|
+
private handleHello;
|
|
149
|
+
/** prompt:admit 三档(④)→ fresh 受理 / duplicate 幂等重收执 / conflict 拒收 */
|
|
150
|
+
private handlePrompt;
|
|
151
|
+
/** interrupt:受理经事件流可观察(turn/end reason='interrupted')——无应答帧 */
|
|
152
|
+
private handleInterrupt;
|
|
153
|
+
/** decide:跨入口审批竞速应答——applied/superseded 回执(03 §10.6 第三腿) */
|
|
154
|
+
private handleDecide;
|
|
155
|
+
/** getEntries:对账读面(since 同 after 语义——05 §3.5 第一腿) */
|
|
156
|
+
private handleGetEntries;
|
|
157
|
+
/**
|
|
158
|
+
* 活体事件推入(双轨律直播段唯一入口)。未订阅会话丢弃;noDelta 订阅剥
|
|
159
|
+
* message_update;seq = 推入时刻高水位(durable 水位语义——弱单调,协议
|
|
160
|
+
* 文注定形)。同步推入顺带推导心跳账(run 态/阶段)。
|
|
161
|
+
*/
|
|
162
|
+
pushEvent(sessionId: string, event: AgentEvent): void;
|
|
163
|
+
/**
|
|
164
|
+
* 非事件帧外推(批 13b-3 后端消费位——ask 审批外推帧的入口):独立帧族
|
|
165
|
+
* 不占 seq 不进 durable(03 §10.6 第三腿),直接过出站单点(背压/静默账
|
|
166
|
+
* 与事件帧同律)。订阅判据由调用方先决(后端 fail-closed 位)。
|
|
167
|
+
*/
|
|
168
|
+
pushFrame(frame: SdkWireFrame, sessionId?: string): void;
|
|
169
|
+
/** 订阅观测面(后端 fail-closed 判据——ask 帧去向位) */
|
|
170
|
+
isSubscribed(sessionId: string): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* 退订(批 13e-2 HTTP 传输位:SSE 流全撤即退——ask fail-closed 判据与真
|
|
173
|
+
* 观众同步;未决 ask 不动〔重连 hello 即重推〕,admit 账不动〔连接级〕)。
|
|
174
|
+
* 退订后同会话再 hello/prompt 即重挂新订阅态。
|
|
175
|
+
*/
|
|
176
|
+
unsubscribe(sessionId: string): void;
|
|
177
|
+
/** 活跃订阅数(后端 hasAudience 探针——零订阅即无观众) */
|
|
178
|
+
get subscriptionCount(): number;
|
|
179
|
+
/** 心跳账推导(事件流 → runState/stage——非事件型 probe 走 tick 时查面) */
|
|
180
|
+
private trackHeartbeat;
|
|
181
|
+
/**
|
|
182
|
+
* 心跳拍(② 协议义务的装配驱动位——宿主定间隔调)。静默填充式:会话级
|
|
183
|
+
* 最近一帧距今超阈才发(活跃流自证活性,心跳只填间隙);阶段优先 retry
|
|
184
|
+
* probe(驱动退避等待期——事件流不可推导的非事件型源)。
|
|
185
|
+
*/
|
|
186
|
+
heartbeatTick(): void;
|
|
187
|
+
/** 出站单点:入有界队列;过载即断连档(SDK_OVERLOADED 尽力直写 + 宿主关传输) */
|
|
188
|
+
private emit;
|
|
189
|
+
private emitError;
|
|
190
|
+
private closedReason;
|
|
191
|
+
/** 闭合观测面(13e HTTP 传输位:闭合面新请求直接 503——不留半开档) */
|
|
192
|
+
get isClosed(): boolean;
|
|
193
|
+
/** 宿主 writable 时冲刷在队帧(背压恢复腿) */
|
|
194
|
+
drain(): number;
|
|
195
|
+
/** 出站队列观测面(dropped 计数——宿主日志/指标) */
|
|
196
|
+
get droppedFrameCount(): number;
|
|
197
|
+
/** 连接收口(宿主关传输时调——清订阅,后续 push/请求全静默) */
|
|
198
|
+
close(): void;
|
|
199
|
+
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { admitMessage } from './admit.js';
|
|
2
|
+
import { validateAfterCursor } from './cursor.js';
|
|
3
|
+
import { SDK_PROTOCOL_VERSION, } from './protocol.js';
|
|
4
|
+
/**
|
|
5
|
+
* 判帧是否纯活体可丢(⑦ 背压 shedding 面——03 §10.6「纯活体帧可丢」):
|
|
6
|
+
* message_update delta 与 tool_execution_update 进度帧——丢帧无害,durable
|
|
7
|
+
* 定稿/锚定帧兜底(双轨律掉帧语义)。其余 event 帧与全部线控/回执帧不可丢。
|
|
8
|
+
*/
|
|
9
|
+
export function isDroppableFrame(frame) {
|
|
10
|
+
return (frame.kind === 'event' && (frame.event.type === 'message_update' || frame.event.type === 'tool_execution_update'));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 出站有界队列(⑦):sink 可写即直写;背压期入队;溢出先丢队内最旧可丢帧
|
|
14
|
+
* (shedding——丢纯活体保线控),无可丢可让则过载(线控帧不可丢 ⇒ 断连档)。
|
|
15
|
+
*/
|
|
16
|
+
export class SdkOutboundQueue {
|
|
17
|
+
sink;
|
|
18
|
+
cap;
|
|
19
|
+
queue = [];
|
|
20
|
+
overloaded = false;
|
|
21
|
+
constructor(sink, cap) {
|
|
22
|
+
this.sink = sink;
|
|
23
|
+
this.cap = cap;
|
|
24
|
+
}
|
|
25
|
+
/** 累计丢弃纯活体帧数(观测面——宿主日志/指标可读) */
|
|
26
|
+
droppedCount = 0;
|
|
27
|
+
/** 容量观测面(过载错误帧文案与宿主指标用) */
|
|
28
|
+
get capacity() {
|
|
29
|
+
return this.cap;
|
|
30
|
+
}
|
|
31
|
+
enqueue(frame) {
|
|
32
|
+
if (this.overloaded)
|
|
33
|
+
return 'dropped'; // 断连档后全弃(宿主即将关传输)
|
|
34
|
+
if (this.queue.length === 0 && this.sink.write(frame))
|
|
35
|
+
return 'sent';
|
|
36
|
+
if (this.queue.length >= this.cap) {
|
|
37
|
+
// 溢出纪律:入站纯活体帧先自弃(最廉 shedding);线控/锚定帧入站则让
|
|
38
|
+
// 位队内最旧纯活体帧,队内无可让 ⇒ 过载(线控帧不可丢——断连档)
|
|
39
|
+
if (isDroppableFrame(frame)) {
|
|
40
|
+
this.droppedCount++;
|
|
41
|
+
return 'dropped';
|
|
42
|
+
}
|
|
43
|
+
while (this.queue.length >= this.cap) {
|
|
44
|
+
const shedIndex = this.queue.findIndex(isDroppableFrame);
|
|
45
|
+
if (shedIndex === -1) {
|
|
46
|
+
this.overloaded = true;
|
|
47
|
+
return 'overload';
|
|
48
|
+
}
|
|
49
|
+
this.queue.splice(shedIndex, 1);
|
|
50
|
+
this.droppedCount++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
this.queue.push(frame);
|
|
54
|
+
return 'queued';
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 宿主 writable 时冲刷(按序 FIFO;sink 再拒即停——余量留下轮)。
|
|
58
|
+
* @returns 本轮冲出帧数
|
|
59
|
+
*/
|
|
60
|
+
drain() {
|
|
61
|
+
let drained = 0;
|
|
62
|
+
while (this.queue.length > 0 && this.sink.write(this.queue[0])) {
|
|
63
|
+
this.queue.shift();
|
|
64
|
+
drained++;
|
|
65
|
+
}
|
|
66
|
+
return drained;
|
|
67
|
+
}
|
|
68
|
+
get isOverloaded() {
|
|
69
|
+
return this.overloaded;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* SDK 线协议核心(单连接状态机)。
|
|
74
|
+
*
|
|
75
|
+
* 生命周期间隙:宿主建核 → 行解码(./jsonl.ts)→ handleRequest 逐请求;
|
|
76
|
+
* 13b-3 后端把活体事件推入 {@link pushEvent};宿主定时调
|
|
77
|
+
* {@link heartbeatTick}(心跳义务的装配驱动位)与 {@link drain}(writable)。
|
|
78
|
+
*/
|
|
79
|
+
export class SdkWireCore {
|
|
80
|
+
deps;
|
|
81
|
+
subs = new Map();
|
|
82
|
+
/** admit 已知键账(连接生命周期内快速档——跨重启档走 lookupDedupeKey) */
|
|
83
|
+
known = new Map();
|
|
84
|
+
/** 连接级 messageId → sessionId 反查账(sessionId 缺席重发的快速档定位面) */
|
|
85
|
+
messageIndex = new Map();
|
|
86
|
+
queue;
|
|
87
|
+
now;
|
|
88
|
+
heartbeatIntervalMs;
|
|
89
|
+
overloadRetryAfterMs;
|
|
90
|
+
/** 连接级缺省 noDelta(连接级 hello 落定;prompt 自动订阅继承) */
|
|
91
|
+
connectionNoDelta;
|
|
92
|
+
closed = false;
|
|
93
|
+
constructor(deps, options = {}) {
|
|
94
|
+
this.deps = deps;
|
|
95
|
+
this.now = options.now ?? Date.now;
|
|
96
|
+
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? 5_000;
|
|
97
|
+
this.overloadRetryAfterMs = options.overloadRetryAfterMs ?? 1_000;
|
|
98
|
+
this.connectionNoDelta = options.initialConnectionNoDelta ?? false;
|
|
99
|
+
this.queue = new SdkOutboundQueue(deps.sink, options.queueCap ?? 512);
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// 请求面六动词受理
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
/** 请求受理总入口(行解码后逐请求路由——本方法即 NDJSON 请求面的全语义) */
|
|
105
|
+
handleRequest(req) {
|
|
106
|
+
if (this.closed) {
|
|
107
|
+
// 版本拒连/过载断连后的残行:同码直写重申(绕队列——连接将亡,宿主随即关传输);
|
|
108
|
+
// 宿主主动 close()(无因)后的残行静默弃(装配纪律位:关传输后不应再喂行)
|
|
109
|
+
if (this.closedReason !== undefined) {
|
|
110
|
+
this.deps.sink.write({
|
|
111
|
+
kind: 'error',
|
|
112
|
+
code: this.closedReason,
|
|
113
|
+
message: '连接已关闭(版本握手拒连或出站过载断连)——残行不受理',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
switch (req.verb) {
|
|
119
|
+
case 'hello':
|
|
120
|
+
this.handleHello(req.protocolVersion, req.sessionId, req.after, req.noDelta);
|
|
121
|
+
break;
|
|
122
|
+
case 'prompt':
|
|
123
|
+
this.handlePrompt(req.sessionId, req.messageId, req.content);
|
|
124
|
+
break;
|
|
125
|
+
case 'interrupt':
|
|
126
|
+
this.handleInterrupt(req.sessionId);
|
|
127
|
+
break;
|
|
128
|
+
case 'decide':
|
|
129
|
+
this.handleDecide(req.approvalId, req.answer, req.note);
|
|
130
|
+
break;
|
|
131
|
+
case 'getEntries':
|
|
132
|
+
this.handleGetEntries(req.sessionId, req.since, req.cursor);
|
|
133
|
+
break;
|
|
134
|
+
case 'sessions':
|
|
135
|
+
this.emit({ kind: 'sessions', sessions: this.deps.listSessions() });
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** hello:版本握手(⑤)→ 会话订阅 + 游标重放(③)→ 衔接界标 */
|
|
140
|
+
handleHello(protocolVersion, sessionId, after, noDelta) {
|
|
141
|
+
// ⑤ 版本握手第一天就有:不符即拒连(先应答后置闭——emit 对 closed 自静默)
|
|
142
|
+
if (protocolVersion !== SDK_PROTOCOL_VERSION) {
|
|
143
|
+
this.emitError('SDK_PROTOCOL_MISMATCH', `线协议版本不符:服务端 ${SDK_PROTOCOL_VERSION} / 调用方 ${protocolVersion}`);
|
|
144
|
+
this.closed = true;
|
|
145
|
+
this.closedReason = 'SDK_PROTOCOL_MISMATCH';
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// 连接级握手(无会话订阅):落定连接缺省 noDelta——显式携值胜出、缺席承
|
|
149
|
+
// 构造初值(07 §5 --no-delta 宿主立场缺省;选项缺席时初值 false 语义不变)
|
|
150
|
+
if (sessionId === undefined) {
|
|
151
|
+
this.connectionNoDelta = noDelta ?? this.connectionNoDelta;
|
|
152
|
+
this.emit({ kind: 'hello', protocolVersion: SDK_PROTOCOL_VERSION, sessionId: '', highWaterSeq: 0 });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const state = this.deps.sessionStateOf(sessionId);
|
|
156
|
+
if (state === 'missing') {
|
|
157
|
+
this.emitError('SESSION_NOT_FOUND', `会话 ${sessionId} 不存在`, sessionId);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// 订阅三步(同步读面下原子——文件头衔接序文注):①快照高水位(先于挂监听,
|
|
161
|
+
// 快照后事件只走直播段——零跨段重复)②重放至快照 ③replay-end 后转直播
|
|
162
|
+
const highWater = this.deps.highWaterOf(sessionId) ?? 0;
|
|
163
|
+
this.emit({ kind: 'hello', protocolVersion: SDK_PROTOCOL_VERSION, sessionId, highWaterSeq: highWater });
|
|
164
|
+
let lastReplayed = -1;
|
|
165
|
+
if (after !== undefined) {
|
|
166
|
+
const check = validateAfterCursor(after, highWater);
|
|
167
|
+
if (!check.ok) {
|
|
168
|
+
this.emitError('SDK_CURSOR_INVALID', `订阅游标非法(${check.reason}):after=${after} 高水位=${highWater}`, sessionId);
|
|
169
|
+
return; // 游标非法即拒订阅(不挂监听——调用方重对账后再来)
|
|
170
|
+
}
|
|
171
|
+
const page = this.deps.queryEntries(sessionId, after);
|
|
172
|
+
if (page.entries.length > 0) {
|
|
173
|
+
this.emit({ kind: 'entries', sessionId, entries: page.entries, nextCursor: page.nextCursor });
|
|
174
|
+
lastReplayed = page.entries[page.entries.length - 1].seq;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
lastReplayed = after; // 空窗(完全追平档)——after 原值即重放尾
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.emit({ kind: 'replay-end', sessionId, lastReplayedSeq: lastReplayed });
|
|
181
|
+
this.subs.set(sessionId, {
|
|
182
|
+
noDelta: noDelta ?? this.connectionNoDelta,
|
|
183
|
+
phase: 'live',
|
|
184
|
+
running: false,
|
|
185
|
+
runStartAt: 0,
|
|
186
|
+
lastElapsedMs: 0,
|
|
187
|
+
stage: null,
|
|
188
|
+
lastFrameAt: this.now(),
|
|
189
|
+
});
|
|
190
|
+
this.deps.onSubscribed?.(sessionId);
|
|
191
|
+
}
|
|
192
|
+
/** prompt:admit 三档(④)→ fresh 受理 / duplicate 幂等重收执 / conflict 拒收 */
|
|
193
|
+
handlePrompt(sessionId, messageId, content) {
|
|
194
|
+
// sessionId 缺席但连接内既见该 messageId:定位既落会话(重发走原会话快速档——
|
|
195
|
+
// 反查账是连接级索引,不参与 durable 语义)
|
|
196
|
+
if (sessionId === undefined) {
|
|
197
|
+
const indexed = this.messageIndex.get(messageId);
|
|
198
|
+
if (indexed !== undefined)
|
|
199
|
+
sessionId = indexed;
|
|
200
|
+
}
|
|
201
|
+
// 显式携带即续接:missing/closed 双拒(已闭对齐 webui 404 语义——读面走 getEntries)
|
|
202
|
+
if (sessionId !== undefined) {
|
|
203
|
+
const state = this.deps.sessionStateOf(sessionId);
|
|
204
|
+
if (state === 'missing') {
|
|
205
|
+
this.emitError('SESSION_NOT_FOUND', `会话 ${sessionId} 不存在`, sessionId);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (state === 'closed') {
|
|
209
|
+
this.emitError('SESSION_CLOSED', `会话 ${sessionId} 已关闭(新发拒收——回放走 getEntries)`, sessionId);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// admit 判定:连接内快速档(known)→ durable 档(lookupDedupeKey 跨重启幂等)
|
|
214
|
+
let sessionKnown = sessionId === undefined ? undefined : this.known.get(sessionId);
|
|
215
|
+
let verdict = admitMessage(sessionKnown ?? new Map(), messageId, content);
|
|
216
|
+
if (verdict.status === 'fresh' && sessionId !== undefined) {
|
|
217
|
+
const durable = this.deps.lookupDedupeKey(sessionId, messageId);
|
|
218
|
+
if (durable !== undefined) {
|
|
219
|
+
// durable 命中——种子进连接内账后按同键判定(后续快速档直达)
|
|
220
|
+
sessionKnown ??= new Map();
|
|
221
|
+
sessionKnown.set(messageId, durable);
|
|
222
|
+
this.known.set(sessionId, sessionKnown);
|
|
223
|
+
verdict = admitMessage(sessionKnown, messageId, content);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (verdict.status === 'conflict') {
|
|
227
|
+
this.emitError('SDK_MESSAGE_CONFLICT', `messageId=${messageId} 同键异内容(幂等 admit 冲突档)`, sessionId);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (verdict.status === 'duplicate') {
|
|
231
|
+
const sid = sessionId;
|
|
232
|
+
this.messageIndex.set(messageId, sid);
|
|
233
|
+
this.emit({
|
|
234
|
+
kind: 'ack',
|
|
235
|
+
sessionId: sid,
|
|
236
|
+
messageId,
|
|
237
|
+
duplicate: true,
|
|
238
|
+
highWaterSeq: this.deps.highWaterOf(sid) ?? 0,
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// fresh:唯一写路径过桥(sessionId 缺席即新建——桥落定回示句柄)
|
|
243
|
+
const outcome = this.deps.submitPrompt({ sessionId, content, messageId });
|
|
244
|
+
const record = this.known.get(outcome.sessionId) ?? new Map();
|
|
245
|
+
record.set(messageId, content);
|
|
246
|
+
this.known.set(outcome.sessionId, record);
|
|
247
|
+
this.messageIndex.set(messageId, outcome.sessionId);
|
|
248
|
+
// 自动订阅(13b 落码定形):prompt 落定会话即挂直播(ack 携句柄 → 事件随后
|
|
249
|
+
// 即流;只直播不重放——重放走显式 hello.after)。既有订阅不覆写(hello 订阅
|
|
250
|
+
// 携 noDelta 等订阅参数胜出);新会话无未决 ask,onSubscribed 只对新挂者发。
|
|
251
|
+
if (!this.subs.has(outcome.sessionId)) {
|
|
252
|
+
this.subs.set(outcome.sessionId, {
|
|
253
|
+
noDelta: this.connectionNoDelta,
|
|
254
|
+
phase: 'live',
|
|
255
|
+
running: false,
|
|
256
|
+
runStartAt: 0,
|
|
257
|
+
lastElapsedMs: 0,
|
|
258
|
+
stage: null,
|
|
259
|
+
lastFrameAt: this.now(),
|
|
260
|
+
});
|
|
261
|
+
this.deps.onSubscribed?.(outcome.sessionId);
|
|
262
|
+
}
|
|
263
|
+
this.emit({
|
|
264
|
+
kind: 'ack',
|
|
265
|
+
sessionId: outcome.sessionId,
|
|
266
|
+
messageId,
|
|
267
|
+
duplicate: false,
|
|
268
|
+
routedChannel: outcome.routedChannel,
|
|
269
|
+
highWaterSeq: this.deps.highWaterOf(outcome.sessionId) ?? 0,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/** interrupt:受理经事件流可观察(turn/end reason='interrupted')——无应答帧 */
|
|
273
|
+
handleInterrupt(sessionId) {
|
|
274
|
+
if (this.deps.sessionStateOf(sessionId) === 'missing') {
|
|
275
|
+
this.emitError('SESSION_NOT_FOUND', `会话 ${sessionId} 不存在`, sessionId);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
this.deps.interruptSession(sessionId);
|
|
279
|
+
}
|
|
280
|
+
/** decide:跨入口审批竞速应答——applied/superseded 回执(03 §10.6 第三腿) */
|
|
281
|
+
handleDecide(approvalId, answer, note) {
|
|
282
|
+
const outcome = this.deps.decideApproval(approvalId, answer, note);
|
|
283
|
+
this.emit({ kind: 'decide-result', approvalId, outcome });
|
|
284
|
+
}
|
|
285
|
+
/** getEntries:对账读面(since 同 after 语义——05 §3.5 第一腿) */
|
|
286
|
+
handleGetEntries(sessionId, since, cursor) {
|
|
287
|
+
if (this.deps.sessionStateOf(sessionId) === 'missing') {
|
|
288
|
+
this.emitError('SESSION_NOT_FOUND', `会话 ${sessionId} 不存在`, sessionId);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const highWater = this.deps.highWaterOf(sessionId) ?? 0;
|
|
292
|
+
const check = validateAfterCursor(since, highWater);
|
|
293
|
+
if (!check.ok) {
|
|
294
|
+
this.emitError('SDK_CURSOR_INVALID', `对账游标非法(${check.reason}):since=${since} 高水位=${highWater}`, sessionId);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const page = this.deps.queryEntries(sessionId, since, cursor);
|
|
298
|
+
this.emit({ kind: 'entries', sessionId, entries: page.entries, nextCursor: page.nextCursor });
|
|
299
|
+
}
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// 活体外推腿(13b-3 后端 onEnvelope → 此处;订阅过滤 + 水位 seq + 心跳账)
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
/**
|
|
304
|
+
* 活体事件推入(双轨律直播段唯一入口)。未订阅会话丢弃;noDelta 订阅剥
|
|
305
|
+
* message_update;seq = 推入时刻高水位(durable 水位语义——弱单调,协议
|
|
306
|
+
* 文注定形)。同步推入顺带推导心跳账(run 态/阶段)。
|
|
307
|
+
*/
|
|
308
|
+
pushEvent(sessionId, event) {
|
|
309
|
+
if (this.closed)
|
|
310
|
+
return;
|
|
311
|
+
const sub = this.subs.get(sessionId);
|
|
312
|
+
if (sub === undefined || sub.phase !== 'live')
|
|
313
|
+
return;
|
|
314
|
+
this.trackHeartbeat(sub, event);
|
|
315
|
+
if (sub.noDelta && event.type === 'message_update')
|
|
316
|
+
return; // ① delta 缺省开、--no-delta 退订
|
|
317
|
+
this.emit({ kind: 'event', seq: this.deps.highWaterOf(sessionId) ?? 0, sessionId, event }, sessionId);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* 非事件帧外推(批 13b-3 后端消费位——ask 审批外推帧的入口):独立帧族
|
|
321
|
+
* 不占 seq 不进 durable(03 §10.6 第三腿),直接过出站单点(背压/静默账
|
|
322
|
+
* 与事件帧同律)。订阅判据由调用方先决(后端 fail-closed 位)。
|
|
323
|
+
*/
|
|
324
|
+
pushFrame(frame, sessionId) {
|
|
325
|
+
this.emit(frame, sessionId);
|
|
326
|
+
}
|
|
327
|
+
/** 订阅观测面(后端 fail-closed 判据——ask 帧去向位) */
|
|
328
|
+
isSubscribed(sessionId) {
|
|
329
|
+
return this.subs.get(sessionId)?.phase === 'live';
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* 退订(批 13e-2 HTTP 传输位:SSE 流全撤即退——ask fail-closed 判据与真
|
|
333
|
+
* 观众同步;未决 ask 不动〔重连 hello 即重推〕,admit 账不动〔连接级〕)。
|
|
334
|
+
* 退订后同会话再 hello/prompt 即重挂新订阅态。
|
|
335
|
+
*/
|
|
336
|
+
unsubscribe(sessionId) {
|
|
337
|
+
this.subs.delete(sessionId);
|
|
338
|
+
}
|
|
339
|
+
/** 活跃订阅数(后端 hasAudience 探针——零订阅即无观众) */
|
|
340
|
+
get subscriptionCount() {
|
|
341
|
+
let count = 0;
|
|
342
|
+
for (const sub of this.subs.values())
|
|
343
|
+
if (sub.phase === 'live')
|
|
344
|
+
count++;
|
|
345
|
+
return count;
|
|
346
|
+
}
|
|
347
|
+
/** 心跳账推导(事件流 → runState/stage——非事件型 probe 走 tick 时查面) */
|
|
348
|
+
trackHeartbeat(sub, event) {
|
|
349
|
+
switch (event.type) {
|
|
350
|
+
case 'agent_start':
|
|
351
|
+
sub.running = true;
|
|
352
|
+
sub.runStartAt = this.now();
|
|
353
|
+
sub.stage = null;
|
|
354
|
+
break;
|
|
355
|
+
case 'agent_end':
|
|
356
|
+
sub.running = false;
|
|
357
|
+
sub.lastElapsedMs = Math.max(0, this.now() - sub.runStartAt);
|
|
358
|
+
sub.stage = null;
|
|
359
|
+
break;
|
|
360
|
+
case 'message_start':
|
|
361
|
+
case 'message_update':
|
|
362
|
+
sub.stage = { type: 'thinking' };
|
|
363
|
+
break;
|
|
364
|
+
case 'tool_execution_start':
|
|
365
|
+
sub.stage = { type: 'tool', name: event.name, sinceMs: this.now() };
|
|
366
|
+
break;
|
|
367
|
+
case 'tool_execution_end':
|
|
368
|
+
sub.stage = null; // 工具间隙(下一 message_start/tool_execution_start 再立)
|
|
369
|
+
break;
|
|
370
|
+
default:
|
|
371
|
+
break; // turn_* 不改阶段账
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* 心跳拍(② 协议义务的装配驱动位——宿主定间隔调)。静默填充式:会话级
|
|
376
|
+
* 最近一帧距今超阈才发(活跃流自证活性,心跳只填间隙);阶段优先 retry
|
|
377
|
+
* probe(驱动退避等待期——事件流不可推导的非事件型源)。
|
|
378
|
+
*/
|
|
379
|
+
heartbeatTick() {
|
|
380
|
+
if (this.closed)
|
|
381
|
+
return;
|
|
382
|
+
const now = this.now();
|
|
383
|
+
for (const [sessionId, sub] of this.subs) {
|
|
384
|
+
if (sub.phase !== 'live' || now - sub.lastFrameAt < this.heartbeatIntervalMs)
|
|
385
|
+
continue;
|
|
386
|
+
const probe = this.deps.retryProbeOf(sessionId);
|
|
387
|
+
const stage = probe !== null && probe.nextAt !== null
|
|
388
|
+
? { type: 'retry', probe }
|
|
389
|
+
: sub.stage === null
|
|
390
|
+
? null
|
|
391
|
+
: sub.stage.type === 'tool'
|
|
392
|
+
? { type: 'tool', name: sub.stage.name, stageElapsedMs: Math.max(0, now - sub.stage.sinceMs) }
|
|
393
|
+
: { type: 'thinking' };
|
|
394
|
+
const elapsedMs = sub.running ? Math.max(0, now - sub.runStartAt) : sub.lastElapsedMs;
|
|
395
|
+
this.emit({ kind: 'heartbeat', sessionId, runState: sub.running ? 'running' : 'idle', stage, elapsedMs }, sessionId);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
// 出站(emit + ⑦ 背压执法)与生命周期间隙
|
|
400
|
+
// ---------------------------------------------------------------------------
|
|
401
|
+
/** 出站单点:入有界队列;过载即断连档(SDK_OVERLOADED 尽力直写 + 宿主关传输) */
|
|
402
|
+
emit(frame, sessionId) {
|
|
403
|
+
if (this.closed)
|
|
404
|
+
return;
|
|
405
|
+
const status = this.queue.enqueue(frame);
|
|
406
|
+
if (sessionId !== undefined) {
|
|
407
|
+
const sub = this.subs.get(sessionId);
|
|
408
|
+
if (sub !== undefined)
|
|
409
|
+
sub.lastFrameAt = this.now(); // 任何携会话帧都刷新静默账
|
|
410
|
+
}
|
|
411
|
+
if (status !== 'overload')
|
|
412
|
+
return;
|
|
413
|
+
// ⑦ 线控帧不可丢 ⇒ 过载断连:错误帧绕队列尽力直写,钩宿主关传输
|
|
414
|
+
this.closed = true;
|
|
415
|
+
this.closedReason = 'SDK_OVERLOADED';
|
|
416
|
+
this.deps.sink.write({
|
|
417
|
+
kind: 'error',
|
|
418
|
+
code: 'SDK_OVERLOADED',
|
|
419
|
+
message: `出站队列溢出(容量 ${this.queue.capacity})——线控帧不可丢,断连档`,
|
|
420
|
+
willRetry: true,
|
|
421
|
+
retryAfterMs: this.overloadRetryAfterMs,
|
|
422
|
+
});
|
|
423
|
+
this.deps.onOverload?.(this.overloadRetryAfterMs);
|
|
424
|
+
}
|
|
425
|
+
emitError(code, message, sessionId) {
|
|
426
|
+
this.emit({ kind: 'error', sessionId, code, message }, sessionId);
|
|
427
|
+
}
|
|
428
|
+
closedReason;
|
|
429
|
+
/** 闭合观测面(13e HTTP 传输位:闭合面新请求直接 503——不留半开档) */
|
|
430
|
+
get isClosed() {
|
|
431
|
+
return this.closed;
|
|
432
|
+
}
|
|
433
|
+
/** 宿主 writable 时冲刷在队帧(背压恢复腿) */
|
|
434
|
+
drain() {
|
|
435
|
+
return this.queue.drain();
|
|
436
|
+
}
|
|
437
|
+
/** 出站队列观测面(dropped 计数——宿主日志/指标) */
|
|
438
|
+
get droppedFrameCount() {
|
|
439
|
+
return this.queue.droppedCount;
|
|
440
|
+
}
|
|
441
|
+
/** 连接收口(宿主关传输时调——清订阅,后续 push/请求全静默) */
|
|
442
|
+
close() {
|
|
443
|
+
this.closed = true;
|
|
444
|
+
this.subs.clear();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
//# sourceMappingURL=wire-core.js.map
|