weifuwu 0.57.0 → 0.58.0

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 CHANGED
@@ -234,6 +234,7 @@ createApp().use(router({ routes })).mount('#root', RouteView, { hydrate: true })
234
234
  | `weifuwu` | **email** | 邮件发送(Resend/SMTP 自研/自定义适配器)→ `ctx.email` | Router |
235
235
  | `weifuwu` | **userSystem** | 用户系统(scrypt 密码哈希 + 混合会话)→ `ctx.user` / `ctx.auth` + `/api/auth/*` | Router, postgres |
236
236
  | `weifuwu` | **queue** | 可靠任务队列(Redis Streams,at-least-once + DLQ)→ `ctx.queue` | Router, redis |
237
+ | `weifuwu` | **ai** | LLM 对话(自研 OpenAI 兼容协议 + 自研 SSE 解码,默认 DeepSeek)→ `ctx.ai` + `aiStream` | Router |
237
238
  | `weifuwu/dev` | **dev loader** | Node loader:服务端直接跑 `.ts/.tsx`(`--import weifuwu/dev`) | esbuild |
238
239
  | `weifuwu` | **graphql** | GraphQL 端点(支持 GraphiQL) | Router |
239
240
  | `weifuwu` | **createMiddleware** | 类型安全中间件工厂 | — |
@@ -2363,6 +2364,13 @@ props 变化 ──────────────────────
2363
2364
  |-----|--------|-----------|------|
2364
2365
  | Divider | `Divider` | `orientation`, `plain` | 分割线(水平/垂直/带文字) |
2365
2366
 
2367
+ ### AI 交互原语(wf: 协议配套)
2368
+
2369
+ | 组件 | 导入名 | 关键 Props | 说明 |
2370
+ |-----|--------|-----------|------|
2371
+ | ToolCallCard | `ToolCallCard` | `call`, `progress?`, `result?`, `renderArgs?` | 工具调用卡片:running(进度条)/ ok / error 三态(协议 §4) |
2372
+ | ApprovalCard | `ApprovalCard` | `request`, `status?`, `onApprove`, `onReject` | 人工审批卡片:待批(允许/拒绝+备注)/ 已批 / 已拒 / 超时(协议 §4.5) |
2373
+
2366
2374
  ### 全局工具
2367
2375
 
2368
2376
  | 组件 | 导入名 | 关键 Props | 说明 |
@@ -2826,6 +2834,74 @@ await worker.stop() // 优雅停止
2826
2834
  - **可靠性**:失败 → 延迟重试(间隔 = `visibilityTimeout`,ZSET 延迟队列)→ attempts 用尽 → DLQ;worker 崩溃 → pending 由其他实例 `XAUTOCLAIM` 接管
2827
2835
  - 裁剪:延迟调度(除重试外)、cron、优先级、指数退避、速率限制不支持
2828
2836
 
