flare-chat-core 0.1.1 → 0.2.1
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/package.json +1 -1
- package/scripts/check.sh +15 -0
- package/src/chat-core/input/useChatInput.js +41 -8
- package/src/chat-core/messages/buildTimelineItems.js +17 -16
- package/src/chat-core/session/useChatSessionReducer.js +80 -2
- package/src/chat-core/stream/sse-client.js +126 -5
- package/src/chat-core/stream/sse-events.js +442 -0
- package/src/chat-core/stream/useSSEStream.js +153 -6
package/package.json
CHANGED
package/scripts/check.sh
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
5
|
+
PKG_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
6
|
+
|
|
7
|
+
level="${1:-quick}"
|
|
8
|
+
case "$level" in
|
|
9
|
+
quick|full) ;;
|
|
10
|
+
*) echo "Unsupported level: $level" >&2; exit 2 ;;
|
|
11
|
+
esac
|
|
12
|
+
|
|
13
|
+
find "$PKG_DIR/src" -type f -name '*.js' | while IFS= read -r file; do
|
|
14
|
+
node --check "$file"
|
|
15
|
+
done
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { useState, useCallback } from 'react';
|
|
2
2
|
|
|
3
|
+
function buildAttachmentFile(file, index = 0, status = 'ready') {
|
|
4
|
+
return {
|
|
5
|
+
uid: file?.uid || `${Date.now()}-${index}`,
|
|
6
|
+
name: file?.name || `附件-${index + 1}`,
|
|
7
|
+
size: Number.isFinite(file?.size) ? file.size : null,
|
|
8
|
+
type: file?.type || '',
|
|
9
|
+
originFileObj: file?.originFileObj || file || null,
|
|
10
|
+
status: file?.status || status,
|
|
11
|
+
error_message: file?.error_message || '',
|
|
12
|
+
created_at: file?.created_at || new Date().toISOString(),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
3
16
|
export function useChatInput() {
|
|
4
17
|
const [inputValue, setInputValue] = useState('');
|
|
5
18
|
const [fileList, setFileList] = useState([]);
|
|
@@ -23,18 +36,37 @@ export function useChatInput() {
|
|
|
23
36
|
setIsDragging(false);
|
|
24
37
|
|
|
25
38
|
const files = Array.from(e.dataTransfer.files);
|
|
26
|
-
const newFiles = files.map((file, index) => (
|
|
27
|
-
uid: `${Date.now()}-${index}`,
|
|
28
|
-
name: file.name,
|
|
29
|
-
size: file.size,
|
|
30
|
-
type: file.type,
|
|
31
|
-
originFileObj: file,
|
|
32
|
-
}));
|
|
39
|
+
const newFiles = files.map((file, index) => buildAttachmentFile(file, index));
|
|
33
40
|
|
|
34
41
|
setFileList((prev) => [...prev, ...newFiles]);
|
|
35
42
|
return files.length;
|
|
36
43
|
}, []);
|
|
37
44
|
|
|
45
|
+
const appendFiles = useCallback((files = [], options = {}) => {
|
|
46
|
+
const normalized = Array.isArray(files)
|
|
47
|
+
? files.map((file, index) => buildAttachmentFile(file, index, options.status || 'ready'))
|
|
48
|
+
: [];
|
|
49
|
+
if (normalized.length === 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
setFileList((prev) => [...prev, ...normalized]);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
const updateFileStatus = useCallback((uid, status, errorMessage = '') => {
|
|
56
|
+
if (!uid) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
setFileList((prev) => prev.map((file) => (
|
|
60
|
+
file.uid === uid
|
|
61
|
+
? {
|
|
62
|
+
...file,
|
|
63
|
+
status: status || file.status,
|
|
64
|
+
error_message: errorMessage || '',
|
|
65
|
+
}
|
|
66
|
+
: file
|
|
67
|
+
)));
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
38
70
|
const handleDragOver = useCallback((e) => {
|
|
39
71
|
e.preventDefault();
|
|
40
72
|
e.stopPropagation();
|
|
@@ -59,6 +91,8 @@ export function useChatInput() {
|
|
|
59
91
|
handleInputChange,
|
|
60
92
|
handleFileChange,
|
|
61
93
|
handleRemoveFile,
|
|
94
|
+
appendFiles,
|
|
95
|
+
updateFileStatus,
|
|
62
96
|
handleDrop,
|
|
63
97
|
handleDragOver,
|
|
64
98
|
handleDragLeave,
|
|
@@ -67,4 +101,3 @@ export function useChatInput() {
|
|
|
67
101
|
}
|
|
68
102
|
|
|
69
103
|
export default useChatInput;
|
|
70
|
-
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export function buildTimelineItems(state = {}) {
|
|
1
|
+
export function buildTimelineItems(state = {}, options = {}) {
|
|
2
2
|
const messages = Array.isArray(state.messages) ? state.messages : [];
|
|
3
|
-
const executionCards = Array.isArray(state.executionCards) ? state.executionCards : [];
|
|
4
3
|
const uiCards = Array.isArray(state.uiCards) ? state.uiCards : [];
|
|
5
4
|
const streaming = state.streaming || {};
|
|
5
|
+
const loading = Boolean(options?.loading);
|
|
6
6
|
|
|
7
7
|
const items = [];
|
|
8
8
|
|
|
@@ -21,23 +21,15 @@ export function buildTimelineItems(state = {}) {
|
|
|
21
21
|
role: msg.role,
|
|
22
22
|
content: msg.content,
|
|
23
23
|
createdAt: msg.created_at,
|
|
24
|
+
client_request_id: msg.client_request_id || '',
|
|
25
|
+
intake_session_id: msg.intake_session_id || '',
|
|
26
|
+
roundKey: msg.round_key || '',
|
|
24
27
|
highlights: msg.highlights ?? [],
|
|
25
28
|
sourceTypes: msg.sourceTypes ?? [],
|
|
29
|
+
attachments: Array.isArray(msg.attachments) ? msg.attachments : [],
|
|
26
30
|
});
|
|
27
31
|
});
|
|
28
32
|
|
|
29
|
-
executionCards
|
|
30
|
-
.filter(Boolean)
|
|
31
|
-
.filter((card) => card.step_id !== streaming.executionTrace?.step_id)
|
|
32
|
-
.forEach((card, index) => {
|
|
33
|
-
const executionId = card.step_id || `execution-fallback-${index}`;
|
|
34
|
-
items.push({
|
|
35
|
-
id: executionId,
|
|
36
|
-
type: 'execution',
|
|
37
|
-
executionTrace: card,
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
33
|
if (uiCards.length > 0) {
|
|
42
34
|
items.push({
|
|
43
35
|
id: 'conversation-ui-cards',
|
|
@@ -54,7 +46,8 @@ export function buildTimelineItems(state = {}) {
|
|
|
54
46
|
});
|
|
55
47
|
}
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
const hasThinkingSignal = Boolean(streaming.agentStatus || streaming.thinkingTrace || streaming.executionTrace);
|
|
50
|
+
if (hasThinkingSignal) {
|
|
58
51
|
items.push({
|
|
59
52
|
id: 'thinking-bubble',
|
|
60
53
|
type: 'thinking',
|
|
@@ -62,6 +55,15 @@ export function buildTimelineItems(state = {}) {
|
|
|
62
55
|
thinkingTrace: streaming.thinkingTrace,
|
|
63
56
|
executionTrace: streaming.executionTrace,
|
|
64
57
|
});
|
|
58
|
+
} else if (loading && !streaming.content) {
|
|
59
|
+
items.push({
|
|
60
|
+
id: 'thinking-bubble-loading',
|
|
61
|
+
type: 'thinking',
|
|
62
|
+
agentStatus: {
|
|
63
|
+
status: 'running',
|
|
64
|
+
agent: '助手',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
return {
|
|
@@ -71,4 +73,3 @@ export function buildTimelineItems(state = {}) {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
export default buildTimelineItems;
|
|
74
|
-
|
|
@@ -8,6 +8,7 @@ const MESSAGES_REFRESHED = 'MESSAGES_REFRESHED';
|
|
|
8
8
|
const MESSAGE_APPENDED = 'MESSAGE_APPENDED';
|
|
9
9
|
const STREAMING_RESET = 'STREAMING_RESET';
|
|
10
10
|
const STREAMING_CHUNK = 'STREAMING_CHUNK';
|
|
11
|
+
const STREAMING_REPLACE = 'STREAMING_REPLACE';
|
|
11
12
|
const AGENT_STATUS_SET = 'AGENT_STATUS_SET';
|
|
12
13
|
const THINKING_TRACE_SET = 'THINKING_TRACE_SET';
|
|
13
14
|
const EXECUTION_TRACE_SET = 'EXECUTION_TRACE_SET';
|
|
@@ -19,6 +20,7 @@ const UI_CARDS_SET = 'UI_CARDS_SET';
|
|
|
19
20
|
const LAST_USER_MESSAGE_SET = 'LAST_USER_MESSAGE_SET';
|
|
20
21
|
const UI_CARD_UPDATED = 'UI_CARD_UPDATED';
|
|
21
22
|
const INSTANCE_PROFILE_SET = 'INSTANCE_PROFILE_SET';
|
|
23
|
+
const SESSION_PROMOTED = 'SESSION_PROMOTED';
|
|
22
24
|
|
|
23
25
|
const initialStreaming = {
|
|
24
26
|
content: '',
|
|
@@ -31,6 +33,7 @@ export const initialState = {
|
|
|
31
33
|
sessionId: null,
|
|
32
34
|
sessionTitle: '',
|
|
33
35
|
sessionStatus: 'active',
|
|
36
|
+
sessionDetail: null,
|
|
34
37
|
instanceProfile: null,
|
|
35
38
|
messages: [],
|
|
36
39
|
executionCards: [],
|
|
@@ -48,12 +51,19 @@ export const initialState = {
|
|
|
48
51
|
function reducer(state, { type, payload }) {
|
|
49
52
|
switch (type) {
|
|
50
53
|
case SESSION_LOADED: {
|
|
51
|
-
const {
|
|
54
|
+
const {
|
|
55
|
+
sessionId,
|
|
56
|
+
title,
|
|
57
|
+
status,
|
|
58
|
+
messages,
|
|
59
|
+
detail,
|
|
60
|
+
} = payload;
|
|
52
61
|
return {
|
|
53
62
|
...state,
|
|
54
63
|
sessionId,
|
|
55
64
|
sessionTitle: title,
|
|
56
65
|
sessionStatus: status || 'active',
|
|
66
|
+
sessionDetail: (detail && typeof detail === 'object' && !Array.isArray(detail)) ? detail : null,
|
|
57
67
|
instanceProfile: state.instanceProfile,
|
|
58
68
|
messages,
|
|
59
69
|
executionCards: [],
|
|
@@ -103,6 +113,40 @@ function reducer(state, { type, payload }) {
|
|
|
103
113
|
case INSTANCE_PROFILE_SET:
|
|
104
114
|
return { ...state, instanceProfile: payload };
|
|
105
115
|
|
|
116
|
+
case SESSION_PROMOTED: {
|
|
117
|
+
const sessionId = String(payload?.sessionId || '').trim();
|
|
118
|
+
if (!sessionId) {
|
|
119
|
+
return state;
|
|
120
|
+
}
|
|
121
|
+
const title = String(payload?.title || '').trim();
|
|
122
|
+
const detailPayload = (
|
|
123
|
+
payload?.detail
|
|
124
|
+
&& typeof payload.detail === 'object'
|
|
125
|
+
&& !Array.isArray(payload.detail)
|
|
126
|
+
) ? payload.detail : null;
|
|
127
|
+
const nextDetail = detailPayload
|
|
128
|
+
? {
|
|
129
|
+
...(state.sessionDetail && typeof state.sessionDetail === 'object' ? state.sessionDetail : {}),
|
|
130
|
+
...detailPayload,
|
|
131
|
+
sessionId,
|
|
132
|
+
}
|
|
133
|
+
: (
|
|
134
|
+
state.sessionDetail && typeof state.sessionDetail === 'object'
|
|
135
|
+
? {
|
|
136
|
+
...state.sessionDetail,
|
|
137
|
+
sessionId,
|
|
138
|
+
}
|
|
139
|
+
: null
|
|
140
|
+
);
|
|
141
|
+
return {
|
|
142
|
+
...state,
|
|
143
|
+
sessionId,
|
|
144
|
+
sessionTitle: title || state.sessionTitle,
|
|
145
|
+
sessionStatus: String(payload?.status || state.sessionStatus || 'active'),
|
|
146
|
+
sessionDetail: nextDetail,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
106
150
|
case EXECUTION_CARD_UPSERTED: {
|
|
107
151
|
const nextCard = payload;
|
|
108
152
|
const cardKey = nextCard.step_id || `${nextCard.agent}-${nextCard.label}-${nextCard.stage}`;
|
|
@@ -135,6 +179,15 @@ function reducer(state, { type, payload }) {
|
|
|
135
179
|
},
|
|
136
180
|
};
|
|
137
181
|
|
|
182
|
+
case STREAMING_REPLACE:
|
|
183
|
+
return {
|
|
184
|
+
...state,
|
|
185
|
+
streaming: {
|
|
186
|
+
...state.streaming,
|
|
187
|
+
content: String(payload || ''),
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
138
191
|
case AGENT_STATUS_SET:
|
|
139
192
|
return {
|
|
140
193
|
...state,
|
|
@@ -223,8 +276,10 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
223
276
|
title: detail.title,
|
|
224
277
|
status: detail.status || 'active',
|
|
225
278
|
messages: msgs.messages,
|
|
279
|
+
detail,
|
|
226
280
|
},
|
|
227
281
|
});
|
|
282
|
+
return detail;
|
|
228
283
|
}, [sessionAPI, messageAPI]);
|
|
229
284
|
|
|
230
285
|
const createSession = useCallback(async ({
|
|
@@ -242,6 +297,7 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
242
297
|
title,
|
|
243
298
|
status: response.status || 'active',
|
|
244
299
|
messages: [],
|
|
300
|
+
detail: response,
|
|
245
301
|
},
|
|
246
302
|
});
|
|
247
303
|
|
|
@@ -295,6 +351,7 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
295
351
|
title,
|
|
296
352
|
status: detail.status || 'active',
|
|
297
353
|
messages: msgs.messages,
|
|
354
|
+
detail,
|
|
298
355
|
},
|
|
299
356
|
});
|
|
300
357
|
|
|
@@ -311,6 +368,10 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
311
368
|
dispatch({ type: TITLE_UPDATED, payload: title });
|
|
312
369
|
}, [sessionAPI]);
|
|
313
370
|
|
|
371
|
+
const promoteSession = useCallback((payload = {}) => {
|
|
372
|
+
dispatch({ type: SESSION_PROMOTED, payload });
|
|
373
|
+
}, []);
|
|
374
|
+
|
|
314
375
|
const refreshMessages = useCallback(async (sessionId) => {
|
|
315
376
|
const messageApi = requireAPI(messageAPI, 'messageAPI');
|
|
316
377
|
const response = await messageApi.list(sessionId);
|
|
@@ -321,7 +382,14 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
321
382
|
dispatch({ type: MESSAGES_REFRESHED, payload: messagesWithIds });
|
|
322
383
|
}, [messageAPI]);
|
|
323
384
|
|
|
324
|
-
const appendUserMessage = useCallback((content) => {
|
|
385
|
+
const appendUserMessage = useCallback((content, metadata = {}) => {
|
|
386
|
+
const payloadMeta = (
|
|
387
|
+
metadata
|
|
388
|
+
&& typeof metadata === 'object'
|
|
389
|
+
&& !Array.isArray(metadata)
|
|
390
|
+
)
|
|
391
|
+
? metadata
|
|
392
|
+
: {};
|
|
325
393
|
dispatch({
|
|
326
394
|
type: MESSAGE_APPENDED,
|
|
327
395
|
payload: {
|
|
@@ -329,6 +397,7 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
329
397
|
role: 'user',
|
|
330
398
|
content,
|
|
331
399
|
created_at: new Date().toISOString(),
|
|
400
|
+
...payloadMeta,
|
|
332
401
|
},
|
|
333
402
|
});
|
|
334
403
|
}, []);
|
|
@@ -353,6 +422,10 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
353
422
|
dispatch({ type: STREAMING_CHUNK, payload: chunk });
|
|
354
423
|
}, []);
|
|
355
424
|
|
|
425
|
+
const replaceStreamContent = useCallback((content) => {
|
|
426
|
+
dispatch({ type: STREAMING_REPLACE, payload: content });
|
|
427
|
+
}, []);
|
|
428
|
+
|
|
356
429
|
const setAgentStatus = useCallback((agentStatus) => {
|
|
357
430
|
dispatch({ type: AGENT_STATUS_SET, payload: agentStatus });
|
|
358
431
|
}, []);
|
|
@@ -389,6 +462,7 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
389
462
|
return useMemo(() => ({
|
|
390
463
|
sessionId: state.sessionId,
|
|
391
464
|
sessionTitle: state.sessionTitle,
|
|
465
|
+
sessionDetail: state.sessionDetail,
|
|
392
466
|
instanceProfile: state.instanceProfile,
|
|
393
467
|
messages: state.messages,
|
|
394
468
|
executionCards: state.executionCards,
|
|
@@ -403,6 +477,7 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
403
477
|
createOrLoadSession,
|
|
404
478
|
resetSession,
|
|
405
479
|
updateTitle,
|
|
480
|
+
promoteSession,
|
|
406
481
|
refreshMessages,
|
|
407
482
|
setSessionError,
|
|
408
483
|
appendUserMessage,
|
|
@@ -411,6 +486,7 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
411
486
|
setLastUserMessage,
|
|
412
487
|
resetStreaming,
|
|
413
488
|
appendStreamChunk,
|
|
489
|
+
replaceStreamContent,
|
|
414
490
|
setAgentStatus,
|
|
415
491
|
setThinkingTrace,
|
|
416
492
|
setExecutionTrace,
|
|
@@ -425,6 +501,7 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
425
501
|
createOrLoadSession,
|
|
426
502
|
resetSession,
|
|
427
503
|
updateTitle,
|
|
504
|
+
promoteSession,
|
|
428
505
|
refreshMessages,
|
|
429
506
|
setSessionError,
|
|
430
507
|
appendUserMessage,
|
|
@@ -433,6 +510,7 @@ export default function useChatSessionReducer(deps = {}) {
|
|
|
433
510
|
setLastUserMessage,
|
|
434
511
|
resetStreaming,
|
|
435
512
|
appendStreamChunk,
|
|
513
|
+
replaceStreamContent,
|
|
436
514
|
setAgentStatus,
|
|
437
515
|
setThinkingTrace,
|
|
438
516
|
setExecutionTrace,
|
|
@@ -13,11 +13,19 @@ function joinUrl(baseUrl, endpoint) {
|
|
|
13
13
|
return `${baseUrl.replace(/\/+$/, '')}/${endpoint.replace(/^\/+/, '')}`;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function buildClientRequestId() {
|
|
17
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
18
|
+
return `req_${Date.now()}_${random}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
export class SSEClient {
|
|
17
22
|
constructor({ endpoint = DEFAULT_ENDPOINT, baseUrl = API_BASE_URL } = {}) {
|
|
18
23
|
this.abortController = null;
|
|
19
24
|
this.endpoint = endpoint;
|
|
20
25
|
this.baseUrl = baseUrl;
|
|
26
|
+
this.activeRequestId = '';
|
|
27
|
+
this.authReadyPromise = null;
|
|
28
|
+
this.authReadyAtMs = 0;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
async sendMessage(sessionOrParams, contentArg, onEventArg, onCompleteArg, onErrorArg, optionsArg = {}) {
|
|
@@ -33,26 +41,124 @@ export class SSEClient {
|
|
|
33
41
|
const sessionId = params.sessionId;
|
|
34
42
|
const content = params.content;
|
|
35
43
|
const enabledCapabilities = Array.isArray(params.enabledCapabilities) ? params.enabledCapabilities : [];
|
|
44
|
+
const modeKey = typeof params.modeKey === 'string' ? params.modeKey.trim() : '';
|
|
45
|
+
const manualModeKey = typeof params.manualModeKey === 'string' ? params.manualModeKey.trim() : '';
|
|
46
|
+
const payloadExtra = (
|
|
47
|
+
params.payloadExtra
|
|
48
|
+
&& typeof params.payloadExtra === 'object'
|
|
49
|
+
&& !Array.isArray(params.payloadExtra)
|
|
50
|
+
)
|
|
51
|
+
? params.payloadExtra
|
|
52
|
+
: {};
|
|
36
53
|
const onEvent = isObjectCall ? contentArg : onEventArg;
|
|
37
54
|
const onComplete = isObjectCall ? onEventArg : onCompleteArg;
|
|
38
55
|
const onError = isObjectCall ? onCompleteArg : onErrorArg;
|
|
39
56
|
const url = params.url || joinUrl(params.baseUrl ?? this.baseUrl, params.endpoint || this.endpoint);
|
|
40
|
-
|
|
57
|
+
const command = typeof params.command === 'string' && params.command.trim()
|
|
58
|
+
? params.command.trim()
|
|
59
|
+
: (typeof payloadExtra.command === 'string' && payloadExtra.command.trim() ? payloadExtra.command.trim() : 'send_message');
|
|
60
|
+
const clientRequestId = typeof params.clientRequestId === 'string' && params.clientRequestId.trim()
|
|
61
|
+
? params.clientRequestId.trim()
|
|
62
|
+
: buildClientRequestId();
|
|
63
|
+
const contractVersion = typeof params.contractVersion === 'string' && params.contractVersion.trim()
|
|
64
|
+
? params.contractVersion.trim()
|
|
65
|
+
: 'flare.v1';
|
|
66
|
+
const lastTurnId = typeof params.lastTurnId === 'string' ? params.lastTurnId.trim() : '';
|
|
67
|
+
const token = typeof params.token === 'string' ? params.token.trim() : '';
|
|
68
|
+
const onTiming = typeof params.onTiming === 'function' ? params.onTiming : null;
|
|
69
|
+
const authProvider = (
|
|
70
|
+
params.auth
|
|
71
|
+
&& typeof params.auth === 'object'
|
|
72
|
+
&& !Array.isArray(params.auth)
|
|
73
|
+
) ? params.auth : null;
|
|
74
|
+
|
|
75
|
+
const nowMs = Date.now();
|
|
76
|
+
onTiming?.({
|
|
77
|
+
point: 't_submit',
|
|
78
|
+
at_ms: nowMs,
|
|
79
|
+
request_id: clientRequestId,
|
|
80
|
+
session_id: String(sessionId || '').trim(),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const waitAuthReady = async () => {
|
|
84
|
+
if (!authProvider || typeof authProvider.ensureReady !== 'function') {
|
|
85
|
+
return {
|
|
86
|
+
authorizationToken: token,
|
|
87
|
+
authReadyAtMs: nowMs,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (!this.authReadyPromise) {
|
|
91
|
+
this.authReadyPromise = Promise.resolve()
|
|
92
|
+
.then(() => authProvider.ensureReady())
|
|
93
|
+
.then((resolved) => {
|
|
94
|
+
this.authReadyAtMs = Date.now();
|
|
95
|
+
return resolved || null;
|
|
96
|
+
})
|
|
97
|
+
.finally(() => {
|
|
98
|
+
this.authReadyPromise = null;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const authResult = await this.authReadyPromise;
|
|
102
|
+
const tokenFromAuthResult = typeof authResult?.token === 'string' ? authResult.token.trim() : '';
|
|
103
|
+
const tokenFromProvider = typeof authProvider.getToken === 'function'
|
|
104
|
+
? String(authProvider.getToken() || '').trim()
|
|
105
|
+
: '';
|
|
106
|
+
return {
|
|
107
|
+
authorizationToken: tokenFromAuthResult || tokenFromProvider || token,
|
|
108
|
+
authReadyAtMs: this.authReadyAtMs || Date.now(),
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const currentRequestId = clientRequestId;
|
|
113
|
+
if (this.abortController) {
|
|
114
|
+
this.abortController.abort();
|
|
115
|
+
this.abortController = null;
|
|
116
|
+
}
|
|
41
117
|
this.abortController = new AbortController();
|
|
118
|
+
this.activeRequestId = currentRequestId;
|
|
42
119
|
|
|
43
120
|
try {
|
|
121
|
+
const authResolved = await waitAuthReady();
|
|
122
|
+
onTiming?.({
|
|
123
|
+
point: 't_auth_ready',
|
|
124
|
+
at_ms: authResolved.authReadyAtMs || Date.now(),
|
|
125
|
+
request_id: currentRequestId,
|
|
126
|
+
session_id: String(sessionId || '').trim(),
|
|
127
|
+
});
|
|
128
|
+
if (this.activeRequestId !== currentRequestId) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
44
131
|
const response = await fetch(url, {
|
|
45
132
|
method: 'POST',
|
|
46
133
|
headers: {
|
|
47
134
|
'Content-Type': 'application/json',
|
|
135
|
+
...(authResolved.authorizationToken ? { Authorization: `Bearer ${authResolved.authorizationToken}` } : {}),
|
|
48
136
|
},
|
|
49
137
|
body: JSON.stringify({
|
|
138
|
+
contract_version: contractVersion,
|
|
139
|
+
client_request_id: clientRequestId,
|
|
140
|
+
command,
|
|
141
|
+
last_turn_id: lastTurnId || null,
|
|
50
142
|
message: content,
|
|
51
143
|
session_id: sessionId,
|
|
52
144
|
enabled_capabilities: enabledCapabilities,
|
|
145
|
+
...(modeKey ? { mode: modeKey } : {}),
|
|
146
|
+
...(manualModeKey ? { manual_mode: manualModeKey } : {}),
|
|
147
|
+
payload: {
|
|
148
|
+
message: content,
|
|
149
|
+
...(modeKey ? { mode: modeKey } : {}),
|
|
150
|
+
...(manualModeKey ? { manual_mode: manualModeKey } : {}),
|
|
151
|
+
...payloadExtra,
|
|
152
|
+
},
|
|
53
153
|
}),
|
|
54
154
|
signal: this.abortController.signal,
|
|
55
155
|
});
|
|
156
|
+
onTiming?.({
|
|
157
|
+
point: 't_stream_connected',
|
|
158
|
+
at_ms: Date.now(),
|
|
159
|
+
request_id: currentRequestId,
|
|
160
|
+
session_id: String(sessionId || '').trim(),
|
|
161
|
+
});
|
|
56
162
|
|
|
57
163
|
if (!response.ok) {
|
|
58
164
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
@@ -88,13 +194,20 @@ export class SSEClient {
|
|
|
88
194
|
|
|
89
195
|
try {
|
|
90
196
|
const eventData = JSON.parse(data);
|
|
197
|
+
if (this.activeRequestId !== currentRequestId) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
91
200
|
onEvent?.({
|
|
92
201
|
type: currentEvent,
|
|
93
202
|
data: eventData,
|
|
94
203
|
});
|
|
95
204
|
|
|
96
|
-
if (currentEvent === 'complete') {
|
|
97
|
-
onComplete?.();
|
|
205
|
+
if (currentEvent === 'complete' || currentEvent === 'done') {
|
|
206
|
+
await onComplete?.();
|
|
207
|
+
if (this.activeRequestId === currentRequestId) {
|
|
208
|
+
this.abortController = null;
|
|
209
|
+
this.activeRequestId = '';
|
|
210
|
+
}
|
|
98
211
|
return;
|
|
99
212
|
}
|
|
100
213
|
} catch (error) {
|
|
@@ -103,13 +216,21 @@ export class SSEClient {
|
|
|
103
216
|
}
|
|
104
217
|
}
|
|
105
218
|
|
|
106
|
-
onComplete?.();
|
|
219
|
+
await onComplete?.();
|
|
220
|
+
if (this.activeRequestId === currentRequestId) {
|
|
221
|
+
this.abortController = null;
|
|
222
|
+
this.activeRequestId = '';
|
|
223
|
+
}
|
|
107
224
|
} catch (error) {
|
|
108
225
|
if (error?.name === 'AbortError') {
|
|
109
226
|
return;
|
|
110
227
|
}
|
|
111
228
|
|
|
112
229
|
onError?.(error instanceof Error ? error.message : '发送消息失败');
|
|
230
|
+
if (this.activeRequestId === currentRequestId) {
|
|
231
|
+
this.abortController = null;
|
|
232
|
+
this.activeRequestId = '';
|
|
233
|
+
}
|
|
113
234
|
}
|
|
114
235
|
}
|
|
115
236
|
|
|
@@ -118,6 +239,6 @@ export class SSEClient {
|
|
|
118
239
|
this.abortController.abort();
|
|
119
240
|
this.abortController = null;
|
|
120
241
|
}
|
|
242
|
+
this.activeRequestId = '';
|
|
121
243
|
}
|
|
122
244
|
}
|
|
123
|
-
|
|
@@ -145,6 +145,49 @@ export const parseContent = (raw) => ({
|
|
|
145
145
|
chunk: raw?.content ?? '',
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
+
export const parseAck = (raw) => ({
|
|
149
|
+
trace_id: String(raw?.trace_id || '').trim(),
|
|
150
|
+
session_id: String(raw?.session_id || '').trim(),
|
|
151
|
+
request_id: String(raw?.request_id || '').trim(),
|
|
152
|
+
mode: String(raw?.mode || '').trim(),
|
|
153
|
+
ts: Number.isFinite(Number(raw?.ts)) ? Number(raw.ts) : null,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
export const parsePhaseEvent = (raw) => ({
|
|
157
|
+
phase: String(raw?.phase || '').trim(),
|
|
158
|
+
label: String(raw?.label || '').trim(),
|
|
159
|
+
status: String(raw?.status || '').trim(),
|
|
160
|
+
progress: Number.isFinite(Number(raw?.progress)) ? Number(raw.progress) : null,
|
|
161
|
+
meta: raw?.meta && typeof raw.meta === 'object' && !Array.isArray(raw.meta) ? raw.meta : {},
|
|
162
|
+
ts: Number.isFinite(Number(raw?.ts)) ? Number(raw.ts) : null,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export const parsePatch = (raw) => ({
|
|
166
|
+
scope: String(raw?.scope || '').trim(),
|
|
167
|
+
payload: raw?.payload && typeof raw.payload === 'object' && !Array.isArray(raw.payload) ? raw.payload : {},
|
|
168
|
+
ts: Number.isFinite(Number(raw?.ts)) ? Number(raw.ts) : null,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export const parseTextDelta = (raw) => ({
|
|
172
|
+
channel: String(raw?.channel || 'assistant').trim() || 'assistant',
|
|
173
|
+
delta: String(raw?.delta || ''),
|
|
174
|
+
ts: Number.isFinite(Number(raw?.ts)) ? Number(raw.ts) : null,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
export const parseTextReplace = (raw) => ({
|
|
178
|
+
channel: String(raw?.channel || 'assistant').trim() || 'assistant',
|
|
179
|
+
content: String(raw?.content || ''),
|
|
180
|
+
ts: Number.isFinite(Number(raw?.ts)) ? Number(raw.ts) : null,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
export const parseDone = (raw) => ({
|
|
184
|
+
trace_id: String(raw?.trace_id || '').trim(),
|
|
185
|
+
session_id: String(raw?.session_id || '').trim(),
|
|
186
|
+
request_id: String(raw?.request_id || '').trim(),
|
|
187
|
+
status: String(raw?.status || 'done').trim() || 'done',
|
|
188
|
+
ts: Number.isFinite(Number(raw?.ts)) ? Number(raw.ts) : null,
|
|
189
|
+
});
|
|
190
|
+
|
|
148
191
|
export const parseTrace = (raw) => ({
|
|
149
192
|
trace: raw?.trace ?? raw?.detail ?? '',
|
|
150
193
|
summary: raw?.summary ?? raw?.label ?? '',
|
|
@@ -169,9 +212,49 @@ function pickFirstArray(payload, keys) {
|
|
|
169
212
|
return [];
|
|
170
213
|
}
|
|
171
214
|
|
|
215
|
+
function parseGuidedSession(rawGuidedSession) {
|
|
216
|
+
const payload = (rawGuidedSession && typeof rawGuidedSession === 'object' && !Array.isArray(rawGuidedSession))
|
|
217
|
+
? rawGuidedSession
|
|
218
|
+
: {};
|
|
219
|
+
const questionQueue = (payload.question_queue && typeof payload.question_queue === 'object' && !Array.isArray(payload.question_queue))
|
|
220
|
+
? payload.question_queue
|
|
221
|
+
: {};
|
|
222
|
+
const summary = (payload.summary && typeof payload.summary === 'object' && !Array.isArray(payload.summary))
|
|
223
|
+
? payload.summary
|
|
224
|
+
: {};
|
|
225
|
+
const decisionPoint = (payload.decision_point && typeof payload.decision_point === 'object' && !Array.isArray(payload.decision_point))
|
|
226
|
+
? payload.decision_point
|
|
227
|
+
: {};
|
|
228
|
+
return {
|
|
229
|
+
state: payload?.state ?? '',
|
|
230
|
+
active: payload?.active === true,
|
|
231
|
+
current_question: payload?.current_question && typeof payload.current_question === 'object' && !Array.isArray(payload.current_question)
|
|
232
|
+
? payload.current_question
|
|
233
|
+
: null,
|
|
234
|
+
question_queue: {
|
|
235
|
+
total: Number.isFinite(Number(questionQueue?.total)) ? Number(questionQueue.total) : 0,
|
|
236
|
+
answered: Number.isFinite(Number(questionQueue?.answered)) ? Number(questionQueue.answered) : 0,
|
|
237
|
+
remaining: Number.isFinite(Number(questionQueue?.remaining)) ? Number(questionQueue.remaining) : 0,
|
|
238
|
+
pending_fields: Array.isArray(questionQueue?.pending_fields) ? questionQueue.pending_fields : [],
|
|
239
|
+
},
|
|
240
|
+
summary: {
|
|
241
|
+
text: summary?.text ?? '',
|
|
242
|
+
ready: summary?.ready === true,
|
|
243
|
+
},
|
|
244
|
+
decision_point: {
|
|
245
|
+
active: decisionPoint?.active === true,
|
|
246
|
+
actions: Array.isArray(decisionPoint?.actions) ? decisionPoint.actions : [],
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
172
251
|
export const parseFieldProgress = (raw) => {
|
|
173
252
|
const payload = resolveModeEventPayload(raw);
|
|
174
253
|
return {
|
|
254
|
+
intake_session_id: payload?.intake_session_id ?? '',
|
|
255
|
+
intake_session_state: payload?.intake_session_state ?? '',
|
|
256
|
+
intake_session_completed: payload?.intake_session_completed === true,
|
|
257
|
+
flow_state: payload?.flow_state ?? payload?.collection_phase ?? '',
|
|
175
258
|
fields: payload?.fields ?? {},
|
|
176
259
|
sources: payload?.sources ?? {},
|
|
177
260
|
field_entries: Array.isArray(payload?.field_entries) ? payload.field_entries : [],
|
|
@@ -197,9 +280,29 @@ export const parseFieldProgress = (raw) => {
|
|
|
197
280
|
required_fields: Array.isArray(payload?.required_fields) ? payload.required_fields : [],
|
|
198
281
|
recommended_fields: Array.isArray(payload?.recommended_fields) ? payload.recommended_fields : [],
|
|
199
282
|
optional_fields: Array.isArray(payload?.optional_fields) ? payload.optional_fields : [],
|
|
283
|
+
intake_core_fields: Array.isArray(payload?.intake_core_fields) ? payload.intake_core_fields : [],
|
|
284
|
+
intake_supplementary_fields: Array.isArray(payload?.intake_supplementary_fields) ? payload.intake_supplementary_fields : [],
|
|
285
|
+
analysis_enrichment_fields: Array.isArray(payload?.analysis_enrichment_fields) ? payload.analysis_enrichment_fields : [],
|
|
200
286
|
field_priorities: payload?.field_priorities && typeof payload.field_priorities === 'object' && !Array.isArray(payload.field_priorities)
|
|
201
287
|
? payload.field_priorities
|
|
202
288
|
: {},
|
|
289
|
+
current_question: payload?.current_question && typeof payload.current_question === 'object' && !Array.isArray(payload.current_question)
|
|
290
|
+
? payload.current_question
|
|
291
|
+
: null,
|
|
292
|
+
question_progress: payload?.question_progress && typeof payload.question_progress === 'object' && !Array.isArray(payload.question_progress)
|
|
293
|
+
? payload.question_progress
|
|
294
|
+
: { current: 0, total: 0 },
|
|
295
|
+
collection_phase: payload?.collection_phase ?? '',
|
|
296
|
+
required_missing_count: Number.isFinite(payload?.required_missing_count) ? payload.required_missing_count : null,
|
|
297
|
+
required_coverage: Number.isFinite(Number(payload?.required_coverage)) ? Number(payload.required_coverage) : 0,
|
|
298
|
+
total_coverage: Number.isFinite(Number(payload?.total_coverage)) ? Number(payload.total_coverage) : 0,
|
|
299
|
+
analysis_entry_threshold: Number.isFinite(Number(payload?.analysis_entry_threshold)) ? Number(payload.analysis_entry_threshold) : 0.8,
|
|
300
|
+
analysis_entry_eligible: payload?.analysis_entry_eligible === true,
|
|
301
|
+
active_collecting: payload?.active_collecting === true,
|
|
302
|
+
ready_for_submit: payload?.ready_for_submit === true,
|
|
303
|
+
decision_point_active: payload?.decision_point_active === true,
|
|
304
|
+
has_active_question: payload?.has_active_question === true,
|
|
305
|
+
guided_session: parseGuidedSession(payload?.guided_session),
|
|
203
306
|
};
|
|
204
307
|
};
|
|
205
308
|
|
|
@@ -222,6 +325,9 @@ export const parseRequirementDraft = (raw) => {
|
|
|
222
325
|
: {};
|
|
223
326
|
|
|
224
327
|
return {
|
|
328
|
+
intake_session_id: payload?.intake_session_id ?? '',
|
|
329
|
+
intake_session_state: payload?.intake_session_state ?? '',
|
|
330
|
+
intake_session_completed: payload?.intake_session_completed === true,
|
|
225
331
|
mode_key: payload?.mode_key ?? '',
|
|
226
332
|
project_id: payload?.project_id ?? null,
|
|
227
333
|
message_excerpt: payload?.message_excerpt ?? '',
|
|
@@ -239,7 +345,11 @@ export const parseNextActions = (raw) => {
|
|
|
239
345
|
? payload.actions
|
|
240
346
|
: (Array.isArray(payload?.next_actions) ? payload.next_actions : []);
|
|
241
347
|
return {
|
|
348
|
+
intake_session_id: payload?.intake_session_id ?? '',
|
|
349
|
+
intake_session_state: payload?.intake_session_state ?? '',
|
|
350
|
+
intake_session_completed: payload?.intake_session_completed === true,
|
|
242
351
|
mode_key: payload?.mode_key ?? '',
|
|
352
|
+
flow_state: payload?.flow_state ?? payload?.collection_phase ?? '',
|
|
243
353
|
project_id: payload?.project_id ?? null,
|
|
244
354
|
message_excerpt: payload?.message_excerpt ?? '',
|
|
245
355
|
actions,
|
|
@@ -247,10 +357,37 @@ export const parseNextActions = (raw) => {
|
|
|
247
357
|
required_missing: Array.isArray(payload?.required_missing) ? payload.required_missing : [],
|
|
248
358
|
recommended_missing: Array.isArray(payload?.recommended_missing) ? payload.recommended_missing : [],
|
|
249
359
|
optional_missing: Array.isArray(payload?.optional_missing) ? payload.optional_missing : [],
|
|
360
|
+
intake_core_fields: Array.isArray(payload?.intake_core_fields) ? payload.intake_core_fields : [],
|
|
361
|
+
intake_supplementary_fields: Array.isArray(payload?.intake_supplementary_fields) ? payload.intake_supplementary_fields : [],
|
|
362
|
+
analysis_enrichment_fields: Array.isArray(payload?.analysis_enrichment_fields) ? payload.analysis_enrichment_fields : [],
|
|
250
363
|
ready_for_sourcing: payload?.ready_for_sourcing ?? null,
|
|
251
364
|
status: payload?.status ?? '',
|
|
252
365
|
reason: payload?.reason ?? '',
|
|
366
|
+
chooser_required: payload?.chooser_required === true,
|
|
367
|
+
blocking_reason: payload?.blocking_reason ?? '',
|
|
368
|
+
blocking: payload?.blocking && typeof payload.blocking === 'object' && !Array.isArray(payload.blocking)
|
|
369
|
+
? payload.blocking
|
|
370
|
+
: null,
|
|
371
|
+
target_field: payload?.target_field ?? '',
|
|
372
|
+
degrade_reason: Array.isArray(payload?.degrade_reason) ? payload.degrade_reason : [],
|
|
373
|
+
current_question: payload?.current_question && typeof payload.current_question === 'object' && !Array.isArray(payload.current_question)
|
|
374
|
+
? payload.current_question
|
|
375
|
+
: null,
|
|
376
|
+
question_progress: payload?.question_progress && typeof payload.question_progress === 'object' && !Array.isArray(payload.question_progress)
|
|
377
|
+
? payload.question_progress
|
|
378
|
+
: { current: 0, total: 0 },
|
|
379
|
+
collection_phase: payload?.collection_phase ?? '',
|
|
380
|
+
required_missing_count: Number.isFinite(payload?.required_missing_count) ? payload.required_missing_count : null,
|
|
253
381
|
render_hint: payload?.render_hint ?? 'next_actions',
|
|
382
|
+
required_coverage: Number.isFinite(Number(payload?.required_coverage)) ? Number(payload.required_coverage) : 0,
|
|
383
|
+
total_coverage: Number.isFinite(Number(payload?.total_coverage)) ? Number(payload.total_coverage) : 0,
|
|
384
|
+
analysis_entry_threshold: Number.isFinite(Number(payload?.analysis_entry_threshold)) ? Number(payload.analysis_entry_threshold) : 0.8,
|
|
385
|
+
analysis_entry_eligible: payload?.analysis_entry_eligible === true,
|
|
386
|
+
active_collecting: payload?.active_collecting === true,
|
|
387
|
+
ready_for_submit: payload?.ready_for_submit === true,
|
|
388
|
+
decision_point_active: payload?.decision_point_active === true,
|
|
389
|
+
has_active_question: payload?.has_active_question === true,
|
|
390
|
+
guided_session: parseGuidedSession(payload?.guided_session),
|
|
254
391
|
};
|
|
255
392
|
};
|
|
256
393
|
|
|
@@ -378,6 +515,151 @@ export const parseEvaluationReportReady = (raw) => {
|
|
|
378
515
|
};
|
|
379
516
|
};
|
|
380
517
|
|
|
518
|
+
export const parsePlanBlock = (raw) => {
|
|
519
|
+
const payload = resolveModeEventPayload(raw);
|
|
520
|
+
return {
|
|
521
|
+
...payload,
|
|
522
|
+
constraints: Array.isArray(payload?.constraints) ? payload.constraints : [],
|
|
523
|
+
plan: Array.isArray(payload?.plan) ? payload.plan : [],
|
|
524
|
+
acceptance: Array.isArray(payload?.acceptance) ? payload.acceptance : [],
|
|
525
|
+
actions: Array.isArray(payload?.actions) ? payload.actions : [],
|
|
526
|
+
};
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
export const parseCanvasState = (raw) => {
|
|
530
|
+
const payload = resolveModeEventPayload(raw);
|
|
531
|
+
const canvasState = payload?.canvas_state && typeof payload.canvas_state === 'object' && !Array.isArray(payload.canvas_state)
|
|
532
|
+
? payload.canvas_state
|
|
533
|
+
: {};
|
|
534
|
+
return {
|
|
535
|
+
...payload,
|
|
536
|
+
canvas_state: {
|
|
537
|
+
...canvasState,
|
|
538
|
+
versions: Array.isArray(canvasState.versions) ? canvasState.versions : [],
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
export const parseCanvasRevision = (raw) => {
|
|
544
|
+
const payload = resolveModeEventPayload(raw);
|
|
545
|
+
return {
|
|
546
|
+
...payload,
|
|
547
|
+
actions: Array.isArray(payload?.actions) ? payload.actions : [],
|
|
548
|
+
};
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
export const parseKnowledgeSearch = (raw) => {
|
|
552
|
+
const payload = resolveModeEventPayload(raw);
|
|
553
|
+
const sourceBreakdown = payload?.source_breakdown && typeof payload.source_breakdown === 'object'
|
|
554
|
+
&& !Array.isArray(payload.source_breakdown)
|
|
555
|
+
? payload.source_breakdown
|
|
556
|
+
: {};
|
|
557
|
+
return {
|
|
558
|
+
...payload,
|
|
559
|
+
run_id: payload?.run_id ?? '',
|
|
560
|
+
query: payload?.query ?? '',
|
|
561
|
+
result_count: Number.isFinite(payload?.result_count) ? payload.result_count : null,
|
|
562
|
+
source_breakdown: {
|
|
563
|
+
local: Number.isFinite(sourceBreakdown.local) ? sourceBreakdown.local : 0,
|
|
564
|
+
mcp: Number.isFinite(sourceBreakdown.mcp) ? sourceBreakdown.mcp : 0,
|
|
565
|
+
web: Number.isFinite(sourceBreakdown.web) ? sourceBreakdown.web : 0,
|
|
566
|
+
},
|
|
567
|
+
results: Array.isArray(payload?.results) ? payload.results : [],
|
|
568
|
+
};
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
export const parseKnowledgeCitation = (raw) => {
|
|
572
|
+
const payload = resolveModeEventPayload(raw);
|
|
573
|
+
return {
|
|
574
|
+
...payload,
|
|
575
|
+
run_id: payload?.run_id ?? '',
|
|
576
|
+
citations: Array.isArray(payload?.citations) ? payload.citations : [],
|
|
577
|
+
};
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
export const parseOrchestrationStatus = (raw) => {
|
|
581
|
+
const payload = resolveModeEventPayload(raw);
|
|
582
|
+
const activeWorkItem = payload?.active_work_item && typeof payload.active_work_item === 'object' && !Array.isArray(payload.active_work_item)
|
|
583
|
+
? payload.active_work_item
|
|
584
|
+
: null;
|
|
585
|
+
return {
|
|
586
|
+
trace_id: payload?.trace_id ?? '',
|
|
587
|
+
intake_session_id: payload?.intake_session_id ?? '',
|
|
588
|
+
intake_session_state: payload?.intake_session_state ?? '',
|
|
589
|
+
intake_session_completed: payload?.intake_session_completed === true,
|
|
590
|
+
current_stage: payload?.current_stage ?? '',
|
|
591
|
+
detected_intent: payload?.detected_intent && typeof payload.detected_intent === 'object' && !Array.isArray(payload.detected_intent)
|
|
592
|
+
? payload.detected_intent
|
|
593
|
+
: {},
|
|
594
|
+
confirmed_updates: Array.isArray(payload?.confirmed_updates) ? payload.confirmed_updates : [],
|
|
595
|
+
open_fields: Array.isArray(payload?.open_fields) ? payload.open_fields : [],
|
|
596
|
+
search_actions: Array.isArray(payload?.search_actions) ? payload.search_actions : [],
|
|
597
|
+
workspace_updates: payload?.workspace_updates && typeof payload.workspace_updates === 'object' && !Array.isArray(payload.workspace_updates)
|
|
598
|
+
? payload.workspace_updates
|
|
599
|
+
: {},
|
|
600
|
+
next_action: payload?.next_action && typeof payload.next_action === 'object' && !Array.isArray(payload.next_action)
|
|
601
|
+
? payload.next_action
|
|
602
|
+
: {},
|
|
603
|
+
next_actions: Array.isArray(payload?.next_actions) ? payload.next_actions : [],
|
|
604
|
+
active_collecting: payload?.active_collecting === true,
|
|
605
|
+
ready_for_submit: payload?.ready_for_submit === true,
|
|
606
|
+
required_coverage: Number.isFinite(Number(payload?.required_coverage)) ? Number(payload.required_coverage) : 0,
|
|
607
|
+
total_coverage: Number.isFinite(Number(payload?.total_coverage)) ? Number(payload.total_coverage) : 0,
|
|
608
|
+
analysis_entry_threshold: Number.isFinite(Number(payload?.analysis_entry_threshold)) ? Number(payload.analysis_entry_threshold) : 0.8,
|
|
609
|
+
analysis_entry_eligible: payload?.analysis_entry_eligible === true,
|
|
610
|
+
decision_point_active: payload?.decision_point_active === true,
|
|
611
|
+
has_active_question: payload?.has_active_question === true,
|
|
612
|
+
guided_session: parseGuidedSession(payload?.guided_session),
|
|
613
|
+
active_work_item: activeWorkItem,
|
|
614
|
+
active_fields: Array.isArray(payload?.active_fields) ? payload.active_fields : [],
|
|
615
|
+
hypotheses: Array.isArray(payload?.hypotheses) ? payload.hypotheses : [],
|
|
616
|
+
planner: payload?.planner && typeof payload.planner === 'object' && !Array.isArray(payload.planner)
|
|
617
|
+
? payload.planner
|
|
618
|
+
: {},
|
|
619
|
+
chooser: payload?.chooser && typeof payload.chooser === 'object' && !Array.isArray(payload.chooser)
|
|
620
|
+
? payload.chooser
|
|
621
|
+
: null,
|
|
622
|
+
stage_transition: payload?.stage_transition && typeof payload.stage_transition === 'object' && !Array.isArray(payload.stage_transition)
|
|
623
|
+
? payload.stage_transition
|
|
624
|
+
: {},
|
|
625
|
+
decision_basis: Array.isArray(payload?.decision_basis) ? payload.decision_basis : [],
|
|
626
|
+
blocking: payload?.blocking && typeof payload.blocking === 'object' && !Array.isArray(payload.blocking)
|
|
627
|
+
? payload.blocking
|
|
628
|
+
: {},
|
|
629
|
+
node: payload?.node && typeof payload.node === 'object' && !Array.isArray(payload.node)
|
|
630
|
+
? payload.node
|
|
631
|
+
: {},
|
|
632
|
+
node_id: payload?.node_id ?? '',
|
|
633
|
+
node_state: payload?.node_state ?? '',
|
|
634
|
+
node_reason: payload?.node_reason ?? '',
|
|
635
|
+
needs_user_choice: payload?.needs_user_choice === true,
|
|
636
|
+
conversation_intent_state: payload?.conversation_intent_state ?? '',
|
|
637
|
+
session_intent_stage: payload?.session_intent_stage ?? '',
|
|
638
|
+
intent_type: payload?.intent_type ?? '',
|
|
639
|
+
procurement_relevance: payload?.procurement_relevance === true,
|
|
640
|
+
consultative_procurement_qa: payload?.consultative_procurement_qa === true,
|
|
641
|
+
escalation_target: payload?.escalation_target ?? '',
|
|
642
|
+
intake_candidate: payload?.intake_candidate === true,
|
|
643
|
+
clarification_recommended: payload?.clarification_recommended === true,
|
|
644
|
+
analysis_ready: payload?.analysis_ready === true,
|
|
645
|
+
draft_seeded: payload?.draft_seeded === true,
|
|
646
|
+
missing_key_fields: Array.isArray(payload?.missing_key_fields) ? payload.missing_key_fields : [],
|
|
647
|
+
recommended_next_step: payload?.recommended_next_step ?? '',
|
|
648
|
+
intent_and_escalation_confidence: Number.isFinite(Number(payload?.intent_and_escalation_confidence))
|
|
649
|
+
? Number(payload.intent_and_escalation_confidence)
|
|
650
|
+
: 0,
|
|
651
|
+
intent_and_escalation_reason: payload?.intent_and_escalation_reason ?? '',
|
|
652
|
+
intent_escalation_policy: payload?.intent_escalation_policy && typeof payload.intent_escalation_policy === 'object' && !Array.isArray(payload.intent_escalation_policy)
|
|
653
|
+
? payload.intent_escalation_policy
|
|
654
|
+
: {},
|
|
655
|
+
intent_escalation_policy_source: payload?.intent_escalation_policy_source ?? '',
|
|
656
|
+
degrade_reason: Array.isArray(payload?.degrade_reason) ? payload.degrade_reason : [],
|
|
657
|
+
content_policy: payload?.content_policy && typeof payload.content_policy === 'object' && !Array.isArray(payload.content_policy)
|
|
658
|
+
? payload.content_policy
|
|
659
|
+
: {},
|
|
660
|
+
};
|
|
661
|
+
};
|
|
662
|
+
|
|
381
663
|
export const parseWorkspaceActivation = (raw) => ({
|
|
382
664
|
function_type: raw?.function_type ?? '',
|
|
383
665
|
mode: raw?.mode ?? '',
|
|
@@ -420,6 +702,166 @@ export const parseInstanceProfile = (raw) => ({
|
|
|
420
702
|
ui_labels: raw?.instance_profile?.ui_labels ?? raw?.ui_labels ?? {},
|
|
421
703
|
});
|
|
422
704
|
|
|
705
|
+
export const parseModeRuntime = (raw) => ({
|
|
706
|
+
mode_key: raw?.mode_key ?? '',
|
|
707
|
+
mode_state: raw?.mode_state ?? '',
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
export const parseAgentRuntime = (raw) => ({
|
|
711
|
+
trace_id: raw?.trace_id ?? '',
|
|
712
|
+
session_id: raw?.session_id ?? '',
|
|
713
|
+
intent: raw?.intent ?? '',
|
|
714
|
+
function_type: raw?.function_type ?? '',
|
|
715
|
+
mode_key: raw?.mode_key ?? '',
|
|
716
|
+
mode_state: raw?.mode_state ?? '',
|
|
717
|
+
agent_step_count: Number.isFinite(raw?.agent_step_count) ? raw.agent_step_count : 0,
|
|
718
|
+
agent_step: Array.isArray(raw?.agent_step) ? raw.agent_step : [],
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
export const parseSkillRuntime = (raw) => ({
|
|
722
|
+
trace_id: raw?.trace_id ?? '',
|
|
723
|
+
session_id: raw?.session_id ?? '',
|
|
724
|
+
intent: raw?.intent ?? '',
|
|
725
|
+
function_type: raw?.function_type ?? '',
|
|
726
|
+
mode_key: raw?.mode_key ?? '',
|
|
727
|
+
mode_state: raw?.mode_state ?? '',
|
|
728
|
+
skill_call_count: Number.isFinite(raw?.skill_call_count) ? raw.skill_call_count : 0,
|
|
729
|
+
skill_call: Array.isArray(raw?.skill_call) ? raw.skill_call : [],
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
export const parseModeSwitchReason = (raw) => ({
|
|
733
|
+
mode_key: raw?.mode_key ?? '',
|
|
734
|
+
intent: raw?.intent ?? '',
|
|
735
|
+
source: raw?.source ?? '',
|
|
736
|
+
previous_mode: raw?.previous_mode ?? '',
|
|
737
|
+
manual_mode: raw?.manual_mode ?? '',
|
|
738
|
+
current_mode: raw?.current_mode ?? '',
|
|
739
|
+
intent_override: raw?.intent_override ?? '',
|
|
740
|
+
summary: raw?.summary ?? '',
|
|
741
|
+
});
|
|
742
|
+
|
|
423
743
|
export const parseSSEError = (raw) => ({
|
|
424
744
|
message: raw?.message ?? '发生错误',
|
|
425
745
|
});
|
|
746
|
+
|
|
747
|
+
export const PATCH_SCOPE_AUTHORITATIVE = Object.freeze([
|
|
748
|
+
'session',
|
|
749
|
+
'assistant_reply',
|
|
750
|
+
'primary_flow',
|
|
751
|
+
'checkpoint',
|
|
752
|
+
'intent_state',
|
|
753
|
+
'question',
|
|
754
|
+
'confirmed_fields',
|
|
755
|
+
'missing_fields',
|
|
756
|
+
'next_actions',
|
|
757
|
+
'analysis',
|
|
758
|
+
'observation',
|
|
759
|
+
'capabilities.field_retrieval',
|
|
760
|
+
'errors',
|
|
761
|
+
]);
|
|
762
|
+
|
|
763
|
+
export const PATCH_SCOPE_LEGACY_COMPAT = Object.freeze([
|
|
764
|
+
'current_question',
|
|
765
|
+
'recommendation_summary',
|
|
766
|
+
]);
|
|
767
|
+
|
|
768
|
+
const PATCH_SCOPE_ALLOWLIST = new Set([
|
|
769
|
+
...PATCH_SCOPE_AUTHORITATIVE,
|
|
770
|
+
...PATCH_SCOPE_LEGACY_COMPAT,
|
|
771
|
+
]);
|
|
772
|
+
|
|
773
|
+
function getByScopePath(payload, scope) {
|
|
774
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
775
|
+
return undefined;
|
|
776
|
+
}
|
|
777
|
+
const segments = String(scope || '').split('.').filter(Boolean);
|
|
778
|
+
if (!segments.length) {
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
let current = payload;
|
|
782
|
+
for (const segment of segments) {
|
|
783
|
+
if (!current || typeof current !== 'object' || Array.isArray(current) || !(segment in current)) {
|
|
784
|
+
return undefined;
|
|
785
|
+
}
|
|
786
|
+
current = current[segment];
|
|
787
|
+
}
|
|
788
|
+
return current;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
export const parsePatchEvent = (raw) => {
|
|
792
|
+
const envelope = (raw && typeof raw === 'object' && !Array.isArray(raw)) ? raw : {};
|
|
793
|
+
const patchScope = Array.isArray(envelope.patch_scope)
|
|
794
|
+
? envelope.patch_scope.map((item) => String(item || '').trim()).filter(Boolean)
|
|
795
|
+
: [];
|
|
796
|
+
const payload = (envelope.payload && typeof envelope.payload === 'object' && !Array.isArray(envelope.payload))
|
|
797
|
+
? envelope.payload
|
|
798
|
+
: {};
|
|
799
|
+
const hasPayload = Object.keys(payload).length > 0;
|
|
800
|
+
const hasUnknownScope = patchScope.some((item) => !(PATCH_SCOPE_ALLOWLIST.has(item) || item.startsWith('confirmed_fields.')));
|
|
801
|
+
const hasScopePayloadMismatch = patchScope.some((scope) => {
|
|
802
|
+
const direct = payload[scope];
|
|
803
|
+
const byPath = getByScopePath(payload, scope);
|
|
804
|
+
const lastSegment = String(scope).split('.').slice(-1)[0];
|
|
805
|
+
const leaf = payload[lastSegment];
|
|
806
|
+
return direct === undefined && byPath === undefined && leaf === undefined;
|
|
807
|
+
});
|
|
808
|
+
const legacyPatchScope = patchScope.filter((scope) => PATCH_SCOPE_LEGACY_COMPAT.includes(scope));
|
|
809
|
+
const authoritativePatchScope = patchScope.filter(
|
|
810
|
+
(scope) => PATCH_SCOPE_AUTHORITATIVE.includes(scope) || scope.startsWith('confirmed_fields.')
|
|
811
|
+
);
|
|
812
|
+
const invalidReason = (() => {
|
|
813
|
+
if (envelope.message_type !== 'patch_event') {
|
|
814
|
+
return 'invalid_message_type';
|
|
815
|
+
}
|
|
816
|
+
if (!['ack', 'patch', 'error', 'final'].includes(String(envelope.event_type || '').trim())) {
|
|
817
|
+
return 'invalid_event_type';
|
|
818
|
+
}
|
|
819
|
+
if (hasPayload && patchScope.length === 0) {
|
|
820
|
+
return 'payload_without_patch_scope';
|
|
821
|
+
}
|
|
822
|
+
if (hasUnknownScope) {
|
|
823
|
+
return 'unknown_patch_scope';
|
|
824
|
+
}
|
|
825
|
+
if (patchScope.length > 0 && hasScopePayloadMismatch) {
|
|
826
|
+
return 'patch_scope_payload_mismatch';
|
|
827
|
+
}
|
|
828
|
+
return '';
|
|
829
|
+
})();
|
|
830
|
+
return {
|
|
831
|
+
contract_version: String(envelope.contract_version || 'flare.v1'),
|
|
832
|
+
message_type: 'patch_event',
|
|
833
|
+
event_type: String(envelope.event_type || '').trim(),
|
|
834
|
+
sequence: Number.isFinite(Number(envelope.sequence)) ? Number(envelope.sequence) : 0,
|
|
835
|
+
trace_id: String(envelope.trace_id || '').trim(),
|
|
836
|
+
client_request_id: String(envelope.client_request_id || '').trim(),
|
|
837
|
+
intake_session_id: String(
|
|
838
|
+
payload?.primary_flow?.intake_session_id
|
|
839
|
+
|| payload?.checkpoint?.intake_session_id
|
|
840
|
+
|| payload?.next_actions?.intake_session_id
|
|
841
|
+
|| payload?.question?.intake_session_id
|
|
842
|
+
|| payload?.intake_session_id
|
|
843
|
+
|| ''
|
|
844
|
+
).trim(),
|
|
845
|
+
intake_session_state: String(
|
|
846
|
+
payload?.primary_flow?.intake_session_state
|
|
847
|
+
|| payload?.next_actions?.intake_session_state
|
|
848
|
+
|| payload?.intake_session_state
|
|
849
|
+
|| ''
|
|
850
|
+
).trim(),
|
|
851
|
+
intake_session_completed: Boolean(
|
|
852
|
+
payload?.primary_flow?.intake_session_completed
|
|
853
|
+
|| payload?.next_actions?.intake_session_completed
|
|
854
|
+
|| payload?.intake_session_completed
|
|
855
|
+
),
|
|
856
|
+
session: (
|
|
857
|
+
envelope.session && typeof envelope.session === 'object' && !Array.isArray(envelope.session)
|
|
858
|
+
) ? envelope.session : { session_id: '', turn_id: '' },
|
|
859
|
+
patch_scope: patchScope,
|
|
860
|
+
authoritative_patch_scope: authoritativePatchScope,
|
|
861
|
+
legacy_patch_scope: legacyPatchScope,
|
|
862
|
+
payload,
|
|
863
|
+
final: envelope.final === true,
|
|
864
|
+
invalid: Boolean(invalidReason),
|
|
865
|
+
invalid_reason: invalidReason,
|
|
866
|
+
};
|
|
867
|
+
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { useRef, useState, useCallback } from 'react';
|
|
1
|
+
import { useRef, useState, useCallback, useEffect } from 'react';
|
|
2
2
|
import { SSEClient } from './sse-client.js';
|
|
3
3
|
import {
|
|
4
|
+
parseAck,
|
|
4
5
|
parseAgentStatus,
|
|
5
6
|
parseThinkingTrace,
|
|
6
7
|
parseExecutionTrace,
|
|
7
8
|
parseContent,
|
|
9
|
+
parseDone,
|
|
10
|
+
parsePatch,
|
|
11
|
+
parsePhaseEvent,
|
|
12
|
+
parseTextDelta,
|
|
13
|
+
parseTextReplace,
|
|
8
14
|
parseTrace,
|
|
9
15
|
parseFieldProgress,
|
|
10
16
|
parseDoc,
|
|
@@ -21,6 +27,17 @@ import {
|
|
|
21
27
|
parseCapabilitySuggestion,
|
|
22
28
|
parseFieldsUpdated,
|
|
23
29
|
parseInstanceProfile,
|
|
30
|
+
parseModeRuntime,
|
|
31
|
+
parseAgentRuntime,
|
|
32
|
+
parseSkillRuntime,
|
|
33
|
+
parseModeSwitchReason,
|
|
34
|
+
parseKnowledgeSearch,
|
|
35
|
+
parseKnowledgeCitation,
|
|
36
|
+
parseOrchestrationStatus,
|
|
37
|
+
parseCanvasState,
|
|
38
|
+
parseCanvasRevision,
|
|
39
|
+
parsePlanBlock,
|
|
40
|
+
parsePatchEvent,
|
|
24
41
|
parseSSEError,
|
|
25
42
|
} from './sse-events.js';
|
|
26
43
|
|
|
@@ -42,19 +59,28 @@ export function useSSEStream(sessionId) {
|
|
|
42
59
|
const lastMessageRef = useRef({ content: '', handlers: {} });
|
|
43
60
|
|
|
44
61
|
const send = useCallback(async (content, handlers = {}, options = {}) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
62
|
+
const hasSessionOverride = Object.prototype.hasOwnProperty.call(options, 'sessionIdOverride');
|
|
63
|
+
const resolvedSessionId = hasSessionOverride ? options.sessionIdOverride : sessionId;
|
|
64
|
+
if (!content.trim()) return;
|
|
47
65
|
|
|
48
66
|
lastMessageRef.current = { content, handlers, options };
|
|
49
67
|
setError(null);
|
|
50
68
|
accumulatedRef.current = '';
|
|
51
69
|
setLoading(true);
|
|
70
|
+
let latestSessionId = String(resolvedSessionId || '').trim();
|
|
52
71
|
|
|
53
72
|
const {
|
|
73
|
+
onAck,
|
|
54
74
|
onAgentStatus,
|
|
55
75
|
onThinkingTrace,
|
|
56
76
|
onExecutionTrace,
|
|
57
77
|
onContent,
|
|
78
|
+
onTextReplace,
|
|
79
|
+
onPhaseStart,
|
|
80
|
+
onPhaseUpdate,
|
|
81
|
+
onPhaseEnd,
|
|
82
|
+
onPatch,
|
|
83
|
+
onDone,
|
|
58
84
|
onTrace,
|
|
59
85
|
onWorkspaceActivation,
|
|
60
86
|
onUICards,
|
|
@@ -69,14 +95,40 @@ export function useSSEStream(sessionId) {
|
|
|
69
95
|
onCategoryIdentified,
|
|
70
96
|
onFieldsUpdated,
|
|
71
97
|
onInstanceProfile,
|
|
98
|
+
onModeRuntime,
|
|
99
|
+
onAgentRuntime,
|
|
100
|
+
onSkillRuntime,
|
|
101
|
+
onModeSwitchReason,
|
|
102
|
+
onKnowledgeSearch,
|
|
103
|
+
onKnowledgeCitation,
|
|
104
|
+
onOrchestrationStatus,
|
|
105
|
+
onCanvasState,
|
|
106
|
+
onCanvasRevision,
|
|
107
|
+
onPlanBlock,
|
|
72
108
|
onDoc,
|
|
73
109
|
onCapabilitySuggestion,
|
|
110
|
+
onPatchEvent,
|
|
74
111
|
onError,
|
|
75
112
|
onComplete,
|
|
76
113
|
} = handlers;
|
|
77
114
|
|
|
78
115
|
const eventHandler = (event) => {
|
|
79
116
|
switch (event.type) {
|
|
117
|
+
case 'ack':
|
|
118
|
+
onAck?.(parseAck(event.data));
|
|
119
|
+
break;
|
|
120
|
+
case 'phase.start':
|
|
121
|
+
onPhaseStart?.(parsePhaseEvent(event.data));
|
|
122
|
+
break;
|
|
123
|
+
case 'phase.update':
|
|
124
|
+
onPhaseUpdate?.(parsePhaseEvent(event.data));
|
|
125
|
+
break;
|
|
126
|
+
case 'phase.end':
|
|
127
|
+
onPhaseEnd?.(parsePhaseEvent(event.data));
|
|
128
|
+
break;
|
|
129
|
+
case 'patch':
|
|
130
|
+
onPatch?.(parsePatch(event.data));
|
|
131
|
+
break;
|
|
80
132
|
case 'agent_status':
|
|
81
133
|
onAgentStatus?.(parseAgentStatus(event.data));
|
|
82
134
|
break;
|
|
@@ -96,6 +148,25 @@ export function useSSEStream(sessionId) {
|
|
|
96
148
|
onContent?.(chunk);
|
|
97
149
|
break;
|
|
98
150
|
}
|
|
151
|
+
case 'text.delta': {
|
|
152
|
+
const parsed = parseTextDelta(event.data);
|
|
153
|
+
if (parsed.channel === 'assistant' && parsed.delta) {
|
|
154
|
+
accumulatedRef.current += parsed.delta;
|
|
155
|
+
onContent?.(parsed.delta);
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case 'text.replace': {
|
|
160
|
+
const parsed = parseTextReplace(event.data);
|
|
161
|
+
if (parsed.channel === 'assistant') {
|
|
162
|
+
accumulatedRef.current = parsed.content;
|
|
163
|
+
onTextReplace?.(parsed.content);
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case 'done':
|
|
168
|
+
onDone?.(parseDone(event.data));
|
|
169
|
+
break;
|
|
99
170
|
case 'trace':
|
|
100
171
|
onTrace?.(parseTrace(event.data));
|
|
101
172
|
break;
|
|
@@ -138,12 +209,52 @@ export function useSSEStream(sessionId) {
|
|
|
138
209
|
case 'instance_profile':
|
|
139
210
|
onInstanceProfile?.(parseInstanceProfile(event.data));
|
|
140
211
|
break;
|
|
212
|
+
case 'mode_runtime':
|
|
213
|
+
onModeRuntime?.(parseModeRuntime(event.data));
|
|
214
|
+
break;
|
|
215
|
+
case 'agent_runtime':
|
|
216
|
+
onAgentRuntime?.(parseAgentRuntime(event.data));
|
|
217
|
+
break;
|
|
218
|
+
case 'skill_runtime':
|
|
219
|
+
onSkillRuntime?.(parseSkillRuntime(event.data));
|
|
220
|
+
break;
|
|
221
|
+
case 'mode_switch_reason':
|
|
222
|
+
onModeSwitchReason?.(parseModeSwitchReason(event.data));
|
|
223
|
+
break;
|
|
224
|
+
case 'knowledge_search':
|
|
225
|
+
onKnowledgeSearch?.(parseKnowledgeSearch(event.data));
|
|
226
|
+
break;
|
|
227
|
+
case 'knowledge_citation':
|
|
228
|
+
onKnowledgeCitation?.(parseKnowledgeCitation(event.data));
|
|
229
|
+
break;
|
|
230
|
+
case 'orchestration_status':
|
|
231
|
+
onOrchestrationStatus?.(parseOrchestrationStatus(event.data));
|
|
232
|
+
break;
|
|
233
|
+
case 'canvas_state':
|
|
234
|
+
onCanvasState?.(parseCanvasState(event.data));
|
|
235
|
+
break;
|
|
236
|
+
case 'canvas_revision':
|
|
237
|
+
onCanvasRevision?.(parseCanvasRevision(event.data));
|
|
238
|
+
break;
|
|
239
|
+
case 'plan_block':
|
|
240
|
+
onPlanBlock?.(parsePlanBlock(event.data));
|
|
241
|
+
break;
|
|
141
242
|
case 'doc':
|
|
142
243
|
onDoc?.(parseDoc(event.data));
|
|
143
244
|
break;
|
|
144
245
|
case 'capability_suggestion':
|
|
145
246
|
onCapabilitySuggestion?.(parseCapabilitySuggestion(event.data));
|
|
146
247
|
break;
|
|
248
|
+
case 'patch_event':
|
|
249
|
+
{
|
|
250
|
+
const parsedPatchEvent = parsePatchEvent(event.data);
|
|
251
|
+
const patchSessionId = String(parsedPatchEvent?.session?.session_id || '').trim();
|
|
252
|
+
if (patchSessionId) {
|
|
253
|
+
latestSessionId = patchSessionId;
|
|
254
|
+
}
|
|
255
|
+
onPatchEvent?.(parsedPatchEvent);
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
147
258
|
case 'error':
|
|
148
259
|
onError?.(parseSSEError(event.data));
|
|
149
260
|
break;
|
|
@@ -152,9 +263,11 @@ export function useSSEStream(sessionId) {
|
|
|
152
263
|
}
|
|
153
264
|
};
|
|
154
265
|
|
|
155
|
-
const completeHandler = () => {
|
|
266
|
+
const completeHandler = async () => {
|
|
156
267
|
setLoading(false);
|
|
157
|
-
onComplete?.(accumulatedRef.current
|
|
268
|
+
await onComplete?.(accumulatedRef.current, {
|
|
269
|
+
sessionId: latestSessionId || null,
|
|
270
|
+
});
|
|
158
271
|
accumulatedRef.current = '';
|
|
159
272
|
};
|
|
160
273
|
|
|
@@ -167,21 +280,51 @@ export function useSSEStream(sessionId) {
|
|
|
167
280
|
};
|
|
168
281
|
|
|
169
282
|
const requestOptions = {
|
|
170
|
-
sessionId: resolvedSessionId,
|
|
283
|
+
sessionId: String(resolvedSessionId || '').trim() || null,
|
|
171
284
|
content,
|
|
172
285
|
enabledCapabilities: Array.isArray(options.enabledCapabilities) ? options.enabledCapabilities : [],
|
|
286
|
+
modeKey: typeof options.modeKey === 'string' ? options.modeKey : '',
|
|
287
|
+
manualModeKey: typeof options.manualModeKey === 'string' ? options.manualModeKey : '',
|
|
288
|
+
command: typeof options.command === 'string' ? options.command : '',
|
|
289
|
+
clientRequestId: typeof options.clientRequestId === 'string' ? options.clientRequestId : '',
|
|
290
|
+
contractVersion: typeof options.contractVersion === 'string' ? options.contractVersion : '',
|
|
291
|
+
lastTurnId: typeof options.lastTurnId === 'string' ? options.lastTurnId : '',
|
|
292
|
+
payloadExtra: (
|
|
293
|
+
options.payloadExtra
|
|
294
|
+
&& typeof options.payloadExtra === 'object'
|
|
295
|
+
&& !Array.isArray(options.payloadExtra)
|
|
296
|
+
)
|
|
297
|
+
? options.payloadExtra
|
|
298
|
+
: {},
|
|
173
299
|
endpoint: options.endpoint,
|
|
174
300
|
baseUrl: options.baseUrl,
|
|
175
301
|
url: options.url,
|
|
302
|
+
token: typeof options.token === 'string' ? options.token : '',
|
|
303
|
+
auth: (
|
|
304
|
+
options.auth
|
|
305
|
+
&& typeof options.auth === 'object'
|
|
306
|
+
&& !Array.isArray(options.auth)
|
|
307
|
+
) ? options.auth : undefined,
|
|
308
|
+
onTiming: typeof options.onTiming === 'function' ? options.onTiming : undefined,
|
|
176
309
|
};
|
|
177
310
|
|
|
178
311
|
if (!requestOptions.enabledCapabilities.length) {
|
|
179
312
|
delete requestOptions.enabledCapabilities;
|
|
180
313
|
}
|
|
314
|
+
if (!requestOptions.modeKey) delete requestOptions.modeKey;
|
|
315
|
+
if (!requestOptions.manualModeKey) delete requestOptions.manualModeKey;
|
|
316
|
+
if (!requestOptions.command) delete requestOptions.command;
|
|
317
|
+
if (!requestOptions.clientRequestId) delete requestOptions.clientRequestId;
|
|
318
|
+
if (!requestOptions.contractVersion) delete requestOptions.contractVersion;
|
|
319
|
+
if (!requestOptions.lastTurnId) delete requestOptions.lastTurnId;
|
|
320
|
+
if (!Object.keys(requestOptions.payloadExtra || {}).length) delete requestOptions.payloadExtra;
|
|
181
321
|
|
|
182
322
|
if (!requestOptions.endpoint) delete requestOptions.endpoint;
|
|
183
323
|
if (!requestOptions.baseUrl) delete requestOptions.baseUrl;
|
|
184
324
|
if (!requestOptions.url) delete requestOptions.url;
|
|
325
|
+
if (!requestOptions.token) delete requestOptions.token;
|
|
326
|
+
if (!requestOptions.auth) delete requestOptions.auth;
|
|
327
|
+
if (!requestOptions.onTiming) delete requestOptions.onTiming;
|
|
185
328
|
|
|
186
329
|
await clientRef.current.sendMessage(
|
|
187
330
|
requestOptions,
|
|
@@ -203,6 +346,10 @@ export function useSSEStream(sessionId) {
|
|
|
203
346
|
accumulatedRef.current = '';
|
|
204
347
|
}, []);
|
|
205
348
|
|
|
349
|
+
useEffect(() => () => {
|
|
350
|
+
clientRef.current.abort();
|
|
351
|
+
}, []);
|
|
352
|
+
|
|
206
353
|
return { send, loading, error, retry, abort };
|
|
207
354
|
}
|
|
208
355
|
|