flare-chat-core 0.2.3 → 0.2.4

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.
Files changed (122) hide show
  1. package/dist/index.js +6343 -0
  2. package/package.json +19 -22
  3. package/docs/CAPABILITY-INVENTORY.md +0 -42
  4. package/docs/CHAT-CORE-BOUNDARY.md +0 -47
  5. package/docs/CORE-APP-REALIGNMENT-WORKLOAD-2026-04-18.md +0 -86
  6. package/docs/SSOT-CHAT-CORE-BOUNDARY.md +0 -73
  7. package/docs/SSOT-CHAT-CORE-DATAFLOW.md +0 -97
  8. package/index.html +0 -12
  9. package/scripts/check.sh +0 -15
  10. package/src/adapters/index.js +0 -6
  11. package/src/adapters/message-api.adapter.js +0 -59
  12. package/src/adapters/session-api.adapter.js +0 -133
  13. package/src/adapters/session-message-api.http.js +0 -161
  14. package/src/adapters/session-message-api.js +0 -34
  15. package/src/adapters/session-message-api.normalize-source-record.test.mjs +0 -180
  16. package/src/adapters/session-message-api.normalizers.js +0 -153
  17. package/src/adapters/source-api.adapter.js +0 -135
  18. package/src/adapters/sse-client.js +0 -244
  19. package/src/adapters/sse-event-dispatcher.js +0 -121
  20. package/src/app/App.jsx +0 -11
  21. package/src/app/AppProviders.jsx +0 -12
  22. package/src/app/ChatWorkspaceScreen.jsx +0 -33
  23. package/src/app/WorkspaceLayout.jsx +0 -190
  24. package/src/app/components/AppCanvasPanel.jsx +0 -64
  25. package/src/app/components/TriggerThresholdPopoverContent.jsx +0 -122
  26. package/src/app/components/WorkspaceBodySection.jsx +0 -156
  27. package/src/app/components/WorkspaceMainPane.jsx +0 -121
  28. package/src/app/components/WorkspaceSessionPane.jsx +0 -70
  29. package/src/app/components/WorkspaceTopBarSection.jsx +0 -71
  30. package/src/app/core-chat-entry/ComposerSectionNode.jsx +0 -241
  31. package/src/app/core-chat-entry/attachmentSendRefs.js +0 -154
  32. package/src/app/core-chat-entry/attachmentSendRefs.test.mjs +0 -101
  33. package/src/app/core-chat-entry/composerActionRouter.js +0 -26
  34. package/src/app/core-chat-entry/constants.js +0 -108
  35. package/src/app/core-chat-entry/selectors.js +0 -28
  36. package/src/app/core-chat-entry/useAppActionErrorGuards.js +0 -68
  37. package/src/app/core-chat-entry/useChatCorePipelines.js +0 -110
  38. package/src/app/core-chat-entry/useComposerModeSuggestion.js +0 -89
  39. package/src/app/core-chat-entry/useDevCapabilityStatusNote.js +0 -22
  40. package/src/app/core-chat-entry/useProjectNameEditing.js +0 -41
  41. package/src/app/core-chat-entry/useProjectSourceUpload.js +0 -341
  42. package/src/app/core-chat-entry/useRealApiReadinessGate.js +0 -103
  43. package/src/app/core-chat-entry/useUnavailableActionError.js +0 -29
  44. package/src/app/core-chat-entry/useWorkspaceCanvasController.jsx +0 -177
  45. package/src/app/core-chat-entry/useWorkspaceCanvasProjection.jsx +0 -171
  46. package/src/app/core-chat-entry/useWorkspaceComposerController.jsx +0 -199
  47. package/src/app/core-chat-entry/useWorkspaceController.jsx +0 -226
  48. package/src/app/core-chat-entry/useWorkspacePanels.js +0 -55
  49. package/src/app/hooks/useComposerAttachmentSync.js +0 -223
  50. package/src/app/hooks/useComposerChooserHandlers.js +0 -52
  51. package/src/app/hooks/useSendWithContextRefs.js +0 -140
  52. package/src/app/hooks/useSendWithContextRefs.test.mjs +0 -29
  53. package/src/app/hooks/useUserThresholdProfile.js +0 -121
  54. package/src/app/index.js +0 -1
  55. package/src/app/selectors/assistantTextSelector.js +0 -73
  56. package/src/app/selectors/canvasEvidenceSummarySelector.js +0 -28
  57. package/src/app/selectors/canvasReportTemplateSelector.js +0 -28
  58. package/src/app/selectors/canvasTabsSelector.js +0 -58
  59. package/src/app/selectors/evidenceProjectionSelector.js +0 -175
  60. package/src/app/selectors/evidenceProjectionSelector.test.mjs +0 -107
  61. package/src/app/selectors/modeSuggestionSelector.js +0 -50
  62. package/src/chat-core/app/mockRuntime.js +0 -291
  63. package/src/chat-core/app/useAppStream.js +0 -187
  64. package/src/chat-core/app/useAppStream.refs.test.mjs +0 -44
  65. package/src/chat-core/app/useAppStream.request-body.test.mjs +0 -116
  66. package/src/chat-core/app/useCoreChatApp.js +0 -115
  67. package/src/chat-core/facade/useBasicConversationFacade.js +0 -280
  68. package/src/chat-core/index.js +0 -14
  69. package/src/chat-core/input/useChatInput.js +0 -103
  70. package/src/chat-core/messages/buildTimelineItems.analysis-route.test.mjs +0 -36
  71. package/src/chat-core/messages/buildTimelineItems.js +0 -201
  72. package/src/chat-core/messages/buildTimelineItems.knowledge-citation.test.mjs +0 -182
  73. package/src/chat-core/messages/contextUsageDefaults.js +0 -3
  74. package/src/chat-core/messages/contextUsageViewModel.js +0 -147
  75. package/src/chat-core/messages/contextUsageViewModel.test.mjs +0 -74
  76. package/src/chat-core/messages/useContextUsageViewModel.js +0 -41
  77. package/src/chat-core/orchestration/useBasicSendHandler.js +0 -55
  78. package/src/chat-core/pipelines/build-action-request.js +0 -46
  79. package/src/chat-core/pipelines/build-stream-request.js +0 -74
  80. package/src/chat-core/pipelines/entity-extraction.js +0 -159
  81. package/src/chat-core/pipelines/preprocess-message.js +0 -16
  82. package/src/chat-core/pipelines/stream-persist-utils.js +0 -32
  83. package/src/chat-core/pipelines/transport/send-mock-stream.js +0 -86
  84. package/src/chat-core/pipelines/transport/send-real-stream.js +0 -330
  85. package/src/chat-core/pipelines/transport/send-real-stream.test.mjs +0 -27
  86. package/src/chat-core/pipelines/transport/send-sourcing-search.js +0 -86
  87. package/src/chat-core/pipelines/transport/send-sourcing-search.test.mjs +0 -14
  88. package/src/chat-core/pipelines/transport/sourcing-response-templates.js +0 -55
  89. package/src/chat-core/pipelines/transport/sourcing-search-api.js +0 -155
  90. package/src/chat-core/runtime/runtimeMode.js +0 -69
  91. package/src/chat-core/session/chatSessionActionTypes.js +0 -24
  92. package/src/chat-core/session/chatSessionReducer.js +0 -352
  93. package/src/chat-core/session/chatSessionReducer.streaming-done.test.mjs +0 -39
  94. package/src/chat-core/session/index.js +0 -2
  95. package/src/chat-core/session/sessionActionsMessages.js +0 -44
  96. package/src/chat-core/session/sessionActionsSessionCrud.js +0 -131
  97. package/src/chat-core/session/sessionActionsStreaming.js +0 -80
  98. package/src/chat-core/session/sessionActionsUiState.js +0 -51
  99. package/src/chat-core/session/useChatSessionReducer.js +0 -131
  100. package/src/chat-core/session/useSessionListController.js +0 -67
  101. package/src/chat-core/stream/sse-client.js +0 -1
  102. package/src/chat-core/stream/sse-event-dispatcher.js +0 -1
  103. package/src/chat-core/stream/sse-events.js +0 -1
  104. package/src/chat-core/stream/useSSEStream.js +0 -1
  105. package/src/chat-core/stream/useStreamSendController.js +0 -46
  106. package/src/contracts/context-ssot.js +0 -47
  107. package/src/contracts/index.js +0 -1
  108. package/src/contracts/sse-events/base-parsers.js +0 -79
  109. package/src/contracts/sse-events/domain-parsers.js +0 -3
  110. package/src/contracts/sse-events/internal-normalizers.js +0 -143
  111. package/src/contracts/sse-events/parsers-intake.js +0 -235
  112. package/src/contracts/sse-events/parsers-runtime.js +0 -37
  113. package/src/contracts/sse-events/parsers-sourcing.js +0 -179
  114. package/src/contracts/sse-events/patch-event-parser.js +0 -121
  115. package/src/contracts/sse-events/runtime-parsers.js +0 -79
  116. package/src/contracts/sse-events.js +0 -4
  117. package/src/index.js +0 -6
  118. package/src/main.jsx +0 -28
  119. package/src/orchestration/index.js +0 -6
  120. package/src/orchestration/useSSEStream.js +0 -221
  121. package/src/state/index.js +0 -4
  122. package/vite.config.js +0 -36
