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 +18 -0
- package/LICENSE +21 -0
- package/README.md +492 -0
- package/dist/cjs/client.js +199 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/knowledge-label.js +71 -0
- package/dist/cjs/normalizer.js +257 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/parser.js +294 -0
- package/dist/cjs/react-flow.js +42 -0
- package/dist/cjs/session.js +100 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/client.d.ts +45 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +195 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/knowledge-label.d.ts +49 -0
- package/dist/esm/knowledge-label.d.ts.map +1 -0
- package/dist/esm/knowledge-label.js +67 -0
- package/dist/esm/normalizer.d.ts +22 -0
- package/dist/esm/normalizer.d.ts.map +1 -0
- package/dist/esm/normalizer.js +252 -0
- package/dist/esm/parser.d.ts +12 -0
- package/dist/esm/parser.d.ts.map +1 -0
- package/dist/esm/parser.js +289 -0
- package/dist/esm/react-flow.d.ts +40 -0
- package/dist/esm/react-flow.d.ts.map +1 -0
- package/dist/esm/react-flow.js +39 -0
- package/dist/esm/session.d.ts +7 -0
- package/dist/esm/session.d.ts.map +1 -0
- package/dist/esm/session.js +96 -0
- package/dist/esm/types.d.ts +151 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isStreamEndSignal = isStreamEndSignal;
|
|
4
|
+
exports.parseSSELine = parseSSELine;
|
|
5
|
+
exports.readSSEStream = readSSEStream;
|
|
6
|
+
const STREAM_END_TYPES = new Set(['done', 'end', 'finish', 'completed', 'stop']);
|
|
7
|
+
const TOOL_LIKE_TYPES = new Set([
|
|
8
|
+
'tool',
|
|
9
|
+
'knowledge',
|
|
10
|
+
'plugin',
|
|
11
|
+
'mcp',
|
|
12
|
+
'memory_var_update',
|
|
13
|
+
'workflow',
|
|
14
|
+
]);
|
|
15
|
+
function extractWorkflowResponse(parsed) {
|
|
16
|
+
const response = parsed.response;
|
|
17
|
+
if (response === undefined || response === null)
|
|
18
|
+
return '';
|
|
19
|
+
if (typeof response === 'object' && response !== null) {
|
|
20
|
+
const output = response.output;
|
|
21
|
+
if (output !== undefined && output !== null)
|
|
22
|
+
return String(output);
|
|
23
|
+
return JSON.stringify(response);
|
|
24
|
+
}
|
|
25
|
+
const text = String(response);
|
|
26
|
+
try {
|
|
27
|
+
const json = JSON.parse(text);
|
|
28
|
+
if (json.output !== undefined && json.output !== null)
|
|
29
|
+
return String(json.output);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// response 为纯文本
|
|
33
|
+
}
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
function asRecord(value) {
|
|
37
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function stringifyOutputValue(value) {
|
|
43
|
+
if (value === undefined || value === null)
|
|
44
|
+
return '';
|
|
45
|
+
if (typeof value === 'string')
|
|
46
|
+
return value;
|
|
47
|
+
if (typeof value === 'object')
|
|
48
|
+
return JSON.stringify(value, null, 2);
|
|
49
|
+
return String(value);
|
|
50
|
+
}
|
|
51
|
+
/** 从 outputs 对象中提取首选文本字段 */
|
|
52
|
+
function extractOutputsContent(outputs) {
|
|
53
|
+
const preferredKeys = ['output', 'text', 'result', 'answer', 'content'];
|
|
54
|
+
for (const key of preferredKeys) {
|
|
55
|
+
const text = stringifyOutputValue(outputs[key]).trim();
|
|
56
|
+
if (text)
|
|
57
|
+
return text;
|
|
58
|
+
}
|
|
59
|
+
const entries = Object.entries(outputs).filter(([, v]) => stringifyOutputValue(v).trim());
|
|
60
|
+
if (entries.length === 0)
|
|
61
|
+
return '';
|
|
62
|
+
return entries.map(([k, v]) => `${k}:\n${stringifyOutputValue(v)}`).join('\n\n');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 工作流直连 Invoke 的 SSE 事件(event: workflow_* / node_* / text_chunk)
|
|
66
|
+
*/
|
|
67
|
+
function extractWorkflowInvokeContent(parsed, eventType) {
|
|
68
|
+
const data = asRecord(parsed.data);
|
|
69
|
+
// 累积型 answer(常见于 message / agent_message)
|
|
70
|
+
if ((eventType === 'message' || eventType === 'agent_message') &&
|
|
71
|
+
parsed.answer !== undefined &&
|
|
72
|
+
parsed.answer !== null) {
|
|
73
|
+
return String(parsed.answer);
|
|
74
|
+
}
|
|
75
|
+
// 流式增量:data.text / delta / chunk
|
|
76
|
+
if (data && eventType !== 'workflow_started' && eventType !== 'workflow_finished') {
|
|
77
|
+
const streamDelta = data.text ?? data.delta ?? data.chunk ?? data.answer;
|
|
78
|
+
if (streamDelta !== undefined && streamDelta !== null) {
|
|
79
|
+
const text = String(streamDelta);
|
|
80
|
+
if (text)
|
|
81
|
+
return text;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (eventType === 'text_chunk' || eventType === 'message' || eventType === 'agent_message') {
|
|
85
|
+
const text = data?.text ?? data?.answer ?? data?.content ?? parsed.text ?? parsed.answer;
|
|
86
|
+
if (text !== undefined && text !== null)
|
|
87
|
+
return String(text);
|
|
88
|
+
}
|
|
89
|
+
if (eventType === 'node_finished' || eventType === 'node_started') {
|
|
90
|
+
const text = data?.text ?? data?.title;
|
|
91
|
+
if (text)
|
|
92
|
+
return String(text);
|
|
93
|
+
}
|
|
94
|
+
if (data?.outputs && eventType !== 'workflow_started') {
|
|
95
|
+
const fromOutputs = extractOutputsContent(asRecord(data.outputs) ?? {});
|
|
96
|
+
if (fromOutputs)
|
|
97
|
+
return fromOutputs;
|
|
98
|
+
}
|
|
99
|
+
if (eventType === 'workflow_finished' && data) {
|
|
100
|
+
const outputs = asRecord(data.outputs);
|
|
101
|
+
if (outputs) {
|
|
102
|
+
const fromOutputs = extractOutputsContent(outputs);
|
|
103
|
+
if (fromOutputs)
|
|
104
|
+
return fromOutputs;
|
|
105
|
+
}
|
|
106
|
+
if (data.error)
|
|
107
|
+
return String(data.error);
|
|
108
|
+
}
|
|
109
|
+
if (eventType === 'workflow_failed' && data?.error) {
|
|
110
|
+
return String(data.error);
|
|
111
|
+
}
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
114
|
+
function isWorkflowInvokeEvent(type) {
|
|
115
|
+
return (type.startsWith('workflow_') ||
|
|
116
|
+
type.startsWith('node_') ||
|
|
117
|
+
type === 'text_chunk' ||
|
|
118
|
+
type === 'message' ||
|
|
119
|
+
type === 'agent_message' ||
|
|
120
|
+
type === 'agent_thought' ||
|
|
121
|
+
type === 'llm_chunk' ||
|
|
122
|
+
type === 'generation' ||
|
|
123
|
+
type.includes('_chunk') ||
|
|
124
|
+
type.includes('_delta'));
|
|
125
|
+
}
|
|
126
|
+
/** 预审工作流流式输出:event=answer 且 text 在 data.text */
|
|
127
|
+
function isWorkflowStreamAnswer(parsed, type) {
|
|
128
|
+
return type === 'answer' && typeof parsed.workflow_run_id === 'string';
|
|
129
|
+
}
|
|
130
|
+
function extractChunkContent(parsed, type) {
|
|
131
|
+
if (type === 'workflow') {
|
|
132
|
+
const fromResponse = extractWorkflowResponse(parsed);
|
|
133
|
+
if (fromResponse)
|
|
134
|
+
return fromResponse;
|
|
135
|
+
const direct = parsed.content ?? parsed.text ?? parsed.delta;
|
|
136
|
+
if (direct !== undefined && direct !== null && String(direct) !== '') {
|
|
137
|
+
return String(direct);
|
|
138
|
+
}
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
if (type === 'memory_var_update') {
|
|
142
|
+
const content = parsed.content ?? parsed.text ?? parsed.delta;
|
|
143
|
+
if (content !== undefined && content !== null && String(content) !== '') {
|
|
144
|
+
return String(content);
|
|
145
|
+
}
|
|
146
|
+
if (parsed.description !== undefined && parsed.description !== null) {
|
|
147
|
+
return String(parsed.description);
|
|
148
|
+
}
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
const direct = parsed.content ?? parsed.text ?? parsed.delta;
|
|
152
|
+
if (direct !== undefined && direct !== null && String(direct) !== '') {
|
|
153
|
+
return String(direct);
|
|
154
|
+
}
|
|
155
|
+
if (TOOL_LIKE_TYPES.has(type)) {
|
|
156
|
+
if (parsed.response !== undefined && parsed.response !== null) {
|
|
157
|
+
return String(parsed.response);
|
|
158
|
+
}
|
|
159
|
+
if (parsed.arg !== undefined && parsed.arg !== null) {
|
|
160
|
+
return String(parsed.arg);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return '';
|
|
164
|
+
}
|
|
165
|
+
function isStreamEndSignal(type, dataStr) {
|
|
166
|
+
return dataStr === '[DONE]' || STREAM_END_TYPES.has(type);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 解析单行 SSE 文本
|
|
170
|
+
* @returns 原始分片 | 'end' 流结束 | null 跳过
|
|
171
|
+
*/
|
|
172
|
+
function parseSSELine(line) {
|
|
173
|
+
const trimmed = line.trim();
|
|
174
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
175
|
+
return null;
|
|
176
|
+
if (trimmed.startsWith('event:')) {
|
|
177
|
+
const eventName = trimmed.slice(6).trim();
|
|
178
|
+
if (STREAM_END_TYPES.has(eventName))
|
|
179
|
+
return 'end';
|
|
180
|
+
if (eventName === 'error') {
|
|
181
|
+
return { type: 'error', content: '执行出错,请稍后重试' };
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
const dataStr = trimmed.startsWith('data: ') ? trimmed.slice(6).trim() : trimmed;
|
|
186
|
+
if (!dataStr)
|
|
187
|
+
return null;
|
|
188
|
+
if (isStreamEndSignal('', dataStr))
|
|
189
|
+
return 'end';
|
|
190
|
+
try {
|
|
191
|
+
const parsed = JSON.parse(dataStr);
|
|
192
|
+
const type = String(parsed.type ?? parsed.event ?? 'answer');
|
|
193
|
+
if (isStreamEndSignal(type, ''))
|
|
194
|
+
return 'end';
|
|
195
|
+
if (isWorkflowStreamAnswer(parsed, type)) {
|
|
196
|
+
const data = asRecord(parsed.data);
|
|
197
|
+
const text = data?.text;
|
|
198
|
+
if (text === undefined || text === null)
|
|
199
|
+
return null;
|
|
200
|
+
return {
|
|
201
|
+
type: 'workflow_answer',
|
|
202
|
+
content: String(text),
|
|
203
|
+
raw: parsed,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (isWorkflowInvokeEvent(type)) {
|
|
207
|
+
if (type === 'workflow_failed') {
|
|
208
|
+
const errMsg = extractWorkflowInvokeContent(parsed, type) ||
|
|
209
|
+
String(asRecord(parsed.data)?.error ?? '工作流执行失败');
|
|
210
|
+
return {
|
|
211
|
+
type: 'error',
|
|
212
|
+
content: errMsg,
|
|
213
|
+
error: errMsg,
|
|
214
|
+
raw: parsed,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const content = extractWorkflowInvokeContent(parsed, type);
|
|
218
|
+
if (type === 'workflow_started' || type === 'workflow_finished' || content) {
|
|
219
|
+
return { type, content, raw: parsed };
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (type === 'error') {
|
|
224
|
+
return {
|
|
225
|
+
type: 'error',
|
|
226
|
+
content: String(parsed.error ?? parsed.message ?? '执行出错,请稍后重试'),
|
|
227
|
+
error: parsed.error,
|
|
228
|
+
error_detail: (parsed.error_detail ?? parsed.errorDetail),
|
|
229
|
+
raw: parsed,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const chunk = {
|
|
233
|
+
type,
|
|
234
|
+
content: extractChunkContent(parsed, type),
|
|
235
|
+
role: parsed.role,
|
|
236
|
+
running_time: parsed.running_time,
|
|
237
|
+
raw: parsed,
|
|
238
|
+
};
|
|
239
|
+
if (chunk.content ||
|
|
240
|
+
chunk.type === 'thinking' ||
|
|
241
|
+
TOOL_LIKE_TYPES.has(chunk.type) ||
|
|
242
|
+
(chunk.type === 'workflow' && chunk.running_time)) {
|
|
243
|
+
return chunk;
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return dataStr ? { type: 'answer', content: dataStr } : null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* 从 ReadableStream 读取并解析 SSE,逐条回调
|
|
253
|
+
*/
|
|
254
|
+
async function readSSEStream(body, onChunk, signal) {
|
|
255
|
+
const reader = body.getReader();
|
|
256
|
+
const decoder = new TextDecoder('utf-8');
|
|
257
|
+
let buffer = '';
|
|
258
|
+
try {
|
|
259
|
+
streamLoop: while (true) {
|
|
260
|
+
if (signal?.aborted) {
|
|
261
|
+
await reader.cancel();
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
const { done, value } = await reader.read();
|
|
265
|
+
if (done)
|
|
266
|
+
break;
|
|
267
|
+
buffer += decoder.decode(value, { stream: true });
|
|
268
|
+
const lines = buffer.split('\n');
|
|
269
|
+
buffer = lines.pop() || '';
|
|
270
|
+
for (const line of lines) {
|
|
271
|
+
const parsed = parseSSELine(line);
|
|
272
|
+
if (parsed === 'end') {
|
|
273
|
+
try {
|
|
274
|
+
await reader.cancel();
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// 连接可能已由服务端关闭
|
|
278
|
+
}
|
|
279
|
+
break streamLoop;
|
|
280
|
+
}
|
|
281
|
+
if (parsed)
|
|
282
|
+
onChunk(parsed);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (buffer.trim()) {
|
|
286
|
+
const parsed = parseSSELine(buffer);
|
|
287
|
+
if (parsed && parsed !== 'end')
|
|
288
|
+
onChunk(parsed);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
reader.releaseLock();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ReAct 智能体流式交互时序说明
|
|
4
|
+
*
|
|
5
|
+
* ┌─────────────┐
|
|
6
|
+
* │ 用户点击发送 │
|
|
7
|
+
* └──────┬──────┘
|
|
8
|
+
* ▼
|
|
9
|
+
* ┌─────────────────────┐ SSE POST
|
|
10
|
+
* │ 展示回复气泡 + Loading │ ◄──────────── 接口
|
|
11
|
+
* └──────┬──────────────┘
|
|
12
|
+
* ▼ 首个 SSE 字段
|
|
13
|
+
* ┌──────────────────────────────────────────────────┐
|
|
14
|
+
* │ ReAct 循环(可多轮) │
|
|
15
|
+
* │ ① thinking_start │
|
|
16
|
+
* │ ② thinking_delta* → UI: 「深度思考中...」(不展示内容)│
|
|
17
|
+
* │ ③ thinking_end → UI: 「深度思考成功,用时 Xs」 │
|
|
18
|
+
* │ ④ tool_call_start → UI: 「工具/知识库调用中...」 │
|
|
19
|
+
* │ ⑤ tool_call_end → UI: 「XX成功,用时 Xs」 │
|
|
20
|
+
* │ └── 可能回到 ① 继续思考 │
|
|
21
|
+
* └──────────────────────┬───────────────────────────┘
|
|
22
|
+
* ▼
|
|
23
|
+
* answer_delta* → 流式输出最终回复
|
|
24
|
+
* ▼
|
|
25
|
+
* stream_end
|
|
26
|
+
*
|
|
27
|
+
* 对应 SSE type 字段:
|
|
28
|
+
* - thinking / tool / knowledge / plugin / mcp / memory_var_update / answer / error / done
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.REACT_SSE_TYPE_MAP = void 0;
|
|
32
|
+
exports.REACT_SSE_TYPE_MAP = {
|
|
33
|
+
thinking: '思考阶段',
|
|
34
|
+
tool: '工具调用',
|
|
35
|
+
knowledge: '知识库调用',
|
|
36
|
+
plugin: '插件调用',
|
|
37
|
+
mcp: 'MCP 调用',
|
|
38
|
+
memory_var_update: '记忆变量更新',
|
|
39
|
+
workflow: '工作流调用',
|
|
40
|
+
answer: '最终回答',
|
|
41
|
+
error: '错误',
|
|
42
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSessionState = createSessionState;
|
|
4
|
+
exports.applyEventToSession = applyEventToSession;
|
|
5
|
+
function createSessionState(query) {
|
|
6
|
+
return {
|
|
7
|
+
status: 'streaming',
|
|
8
|
+
query,
|
|
9
|
+
thinkingContent: '',
|
|
10
|
+
isThinking: false,
|
|
11
|
+
answerContent: '',
|
|
12
|
+
processes: [],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
let processIdSeq = 0;
|
|
16
|
+
function nextProcessId() {
|
|
17
|
+
processIdSeq += 1;
|
|
18
|
+
return `proc_${processIdSeq}_${Date.now()}`;
|
|
19
|
+
}
|
|
20
|
+
function upsertProcess(processes, id, item) {
|
|
21
|
+
const index = processes.findIndex((p) => p.id === id);
|
|
22
|
+
if (index >= 0) {
|
|
23
|
+
const next = [...processes];
|
|
24
|
+
next[index] = { ...next[index], ...item };
|
|
25
|
+
return next;
|
|
26
|
+
}
|
|
27
|
+
return [...processes, item];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 根据标准化事件更新会话聚合状态(ReAct 多轮)
|
|
31
|
+
*/
|
|
32
|
+
function applyEventToSession(state, event) {
|
|
33
|
+
switch (event.type) {
|
|
34
|
+
case 'stream_start':
|
|
35
|
+
return { ...state, status: 'streaming', query: event.query };
|
|
36
|
+
case 'thinking_start':
|
|
37
|
+
return { ...state, isThinking: true, thinkingContent: '' };
|
|
38
|
+
case 'thinking_delta':
|
|
39
|
+
return { ...state, isThinking: true, thinkingContent: event.content };
|
|
40
|
+
case 'thinking_end':
|
|
41
|
+
return {
|
|
42
|
+
...state,
|
|
43
|
+
isThinking: false,
|
|
44
|
+
thinkingContent: event.content,
|
|
45
|
+
processes: event.content
|
|
46
|
+
? [
|
|
47
|
+
...state.processes,
|
|
48
|
+
{
|
|
49
|
+
id: nextProcessId(),
|
|
50
|
+
type: 'thinking',
|
|
51
|
+
title: '深度思考',
|
|
52
|
+
time: event.runningTime || '0',
|
|
53
|
+
content: event.content,
|
|
54
|
+
status: 'completed',
|
|
55
|
+
},
|
|
56
|
+
]
|
|
57
|
+
: state.processes,
|
|
58
|
+
};
|
|
59
|
+
case 'tool_call_start':
|
|
60
|
+
case 'knowledge_call_start':
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
isThinking: false,
|
|
64
|
+
processes: upsertProcess(state.processes, event.toolId, {
|
|
65
|
+
id: event.toolId,
|
|
66
|
+
type: event.callKind,
|
|
67
|
+
title: event.title,
|
|
68
|
+
time: '0',
|
|
69
|
+
content: '',
|
|
70
|
+
status: 'running',
|
|
71
|
+
}),
|
|
72
|
+
};
|
|
73
|
+
case 'tool_call_end':
|
|
74
|
+
case 'knowledge_call_end':
|
|
75
|
+
return {
|
|
76
|
+
...state,
|
|
77
|
+
processes: upsertProcess(state.processes, event.toolId, {
|
|
78
|
+
id: event.toolId,
|
|
79
|
+
type: event.callKind,
|
|
80
|
+
title: event.title,
|
|
81
|
+
time: event.runningTime || '0',
|
|
82
|
+
content: event.content,
|
|
83
|
+
status: 'completed',
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
case 'answer_delta':
|
|
87
|
+
return { ...state, answerContent: event.content };
|
|
88
|
+
case 'error':
|
|
89
|
+
return {
|
|
90
|
+
...state,
|
|
91
|
+
status: 'error',
|
|
92
|
+
isThinking: false,
|
|
93
|
+
error: { message: event.message, detail: event.detail },
|
|
94
|
+
};
|
|
95
|
+
case 'stream_end':
|
|
96
|
+
return { ...state, status: state.status === 'error' ? 'error' : 'completed', isThinking: false };
|
|
97
|
+
default:
|
|
98
|
+
return state;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AgentChatClientConfig, AgentChatStreamHandlers, AgentChatStreamOptions, AgentStreamEvent } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 智能体流式对话客户端
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* const client = new AgentChatClient({
|
|
8
|
+
* url: 'https://your-host/appforge/openapi/v1/InvokeApp/app-xxx',
|
|
9
|
+
* appId: 'app-xxx',
|
|
10
|
+
* appKey: 'your-key',
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* await client.chatStream(
|
|
14
|
+
* { query: '你好', dataMode: 'normalized' },
|
|
15
|
+
* {
|
|
16
|
+
* onEvent: (event) => {
|
|
17
|
+
* if (event.type === 'answer_delta') {
|
|
18
|
+
* console.log(event.content);
|
|
19
|
+
* }
|
|
20
|
+
* },
|
|
21
|
+
* },
|
|
22
|
+
* );
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare class AgentChatClient {
|
|
26
|
+
private config;
|
|
27
|
+
private abortController;
|
|
28
|
+
constructor(config: AgentChatClientConfig);
|
|
29
|
+
/** 更新配置 */
|
|
30
|
+
updateConfig(config: Partial<AgentChatClientConfig>): void;
|
|
31
|
+
getConfig(): Readonly<AgentChatClientConfig>;
|
|
32
|
+
/** 中止当前流式请求 */
|
|
33
|
+
abort(): void;
|
|
34
|
+
/** 当前配置的请求 URL */
|
|
35
|
+
getUrl(): string;
|
|
36
|
+
/**
|
|
37
|
+
* 发起流式对话
|
|
38
|
+
*/
|
|
39
|
+
chatStream(options: AgentChatStreamOptions, handlers?: AgentChatStreamHandlers): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* 以 AsyncIterable 消费流式事件(便于 for-await)
|
|
42
|
+
*/
|
|
43
|
+
streamEvents(options: AgentChatStreamOptions): AsyncGenerator<AgentStreamEvent, void, undefined>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,qBAAqB,EAErB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AAuBpB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,eAAe,CAAgC;gBAE3C,MAAM,EAAE,qBAAqB;IAIzC,WAAW;IACX,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI;IAI1D,SAAS,IAAI,QAAQ,CAAC,qBAAqB,CAAC;IAI5C,eAAe;IACf,KAAK,IAAI,IAAI;IAKb,kBAAkB;IAClB,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,UAAU,CACd,OAAO,EAAE,sBAAsB,EAC/B,QAAQ,GAAE,uBAA4B,GACrC,OAAO,CAAC,IAAI,CAAC;IA2FhB;;OAEG;IACI,YAAY,CACjB,OAAO,EAAE,sBAAsB,GAC9B,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,SAAS,CAAC;CAkDrD"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { readSSEStream } from './parser.js';
|
|
2
|
+
import { createNormalizeContext, finalizeNormalizeContext, normalizeRawChunk, } from './normalizer.js';
|
|
3
|
+
const DEFAULT_BUILD_BODY = (query, config) => ({
|
|
4
|
+
id: config.appId,
|
|
5
|
+
query,
|
|
6
|
+
});
|
|
7
|
+
const DEFAULT_BUILD_HEADERS = (config) => {
|
|
8
|
+
const headers = {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
Accept: 'text/event-stream',
|
|
11
|
+
};
|
|
12
|
+
if (config.appKey) {
|
|
13
|
+
headers.Authorization = `Bearer ${config.appKey}`;
|
|
14
|
+
}
|
|
15
|
+
return headers;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 智能体流式对话客户端
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const client = new AgentChatClient({
|
|
23
|
+
* url: 'https://your-host/appforge/openapi/v1/InvokeApp/app-xxx',
|
|
24
|
+
* appId: 'app-xxx',
|
|
25
|
+
* appKey: 'your-key',
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* await client.chatStream(
|
|
29
|
+
* { query: '你好', dataMode: 'normalized' },
|
|
30
|
+
* {
|
|
31
|
+
* onEvent: (event) => {
|
|
32
|
+
* if (event.type === 'answer_delta') {
|
|
33
|
+
* console.log(event.content);
|
|
34
|
+
* }
|
|
35
|
+
* },
|
|
36
|
+
* },
|
|
37
|
+
* );
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export class AgentChatClient {
|
|
41
|
+
constructor(config) {
|
|
42
|
+
this.abortController = null;
|
|
43
|
+
this.config = config;
|
|
44
|
+
}
|
|
45
|
+
/** 更新配置 */
|
|
46
|
+
updateConfig(config) {
|
|
47
|
+
this.config = { ...this.config, ...config };
|
|
48
|
+
}
|
|
49
|
+
getConfig() {
|
|
50
|
+
return { ...this.config };
|
|
51
|
+
}
|
|
52
|
+
/** 中止当前流式请求 */
|
|
53
|
+
abort() {
|
|
54
|
+
this.abortController?.abort();
|
|
55
|
+
this.abortController = null;
|
|
56
|
+
}
|
|
57
|
+
/** 当前配置的请求 URL */
|
|
58
|
+
getUrl() {
|
|
59
|
+
return this.config.url;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 发起流式对话
|
|
63
|
+
*/
|
|
64
|
+
async chatStream(options, handlers = {}) {
|
|
65
|
+
const { query, signal, dataMode = 'normalized', extraBody = {}, extraHeaders = {}, } = options;
|
|
66
|
+
this.abort();
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
this.abortController = controller;
|
|
69
|
+
const combinedSignal = signal
|
|
70
|
+
? combineAbortSignals(signal, controller.signal)
|
|
71
|
+
: controller.signal;
|
|
72
|
+
const emit = (event) => handlers.onEvent?.(event);
|
|
73
|
+
const emitRaw = (chunk) => handlers.onRawChunk?.(chunk);
|
|
74
|
+
const shouldEmitNormalized = dataMode === 'normalized' || dataMode === 'both';
|
|
75
|
+
const shouldEmitRaw = dataMode === 'raw' || dataMode === 'both';
|
|
76
|
+
if (shouldEmitNormalized) {
|
|
77
|
+
emit({ type: 'stream_start', timestamp: Date.now(), query });
|
|
78
|
+
}
|
|
79
|
+
const normCtx = createNormalizeContext();
|
|
80
|
+
try {
|
|
81
|
+
const fetchImpl = this.config.fetch ?? globalThis.fetch;
|
|
82
|
+
const url = this.config.url;
|
|
83
|
+
const buildBody = this.config.buildRequestBody ?? DEFAULT_BUILD_BODY;
|
|
84
|
+
const buildHeaders = this.config.buildHeaders ?? DEFAULT_BUILD_HEADERS;
|
|
85
|
+
const response = await fetchImpl(url, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { ...buildHeaders(this.config), ...extraHeaders },
|
|
88
|
+
body: JSON.stringify({ ...buildBody(query, this.config), ...extraBody }),
|
|
89
|
+
signal: combinedSignal,
|
|
90
|
+
cache: 'no-store',
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const errorText = await response.text();
|
|
94
|
+
throw new Error(`HTTP ${response.status}: ${errorText || response.statusText}`);
|
|
95
|
+
}
|
|
96
|
+
if (!response.body) {
|
|
97
|
+
throw new Error('Response body is empty');
|
|
98
|
+
}
|
|
99
|
+
await readSSEStream(response.body, (rawChunk) => {
|
|
100
|
+
if (shouldEmitRaw)
|
|
101
|
+
emitRaw(rawChunk);
|
|
102
|
+
if (shouldEmitNormalized) {
|
|
103
|
+
const events = normalizeRawChunk(rawChunk, normCtx);
|
|
104
|
+
events.forEach(emit);
|
|
105
|
+
}
|
|
106
|
+
}, combinedSignal);
|
|
107
|
+
if (shouldEmitNormalized) {
|
|
108
|
+
finalizeNormalizeContext(normCtx).forEach(emit);
|
|
109
|
+
emit({ type: 'stream_end', timestamp: Date.now() });
|
|
110
|
+
}
|
|
111
|
+
handlers.onComplete?.();
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (combinedSignal.aborted)
|
|
115
|
+
return;
|
|
116
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
117
|
+
handlers.onError?.(err);
|
|
118
|
+
if (shouldEmitNormalized) {
|
|
119
|
+
emit({
|
|
120
|
+
type: 'error',
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
message: err.message,
|
|
123
|
+
});
|
|
124
|
+
emit({ type: 'stream_end', timestamp: Date.now() });
|
|
125
|
+
}
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
if (this.abortController === controller) {
|
|
130
|
+
this.abortController = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 以 AsyncIterable 消费流式事件(便于 for-await)
|
|
136
|
+
*/
|
|
137
|
+
async *streamEvents(options) {
|
|
138
|
+
const queue = [];
|
|
139
|
+
let resolveWait = null;
|
|
140
|
+
let done = false;
|
|
141
|
+
let streamError = null;
|
|
142
|
+
const notify = () => {
|
|
143
|
+
resolveWait?.();
|
|
144
|
+
resolveWait = null;
|
|
145
|
+
};
|
|
146
|
+
const runPromise = this.chatStream({ ...options, dataMode: options.dataMode ?? 'normalized' }, {
|
|
147
|
+
onEvent: (event) => {
|
|
148
|
+
queue.push(event);
|
|
149
|
+
notify();
|
|
150
|
+
},
|
|
151
|
+
onError: (err) => {
|
|
152
|
+
streamError = err;
|
|
153
|
+
done = true;
|
|
154
|
+
notify();
|
|
155
|
+
},
|
|
156
|
+
onComplete: () => {
|
|
157
|
+
done = true;
|
|
158
|
+
notify();
|
|
159
|
+
},
|
|
160
|
+
}).catch((err) => {
|
|
161
|
+
streamError = err;
|
|
162
|
+
done = true;
|
|
163
|
+
notify();
|
|
164
|
+
});
|
|
165
|
+
try {
|
|
166
|
+
while (true) {
|
|
167
|
+
if (queue.length > 0) {
|
|
168
|
+
yield queue.shift();
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (done)
|
|
172
|
+
break;
|
|
173
|
+
await new Promise((resolve) => {
|
|
174
|
+
resolveWait = resolve;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (streamError)
|
|
178
|
+
throw streamError;
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
await runPromise.catch(() => undefined);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function combineAbortSignals(...signals) {
|
|
186
|
+
const controller = new AbortController();
|
|
187
|
+
const onAbort = () => controller.abort();
|
|
188
|
+
signals.forEach((s) => {
|
|
189
|
+
if (s.aborted)
|
|
190
|
+
controller.abort();
|
|
191
|
+
else
|
|
192
|
+
s.addEventListener('abort', onAbort, { once: true });
|
|
193
|
+
});
|
|
194
|
+
return controller.signal;
|
|
195
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { AgentChatClient } from './client.js';
|
|
2
|
+
export { parseSSELine, readSSEStream, isStreamEndSignal } from './parser.js';
|
|
3
|
+
export { normalizeRawChunk, createNormalizeContext, finalizeNormalizeContext, } from './normalizer.js';
|
|
4
|
+
export { createSessionState, applyEventToSession } from './session.js';
|
|
5
|
+
export { REACT_SSE_TYPE_MAP } from './react-flow.js';
|
|
6
|
+
export { parseKnowledgeSupLabels, stripKnowledgeSupLabels, } from './knowledge-label.js';
|
|
7
|
+
export type { KnowledgeCitationLabel, ParseKnowledgeLabelsResult } from './knowledge-label.js';
|
|
8
|
+
export type { AgentChatClientConfig, AgentChatDataMode, AgentChatProcessItem, AgentChatSessionState, AgentChatStreamHandlers, AgentChatStreamOptions, AgentStreamEvent, AgentStreamEventType, RawSSEChunk, StreamStartEvent, StreamEndEvent, ThinkingStartEvent, ThinkingDeltaEvent, ThinkingEndEvent, AnswerDeltaEvent, ToolCallStartEvent, ToolCallEndEvent, ProcessCallKind, ErrorEvent, RawEvent, } from './types.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|