agent-chat-sdk-core 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ 本项目的所有显著变更都记录在此文件中。
4
+
5
+ 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
6
+ 版本管理遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
7
+
8
+ ## [0.1.0] - 2026-09-09
9
+
10
+ ### Added
11
+
12
+ - `AgentChatClient` 流式对话客户端:回调(`chatStream`)与异步迭代器(`streamEvents`)双消费模式,支持 `AbortSignal`、`extraBody` / `extraHeaders`、自定义 `fetch` / 请求体 / 请求头。
13
+ - SSE 解析器:`parseSSELine` / `readSSEStream` / `isStreamEndSignal`,兼容普通分片、工作流直连事件(`workflow_*` / `node_*` / `text_chunk`)与 `[DONE]` 结束信号。
14
+ - ReAct 事件归一化:`createNormalizeContext` / `normalizeRawChunk` / `finalizeNormalizeContext`,输出标准化事件流(多轮思考、工具/知识库调用、最终回答)。
15
+ - 可选会话状态机:`createSessionState` / `applyEventToSession`。
16
+ - 知识库引用标注解析:`parseKnowledgeSupLabels` / `stripKnowledgeSupLabels`。
17
+ - 三种数据推送模式:`normalized` / `raw` / `both`。
18
+ - 双格式构建产物(ESM + CJS)与完整 TypeScript 类型声明。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 paco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,492 @@
1
+ # agent-chat-sdk-core
2
+
3
+ 智能体(ReAct)流式对话 SDK。负责调用 SSE 接口、解析服务端分片,并将 **思考 / 工具调用 / 最终回答** 等过程推送为标准化事件,**不包含任何 UI 组件**,由使用方自行决定如何渲染。
4
+
5
+ ---
6
+
7
+ ## 安装与引用
8
+
9
+ ### 在本仓库(Monorepo)内引用
10
+
11
+ `tsconfig.json` 已配置路径别名(指向源码,便于本地联调):
12
+
13
+ ```json
14
+ {
15
+ "paths": {
16
+ "agent-chat-sdk-core": ["./packages/agent-chat-sdk/src/index.ts"],
17
+ "agent-chat-sdk-core/*": ["./packages/agent-chat-sdk/src/*"]
18
+ }
19
+ }
20
+ ```
21
+
22
+ ```typescript
23
+ import { AgentChatClient } from 'agent-chat-sdk-core';
24
+ ```
25
+
26
+ ### 作为独立包使用
27
+
28
+ 已发布至 npm,直接安装:
29
+
30
+ ```bash
31
+ npm install agent-chat-sdk-core
32
+ ```
33
+
34
+ ```typescript
35
+ import { AgentChatClient } from 'agent-chat-sdk-core';
36
+ ```
37
+
38
+ > 包内提供双格式产物:`dist/esm`(ESM)与 `dist/cjs`(CommonJS),通过 `exports` 映射自动适配;类型声明随包发布,无需额外安装 `@types`。要求 Node.js >= 18。
39
+
40
+ ---
41
+
42
+ ## 快速开始
43
+
44
+ ```typescript
45
+ import { AgentChatClient } from 'agent-chat-sdk-core';
46
+
47
+ const client = new AgentChatClient({
48
+ url: 'https://your-host/appforge/openapi/v1/InvokeApp/app-xxxxxxxx',
49
+ appId: 'app-xxxxxxxx',
50
+ appKey: 'your-app-key',
51
+ });
52
+
53
+ await client.chatStream(
54
+ { query: '帮我查一下水费' },
55
+ {
56
+ onEvent: (event) => {
57
+ switch (event.type) {
58
+ case 'thinking_start':
59
+ console.log('开始思考,第', event.round, '轮');
60
+ break;
61
+ case 'thinking_end':
62
+ console.log('思考完成,用时', event.runningTime, '秒');
63
+ break;
64
+ case 'tool_call_start':
65
+ console.log('调用中:', event.title);
66
+ break;
67
+ case 'tool_call_end':
68
+ console.log('调用完成:', event.title, event.runningTime);
69
+ break;
70
+ case 'answer_delta':
71
+ // event.content 为当前累积的完整回复
72
+ renderAnswer(event.content);
73
+ break;
74
+ case 'error':
75
+ showError(event.message, event.detail);
76
+ break;
77
+ case 'stream_end':
78
+ stopLoading();
79
+ break;
80
+ }
81
+ },
82
+ onError: (err) => console.error(err),
83
+ onComplete: () => console.log('流结束'),
84
+ },
85
+ );
86
+ ```
87
+
88
+ ---
89
+
90
+ ## 配置说明
91
+
92
+ ### `AgentChatClientConfig`
93
+
94
+ | 字段 | 类型 | 必填 | 说明 |
95
+ |------|------|------|------|
96
+ | `url` | `string` | 是 | 流式对话**完整请求地址**(SDK 不做路径拼接) |
97
+ | `appId` | `string` | 是 | 智能体应用 ID(默认写入请求体 `id` 字段) |
98
+ | `appKey` | `string` | 否 | 鉴权 Key,自动设置 `Authorization: Bearer {appKey}` |
99
+ | `fetch` | `typeof fetch` | 否 | 自定义 fetch(测试 / Node 环境) |
100
+ | `buildRequestBody` | `function` | 否 | 自定义请求体 |
101
+ | `buildHeaders` | `function` | 否 | 自定义请求头 |
102
+
103
+ ### 关于 `url`
104
+
105
+ SDK **只接收完整链接**。代理、网关、路径规则由各项目自行处理,例如:
106
+
107
+ ```typescript
108
+ // 直连后端
109
+ new AgentChatClient({
110
+ url: 'http://58.19.19.197:9080/appforge/openapi/v1/InvokeApp/app-xxx',
111
+ appId: 'app-xxx',
112
+ appKey: 'your-key',
113
+ });
114
+
115
+ // 本仓库 Demo:Next.js rewrite 到 `/api/chat/:appId`
116
+ new AgentChatClient({
117
+ url: '/api/chat/app-xxx',
118
+ appId: 'app-xxx',
119
+ appKey: 'your-key',
120
+ });
121
+ ```
122
+
123
+ 见 `src/utils/chat-api.ts` 中的 `CHAT_API_URL`。
124
+
125
+ ### 默认请求格式
126
+
127
+ - **Method**: `POST`
128
+ - **Headers**: `Content-Type: application/json`、`Accept: text/event-stream`、`Authorization: Bearer {appKey}`
129
+ - **Body**:
130
+
131
+ ```json
132
+ {
133
+ "id": "app-xxx",
134
+ "query": "用户输入的问题"
135
+ }
136
+ ```
137
+
138
+ 可通过 `extraBody` / `extraHeaders` 或 `buildRequestBody` / `buildHeaders` 扩展。
139
+
140
+ ---
141
+
142
+ ## 数据推送模式
143
+
144
+ 通过 `chatStream` 的 `dataMode` 参数选择:
145
+
146
+ | 模式 | 说明 | 适用场景 |
147
+ |------|------|----------|
148
+ | `normalized`(默认) | 推送标准化 `AgentStreamEvent` | 推荐,直接驱动 UI |
149
+ | `raw` | 仅推送原始 `RawSSEChunk` | 完全自定义解析逻辑 |
150
+ | `both` | 同时推送上述两种 | 调试、渐进迁移 |
151
+
152
+ ```typescript
153
+ await client.chatStream(
154
+ { query: '你好', dataMode: 'raw' },
155
+ {
156
+ onRawChunk: (chunk) => {
157
+ console.log(chunk.type, chunk.content, chunk.raw);
158
+ },
159
+ },
160
+ );
161
+ ```
162
+
163
+ ---
164
+
165
+ ## ReAct 交互时序
166
+
167
+ SDK 将服务端 SSE 归一化为以下事件流,对应 ReAct 智能体的多轮 **思考 → 工具 → 再思考 → 回答** 过程:
168
+
169
+ ```
170
+ 用户发送
171
+ → stream_start
172
+ → [可多轮]
173
+ thinking_start
174
+ thinking_delta × N (思考中,建议 UI 不展示正文)
175
+ thinking_end (思考完成,展示耗时)
176
+ tool_call_start (工具/知识库调用中)
177
+ tool_call_end (调用完成)
178
+ → answer_delta × N (流式最终回复)
179
+ → stream_end
180
+ ```
181
+
182
+ ### 服务端 SSE `type` 与 SDK 事件对应
183
+
184
+ | 服务端 `type` | SDK 事件 |
185
+ |---------------|----------|
186
+ | `thinking` | `thinking_start` / `thinking_delta` / `thinking_end` |
187
+ | `tool` / `plugin` | `tool_call_start` / `tool_call_end` |
188
+ | `knowledge` | `knowledge_call_start` / `knowledge_call_end` |
189
+ | `answer` / `text` / `message` | `answer_delta` |
190
+ | `error` | `error` |
191
+ | `done` / `[DONE]` | `stream_end` |
192
+
193
+ 常量 `REACT_SSE_TYPE_MAP` 提供中文说明,便于文档与调试。
194
+
195
+ ---
196
+
197
+ ## 标准化事件类型
198
+
199
+ ### 生命周期
200
+
201
+ | 事件 | 字段 | 说明 |
202
+ |------|------|------|
203
+ | `stream_start` | `query` | 流开始 |
204
+ | `stream_end` | — | 流正常结束 |
205
+
206
+ ### 思考阶段
207
+
208
+ | 事件 | 主要字段 | 说明 |
209
+ |------|----------|------|
210
+ | `thinking_start` | `round` | 新一轮思考开始 |
211
+ | `thinking_delta` | `delta`, `content`, `round` | 思考内容增量(`content` 为累积全文) |
212
+ | `thinking_end` | `content`, `runningTime`, `round` | 思考结束 |
213
+
214
+ > **数据与 UI 分离**:SDK **始终推送**完整 `content`(`thinking_delta` / `thinking_end` / `tool_call_end`)。是否渲染正文由调用方决定,例如仅展示步骤与耗时、调试时展开全文等。
215
+
216
+ ```typescript
217
+ // 调用方:只用步骤,不渲染思考正文
218
+ onEvent: (event) => {
219
+ if (event.type === 'thinking_delta') {
220
+ setStatus('thinking'); // 忽略 event.content
221
+ }
222
+ if (event.type === 'thinking_end') {
223
+ addStep({ label: '深度思考', time: event.runningTime }); // 可选存 event.content 但不展示
224
+ }
225
+ };
226
+
227
+ // 调用方:需要调试/审计时渲染正文
228
+ if (event.type === 'thinking_delta') {
229
+ debugPanel.append(event.delta);
230
+ }
231
+ ```
232
+
233
+ ### 工具调用
234
+
235
+ | 事件 | 主要字段 | 说明 |
236
+ |------|----------|------|
237
+ | `tool_call_start` | `toolId`, `title` | 工具开始调用 |
238
+ | `tool_call_end` | `toolId`, `title`, `content`, `runningTime` | 工具调用完成 |
239
+ | `knowledge_call_start` | 同上 | 知识库调用开始 |
240
+ | `knowledge_call_end` | 同上 | 知识库调用完成 |
241
+
242
+ ### 回答与错误
243
+
244
+ | 事件 | 主要字段 | 说明 |
245
+ |------|----------|------|
246
+ | `answer_delta` | `delta`, `content` | 最终回复增量(`content` 为累积全文) |
247
+ | `error` | `message`, `detail?` | 执行错误 |
248
+ | `raw` | `chunk` | 未识别的原始分片 |
249
+
250
+ ---
251
+
252
+ ## API 参考
253
+
254
+ ### `AgentChatClient`
255
+
256
+ #### `constructor(config: AgentChatClientConfig)`
257
+
258
+ 创建客户端实例。
259
+
260
+ #### `chatStream(options, handlers?): Promise<void>`
261
+
262
+ 发起一次流式对话。
263
+
264
+ **`options`(`AgentChatStreamOptions`)**
265
+
266
+ | 字段 | 类型 | 说明 |
267
+ |------|------|------|
268
+ | `query` | `string` | 用户问题 |
269
+ | `dataMode` | `'normalized' \| 'raw' \| 'both'` | 数据模式,默认 `normalized` |
270
+ | `signal` | `AbortSignal` | 外部取消信号 |
271
+ | `extraBody` | `object` | 合并到请求体 |
272
+ | `extraHeaders` | `object` | 合并到请求头 |
273
+
274
+ **`handlers`(`AgentChatStreamHandlers`)**
275
+
276
+ | 回调 | 说明 |
277
+ |------|------|
278
+ | `onEvent` | 收到标准化事件 |
279
+ | `onRawChunk` | 收到原始 SSE 分片(`dataMode` 为 `raw` 或 `both`) |
280
+ | `onError` | 请求或解析异常 |
281
+ | `onComplete` | 流正常结束 |
282
+
283
+ #### `streamEvents(options): AsyncGenerator<AgentStreamEvent>`
284
+
285
+ 以异步迭代器消费事件,适合 `for await...of`:
286
+
287
+ ```typescript
288
+ for await (const event of client.streamEvents({ query: '你好' })) {
289
+ if (event.type === 'answer_delta') {
290
+ updateUI(event.content);
291
+ }
292
+ }
293
+ ```
294
+
295
+ #### `abort(): void`
296
+
297
+ 中止当前进行中的流式请求。
298
+
299
+ #### `updateConfig(partial): void` / `getConfig()` / `getUrl()`
300
+
301
+ 更新配置、读取配置、获取当前请求 URL。
302
+
303
+ ---
304
+
305
+ ## 进阶用法
306
+
307
+ ### 1. 会话状态聚合(可选)
308
+
309
+ 若不想逐事件手写 UI 逻辑,可使用内置状态机:
310
+
311
+ ```typescript
312
+ import {
313
+ AgentChatClient,
314
+ createSessionState,
315
+ applyEventToSession,
316
+ } from 'agent-chat-sdk-core';
317
+
318
+ let session = createSessionState('用户问题');
319
+
320
+ await client.chatStream({ query: '用户问题' }, {
321
+ onEvent: (event) => {
322
+ session = applyEventToSession(session, event);
323
+ // session.status / session.processes / session.answerContent
324
+ bindToYourUI(session);
325
+ },
326
+ });
327
+ ```
328
+
329
+ ### 2. 仅使用解析器(无 HTTP)
330
+
331
+ 自行拿到 `ReadableStream` 或 SSE 文本行时:
332
+
333
+ ```typescript
334
+ import {
335
+ parseSSELine,
336
+ readSSEStream,
337
+ createNormalizeContext,
338
+ normalizeRawChunk,
339
+ } from 'agent-chat-sdk-core';
340
+
341
+ const ctx = createNormalizeContext();
342
+
343
+ // 按行解析
344
+ const chunk = parseSSELine('data: {"type":"thinking","content":"..."}');
345
+ if (chunk && chunk !== 'end') {
346
+ const events = normalizeRawChunk(chunk, ctx);
347
+ events.forEach(handleEvent);
348
+ }
349
+
350
+ // 或从 Response.body 读取
351
+ await readSSEStream(response.body!, (raw) => {
352
+ normalizeRawChunk(raw, ctx).forEach(handleEvent);
353
+ });
354
+ ```
355
+
356
+ ### 3. 中止请求
357
+
358
+ ```typescript
359
+ const controller = new AbortController();
360
+
361
+ client.chatStream(
362
+ { query: '...', signal: controller.signal },
363
+ { onEvent: handleEvent },
364
+ );
365
+
366
+ // 用户点击停止
367
+ controller.abort();
368
+ // 或
369
+ client.abort();
370
+ ```
371
+
372
+ ### 4. React 集成示例
373
+
374
+ ```tsx
375
+ const [answer, setAnswer] = useState('');
376
+ const [steps, setSteps] = useState<ProcessStep[]>([]);
377
+ const [loading, setLoading] = useState(false);
378
+
379
+ const send = async (query: string) => {
380
+ setLoading(true);
381
+ setAnswer('');
382
+ setSteps([]);
383
+
384
+ const client = new AgentChatClient({ url: chatApiUrl, appId, appKey });
385
+
386
+ try {
387
+ await client.chatStream({ query }, {
388
+ onEvent: (event) => {
389
+ if (event.type === 'answer_delta') setAnswer(event.content);
390
+ if (event.type === 'thinking_end') {
391
+ setSteps((s) => [...s, { type: 'thinking', time: event.runningTime }]);
392
+ }
393
+ if (event.type === 'tool_call_end') {
394
+ setSteps((s) => [...s, { type: 'tool', title: event.title, time: event.runningTime }]);
395
+ }
396
+ if (event.type === 'stream_end') setLoading(false);
397
+ if (event.type === 'error') {
398
+ setAnswer(event.message);
399
+ setLoading(false);
400
+ }
401
+ },
402
+ });
403
+ } catch {
404
+ setLoading(false);
405
+ }
406
+ };
407
+ ```
408
+
409
+ 本仓库 Demo 的适配层见:`src/lib/stream-to-message.ts`。
410
+
411
+ ---
412
+
413
+ ## 推荐 UI 映射
414
+
415
+ | 阶段 | SDK 推送数据 | 常见 UI(可选) |
416
+ |------|-------------|----------------|
417
+ | 等待首包 | — | Loading |
418
+ | `thinking_delta` | `delta`, `content`(累积全文) | 仅「思考中」,**可不渲染 content** |
419
+ | `thinking_end` | `content`, `runningTime` | 「深度思考成功,用时 Xs」 |
420
+ | `tool_call_end` | `content`, `runningTime` | 「XX 成功,用时 Xs」,**可不渲染 content** |
421
+ | `answer_delta` | `content`(累积全文) | 流式展示最终回复 |
422
+ | `error` | `message`, `detail` | 错误摘要 + 可展开详情 |
423
+
424
+ 本仓库 Demo 的 `ChatMessage` 默认 `showProcessContent={false}`,数据仍写入 `message.processes[].content` 供其他场景使用。
425
+
426
+ ---
427
+
428
+ ## 导出清单
429
+
430
+ ```typescript
431
+ // 客户端
432
+ export { AgentChatClient } from './client';
433
+
434
+ // 解析与归一化
435
+ export { parseSSELine, readSSEStream, isStreamEndSignal } from './parser';
436
+ export { normalizeRawChunk, createNormalizeContext, finalizeNormalizeContext } from './normalizer';
437
+
438
+ // 会话状态(可选)
439
+ export { createSessionState, applyEventToSession } from './session';
440
+
441
+ // 文档常量
442
+ export { REACT_SSE_TYPE_MAP } from './react-flow';
443
+
444
+ // 类型
445
+ export type { AgentChatClientConfig, AgentStreamEvent, RawSSEChunk, ... } from './types';
446
+ ```
447
+
448
+ ---
449
+
450
+ ## 常见问题
451
+
452
+ ### Q: 为什么收不到 `answer_delta`?
453
+
454
+ 检查服务端是否返回 `type: "answer"` 及 `content` 字段;也可先用 `dataMode: 'raw'` 打印原始分片排查。
455
+
456
+ ### Q: 思考内容要不要展示?
457
+
458
+ SDK 会在 `thinking_delta` 中累积全文,但是否展示由使用方决定。ReAct 场景建议思考中仅显示状态,结束后显示耗时。
459
+
460
+ ### Q: 工具调用为什么只有一条事件?
461
+
462
+ 若服务端一次返回完整工具结果(含 `running_time`),SDK 会连续推送 `tool_call_start` 与 `tool_call_end`,UI 可分别展示「调用中」和「调用完成」。
463
+
464
+ ### Q: 流结束后无法再次发送?
465
+
466
+ 确保在 `stream_end` 或 `onComplete` 中重置 loading 状态;必要时调用 `client.abort()` 后再发下一条。
467
+
468
+ ---
469
+
470
+ ## 开发与发布
471
+
472
+ ```bash
473
+ npm install # 安装开发依赖(typescript / vitest)
474
+ npm run typecheck # 类型检查
475
+ npm test # 运行单元测试
476
+ npm run build # 构建双格式产物到 dist/(ESM + CJS + d.ts)
477
+ npm pack --dry-run # 发布前干跑,检查 tarball 内容
478
+ npm publish # 发布(prepublishOnly 会自动执行检查与构建)
479
+ ```
480
+
481
+ ## 相关文件
482
+
483
+ | 路径 | 说明 |
484
+ |------|------|
485
+ | `src/client.ts` | HTTP 请求与流调度 |
486
+ | `src/parser.ts` | SSE 行解析 |
487
+ | `src/normalizer.ts` | ReAct 事件归一化 |
488
+ | `src/session.ts` | 可选会话状态机 |
489
+ | `src/knowledge-label.ts` | 知识库引用标注解析 |
490
+ | `src/react-flow.ts` | 交互时序说明 |
491
+ | `src/types.ts` | 类型定义 |
492
+ | `tests/` | 单元测试(解析 / 归一化 / 会话 / 标注 / 客户端) |