@@ -1,330 +0,0 @@
1
- import {
2
- parseAgentStatus,
3
- parseExecutionTrace,
4
- parseKnowledgeSearch,
5
- parseThinkingTrace,
6
- parseSourcingCandidates,
7
- parseKnowledgeCitation,
8
- } from '../../../contracts/sse-events.js';
9
- import { debugCoreLog, getCoreRuntimeMode } from '../../runtime/runtimeMode.js';
10
- import { buildRealStreamRequestBody, resolvePayloadExtra } from '../build-stream-request.js';
11
- import { buildRealActionRequestBody } from '../build-action-request.js';
12
- import { collectSourceIdsFromRefs, resolvePersistExchangeResult } from '../stream-persist-utils.js';
13
- import { requestSourcingSearch } from './send-sourcing-search.js';
14
-
15
- function joinUrl(baseUrl = '', path = '') {
16
- if (!baseUrl) {
17
- return path || '';
18
- }
19
- if (!path) {
20
- return baseUrl;
21
- }
22
- return `${baseUrl.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
23
- }
24
-
25
- export function shouldUseSourcingSearchPath(options = {}, content = '') {
26
- void content;
27
- const modeKey = String(options?.modeKey || options?.manualModeKey || '').trim();
28
- if (modeKey !== 'intelligent_sourcing') {
29
- return false;
30
- }
31
- const payloadExtra = resolvePayloadExtra(options);
32
- if (payloadExtra.append_mode === true) {
33
- return true;
34
- }
35
- if (payloadExtra.force_sourcing_search === true) {
36
- return true;
37
- }
38
- return false;
39
- }
40
-
41
- export async function sendRealStreamMessage({
42
- apiBaseUrl,
43
- content,
44
- handlers = {},
45
- options = {},
46
- scope = {},
47
- persistExchange,
48
- signal,
49
- }) {
50
- const resolvedProjectId = String(scope?.projectId || '').trim();
51
- const resolvedUserId = String(scope?.userId || '').trim();
52
- const sessionId = String(options.sessionIdOverride || '').trim();
53
- const requestBody = buildRealStreamRequestBody({
54
- content,
55
- options,
56
- scope: {
57
- projectId: resolvedProjectId,
58
- userId: resolvedUserId,
59
- },
60
- });
61
-
62
- const runtimeMode = getCoreRuntimeMode();
63
- // Dev-only debug: show request-side mode and key stream switches.
64
- debugCoreLog('stream.send.start', {
65
- mode: runtimeMode.mode,
66
- sessionId,
67
- modeKey: String(options?.modeKey || ''),
68
- manualModeKey: String(options?.manualModeKey || ''),
69
- knowledgeRefsCount: Array.isArray(requestBody?.knowledge_refs) ? requestBody.knowledge_refs.length : 0,
70
- });
71
- const requestSourceIds = collectSourceIdsFromRefs([
72
- ...(Array.isArray(requestBody?.context_refs) ? requestBody.context_refs : []),
73
- ...(Array.isArray(requestBody?.knowledge_refs) ? requestBody.knowledge_refs : []),
74
- ]);
75
- const resolvedModeKey = String(options?.modeKey || options?.manualModeKey || '').trim() || 'auto';
76
- handlers.onExecutionTrace?.({
77
- session_id: sessionId || '',
78
- step_id: 'context_binding',
79
- agent: 'context_router',
80
- stage: 'orchestration',
81
- label: '上下文绑定',
82
- detail: requestSourceIds.length > 0
83
- ? `mode=${resolvedModeKey}; 绑定 ${requestSourceIds.length} 个来源: ${requestSourceIds.join(', ')}`
84
- : `mode=${resolvedModeKey}; 未绑定来源,按常规上下文流程处理`,
85
- reason: '',
86
- status: 'running',
87
- mode_key: resolvedModeKey,
88
- sources: requestSourceIds.map((sourceId) => ({ source_id: sourceId })),
89
- timestamp: Date.now(),
90
- });
91
-
92
- let finalContent = '';
93
- let resolvedSessionId = sessionId;
94
- let latestContextUsage = null;
95
-
96
- if (shouldUseSourcingSearchPath(options, content)) {
97
- await requestSourcingSearch({
98
- apiBaseUrl,
99
- content,
100
- options,
101
- scope: { projectId: resolvedProjectId, userId: resolvedUserId },
102
- handlers,
103
- persistExchange,
104
- resolvedSessionId,
105
- });
106
- return;
107
- }
108
-
109
- const response = await fetch(joinUrl(apiBaseUrl, '/chat/stream'), {
110
- method: 'POST',
111
- headers: { 'Content-Type': 'application/json' },
112
- body: JSON.stringify(requestBody),
113
- signal,
114
- });
115
-
116
- if (!response.ok) {
117
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
118
- }
119
-
120
- if (!response.body) {
121
- throw new Error('stream response body is empty');
122
- }
123
-
124
- const reader = response.body.getReader();
125
- const decoder = new TextDecoder();
126
- let buffer = '';
127
- let currentEvent = '';
128
- let dataLines = [];
129
-
130
- const emitEvent = () => {
131
- if (!currentEvent || dataLines.length === 0) {
132
- currentEvent = '';
133
- dataLines = [];
134
- return;
135
- }
136
-
137
- const rawData = dataLines.join('\n');
138
- let payload = {};
139
- try {
140
- payload = JSON.parse(rawData);
141
- } catch {
142
- payload = {};
143
- }
144
- // Dev-only debug: trace the incoming SSE event types and payload outline.
145
- debugCoreLog('stream.event', {
146
- type: currentEvent,
147
- keys: Object.keys(payload || {}),
148
- });
149
-
150
- if (currentEvent === 'content') {
151
- const chunk = String(payload?.content || '');
152
- finalContent += chunk;
153
- handlers.onContent?.(chunk);
154
- } else if (currentEvent === 'agent_status') {
155
- handlers.onAgentStatus?.(parseAgentStatus(payload));
156
- } else if (currentEvent === 'thinking_trace') {
157
- handlers.onThinkingTrace?.(parseThinkingTrace(payload)?.trace || '');
158
- } else if (currentEvent === 'execution_trace') {
159
- handlers.onExecutionTrace?.(parseExecutionTrace(payload));
160
- } else if (currentEvent === 'context_usage') {
161
- latestContextUsage = (
162
- payload
163
- && typeof payload === 'object'
164
- && !Array.isArray(payload)
165
- ) ? { ...payload } : null;
166
- const modeKey = String(payload?.mode_key || '').trim();
167
- const modeState = String(payload?.mode_state || '').trim();
168
- const profile = String(payload?.profile || '').trim();
169
- const totalTokens = Number(payload?.token_estimate?.total_prompt_tokens_estimate || 0);
170
- handlers.onExecutionTrace?.({
171
- session_id: String(payload?.session_id || '').trim(),
172
- step_id: 'context_usage',
173
- agent: 'context_router',
174
- stage: 'orchestration',
175
- label: '上下文预算评估',
176
- detail: `mode=${modeKey || 'default'}/${modeState || 'default'}; profile=${profile || 'default'}; tokens≈${totalTokens}`,
177
- reason: '',
178
- status: 'completed',
179
- timestamp: Number.isFinite(Number(payload?.ts)) ? Number(payload.ts) : Date.now(),
180
- });
181
- } else if (currentEvent === 'knowledge_search') {
182
- handlers.onKnowledgeSearch?.(parseKnowledgeSearch(payload));
183
- } else if (currentEvent === 'sourcing_candidates') {
184
- handlers.onSourcingCandidates?.(parseSourcingCandidates(payload));
185
- } else if (currentEvent === 'knowledge_citation') {
186
- handlers.onKnowledgeCitation?.(parseKnowledgeCitation(payload));
187
- } else if (currentEvent === 'patch_event') {
188
- const patchSessionId = String(payload?.session?.session_id || '').trim();
189
- if (patchSessionId) {
190
- resolvedSessionId = patchSessionId;
191
- }
192
- handlers.onPatchEvent?.(payload);
193
- } else if (currentEvent === 'complete') {
194
- let resolvedSessionTitle = '';
195
- if (typeof persistExchange === 'function') {
196
- const persistedResult = persistExchange(resolvedSessionId, content, finalContent, {
197
- functionType: 'chat_component_debug',
198
- projectId: resolvedProjectId || null,
199
- userId: resolvedUserId || null,
200
- });
201
- const persistedSession = resolvePersistExchangeResult(persistedResult, resolvedSessionId);
202
- resolvedSessionId = persistedSession.sessionId;
203
- resolvedSessionTitle = persistedSession.title;
204
- }
205
- handlers.onPatchEvent?.({
206
- session: {
207
- session_id: resolvedSessionId,
208
- turn_id: '',
209
- },
210
- payload: resolvedSessionTitle
211
- ? {
212
- session: {
213
- title: resolvedSessionTitle,
214
- },
215
- }
216
- : {},
217
- patch_scope: ['session'],
218
- event_type: 'final',
219
- final: true,
220
- invalid: false,
221
- });
222
- handlers.onComplete?.(finalContent, {
223
- sessionId: resolvedSessionId,
224
- ...(latestContextUsage
225
- ? {
226
- context_usage: latestContextUsage,
227
- }
228
- : {}),
229
- ...(resolvedSessionTitle
230
- ? {
231
- payload: {
232
- session: {
233
- title: resolvedSessionTitle,
234
- },
235
- },
236
- }
237
- : {}),
238
- });
239
- debugCoreLog('stream.send.complete', {
240
- sessionId: resolvedSessionId,
241
- finalContentLength: finalContent.length,
242
- });
243
- }
244
-
245
- currentEvent = '';
246
- dataLines = [];
247
- };
248
-
249
- while (true) {
250
- const { done, value } = await reader.read();
251
- if (done) {
252
- break;
253
- }
254
-
255
- buffer += decoder.decode(value, { stream: true });
256
- const lines = buffer.split('\n');
257
- buffer = lines.pop() || '';
258
-
259
- for (const line of lines) {
260
- const normalized = line.replace(/\r$/, '');
261
- if (!normalized) {
262
- emitEvent();
263
- continue;
264
- }
265
- if (normalized.startsWith('event:')) {
266
- currentEvent = normalized.slice(6).trim();
267
- continue;
268
- }
269
- if (normalized.startsWith('data:')) {
270
- dataLines.push(normalized.slice(5).trim());
271
- }
272
- }
273
- }
274
-
275
- emitEvent();
276
- handlers.onExecutionTrace?.({
277
- session_id: resolvedSessionId || '',
278
- step_id: 'context_binding',
279
- agent: 'context_router',
280
- stage: 'orchestration',
281
- label: '上下文绑定',
282
- detail: requestSourceIds.length > 0
283
- ? `已完成来源绑定(${requestSourceIds.length})`
284
- : '已完成常规上下文绑定',
285
- reason: '',
286
- status: 'completed',
287
- sources: requestSourceIds.map((sourceId) => ({ source_id: sourceId })),
288
- timestamp: Date.now(),
289
- });
290
- }
291
-
292
- export async function sendRealStreamAction({
293
- apiBaseUrl,
294
- action,
295
- handlers = {},
296
- options = {},
297
- scope = {},
298
- signal,
299
- }) {
300
- const requestBody = buildRealActionRequestBody({
301
- action,
302
- options,
303
- scope,
304
- });
305
-
306
- const response = await fetch(joinUrl(apiBaseUrl, '/chat/action'), {
307
- method: 'POST',
308
- headers: { 'Content-Type': 'application/json' },
309
- body: JSON.stringify(requestBody),
310
- signal,
311
- });
312
-
313
- if (!response.ok) {
314
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
315
- }
316
-
317
- const payload = await response.json();
318
- const result = (
319
- payload?.result
320
- && typeof payload.result === 'object'
321
- && !Array.isArray(payload.result)
322
- ) ? payload.result : {};
323
- const content = String(result.message || payload?.message || '').trim();
324
-
325
- if (content) {
326
- handlers.onContent?.(content);
327
- }
328
- handlers.onComplete?.(content);
329
- return payload;
330
- }
@@ -1,27 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { shouldUseSourcingSearchPath } from './send-real-stream.js';
4
-
5
- test('shouldUseSourcingSearchPath keeps sourcing mode on stream path by default', () => {
6
- const shouldUse = shouldUseSourcingSearchPath({
7
- modeKey: 'intelligent_sourcing',
8
- payloadExtra: {},
9
- }, '来源1');
10
- assert.equal(shouldUse, false);
11
- });
12
-
13
- test('shouldUseSourcingSearchPath uses direct path in append rounds', () => {
14
- const shouldUse = shouldUseSourcingSearchPath({
15
- modeKey: 'intelligent_sourcing',
16
- payloadExtra: { append_mode: true },
17
- }, '能不能换一批');
18
- assert.equal(shouldUse, true);
19
- });
20
-
21
- test('shouldUseSourcingSearchPath uses direct path only with explicit force flag', () => {
22
- const shouldUse = shouldUseSourcingSearchPath({
23
- modeKey: 'intelligent_sourcing',
24
- payloadExtra: { force_sourcing_search: true },
25
- }, '任意输入');
26
- assert.equal(shouldUse, true);
27
- });
@@ -1,86 +0,0 @@
1
- import { parseSourcingCandidates } from '../../../contracts/sse-events.js';
2
- import { resolvePayloadExtra, resolveStreamRefs } from '../build-stream-request.js';
3
- import { resolvePersistExchangeResult } from '../stream-persist-utils.js';
4
- import { buildSourcingResponseText } from './sourcing-response-templates.js';
5
- import { requestSourcingSearchApi } from './sourcing-search-api.js';
6
-
7
- const SOURCING_REFRESH_COMMANDS = new Set(['换一批', '继续', '继续寻源']);
8
-
9
- function normalizeText(value) {
10
- return String(value || '').trim();
11
- }
12
-
13
- export function resolveSourcingSearchQuery(content, payloadExtra = {}) {
14
- const normalizedContent = normalizeText(content);
15
- const payloadSourcingQuery = normalizeText(payloadExtra?.sourcing_query);
16
- if (payloadSourcingQuery && SOURCING_REFRESH_COMMANDS.has(normalizedContent)) {
17
- return payloadSourcingQuery;
18
- }
19
- return normalizedContent || payloadSourcingQuery;
20
- }
21
-
22
- export async function requestSourcingSearch({
23
- apiBaseUrl,
24
- content,
25
- options,
26
- scope,
27
- handlers,
28
- persistExchange,
29
- resolvedSessionId,
30
- }) {
31
- const payloadExtra = resolvePayloadExtra(options);
32
- const resolvedQuery = resolveSourcingSearchQuery(content, payloadExtra);
33
- const streamRefs = resolveStreamRefs(options);
34
- const sourceIds = Array.isArray(streamRefs.knowledge_refs)
35
- ? streamRefs.knowledge_refs.map((item) => String(item || '').trim()).filter(Boolean)
36
- : [];
37
- const appendMode = payloadExtra.append_mode === true;
38
- const { payload, results, sourceBreakdown, sourcingPayload } = await requestSourcingSearchApi({
39
- apiBaseUrl,
40
- projectId: String(scope?.projectId || '').trim() || 'default',
41
- userId: String(scope?.userId || '').trim() || 'default',
42
- sessionId: String(resolvedSessionId || '').trim() || 'default',
43
- query: resolvedQuery,
44
- topK: Number.isFinite(Number(payloadExtra.top_k)) ? Number(payloadExtra.top_k) : 8,
45
- appendMode,
46
- lockExistingOrder: payloadExtra.lock_existing_order !== false,
47
- baseResultIds: Array.isArray(payloadExtra.base_result_ids) ? payloadExtra.base_result_ids : [],
48
- sourceScope: String(payloadExtra.source_scope || 'hybrid').trim() || 'hybrid',
49
- contextRefs: Array.isArray(streamRefs.context_refs) ? streamRefs.context_refs : [],
50
- knowledgeRefs: sourceIds,
51
- sourceIds,
52
- });
53
- handlers.onSourcingCandidates?.(parseSourcingCandidates(sourcingPayload));
54
-
55
- const sourceSummary = `来源分布:本地资料 ${Number(sourceBreakdown.local) || 0} / 外部连接 ${Number(sourceBreakdown.mcp) || 0} / 网页来源 ${Number(sourceBreakdown.web) || 0}`;
56
- const citableResults = results
57
- .filter((item) => String(item?.canonical_url || item?.url || '').trim())
58
- .slice(0, 2);
59
- const topRefs = citableResults
60
- .map((item, index) => `- [来源${index + 1}] ${String(item?.source_title || item?.title || '未命名来源')}(${String(item?.canonical_url || item?.url || '')})`)
61
- .join('\n');
62
-
63
- const finalContent = buildSourcingResponseText({
64
- count: results.length,
65
- sourceBreakdownText: sourceSummary,
66
- topRefsText: topRefs,
67
- nextActionText: '继续补充你的目标与约束,我会基于当前上下文递进处理。',
68
- });
69
-
70
- if (appendMode) {
71
- handlers.onComplete?.(finalContent, { sessionId: String(resolvedSessionId || '').trim() });
72
- return;
73
- }
74
-
75
- let finalSessionId = String(resolvedSessionId || '').trim();
76
- if (typeof persistExchange === 'function') {
77
- const persistedResult = persistExchange(finalSessionId, content, finalContent, {
78
- functionType: 'intelligent_sourcing',
79
- projectId: String(scope?.projectId || '').trim() || null,
80
- userId: String(scope?.userId || '').trim() || null,
81
- });
82
- const persistedSession = resolvePersistExchangeResult(persistedResult, finalSessionId);
83
- finalSessionId = persistedSession.sessionId;
84
- }
85
- handlers.onComplete?.(finalContent, { sessionId: finalSessionId });
86
- }
@@ -1,14 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { resolveSourcingSearchQuery } from './send-sourcing-search.js';
4
-
5
- test('resolveSourcingSearchQuery uses payload sourcing_query for refresh commands', () => {
6
- const query = resolveSourcingSearchQuery('换一批', { sourcing_query: '露营地 上海 亲子' });
7
- assert.equal(query, '露营地 上海 亲子');
8
- });
9
-
10
- test('resolveSourcingSearchQuery keeps direct semantic input', () => {
11
- const query = resolveSourcingSearchQuery('上海露营地推荐', { sourcing_query: '旧查询' });
12
- assert.equal(query, '上海露营地推荐');
13
- });
14
-
@@ -1,55 +0,0 @@
1
- const SOURCING_RESPONSE_TEMPLATES = {
2
- sufficient_hits: [
3
- '已完成寻源:共 {count} 条候选。',
4
- '{source_breakdown}',
5
- '{top_refs}',
6
- '{next_action}',
7
- ],
8
- limited_hits: [
9
- '已完成初步寻源:当前仅命中 {count} 条候选。',
10
- '{source_breakdown}',
11
- '{top_refs}',
12
- '建议:补充更具体的地区/行业/预算关键词后,我可以继续扩展检索。',
13
- '{next_action}',
14
- ],
15
- no_hits: [
16
- '本轮未命中有效候选。',
17
- '{source_breakdown}',
18
- '可能原因:关键词过宽或外部通道未返回有效内容。',
19
- '建议:补充地区/行业词,或检查外部网络与网关配置后重试。',
20
- '{next_action}',
21
- ],
22
- };
23
-
24
- function renderTemplate(templateLines, variables = {}) {
25
- const lines = Array.isArray(templateLines) ? templateLines : [];
26
- return lines
27
- .map((line) => String(line || '').replace(/\{([^}]+)\}/g, (_, key) => String(variables[key] || '')))
28
- .map((line) => line.trim())
29
- .filter(Boolean)
30
- .join('\n');
31
- }
32
-
33
- export function buildSourcingResponseText({
34
- count = 0,
35
- sourceBreakdownText = '',
36
- topRefsText = '',
37
- nextActionText = '你可以直接回复序号或补充信息,我来继续整理。',
38
- } = {}) {
39
- const normalizedCount = Number.isFinite(Number(count)) ? Number(count) : 0;
40
- const variables = {
41
- count: normalizedCount,
42
- source_breakdown: sourceBreakdownText,
43
- top_refs: topRefsText || '暂无可展示引用来源。',
44
- next_action: nextActionText,
45
- };
46
- if (normalizedCount <= 0) {
47
- return renderTemplate(SOURCING_RESPONSE_TEMPLATES.no_hits, variables);
48
- }
49
- if (normalizedCount <= 2) {
50
- return renderTemplate(SOURCING_RESPONSE_TEMPLATES.limited_hits, variables);
51
- }
52
- return renderTemplate(SOURCING_RESPONSE_TEMPLATES.sufficient_hits, variables);
53
- }
54
-
55
- export { SOURCING_RESPONSE_TEMPLATES };
@@ -1,155 +0,0 @@
1
- import { resolveEntities } from '../entity-extraction.js';
2
-
3
- function joinUrl(baseUrl = '', path = '') {
4
- if (!baseUrl) {
5
- return path || '';
6
- }
7
- if (!path) {
8
- return baseUrl;
9
- }
10
- return `${baseUrl.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
11
- }
12
-
13
- function normalizeSourceType(value) {
14
- const rawType = String(value || '').trim().toLowerCase();
15
- if (rawType.includes('web')) {
16
- return 'web';
17
- }
18
- if (rawType.includes('mcp')) {
19
- return 'mcp';
20
- }
21
- return 'local';
22
- }
23
-
24
- function normalizeSourceBreakdown(payload = {}) {
25
- const sourceBreakdown = (
26
- payload?.source_breakdown
27
- && typeof payload.source_breakdown === 'object'
28
- && !Array.isArray(payload.source_breakdown)
29
- ) ? payload.source_breakdown : {};
30
- return {
31
- local: Number(sourceBreakdown.local) || 0,
32
- mcp: Number(sourceBreakdown.mcp) || 0,
33
- web: Number(sourceBreakdown.web) || 0,
34
- };
35
- }
36
-
37
- function mapResultToCandidate(item = {}, runId = '', resolvedQuery = '') {
38
- return {
39
- id: String(item?.result_id || item?.record_id || item?.title || ''),
40
- result_id: String(item?.result_id || item?.record_id || item?.title || ''),
41
- title: String(item?.source_title || item?.title || ''),
42
- supplier_name: String(item?.source_title || item?.title || ''),
43
- evidence_kind: 'sourcing',
44
- match_score: Number(item?.score || 0),
45
- match_reasons: [String(item?.snippet || '')],
46
- evidence_refs: [
47
- {
48
- url: String(item?.canonical_url || item?.url || ''),
49
- snippet: String(item?.snippet || ''),
50
- title: String(item?.source_title || item?.title || ''),
51
- },
52
- ],
53
- source_type: normalizeSourceType(item?.source_type || item?.source),
54
- source_title: String(item?.source_title || item?.title || ''),
55
- source_domain: String(item?.source_domain || ''),
56
- source_meta: (
57
- item?.source_meta
58
- && typeof item.source_meta === 'object'
59
- && !Array.isArray(item.source_meta)
60
- ) ? item.source_meta : {},
61
- canonical_url: String(item?.canonical_url || item?.url || ''),
62
- confidence: item?.confidence ?? '',
63
- confidence_detail: (
64
- item?.confidence_detail
65
- && typeof item.confidence_detail === 'object'
66
- && !Array.isArray(item.confidence_detail)
67
- ) ? item.confidence_detail : null,
68
- summary: String(item?.snippet || ''),
69
- run_id: runId,
70
- query: resolvedQuery,
71
- };
72
- }
73
-
74
- export async function requestSourcingSearchApi({
75
- apiBaseUrl,
76
- projectId = 'default',
77
- userId = 'default',
78
- sessionId = 'default',
79
- query,
80
- topK = 8,
81
- sourceScope = 'hybrid',
82
- contextRefs = [],
83
- knowledgeRefs = [],
84
- sourceIds = [],
85
- entities = [],
86
- appendMode = false,
87
- lockExistingOrder = true,
88
- baseResultIds = [],
89
- }) {
90
- const resolvedEntities = resolveEntities({ text: query, entities });
91
- const response = await fetch(joinUrl(apiBaseUrl, '/kernel/sourcing/search'), {
92
- method: 'POST',
93
- headers: { 'Content-Type': 'application/json' },
94
- body: JSON.stringify({
95
- project_id: String(projectId || '').trim() || 'default',
96
- user_id: String(userId || '').trim() || 'default',
97
- session_id: String(sessionId || '').trim() || 'default',
98
- query: String(query || '').trim(),
99
- top_k: Number.isFinite(Number(topK)) ? Number(topK) : 8,
100
- append_mode: appendMode === true,
101
- lock_existing_order: lockExistingOrder !== false,
102
- base_result_ids: Array.isArray(baseResultIds)
103
- ? baseResultIds.map((item) => String(item || '').trim()).filter(Boolean)
104
- : [],
105
- source_scope: String(sourceScope || 'hybrid').trim() || 'hybrid',
106
- sourcing_enabled: true,
107
- context_refs: Array.isArray(contextRefs) ? contextRefs : [],
108
- knowledge_refs: Array.isArray(knowledgeRefs) ? knowledgeRefs : [],
109
- source_ids: Array.isArray(sourceIds) ? sourceIds : [],
110
- entities: resolvedEntities,
111
- }),
112
- });
113
-
114
- if (!response.ok) {
115
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
116
- }
117
-
118
- const payload = await response.json();
119
- const runId = String(payload?.retrieval_run_id || '').trim();
120
- const resolvedQuery = String(payload?.query || query).trim();
121
- const results = Array.isArray(payload?.results) ? payload.results : [];
122
- const newResults = Array.isArray(payload?.new_results) ? payload.new_results : [];
123
- const allResults = Array.isArray(payload?.all_results) ? payload.all_results : [];
124
- const sourceBreakdown = normalizeSourceBreakdown(payload);
125
- const sourcingPayload = {
126
- run_id: runId,
127
- query: resolvedQuery,
128
- candidate_count: results.length,
129
- requested_top_k: Number(payload?.requested_top_k) || Number(topK) || 8,
130
- returned_count: Number(payload?.returned_count) || results.length,
131
- has_more: payload?.has_more === true,
132
- next_top_k: Number(payload?.next_top_k) || Number(topK) || 8,
133
- append_mode: appendMode === true,
134
- lock_existing_order: lockExistingOrder !== false,
135
- candidates: results.map((item) => mapResultToCandidate(item, runId, resolvedQuery)),
136
- new_results: newResults.map((item) => mapResultToCandidate(item, runId, resolvedQuery)),
137
- all_results: allResults.map((item) => mapResultToCandidate(item, runId, resolvedQuery)),
138
- shortlist: Array.isArray(payload?.shortlist) ? payload.shortlist : [],
139
- source_meta: {
140
- source_breakdown: sourceBreakdown,
141
- },
142
- summary: '',
143
- };
144
-
145
- return {
146
- payload,
147
- runId,
148
- resolvedQuery,
149
- results,
150
- sourceBreakdown,
151
- sourcingPayload,
152
- };
153
- }
154
-
155
- export { normalizeSourceType, joinUrl };