核心能力
能力包含四项:流式对话、工具调用可视化、人工审批、四端同一协议。
流式对话
token 级增量渲染。assistant.delta / reasoning.delta / tool.progress 三类 live-only 事件不落盘、直推界面;回合结束由 assistant.message 落盘定稿。
27 种事件经 SSE 单端点(GET /api/event,since=seq 断线续播)推送。各端用同一份 applyEvent reducer 把事件流折叠成 UI 状态——协议层是运行时代码,不是类型包。注意两个「投影」不同义:UI 投影在各端的 reducer;引擎侧 Projector 投影的是模型上下文(surface 事件 → LlmMessage)。
// packages/protocol/src/events.ts — 三分类是类型级事实,不是注释
export type LiveOnlyEventType =
| 'assistant.delta'
| 'reasoning.delta'
| 'tool.progress'
/** surface 事件强制带 surface:true(编译期纪律) */
export type SurfaceEventType = 'user.message' | 'assistant.message'
export type DurableEventType = Exclude<SparkEventType, LiveOnlyEventType>
// 词表共 27 种(EventSchemas 键数):durable 24 / live-only 3;surface 2
// 生成物 apps/docs/events.md 由 CI 重跑并 git diff --exit-code 校同步工具调用可视化
每次工具调用是会话流里一个可折叠执行块:入参、输出、耗时、是否出错全量展示,失败时错误码同屏。
工具状态机 started → [progress] → completed。tool.completed 携带 isError 与 durationMs,两者都是 durable 事件——回放一遍就能重建当时看到的执行块,不靠额外埋点。
// packages/protocol/src/events.ts — 工具(状态机 started → [progress] → completed)
'tool.started': z.strictObject({
turnId: TurnIdSchema,
callId: CallIdSchema,
name: z.string(),
input: z.unknown(),
}),
'tool.progress': z.strictObject({
turnId: TurnIdSchema,
callId: CallIdSchema,
chunk: z.string(),
}), // live-only
'tool.completed': z.strictObject({
turnId: TurnIdSchema,
callId: CallIdSchema,
output: z.unknown(),
isError: z.boolean(),
durationMs: z.number().int().nonnegative(),
}),人工审批 (fail-closed)
写类工具触发审批卡,内联在调用位置。超时、异常、中断一律拒绝而不是放行。
permission.asked 携带 requestId / action / resource / reason,durable 落盘;用户回复写 permission.resolved,reply 只有 once / always / reject 三值。超时由引擎 settle 成 reject——默认拒绝是一条代码路径,不是一句提示语。审批事件 log-only,永不进模型历史。
// packages/protocol/src/primitives.ts
export const PermissionReplySchema = z.enum(['once', 'always', 'reject'])
// packages/engine/src/permission/service.ts — 超时即拒绝(fail-closed)
timer: setTimeout(() => {
void this.settle(entry, false, 'reject', 'timeout')
}, this.deps.timeoutMs),
// timeoutMs 来自 ~/.spark/spark.json 的 engine.permissionTimeoutMs,缺省 300_000(5min)
// origin: 'reply' | 'timeout' | 'abort' | 'shutdown' | 'cascade' | 'mode-change'四端同一协议
web / 桌面 / CLI / 移动端与小程序共用 @spark/protocol:事件词表、zod schema、applyEvent reducer、Transport 都在这个包里,是运行时代码而不是类型声明。
Transport 是前端唯一数据通道抽象;HttpTransport(SSE)与 MockTransport 同构实现,后端不存在时前端可全量开发。@spark/sdk 再分两个子入口:根入口走 HTTP(零 engine 依赖、浏览器可用),./inprocess 进程内直连引擎(engine 为 optional peer)。
// packages/protocol/src/transport.ts — 接口面节选(全量 84 个方法)
export interface Transport {
/** 订阅事件流;返回退订函数 */
onEvent(handler: (e: SparkEventEnvelope) => void): () => void
sendMessage(sessionId: SessionId, text: string, opts?: SendMessageOptions): Promise<SubmitOutcome>
interrupt(sessionId: SessionId): Promise<void>
replyPermission(requestId: RequestId, reply: PermissionReply, feedback?: string): Promise<void>
/** GET /api/sessions/:id:meta + durable 事件(seq 升序——冷启动回放数据源) */
getSession(sessionId: SessionId, query?: SessionEventsQuery): Promise<SessionDto>
/** POST /api/pair:短码兑长效 token(移动端鉴权自举,ADR D24) */
redeemPair(body: PairRedeemBody): Promise<PairTokenDto>
dispose(): void
}
// 实现:HttpTransport(protocol,SSE)/ MockTransport(apps/web 开发态)