2837
+ ## ai — LLM 对话(自研协议 + 零依赖客户端)
2838
+
2839
+ ```ts
2840
+ import { ai } from 'weifuwu'
2841
+
2842
+ const a = ai() // DEEPSEEK_API_KEY / BASE_URL / MODEL 自动读 env,默认 deepseek-v4-flash
2843
+ app.use(a) // 注入 ctx.ai(worker/非请求场景也可直接 a.chat())
2844
+
2845
+ // 流式对话:路由一行返回 SSE(wf: 协议,详见 docs/ai-contract.md)
2846
+ app.post('/api/chat', async (req, ctx) => {
2847
+ const { messages } = await req.json()
2848
+ return ctx.ai.stream({ messages }, {
2849
+ signal: req.signal, // 断开即取消 provider 请求
2850
+ traceId: req.headers.get('x-trace-id') ?? undefined, // 追踪关联(协议 §7)
2851
+ })
2852
+ })
2853
+
2854
+ // 非流式(worker/后台):
2855
+ const res = await a.chat({ messages: [{ role: 'user', content: 'hi' }] })
2856
+
2857
+ // agent 引擎:工具循环 + 人工审批(HITL)
2858
+ const agent = a.agent({
2859
+ systemPrompt: '你是助手。查询天气时调用 query_weather 工具。',
2860
+ tools: [{
2861
+ name: 'query_weather',
2862
+ description: '查询城市天气',
2863
+ parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
2864
+ run: async (args, { emit }) => {
2865
+ emit('wf:tool_progress', { toolCallId: 'x', step: 1, total: 2, message: '查询中…', status: 'running' })
2866
+ return { city: args.city, temp: 25 }
2867
+ },
2868
+ }],
2869
+ humanInTheLoop: true, // 每个工具执行前等人工审批
2870
+ })
2871
+
2872
+ app.post('/api/agent', async (req, ctx) => {
2873
+ const { messages } = await req.json()
2874
+ return agent.run(messages, { signal: req.signal, traceId: req.headers.get('x-trace-id') ?? undefined })
2875
+ })
2876
+
2877
+ // HITL 审批响应(前端点"允许/拒绝" → POST 到这里)
2878
+ app.post('/api/approve', async (req, ctx) => {
2879
+ ctx.ai.approve(await req.json()) // { id, decision, modifiedArgs?, note? }
2880
+ return new Response(null, { status: 200 })
2881
+ })
2882
+ ```
2883
+
2884
+ 前端解码(`weifuwu/client`):
2885
+
2886
+ ```ts
2887
+ import { aiStream } from 'weifuwu/client'
2888
+
2889
+ const handle = aiStream('/api/chat', { messages }, {
2890
+ onToken: (text) => { /* 增量 append 到消息 */ },
2891
+ onToolCall: (call) => { /* 渲染工具卡片 */ },
2892
+ onDone: () => { /* 收尾 */ },
2893
+ onError: (e) => { /* 按 e.code 降级 */ },
2894
+ onEvent: (name, data) => { /* x:* 自定义事件透传 */ },
2895
+ })
2896
+ handle.abort() // 用户停止/组件卸载/导航跳走
2897
+ ```
2898
+
2899
+ - **协议**:`wf:` 命名空间(message_start/token/tool_call/tool_progress/usage/done/error + agent 扩展 step/approval_request),SSE 下行 + POST 上行,错误即值、未知事件透传、`x:*` 自定义事件(详见 [docs/ai-contract.md](./docs/ai-contract.md))
2900
+ - **agent 引擎**:`a.agent({ systemPrompt, tools, humanInTheLoop })` 工具循环(LLM → tool_call → 执行 → 回喂 → 重复);工具可 `emit` 进度/自定义事件、接收 `signal` 取消;HITL 审批(`ctx.ai.approve` 响应,拒绝≠终止、modified 改参、超时兜底)
2901
+ - **零依赖**:自研 OpenAI 兼容客户端(fetch + SSE 解析),默认 DeepSeek,`baseUrl` 可换任意 OpenAI 兼容端点(Ollama/vLLM/Moonshot…)
2902
+ - **追踪**:前端自动生成 `X-Trace-Id` → 后端以之作为 `message_start.id` → 工具内请求继承同一 traceId,整个 agent run 一次搜完
2903
+ - **裁剪**:embeddings、Anthropic 原生协议、审批持久化(连接断=会话亡)暂不支持;多 agent 编排不承诺(子 agent = 工具已支持)
2904
+
2829
2905
  ## 组合示例:注册 → 验证邮件 → 欢迎任务 → 登录防爆破
2830
2906
 
