kiku-agent 1.2.0 → 1.3.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/README.md +459 -407
- package/package.json +32 -3
- package/server/agent/agentCore.js +635 -267
- package/server/agent/appPaths.js +2 -0
- package/server/agent/catalogEntityResolver.js +637 -0
- package/server/agent/citations.js +0 -21
- package/server/agent/clarificationAnalytics.js +1 -1
- package/server/agent/clarifyingQuestions.js +286 -216
- package/server/agent/cli.js +532 -73
- package/server/agent/commandFeatures.js +123 -24
- package/server/agent/commandRegistry.js +128 -62
- package/server/agent/commands.js +1410 -95
- package/server/agent/config.js +253 -163
- package/server/agent/contextCompactor.js +239 -25
- package/server/agent/dynamicCatalog.js +192 -0
- package/server/agent/envBootstrap.js +46 -0
- package/server/agent/envManager.js +317 -0
- package/server/agent/eventBus.js +3 -3
- package/server/agent/feedbackLearning.js +0 -8
- package/server/agent/gpuMetrics.js +188 -55
- package/server/agent/ingestionWorker.js +339 -0
- package/server/agent/ink/App.jsx +348 -0
- package/server/agent/ink/Panel.jsx +54 -0
- package/server/agent/ink/PromptInput.jsx +321 -0
- package/server/agent/ink/TaskPlanPanel.jsx +88 -0
- package/server/agent/ink/ThinkingPanel.jsx +68 -0
- package/server/agent/ink/ToolResult.jsx +133 -0
- package/server/agent/ink/main.jsx +195 -0
- package/server/agent/ink/useEventBus.js +218 -0
- package/server/agent/lanDiscovery.js +369 -0
- package/server/agent/llmClient.js +111 -39
- package/server/agent/lmStudioWakeup.js +144 -0
- package/server/agent/mcpClient.js +2 -2
- package/server/agent/memory/contextAllocator.js +214 -0
- package/server/agent/memory/hierarchicalStore.js +523 -0
- package/server/agent/memory/reflectionWorker.js +110 -0
- package/server/agent/memory.js +635 -494
- package/server/agent/modelConfig.js +487 -0
- package/server/agent/newsIntelligence.js +219 -0
- package/server/agent/newsWatch.js +85 -0
- package/server/agent/prompts.js +146 -0
- package/server/agent/publicTools.js +7 -0
- package/server/agent/queryDetails.js +62 -1
- package/server/agent/research.js +314 -35
- package/server/agent/routingInterceptor.js +77 -0
- package/server/agent/rumoredProjects.js +15 -0
- package/server/agent/sanitizer.js +117 -17
- package/server/agent/subagent.js +52 -9
- package/server/agent/suggestionTui.js +33 -13
- package/server/agent/suggestions.js +29 -13
- package/server/agent/telemetry/hardwareTiers.js +332 -0
- package/server/agent/telemetry/runtimeWatchdog.js +66 -0
- package/server/agent/telemetry/techPowerUpClient.js +682 -0
- package/server/agent/telemetry/techpowerup-mcp.json +22 -0
- package/server/agent/telemetry.js +0 -11
- package/server/agent/terminalImage.js +0 -1
- package/server/agent/textToolProtocol.js +190 -0
- package/server/agent/tokenTracker.js +220 -0
- package/server/agent/toolRouter.js +362 -216
- package/server/agent/tools.js +1155 -194
- package/server/agent/ui.js +144 -41
- package/server/agent/vramService.js +572 -0
- package/server/agent/webSearch.js +2279 -273
- package/server/agent/websearch/adapters/duckduckgo.js +97 -0
- package/server/agent/websearch/adapters/jina.js +77 -0
- package/server/agent/websearch/adapters/searxng.js +57 -0
- package/server/agent/websearch/core/canonical.js +89 -0
- package/server/agent/websearch/core/fusion.js +81 -0
- package/server/agent/websearch/core/types.js +29 -0
- package/server/agent/websearch/extract.js +605 -0
- package/server/agent/websearch/index.js +43 -0
- package/server/agent/websearch/orchestrator.js +266 -0
- package/server/agent/websearch/resilience/breaker.js +97 -0
- package/server/agent/websearch/resilience/cache.js +59 -0
- package/server/agent/websearch/resilience/limiter.js +93 -0
- package/server/agent/websearch/security/filter.js +36 -0
- package/server/agent/websearch/security/ssrf.js +289 -0
- package/server/agent/websearch/telemetry.js +71 -0
- package/server/agent/youtubeAuth.js +82 -73
- package/server/agent/youtubeTranscript.js +209 -0
- package/server/apiRouter.js +390 -190
- package/server/mcp/data/brands.js +5 -5
- package/server/mcp/data/discography.js +56 -45
- package/server/mcp/data/gallery.js +9 -0
- package/server/mcp/data/index.js +1 -0
- package/server/mcp/data/industryIndex.js +251 -0
- package/server/mcp/data/profile.js +5 -5
- package/server/mcp/data/timeline.js +2 -2
- package/server/mcp/data/works.js +2 -2
- package/server/mcp/handler.js +0 -2
- package/server/mcp/prompts.js +42 -2
- package/server/mcp/resources.js +5 -1
- package/server/mcp/security.js +25 -14
- package/server/mcp/server.js +92 -3
- package/server/mcp/tools.js +20 -11
- package/server/routes/agent.js +55 -0
- package/server/web-chat/core/connection.js +218 -194
- package/server/web-chat/core/conversationContext.js +1 -1
- package/server/web-chat/core/directCatalogEngine.js +190 -184
- package/server/web-chat/core/followUpEngine.js +88 -11
- package/server/web-chat/core/security.js +7 -4
- package/server/web-chat/router.js +54 -17
- package/server/web-chat/routes/bilibiliRoute.js +34 -0
- package/server/web-chat/routes/chatRoute.js +488 -397
- package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
- package/server/web-chat/routes/exportRoute.js +2 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
- package/server/web-chat/routes/newsRoute.js +36 -6
- package/server/web-chat/routes/providerRoute.js +87 -0
- package/server/web-chat/routes/staticRoute.js +61 -13
- package/server/web-chat/routes/statusRoute.js +23 -2
- package/server/web-chat/routes/vramRoute.js +22 -0
- package/server/web-chat/server.js +135 -106
- package/server/web-chat/ui/htmlBuilder.js +8 -8
- package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
- package/server/web-chat/ui/scripts/client.js +10526 -8397
- package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
- package/server/web-chat/ui/styles/main.css +7471 -4246
- package/server/web-chat/ui/templates/body.html +305 -179
- package/src/data/brandsData.js +29 -6
- package/src/data/discographyData.js +918 -900
- package/src/data/galleryData.js +66 -0
- package/src/data/industryIndexData.js +292 -0
- package/src/data/musicVideoData.js +48 -5
- package/src/data/profileData.js +15 -1
- package/src/data/timelineData.js +4 -3
- package/src/data/worksData.js +536 -535
- package/src/lib/vram/estimator.js +464 -0
- package/src/lib/vram/modelRegistry.js +387 -0
- package/tokens.css +33 -2
|
@@ -1,397 +1,488 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { resolveAgentConnection } from '../core/connection.js';
|
|
3
|
-
import { executeDirectCatalogKnowledge } from '../core/directCatalogEngine.js';
|
|
4
|
-
import { makeInspectableToolPayload } from '../core/toolInspector.js';
|
|
5
|
-
import { getAgentMemorySnapshot } from '../core/memorySnapshot.js';
|
|
6
|
-
import { parseJsonBody, sendJson, sendError } from '../core/errorHandler.js';
|
|
7
|
-
import { registerClarificationResolver } from './clarifyRoute.js';
|
|
8
|
-
import { extractAgentFollowUpQuestions, generateDynamicFollowUps } from '../core/followUpEngine.js';
|
|
9
|
-
import { resolveConversationContext } from '../core/conversationContext.js';
|
|
10
|
-
import { applyFutureEventGuard } from '../core/futureEventGuard.js';
|
|
11
|
-
import { extractApiKey } from '../core/security.js';
|
|
12
|
-
import { assessDramaAnswerQuality } from '../core/dramaKnowledge.js';
|
|
13
|
-
|
|
14
|
-
function stripInvalidYouTubeLinks(text) {
|
|
15
|
-
if (!text || typeof text !== 'string') return text || '';
|
|
16
|
-
return text.replace(/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{1,20})/gi, (m, id) => (id.length === 11 ? m : ''));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function replaceLowQualityDramaAnswer({ query, answer, persona, conversationContext, toolsUsed }) {
|
|
20
|
-
const assessment = assessDramaAnswerQuality(query, answer);
|
|
21
|
-
if (!assessment.applicable || assessment.valid) return null;
|
|
22
|
-
|
|
23
|
-
const fallback = await executeDirectCatalogKnowledge(query, persona, { conversationContext });
|
|
24
|
-
if (Array.isArray(toolsUsed)) toolsUsed.push(...fallback.tools);
|
|
25
|
-
return {
|
|
26
|
-
answer: fallback.answer,
|
|
27
|
-
suggestions: fallback.suggestions || [],
|
|
28
|
-
issues: assessment.issues
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { resolveAgentConnection } from '../core/connection.js';
|
|
3
|
+
import { executeDirectCatalogKnowledge } from '../core/directCatalogEngine.js';
|
|
4
|
+
import { makeInspectableToolPayload } from '../core/toolInspector.js';
|
|
5
|
+
import { getAgentMemorySnapshot } from '../core/memorySnapshot.js';
|
|
6
|
+
import { parseJsonBody, sendJson, sendError } from '../core/errorHandler.js';
|
|
7
|
+
import { registerClarificationResolver } from './clarifyRoute.js';
|
|
8
|
+
import { extractAgentFollowUpQuestions, generateDynamicFollowUps, dedupeSuggestions, normalizeSuggestionKey } from '../core/followUpEngine.js';
|
|
9
|
+
import { resolveConversationContext } from '../core/conversationContext.js';
|
|
10
|
+
import { applyFutureEventGuard } from '../core/futureEventGuard.js';
|
|
11
|
+
import { extractApiKey } from '../core/security.js';
|
|
12
|
+
import { assessDramaAnswerQuality } from '../core/dramaKnowledge.js';
|
|
13
|
+
|
|
14
|
+
function stripInvalidYouTubeLinks(text) {
|
|
15
|
+
if (!text || typeof text !== 'string') return text || '';
|
|
16
|
+
return text.replace(/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{1,20})/gi, (m, id) => (id.length === 11 ? m : ''));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function replaceLowQualityDramaAnswer({ query, answer, persona, conversationContext, toolsUsed }) {
|
|
20
|
+
const assessment = assessDramaAnswerQuality(query, answer);
|
|
21
|
+
if (!assessment.applicable || assessment.valid) return null;
|
|
22
|
+
|
|
23
|
+
const fallback = await executeDirectCatalogKnowledge(query, persona, { conversationContext });
|
|
24
|
+
if (Array.isArray(toolsUsed)) toolsUsed.push(...fallback.tools);
|
|
25
|
+
return {
|
|
26
|
+
answer: fallback.answer,
|
|
27
|
+
suggestions: fallback.suggestions || [],
|
|
28
|
+
issues: assessment.issues
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function extractSearchImagesFromTools(tools = [], query = '') {
|
|
33
|
+
const images = [];
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
const add = (img) => {
|
|
36
|
+
const url = img?.imageUrl || img?.thumbUrl || img?.url || img?.src || img?.image;
|
|
37
|
+
if (!url || typeof url !== 'string' || seen.has(url)) return;
|
|
38
|
+
seen.add(url);
|
|
39
|
+
images.push({
|
|
40
|
+
title: img.title || img.alt || query || 'Ju Jingyi',
|
|
41
|
+
imageUrl: url,
|
|
42
|
+
thumbUrl: img.thumbUrl || img.imageUrl || url,
|
|
43
|
+
source: img.source || 'Search'
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
for (const t of tools) {
|
|
48
|
+
const res = t?.result;
|
|
49
|
+
if (!res) continue;
|
|
50
|
+
if (Array.isArray(res.images)) res.images.forEach(add);
|
|
51
|
+
if (Array.isArray(res.photos)) res.photos.forEach(add);
|
|
52
|
+
if (t.name === 'search_web_images' && Array.isArray(res.results)) res.results.forEach(add);
|
|
53
|
+
if (Array.isArray(res.works)) {
|
|
54
|
+
res.works.forEach(w => {
|
|
55
|
+
if (w.poster) add({ title: w.titleTh || w.titleCn || w.titleEn, imageUrl: w.poster, source: 'Series Poster' });
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const rowAlignedCount = Math.floor(images.length / 3) * 3;
|
|
61
|
+
if (rowAlignedCount >= 3) {
|
|
62
|
+
return images.slice(0, Math.min(rowAlignedCount, 9));
|
|
63
|
+
}
|
|
64
|
+
return images.slice(0, 6);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
export function sanitizeSubagentProgress(event = {}) {
|
|
69
|
+
const text = (value, max = 200) => String(value ?? '').slice(0, max);
|
|
70
|
+
const num = (value) => (Number.isFinite(Number(value)) ? Number(value) : undefined);
|
|
71
|
+
const out = { type: 'subagent_progress' };
|
|
72
|
+
if (['subagent', 'research', 'verify'].includes(event.source)) out.source = event.source;
|
|
73
|
+
else out.source = 'subagent';
|
|
74
|
+
if (['start', 'iteration', 'tool', 'worker-start', 'worker-complete', 'complete'].includes(event.type)) out.kind = event.type;
|
|
75
|
+
else out.kind = 'tool';
|
|
76
|
+
const subject = event.task ?? event.question ?? event.claim ?? '';
|
|
77
|
+
if (subject) out.subject = text(subject);
|
|
78
|
+
if (event.iteration !== undefined) out.iteration = num(event.iteration);
|
|
79
|
+
if (event.maxIterations !== undefined) out.maxIterations = num(event.maxIterations);
|
|
80
|
+
if (event.name) out.tool = text(event.name, 80);
|
|
81
|
+
if (event.label) out.label = text(event.label, 120);
|
|
82
|
+
if (event.id) out.workerId = text(event.id, 80);
|
|
83
|
+
if (event.workerId) out.workerId = text(event.workerId, 80);
|
|
84
|
+
if (event.sources !== undefined) out.sources = num(event.sources);
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
const recentSuggestionKeys = new Map();
|
|
90
|
+
|
|
91
|
+
function getRecentSuggestionSet(sessionKey) {
|
|
92
|
+
let set = recentSuggestionKeys.get(sessionKey);
|
|
93
|
+
if (!set) {
|
|
94
|
+
set = new Set();
|
|
95
|
+
recentSuggestionKeys.set(sessionKey, set);
|
|
96
|
+
if (recentSuggestionKeys.size > 200) {
|
|
97
|
+
const oldest = recentSuggestionKeys.keys().next().value;
|
|
98
|
+
recentSuggestionKeys.delete(oldest);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return set;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function finalizeSuggestions(sessionKey, baseSuggestions, { query, answer, tools }) {
|
|
105
|
+
const recentKeys = getRecentSuggestionSet(sessionKey);
|
|
106
|
+
let out = dedupeSuggestions(Array.isArray(baseSuggestions) ? baseSuggestions : [], recentKeys);
|
|
107
|
+
if (out.length < 3) {
|
|
108
|
+
const dynamic = generateDynamicFollowUps({ query, answer, tools, recentKeys });
|
|
109
|
+
const localSeen = new Set(recentKeys);
|
|
110
|
+
for (const s of out) localSeen.add(normalizeSuggestionKey(s.query || s.label));
|
|
111
|
+
out = [...out, ...dedupeSuggestions(dynamic, localSeen)].slice(0, 3);
|
|
112
|
+
}
|
|
113
|
+
if (out.length) {
|
|
114
|
+
const set = getRecentSuggestionSet(sessionKey);
|
|
115
|
+
for (const s of out) {
|
|
116
|
+
set.add(normalizeSuggestionKey(s.query || s.label));
|
|
117
|
+
if (set.size > 60) {
|
|
118
|
+
const oldest = set.values().next().value;
|
|
119
|
+
set.delete(oldest);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function handleChatRoute(req, res, urlObj) {
|
|
127
|
+
let body;
|
|
128
|
+
try {
|
|
129
|
+
body = await parseJsonBody(req);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
return sendError(res, 400, `Invalid JSON body: ${err.message}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const query = String(body.query || '').trim();
|
|
135
|
+
|
|
136
|
+
const persona = 'default';
|
|
137
|
+
const sessionKey = String(body.sessionId || 'global');
|
|
138
|
+
const wantsStream = body.stream === true || (req.headers.accept && req.headers.accept.includes('text/event-stream'));
|
|
139
|
+
|
|
140
|
+
if (!query) {
|
|
141
|
+
return sendError(res, 400, 'Query is required');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const apiKey = extractApiKey(req);
|
|
145
|
+
const abortController = new AbortController();
|
|
146
|
+
req.on('close', () => {
|
|
147
|
+
if (!res.writableEnded) {
|
|
148
|
+
abortController.abort();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const startTime = Date.now();
|
|
153
|
+
const clarificationIds = new Map();
|
|
154
|
+
const externalClarificationId = (internalId) => {
|
|
155
|
+
if (!clarificationIds.has(internalId)) clarificationIds.set(internalId, randomUUID());
|
|
156
|
+
return clarificationIds.get(internalId);
|
|
157
|
+
};
|
|
158
|
+
const conversationContext = resolveConversationContext(query, body.history);
|
|
159
|
+
const publicContext = {
|
|
160
|
+
contextual: conversationContext.contextual,
|
|
161
|
+
domain: conversationContext.domain
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const conn = await resolveAgentConnection({
|
|
166
|
+
provider: body.provider,
|
|
167
|
+
model: body.model,
|
|
168
|
+
thinking: body.thinking,
|
|
169
|
+
apiKey,
|
|
170
|
+
sessionId: body.sessionId,
|
|
171
|
+
history: body.history,
|
|
172
|
+
baseUrl: typeof body.baseUrl === 'string' && body.baseUrl.trim() ? body.baseUrl.trim() : undefined,
|
|
173
|
+
}, { signal: abortController.signal });
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
const queryForAgent = conversationContext.resolvedQuery;
|
|
177
|
+
const systemDirectives = [
|
|
178
|
+
`[SYSTEM DIRECTIVE (Fact Grounding & Multi-Domain Retrieval):
|
|
179
|
+
1. อ้างอิงเฉพาะเพลง ซีรีส์ แบรนด์พรีเซนเตอร์ และข้อมูลจริงที่ได้จากผลลัพธ์เครื่องมือ (search_works, search_discography, get_brand_endorsements, get_character_roles, get_awards, get_career_timeline, search_bilibili, search_chinese_web, web_search, get_media_links) เท่านั้น ห้ามแต่งชื่อผลงาน ชื่อแบรนด์ ตัวเลข หรือลิงก์สื่อขึ้นเอง หากตรวจสอบเครื่องมือที่เกี่ยวข้องแล้วไม่พบ จึงแจ้งผู้ใช้ตามหลักฐานจริง
|
|
180
|
+
2. เมื่อพูดถึงเพลงหรือแบรนด์ใด ให้ระบุ "ชื่อจริง" จากคลังข้อมูลกำกับในประโยคเสมอ (เช่น "เพลง 千年等一回", "แบรนด์ PRSR (帕莎眼镜)") และห้ามแนบ URL ที่ไม่ได้มาจากผลลัพธ์เครื่องมือโดยเด็ดขาด
|
|
181
|
+
3. เงื่อนไขการเสนอเปิดเพลงใน Mini Player: ให้เสนอเปิดเพลงเฉพาะเมื่อผู้ใช้ถามเรื่องบทเพลง การฟังเพลง หรือ OST โดยตรงเท่านั้น และต้องระบุชื่อเพลงที่เจาะจงในประโยคเดียวกัน ห้ามเสนอเปิดเพลงหรือถามชวนเปิดเพลงเมื่อผู้ใช้ถามเรื่องซีรีส์ แบรนด์ ข่าวลือ หรือเนื้อหาทั่วไป
|
|
182
|
+
4. การแยกแยะชื่อเรื่อง แบรนด์ และข่าวลือ (Entity Disambiguation & Anti-Hallucination): ตรวจสอบชื่อเฉพาะให้ตรงกับเอนทิตีที่ถูกต้อง (เช่น เมื่อค้นหาแคมเปญโฆษณาหรือแบรนด์ ให้ตรวจสอบด้วย get_brand_endorsements และค้นหาคลิปบน Bilibili ควบคู่กับชื่อภาษาจีนของแบรนด์ เช่น 帕莎/PRSR เสมอ; และห้ามเดาหรือจับคู่คำค้นหาที่ออกเสียงคล้ายกันเข้ากับผลงานอื่นในคลังโดยไม่มีหลักฐานเด็ดขาด)
|
|
183
|
+
5. หากผู้ใช้ส่งคำว่า "ดำเนินการต่อ" หรือตอบตกลงต่อคำถามก่อนหน้า ให้ลงมือทำสิ่งที่เสนอไว้ทันทีด้วยข้อมูลจริงจากเครื่องมือ อย่าถามคำถามเดิมซ้ำอีก
|
|
184
|
+
6. ข้อมูลที่เกี่ยวกับแบรนด์ สินค้า แคมเปญโฆษณา และสื่อบนแพลตฟอร์มโซเชียล (Bilibili, YouTube, Weibo): ให้ใช้ get_brand_endorsements ควบคู่กับ search_bilibili หรือ get_media_links เสมอ โดยระบุคำค้นภาษาจีนของศิลปินและแบรนด์อย่างครบถ้วน]`,
|
|
185
|
+
|
|
186
|
+
`[SYSTEM DIRECTIVE (Answer Quality for Drama Analysis):
|
|
187
|
+
1. แยก "ข้อเท็จจริงจากเครื่องมือ" ออกจาก "บทวิเคราะห์" ให้ชัดเจน ห้ามนำเรื่องย่อไปสรุปเป็นเสียงวิจารณ์หรือการยอมรับของวงการ
|
|
188
|
+
2. ห้ามเหมารวมว่าผลงานใหม่กว่าแปลว่าลึกกว่า สมจริงกว่า หรือแสดงดีกว่า ให้เทียบจากภาระของบท แรงขับของตัวละคร แนวเรื่อง และฉากที่มีหลักฐานเท่านั้น
|
|
189
|
+
3. ห้ามใช้คำยกย่องแบบไม่มีหลักฐาน เช่น "ระดับแนวหน้า", "ไม่มีใครเทียบได้", "ก้าวข้ามไอดอลอย่างสมบูรณ์" หรือ "ได้รับรางวัลมากมาย"
|
|
190
|
+
4. คะแนนในแคตตาล็อกที่ไม่มีชื่อแหล่งและเกณฑ์คะแนนเป็นเพียง metadata ภายใน ห้ามใช้ยืนยันคุณภาพและไม่ต้องแสดงในบทวิเคราะห์
|
|
191
|
+
5. In Blossom: จวีจิ้งอีแสดงตัวตนของหยางไฉ่เวยภายใต้รูปลักษณ์ซ่างกวนจื่อหลัง premise การเปลี่ยนใบหน้า ไม่ใช่สองตัวละครอิสระที่สลับเล่นตลอดเรื่อง
|
|
192
|
+
6. The Legend of White Snake: ไป๋ซู่เจินคือวิญญาณ/ปีศาจงูขาวที่แปลงเป็นหญิงมนุษย์ ส่วนสวี่เซียนคือมนุษย์ชาย ห้ามสลับเพศหรือเผ่าพันธุ์
|
|
193
|
+
7. หากเป็นคำสั่ง "ดำเนินการต่อ" ให้ตอบเฉพาะมุมใหม่ที่ผู้ใช้ขอ ห้ามคัดลอกคำตอบก่อนหน้ามาตอบซ้ำ]`,
|
|
194
|
+
|
|
195
|
+
`[SYSTEM DIRECTIVE (Topic & Future-event Safety):
|
|
196
|
+
1. คำถามสั้นที่มีสรรพนามหรืออ้างถึง "เรื่องนี้/งานนี้/ทำไม/น่าจับตามอง" ต้องยึดหัวข้อหลักล่าสุดในบทสนทนาก่อน ห้ามเปลี่ยนไปตอบเรื่องศิลปินเพียงเพราะ Persona หรือฐานข้อมูลเน้นศิลปิน
|
|
197
|
+
2. วันนี้คือ ${conversationContext.today}. เหตุการณ์หลังวันนี้เป็นข้อมูลอนาคต: ใช้คำว่า "มีกำหนด", "มีรายงาน", หรือ "ยังไม่ยืนยัน" ตามหลักฐาน ห้ามฟันธงว่าได้รับเชิญหรือจะเข้าร่วมแน่นอนโดยไม่มีแหล่งทางการที่เป็นปัจจุบัน
|
|
198
|
+
3. ห้ามนำวัน เดือน รายชื่อแขก หรือสถานะการเข้าร่วมจากงานคนละปีมาใช้กับฉบับปีปัจจุบัน หากต้องระบุวันที่แน่นอนหรือผู้เข้าร่วมงานอนาคต ให้ตรวจสอบด้วยเครื่องมือเว็บก่อน]`,
|
|
199
|
+
|
|
200
|
+
`[SYSTEM DIRECTIVE: After completing your answer, proactively formulate and suggest 2-3 engaging, creative, and relevant follow-up questions tailored specifically to what you just discussed for the user to explore deeper. Place them at the very end of your response strictly formatted as:
|
|
201
|
+
[SUGGESTED_FOLLOW_UPS]
|
|
202
|
+
- [คำถามแนะนำที่ 1 ที่เชื่อมโยงกับเรื่องที่เพิ่งเล่า]
|
|
203
|
+
- [คำถามแนะนำที่ 2 ที่เชื่อมโยงกับเรื่องที่เพิ่งเล่า]
|
|
204
|
+
- [คำถามแนะนำที่ 3 ที่เชื่อมโยงกับเรื่องที่เพิ่งเล่า]
|
|
205
|
+
[/SUGGESTED_FOLLOW_UPS]
|
|
206
|
+
Rules for the block: one question per line, plain text only — no markdown bold/italic, no extra headers, no lead-in sentences, no numbering inside the block.]`,
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
if (wantsStream) {
|
|
210
|
+
res.writeHead(200, {
|
|
211
|
+
'Content-Type': 'text/event-stream',
|
|
212
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
213
|
+
'Connection': 'keep-alive',
|
|
214
|
+
'X-Accel-Buffering': 'no'
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const sendSse = (eventObj) => {
|
|
218
|
+
res.write(`data: ${JSON.stringify(eventObj)}\n\n`);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (conn.ready && conn.agent) {
|
|
222
|
+
try {
|
|
223
|
+
const toolsUsed = [];
|
|
224
|
+
let reasoningStarted = false;
|
|
225
|
+
const runRes = await conn.agent.run(queryForAgent, {
|
|
226
|
+
signal: abortController.signal,
|
|
227
|
+
systemDirectives,
|
|
228
|
+
onReasoningDelta: () => {
|
|
229
|
+
if (reasoningStarted) return;
|
|
230
|
+
reasoningStarted = true;
|
|
231
|
+
sendSse({ type: 'reasoning', chunk: 'กำลังวิเคราะห์บริบทและวางแผนคำตอบ' });
|
|
232
|
+
},
|
|
233
|
+
onToolStart: (name, input) => {
|
|
234
|
+
toolsUsed.push({ name, input });
|
|
235
|
+
sendSse({ type: 'tool_start', name, input: makeInspectableToolPayload(input) });
|
|
236
|
+
},
|
|
237
|
+
onToolResult: (name, result, durationMs) => {
|
|
238
|
+
const existing = toolsUsed.find(t => t.name === name);
|
|
239
|
+
if (existing) { existing.result = result; existing.durationMs = durationMs; }
|
|
240
|
+
sendSse({
|
|
241
|
+
type: 'tool_result',
|
|
242
|
+
name,
|
|
243
|
+
durationMs,
|
|
244
|
+
resultCount: result?.count ?? 0,
|
|
245
|
+
result: makeInspectableToolPayload(result)
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
onSubagentProgress: (event) => {
|
|
249
|
+
sendSse(sanitizeSubagentProgress(event));
|
|
250
|
+
},
|
|
251
|
+
onTokenDelta: (chunk) => {
|
|
252
|
+
sendSse({ type: 'token', chunk });
|
|
253
|
+
},
|
|
254
|
+
...(conversationContext.contextual ? {} : {
|
|
255
|
+
onClarifyingQuestions: (questions, queryId) => {
|
|
256
|
+
sendSse({ type: 'clarifying_questions', questions, queryId: externalClarificationId(queryId) });
|
|
257
|
+
},
|
|
258
|
+
onClarifyingAnswer: (queryId) => {
|
|
259
|
+
return new Promise((resolve) => {
|
|
260
|
+
registerClarificationResolver(externalClarificationId(queryId), resolve);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
let finalAnswer = runRes?.answer || String(runRes || '');
|
|
267
|
+
finalAnswer = finalAnswer.replace(/The user has provided a complex instruction set:[\s\S]*?(?=\n\n|\n[A-Zก-๙]|$)/i, '').trim();
|
|
268
|
+
finalAnswer = finalAnswer.replace(/^\[SYSTEM (?:INSTRUCTION|DIRECTIVE):[\s\S]*?\]\s*/i, '').trim();
|
|
269
|
+
|
|
270
|
+
let suggestions = [];
|
|
271
|
+
const extracted = extractAgentFollowUpQuestions(finalAnswer);
|
|
272
|
+
finalAnswer = stripInvalidYouTubeLinks(extracted.cleanText);
|
|
273
|
+
finalAnswer = applyFutureEventGuard(finalAnswer, { tools: toolsUsed });
|
|
274
|
+
suggestions = extracted.suggestions;
|
|
275
|
+
|
|
276
|
+
if (!finalAnswer.trim()) {
|
|
277
|
+
const fallback = await executeDirectCatalogKnowledge(query, persona, { conversationContext });
|
|
278
|
+
finalAnswer = applyFutureEventGuard(fallback.answer, { tools: fallback.tools });
|
|
279
|
+
if (!suggestions.length) suggestions = fallback.suggestions || [];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const qualityFallback = await replaceLowQualityDramaAnswer({
|
|
283
|
+
query,
|
|
284
|
+
answer: finalAnswer,
|
|
285
|
+
persona,
|
|
286
|
+
conversationContext,
|
|
287
|
+
toolsUsed
|
|
288
|
+
});
|
|
289
|
+
if (qualityFallback) {
|
|
290
|
+
finalAnswer = applyFutureEventGuard(qualityFallback.answer, { tools: toolsUsed });
|
|
291
|
+
suggestions = qualityFallback.suggestions;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
suggestions = finalizeSuggestions(sessionKey, suggestions, { query, answer: finalAnswer, tools: toolsUsed });
|
|
295
|
+
|
|
296
|
+
const searchImages = extractSearchImagesFromTools(toolsUsed, query);
|
|
297
|
+
|
|
298
|
+
sendSse({
|
|
299
|
+
type: 'final',
|
|
300
|
+
answer: finalAnswer,
|
|
301
|
+
suggestions,
|
|
302
|
+
searchImages,
|
|
303
|
+
durationMs: Date.now() - startTime,
|
|
304
|
+
tokens: conn.agent?.lastTelemetry?.tokens || conn.agent?.session?.lastQueryTokens || null,
|
|
305
|
+
metrics: conn.agent?.lastQueryMetrics || null,
|
|
306
|
+
costUsd: conn.agent?.lastTelemetry?.costUsd ?? conn.agent?.session?.lastQueryCostUsd ?? 0,
|
|
307
|
+
citations: conn.agent?.lastCitations || [],
|
|
308
|
+
context: publicContext,
|
|
309
|
+
memory: getAgentMemorySnapshot(conn.agent, body.sessionId)
|
|
310
|
+
});
|
|
311
|
+
res.write('data: [DONE]\n\n');
|
|
312
|
+
res.end();
|
|
313
|
+
return;
|
|
314
|
+
} catch (streamErr) {
|
|
315
|
+
if (abortController.signal.aborted || streamErr?.name === 'AbortError') {
|
|
316
|
+
if (!res.writableEnded) {
|
|
317
|
+
res.write('data: [DONE]\n\n');
|
|
318
|
+
res.end();
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const fallback = await executeDirectCatalogKnowledge(query, persona, { conversationContext });
|
|
323
|
+
const fallbackImages = extractSearchImagesFromTools(fallback.tools, query);
|
|
324
|
+
sendSse({
|
|
325
|
+
type: 'final',
|
|
326
|
+
answer: applyFutureEventGuard(fallback.answer, { tools: fallback.tools }),
|
|
327
|
+
suggestions: finalizeSuggestions(sessionKey, fallback.suggestions || [], { query, answer: fallback.answer, tools: fallback.tools }),
|
|
328
|
+
searchImages: fallbackImages,
|
|
329
|
+
durationMs: Date.now() - startTime,
|
|
330
|
+
citations: [],
|
|
331
|
+
context: publicContext,
|
|
332
|
+
memory: { sessionId: body.sessionId || null, messageCount: body.history?.length || 0, persistent: false }
|
|
333
|
+
});
|
|
334
|
+
res.write('data: [DONE]\n\n');
|
|
335
|
+
res.end();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
sendSse({ type: 'reasoning', chunk: '⚡ ตรวจพบ LM Studio ออฟไลน์ -> เปิดใช้งาน Auto Direct Catalog Knowledge Engine...' });
|
|
341
|
+
|
|
342
|
+
const fallback = await executeDirectCatalogKnowledge(query, persona, { conversationContext });
|
|
343
|
+
const guardedFallbackAnswer = applyFutureEventGuard(fallback.answer, { tools: fallback.tools });
|
|
344
|
+
const fallbackImages = extractSearchImagesFromTools(fallback.tools, query);
|
|
345
|
+
for (const t of fallback.tools) {
|
|
346
|
+
sendSse({ type: 'tool_start', name: t.name, input: t.input });
|
|
347
|
+
sendSse({ type: 'tool_result', name: t.name, durationMs: t.durationMs, resultCount: t.resultCount, result: t.result });
|
|
348
|
+
}
|
|
349
|
+
sendSse({ type: 'token', chunk: guardedFallbackAnswer });
|
|
350
|
+
sendSse({
|
|
351
|
+
type: 'final',
|
|
352
|
+
answer: guardedFallbackAnswer,
|
|
353
|
+
suggestions: finalizeSuggestions(sessionKey, fallback.suggestions || [], { query, answer: guardedFallbackAnswer, tools: fallback.tools }),
|
|
354
|
+
searchImages: fallbackImages,
|
|
355
|
+
durationMs: Date.now() - startTime,
|
|
356
|
+
citations: [],
|
|
357
|
+
context: publicContext,
|
|
358
|
+
memory: { sessionId: body.sessionId || null, messageCount: body.history?.length || 0, persistent: false }
|
|
359
|
+
});
|
|
360
|
+
res.write('data: [DONE]\n\n');
|
|
361
|
+
res.end();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (conn.ready && conn.agent) {
|
|
366
|
+
let answer = '';
|
|
367
|
+
let thinking = '';
|
|
368
|
+
let thinkingDurationMs = 0;
|
|
369
|
+
const toolsUsed = [];
|
|
370
|
+
const subagentActivity = [];
|
|
371
|
+
|
|
372
|
+
let runRes;
|
|
373
|
+
try {
|
|
374
|
+
runRes = await conn.agent.run(queryForAgent, {
|
|
375
|
+
signal: abortController.signal,
|
|
376
|
+
systemDirectives,
|
|
377
|
+
onReasoningDelta: () => { thinking = 'วิเคราะห์บริบทและวางแผนคำตอบแล้ว'; },
|
|
378
|
+
onReasoningEnd: () => { thinkingDurationMs = Date.now() - startTime; },
|
|
379
|
+
onSubagentProgress: (event) => {
|
|
380
|
+
subagentActivity.push(sanitizeSubagentProgress(event));
|
|
381
|
+
},
|
|
382
|
+
onToolStart: (name, input) => {
|
|
383
|
+
toolsUsed.push({ name, input: makeInspectableToolPayload(input), status: 'running' });
|
|
384
|
+
},
|
|
385
|
+
onToolResult: (name, result, dur) => {
|
|
386
|
+
let tool = null;
|
|
387
|
+
for (let index = toolsUsed.length - 1; index >= 0; index--) {
|
|
388
|
+
if (toolsUsed[index].name === name && toolsUsed[index].status === 'running') {
|
|
389
|
+
tool = toolsUsed[index];
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (!tool) {
|
|
394
|
+
tool = { name };
|
|
395
|
+
toolsUsed.push(tool);
|
|
396
|
+
}
|
|
397
|
+
Object.assign(tool, {
|
|
398
|
+
status: 'done',
|
|
399
|
+
result: makeInspectableToolPayload(result),
|
|
400
|
+
resultCount: result?.count ?? 0,
|
|
401
|
+
durationMs: dur
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
} catch (agentErr) {
|
|
406
|
+
if (abortController.signal.aborted || agentErr?.name === 'AbortError') {
|
|
407
|
+
if (!res.writableEnded) {
|
|
408
|
+
return sendError(res, 499, 'Request aborted by client');
|
|
409
|
+
}
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
throw agentErr;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
answer = runRes?.answer || String(runRes || '');
|
|
416
|
+
let suggestions = [];
|
|
417
|
+
const extracted = extractAgentFollowUpQuestions(answer);
|
|
418
|
+
answer = stripInvalidYouTubeLinks(extracted.cleanText);
|
|
419
|
+
answer = applyFutureEventGuard(answer, { tools: toolsUsed });
|
|
420
|
+
suggestions = extracted.suggestions;
|
|
421
|
+
|
|
422
|
+
if (!answer.trim()) {
|
|
423
|
+
const fallback = await executeDirectCatalogKnowledge(query, persona, { conversationContext });
|
|
424
|
+
answer = applyFutureEventGuard(fallback.answer, { tools: fallback.tools });
|
|
425
|
+
if (!suggestions.length) suggestions = fallback.suggestions || [];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const qualityFallback = await replaceLowQualityDramaAnswer({
|
|
429
|
+
query,
|
|
430
|
+
answer,
|
|
431
|
+
persona,
|
|
432
|
+
conversationContext,
|
|
433
|
+
toolsUsed
|
|
434
|
+
});
|
|
435
|
+
if (qualityFallback) {
|
|
436
|
+
answer = applyFutureEventGuard(qualityFallback.answer, { tools: toolsUsed });
|
|
437
|
+
suggestions = qualityFallback.suggestions;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!suggestions.length) {
|
|
441
|
+
suggestions = finalizeSuggestions(sessionKey, suggestions, { query, answer, tools: toolsUsed });
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const searchImages = extractSearchImagesFromTools(toolsUsed, query);
|
|
445
|
+
|
|
446
|
+
return sendJson(res, 200, {
|
|
447
|
+
status: 'ok',
|
|
448
|
+
query,
|
|
449
|
+
persona,
|
|
450
|
+
answer,
|
|
451
|
+
suggestions,
|
|
452
|
+
searchImages,
|
|
453
|
+
thinking,
|
|
454
|
+
thinkingDurationMs,
|
|
455
|
+
tools: toolsUsed,
|
|
456
|
+
subagents: subagentActivity,
|
|
457
|
+
tokens: conn.agent?.lastTelemetry?.tokens || conn.agent?.session?.lastQueryTokens || null,
|
|
458
|
+
metrics: conn.agent?.lastQueryMetrics || null,
|
|
459
|
+
costUsd: conn.agent?.lastTelemetry?.costUsd ?? conn.agent?.session?.lastQueryCostUsd ?? 0,
|
|
460
|
+
citations: conn.agent?.lastCitations || [],
|
|
461
|
+
context: publicContext,
|
|
462
|
+
memory: getAgentMemorySnapshot(conn.agent, body.sessionId),
|
|
463
|
+
durationMs: Date.now() - startTime
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const fallback = await executeDirectCatalogKnowledge(query, persona, { conversationContext });
|
|
468
|
+
const searchImages = extractSearchImagesFromTools(fallback.tools, query);
|
|
469
|
+
return sendJson(res, 200, {
|
|
470
|
+
status: 'ok',
|
|
471
|
+
query,
|
|
472
|
+
persona,
|
|
473
|
+
answer: applyFutureEventGuard(fallback.answer, { tools: fallback.tools }),
|
|
474
|
+
suggestions: finalizeSuggestions(sessionKey, fallback.suggestions || [], { query, answer: fallback.answer, tools: fallback.tools }),
|
|
475
|
+
searchImages,
|
|
476
|
+
thinking: fallback.thinking,
|
|
477
|
+
thinkingDurationMs: Date.now() - startTime,
|
|
478
|
+
tools: fallback.tools,
|
|
479
|
+
citations: [],
|
|
480
|
+
context: publicContext,
|
|
481
|
+
memory: { sessionId: body.sessionId || null, messageCount: body.history?.length || 0, persistent: false },
|
|
482
|
+
durationMs: Date.now() - startTime
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
} catch (err) {
|
|
486
|
+
return sendError(res, 500, err.message);
|
|
487
|
+
}
|
|
488
|
+
}
|