2831
2907
  ```ts
@@ -0,0 +1,50 @@
1
+ /**
2
+ * weifuwu AI — agent 工具循环引擎(协议 §5,agent 扩展实现)
3
+ *
4
+ * 循环:LLM 流式(emit wf:token)→ tool_calls → 执行工具 → 结果回喂 → 重复
5
+ *
6
+ * - 工具执行期间可 emit(wf:tool_progress / x:* 自定义)与接收 signal(取消)
7
+ * - HITL 审批(协议 §4.5):humanInTheLoop 时每个工具执行前挂起等待
8
+ * ctx.ai.approve() 响应(或超时按拒绝处理)——拒绝 ≠ 终止,agent 换方案
9
+ * - 事件序列:message_start → (step:llm → token* → tool_call → step:tool
10
+ * → [approval_request → approve] → tool_result)* → usage → done
11
+ *
12
+ * 子 agent = 工具:委派工具的 run 内部调另一个 createAgent().run()(异步),
13
+ * 其最终输出即 tool_result——多 agent 沟通不新增协议事件(协议 §5.2)。
14
+ */
15
+ import { type WfEmitter } from './sse.ts';
16
+ import type { AiClient } from './client.ts';
17
+ import type { ChatMessage } from './types.ts';
18
+ export interface ToolContext {
19
+ /** 工具执行声道:emit('wf:tool_progress', ...) 或 emit('x:*', ...) */
20
+ emit: WfEmitter;
21
+ /** 用户取消 → abort(长任务应响应此 signal) */
22
+ signal?: AbortSignal;
23
+ }
24
+ export interface AgentTool {
25
+ name: string;
26
+ description?: string;
27
+ parameters?: Record<string, unknown>;
28
+ /** args 来自 LLM(JSON),未类型化——工具内部自行收窄 */
29
+ run: (args: Record<string, unknown>, tool: ToolContext) => unknown;
30
+ }
31
+ export interface AgentConfig {
32
+ model?: string;
33
+ systemPrompt: string;
34
+ tools: AgentTool[];
35
+ /** 默认 10 */
36
+ maxSteps?: number;
37
+ /** 每个工具执行前要求人工审批(协议 §4.5) */
38
+ humanInTheLoop?: boolean;
39
+ /** 审批超时(默认 5 分钟),到期按拒绝处理 */
40
+ approvalTimeoutMs?: number;
41
+ }
42
+ export interface AgentRunOptions {
43
+ signal?: AbortSignal;
44
+ traceId?: string;
45
+ }
46
+ export interface AgentRunner {
47
+ /** 运行 agent → SSE Response(wf: 协议事件流),路由直接 return */
48
+ run: (messages: ChatMessage[], options?: AgentRunOptions) => Response;
49
+ }
50
+ export declare function createAgent(client: AiClient, config: AgentConfig): AgentRunner;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * weifuwu AI — OpenAI 兼容客户端(自研,零依赖)
3
+ *
4
+ * 协议(docs/ai-contract.md)的后端参考实现:把 provider 的
5
+ * chat/completions 流归一化成 wf: 事件。
6
+ *
7
+ * - 零依赖:fetch + 自研 SSE 解析
8
+ * - 默认 DeepSeek(baseUrl 可换 → 任意 OpenAI 兼容端点:Ollama/vLLM/Moonshot…)
9
+ * - 错误映射:provider HTTP 状态/错误体 → WfErrorCode(错误即值)
10
+ * - tool_calls 聚合:id 只在首 chunk(DeepSeek),后端聚合成完整 wf:tool_call
11
+ * - abort:外部 signal + 客户端断开 → 取消 provider 请求
12
+ *
13
+ * 诚实裁剪(CS-05):embeddings 不做(DeepSeek 无此 API)、
14
+ * reasoning 事件不进 v1 协议(reasoning_content 仅随消息往返)。
15
+ */
16
+ import { type WfEmitter } from './sse.ts';
17
+ import type { ChatMessage, ChatParams, ToolCall, WfApprovalResponse, WfErrorCode } from './types.ts';
18
+ export interface ChatChunk {
19
+ id: string;
20
+ model: string;
21
+ choices: {
22
+ index: number;
23
+ delta: {
24
+ role?: string;
25
+ content?: string;
26
+ reasoning_content?: string;
27
+ tool_calls?: ToolCall[];
28
+ };
29
+ finish_reason: 'stop' | 'length' | 'tool_calls' | null;
30
+ }[];
31
+ usage?: {
32
+ prompt_tokens: number;
33
+ completion_tokens: number;
34
+ total_tokens: number;
35
+ };
36
+ }
37
+ export interface ChatResponse {
38
+ id: string;
39
+ model: string;
40
+ choices: {
41
+ index: number;
42
+ message: ChatMessage;
43
+ finish_reason: 'stop' | 'length' | 'tool_calls' | null;
44
+ }[];
45
+ usage?: {
46
+ prompt_tokens: number;
47
+ completion_tokens: number;
48
+ total_tokens: number;
49
+ };
50
+ }
51
+ /** 协议错误:chat() 非流式场景抛出,stream() 场景编码为 wf:error 事件 */
52
+ export declare class AiError extends Error {
53
+ code: WfErrorCode;
54
+ constructor(code: WfErrorCode, message: string);
55
+ }
56
+ export interface AiClientOptions {
57
+ baseUrl: string;
58
+ apiKey: string;
59
+ defaultModel: string;
60
+ }
61
+ /** 单轮 LLM 流式调用的聚合结果(agent 循环用) */
62
+ export interface StreamFinishResult {
63
+ content: string;
64
+ /** DeepSeek thinking 模式:必须随 assistant 消息回传(协议陷阱清单 #4) */
65
+ reasoning_content?: string;
66
+ toolCalls: ToolCall[];
67
+ usage?: ChatResponse['usage'];
68
+ }
69
+ export interface AiClient {
70
+ /** 非流式对话(worker/后台场景) */
71
+ chat(params: ChatParams, options?: {
72
+ signal?: AbortSignal;
73
+ }): Promise<ChatResponse>;
74
+ /** 流式对话 → SSE Response(协议 §1.1),路由直接 return */
75
+ stream(params: ChatParams, options?: {
76
+ signal?: AbortSignal;
77
+ traceId?: string;
78
+ }): Response;
79
+ /** 低层:app 完全控制事件序列(自定义 x:* 事件、HITL 等) */
80
+ sse(run: (emit: WfEmitter) => Promise<void> | void, options?: {
81
+ signal?: AbortSignal;
82
+ }): Response;
83
+ /** 内部:单轮 LLM 流式 → emit 事件 + onFinish 聚合结果(agent 引擎用) */
84
+ streamStep(params: ChatParams, opts: {
85
+ emit: WfEmitter;
86
+ signal?: AbortSignal;
87
+ onFinish?: (r: StreamFinishResult) => void;
88
+ emitUsage?: boolean;
89
+ }): Promise<void>;
90
+ /** 响应一个挂起的 HITL 审批(协议 §4.5,app 的 POST /approve 路由调用) */
91
+ approve(response: WfApprovalResponse): boolean;
92
+ /** 内部:agent 循环挂起等待审批 */
93
+ waitApproval(req: {
94
+ id: string;
95
+ toolCallId: string;
96
+ name: string;
97
+ args: Record<string, unknown>;
98
+ }, emit: WfEmitter, timeoutMs?: number): Promise<WfApprovalResponse>;
99
+ }
100
+ export declare function createAiClient(opts: AiClientOptions): AiClient;
101
+ /** 审批默认超时:5 分钟 */
102
+ export declare const DEFAULT_APPROVAL_TIMEOUT: number;
103
+ /** 工具参数可能是 JSON 字符串;解析失败给空对象(不抛错) */
104
+ export declare function safeParseArgs(raw: string): Record<string, unknown>;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * weifuwu AI — 中间件工厂(queue 式混合:模块即中间件,也独立可用)
3
+ *
4
+ * ```ts
5
+ * import { ai } from 'weifuwu'
6
+ *
7
+ * const a = ai() // DEEPSEEK_API_KEY / BASE_URL / MODEL 自动读 env
8
+ * app.use(a) // → ctx.ai.chat / ctx.ai.stream / ctx.ai.sse
9
+ *
10
+ * app.post('/api/chat', async (req, ctx) => {
11
+ * const { messages } = await req.json()
12
+ * return ctx.ai.stream({ messages }, {
13
+ * signal: req.signal,
14
+ * traceId: req.headers.get('x-trace-id') ?? undefined, // 追踪关联(协议 §7)
15
+ * })
16
+ * })
17
+ *
18
+ * // worker / 非请求场景:同一个实例直接调用
19
+ * q.worker('llm.batch', async (job) => {
20
+ * await a.chat({ messages: job.data.messages })
21
+ * })
22
+ * ```
23
+ *
24
+ * 配置优先级:显式参数 > env > 默认值。
25
+ * apiKey: DEEPSEEK_API_KEY
26
+ * baseUrl: DEEPSEEK_BASE_URL → 'https://api.deepseek.com/v1'
27
+ * defaultModel: DEEPSEEK_MODEL → 'deepseek-v4-flash'
28
+ */
29
+ import type { Context, Middleware } from '../types.ts';
30
+ import { type AiClient, type AiClientOptions } from './client.ts';
31
+ import { type AgentConfig, type AgentRunner } from './agent.ts';
32
+ export interface AiOptions extends Partial<AiClientOptions> {
33
+ }
34
+ export interface AiInjected {
35
+ ai: AiClient;
36
+ }
37
+ /** 模块 = 中间件 + 客户端(queue 式混合:app.use(a) + worker 直接 a.chat()) */
38
+ export interface AiClientModule extends Middleware<Context, Context & AiInjected>, AiClient {
39
+ /** agent 引擎(工具循环 + HITL 审批) */
40
+ agent: (config: AgentConfig) => AgentRunner;
41
+ close: () => Promise<void>;
42
+ }
43
+ declare module '../types.ts' {
44
+ interface Context {
45
+ /** 注入模块本身(含 agent / approve),worker 场景直接 a.chat() */
46
+ ai?: AiClientModule;
47
+ }
48
+ }
49
+ export declare function ai(options?: AiOptions): AiClientModule;
50
+ export type { WfStreamEvent, WfMessageStart, WfToken, WfUsage, WfDone, WfError, WfErrorCode, WfToolCall, WfToolResult, WfToolProgress, WfStep, WfApprovalRequest, WfApprovalResponse, WfApprovalDecision, ChatMessage, ChatParams, MessageRole, ToolCall, ToolDefinition, } from './types.ts';
51
+ export type { WfEmitter } from './sse.ts';
52
+ export { AiError } from './client.ts';
53
+ export type { AiClient, ChatResponse } from './client.ts';
54
+ export type { AgentConfig, AgentTool, AgentRunner, ToolContext } from './agent.ts';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * weifuwu AI — SSE 编码器
3
+ *
4
+ * 把 `wf:` 事件流编码成 text/event-stream Response(协议 §1.1)。
5
+ * 与 docs/ai-contract.md 对应。
6
+ *
7
+ * - 错误即值:run 内部抛错 → 编码为 wf:error 事件,而非断流
8
+ * - abort:客户端断开(cancel)→ onAbort 回调(用于取消 provider 请求)
9
+ */
10
+ export type WfEmitter = (name: string, data: unknown) => void;
11
+ export interface SseResponseOptions {
12
+ /** 客户端断开回调(取消上游请求) */
13
+ onAbort?: () => void;
14
+ }
15
+ /**
16
+ * 构造 SSE Response。run 收到 emit,负责输出完整事件序列。
17
+ *
18
+ * ```ts
19
+ * return sseResponse(async (emit) => {
20
+ * emit('wf:message_start', { id })
21
+ * emit('wf:token', { text: '你好' })
22
+ * emit('wf:done', { content })
23
+ * })
24
+ * ```
25
+ */
26
+ export declare function sseResponse(run: (emit: WfEmitter) => Promise<void> | void, options?: SseResponseOptions): Response;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * weifuwu AI 协议共享类型 —— 与 docs/ai-contract.md 规范一一对应
3
+ *
4
+ * 纯类型,零运行时成本。两端同源:
5
+ * - 后端:从 weifuwu 主包导入(src/index.ts re-export)
6
+ * - 前端:从 weifuwu/client 导入(src/client/index.ts re-export)
7
+ *
8
+ * 修改本文件 = 修改协议,需同步更新 docs/ai-contract.md。
9
+ */
10
+ export interface WfMessageStart {
11
+ id: string;
12
+ }
13
+ export interface WfToken {
14
+ /** 增量文本,前端直接 append */
15
+ text: string;
16
+ }
17
+ export interface WfUsage {
18
+ prompt_tokens: number;
19
+ completion_tokens: number;
20
+ total_tokens?: number;
21
+ }
22
+ export interface WfDone {
23
+ content: string;
24
+ usage?: WfUsage;
25
+ }
26
+ export type WfErrorCode = 'auth_failed' | 'rate_limited' | 'context_length' | 'timeout' | 'provider_error' | 'invalid_request' | 'unsupported' | 'aborted';
27
+ export interface WfError {
28
+ code: WfErrorCode;
29
+ message: string;
30
+ }
31
+ export interface WfToolCall {
32
+ /** 工具调用 id(provider 给 / 后端生成),聚合完成后才发 */
33
+ id: string;
34
+ /** 工具名(app 定义的业务语义,协议不解释) */
35
+ name: string;
36
+ /** 完整参数 */
37
+ args: Record<string, unknown>;
38
+ }
39
+ export interface WfToolResult {
40
+ id: string;
41
+ ok: boolean;
42
+ output?: unknown;
43
+ /** ok:false 时:rejected(人工拒绝)/ timeout(审批超时)/ tool_error / app 自定义 */
44
+ error?: {
45
+ code: string;
46
+ message: string;
47
+ };
48
+ }
49
+ export interface WfToolProgress {
50
+ toolCallId: string;
51
+ step: number;
52
+ total: number;
53
+ message?: string;
54
+ status: 'running' | 'error' | 'done';
55
+ }
56
+ export interface WfStep {
57
+ type: 'llm' | 'tool';
58
+ content?: string;
59
+ toolCallId?: string;
60
+ name?: string;
61
+ }
62
+ export interface WfApprovalRequest {
63
+ id: string;
64
+ toolCallId: string;
65
+ name: string;
66
+ args: Record<string, unknown>;
67
+ reason?: string;
68
+ /** 审批超时;到期按 rejected 处理(error.code: 'timeout') */
69
+ expiresAt?: number;
70
+ }
71
+ export type WfApprovalDecision = 'approved' | 'rejected' | 'modified';
72
+ /** 上行 POST 载荷(非 SSE 事件) */
73
+ export interface WfApprovalResponse {
74
+ id: string;
75
+ decision: WfApprovalDecision;
76
+ /** 仅 modified:按修改后的参数执行 */
77
+ modifiedArgs?: Record<string, unknown>;
78
+ /** 进 agent 上下文 */
79
+ note?: string;
80
+ }
81
+ /** 所有框架事件联合类型(前端 switch 收窄用) */
82
+ export type WfStreamEvent = {
83
+ name: 'wf:message_start';
84
+ data: WfMessageStart;
85
+ } | {
86
+ name: 'wf:token';
87
+ data: WfToken;
88
+ } | {
89
+ name: 'wf:usage';
90
+ data: WfUsage;
91
+ } | {
92
+ name: 'wf:done';
93
+ data: WfDone;
94
+ } | {
95
+ name: 'wf:error';
96
+ data: WfError;
97
+ } | {
98
+ name: 'wf:tool_call';
99
+ data: WfToolCall;
100
+ } | {
101
+ name: 'wf:tool_result';
102
+ data: WfToolResult;
103
+ } | {
104
+ name: 'wf:tool_progress';
105
+ data: WfToolProgress;
106
+ } | {
107
+ name: 'wf:step';
108
+ data: WfStep;
109
+ } | {
110
+ name: 'wf:approval_request';
111
+ data: WfApprovalRequest;
112
+ };
113
+ export type MessageRole = 'system' | 'user' | 'assistant' | 'tool';
114
+ export interface ChatMessage {
115
+ role: MessageRole;
116
+ content: string;
117
+ /** DeepSeek thinking mode:前一轮的 reasoning_content 必须回传 */
118
+ reasoning_content?: string;
119
+ tool_call_id?: string;
120
+ tool_calls?: ToolCall[];
121
+ name?: string;
122
+ }
123
+ export interface ToolCall {
124
+ id: string;
125
+ type: 'function';
126
+ function: {
127
+ name: string;
128
+ arguments: string;
129
+ };
130
+ }
131
+ export interface ToolDefinition {
132
+ type: 'function';
133
+ function: {
134
+ name: string;
135
+ description: string;
136
+ parameters: Record<string, unknown>;
137
+ };
138
+ }
139
+ export interface ChatParams {
140
+ model?: string;
141
+ messages: ChatMessage[];
142
+ temperature?: number;
143
+ max_tokens?: number;
144
+ stream?: boolean;
145
+ tools?: ToolDefinition[];
146
+ tool_choice?: 'auto' | 'none' | 'required';
147
+ stop?: string[];
148
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * weifuwu/client AI 解码器 — 消费 wf: 协议(docs/ai-contract.md)
3
+ *
4
+ * 协议的前端参考实现:POST → 解析 SSE → 按事件名分发回调。
5
+ *
6
+ * - 零依赖:fetch + ReadableStream + 自研 SSE 解析
7
+ * - 未知事件透传不抛错(协议 §6):x:* 走 onEvent,未订阅的跳过
8
+ * - 事件录制(副产品):record 开启时记录完整事件序列,可导出为测试 fixture
9
+ * - trace 桥(协议 §7):默认自动生成 X-Trace-Id 并随请求发送,
10
+ * 后端以之作为 wf:message_start.id → 整个 agent run 一次搜完
11
+ * - abort:handle.abort() 或外部 signal → 取消请求 → 后端断开 → provider 取消
12
+ */
13
+ import type { WfApprovalRequest, WfDone, WfError, WfStreamEvent, WfToolCall, WfToolProgress, WfToolResult, WfUsage } from '../ai/types.ts';
14
+ export interface AiStreamCallbacks {
15
+ /** wf:token — 增量文本,直接 append */
16
+ onToken?: (text: string) => void;
17
+ /** wf:tool_call — 完整工具调用(后端已聚合) */
18
+ onToolCall?: (call: WfToolCall) => void;
19
+ /** wf:tool_result — ok:false 不代表会话结束 */
20
+ onToolResult?: (result: WfToolResult) => void;
21
+ /** wf:tool_progress — 长任务进度 */
22
+ onToolProgress?: (p: WfToolProgress) => void;
23
+ /** wf:step — 步骤可视化(agent 扩展) */
24
+ onStep?: (s: {
25
+ type: 'llm' | 'tool';
26
+ content?: string;
27
+ toolCallId?: string;
28
+ name?: string;
29
+ }) => void;
30
+ /** wf:approval_request — 渲染审批卡片 */
31
+ onApproval?: (req: WfApprovalRequest) => void;
32
+ /** wf:usage — token 计数 */
33
+ onUsage?: (u: WfUsage) => void;
34
+ /** wf:done — 收尾 */
35
+ onDone?: (d: WfDone) => void;
36
+ /** wf:error — 错误即值,结构化降级 */
37
+ onError?: (e: WfError) => void;
38
+ /** x:* 自定义事件透传兜底(协议 §6:框架不解释) */
39
+ onEvent?: (name: string, data: unknown) => void;
40
+ }
41
+ export interface AiStreamOptions extends AiStreamCallbacks {
42
+ signal?: AbortSignal;
43
+ headers?: Record<string, string>;
44
+ /** 显式指定 traceId;默认自动生成(协议 §7) */
45
+ traceId?: string;
46
+ /** 录制事件序列(调试/导出 fixture),默认开启,环形上限 1000 */
47
+ record?: boolean;
48
+ }
49
+ export interface AiStreamHandle {
50
+ /** 取消请求(用户停止/组件卸载/导航跳走时调用) */
51
+ abort: () => void;
52
+ /** 流结束(正常 done / error / abort 都 resolve) */
53
+ done: Promise<void>;
54
+ /** 本次请求使用的 traceId(自动生成的也能拿到) */
55
+ traceId: string;
56
+ /** 录制的事件序列(record: true 时) */
57
+ events: WfStreamEvent[];
58
+ }
59
+ export declare function aiStream(url: string, body: unknown, options?: AiStreamOptions): AiStreamHandle;
@@ -31,6 +31,9 @@ export { ApiError } from './middleware/api.ts';
31
31
  export type { AuthClient, AuthOptions, AuthInjected } from './middleware/auth.ts';
32
32
  export { extendCtx } from './types.ts';
33
33
  export type { WfuiContext, AppMiddleware, RouteDef } from './types.ts';
34
+ export { aiStream } from './ai.ts';
35
+ export type { AiStreamCallbacks, AiStreamOptions, AiStreamHandle } from './ai.ts';
36
+ export type { WfStreamEvent, WfMessageStart, WfToken, WfUsage, WfDone, WfError, WfErrorCode, WfToolCall, WfToolResult, WfToolProgress, WfStep, WfApprovalRequest, WfApprovalResponse, WfApprovalDecision, ChatMessage, ChatParams, MessageRole, ToolCall, ToolDefinition, } from '../ai/types.ts';
34
37
  export { ErrorBoundary } from './error-boundary.ts';
35
38
  export type { ErrorBoundaryProps } from './error-boundary.ts';
36
39
  export { i18n } from './i18n.ts';