@jsonstudio/llms 0.6.375 → 0.6.467

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 (37) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.js +15 -1
  2. package/dist/conversion/compat/actions/iflow-web-search.d.ts +18 -0
  3. package/dist/conversion/compat/actions/iflow-web-search.js +87 -0
  4. package/dist/conversion/compat/profiles/chat-glm.json +4 -0
  5. package/dist/conversion/compat/profiles/chat-iflow.json +5 -1
  6. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
  7. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +2 -0
  8. package/dist/conversion/hub/pipeline/hub-pipeline.js +5 -1
  9. package/dist/conversion/hub/pipeline/session-identifiers.d.ts +9 -0
  10. package/dist/conversion/hub/pipeline/session-identifiers.js +76 -0
  11. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +31 -2
  12. package/dist/conversion/hub/process/chat-process.js +89 -25
  13. package/dist/conversion/responses/responses-openai-bridge.js +75 -4
  14. package/dist/conversion/shared/anthropic-message-utils.js +41 -6
  15. package/dist/conversion/shared/errors.d.ts +20 -0
  16. package/dist/conversion/shared/errors.js +28 -0
  17. package/dist/conversion/shared/responses-conversation-store.js +30 -3
  18. package/dist/conversion/shared/responses-output-builder.js +68 -6
  19. package/dist/filters/special/request-toolcalls-stringify.d.ts +13 -0
  20. package/dist/filters/special/request-toolcalls-stringify.js +103 -3
  21. package/dist/filters/special/response-tool-text-canonicalize.d.ts +16 -0
  22. package/dist/filters/special/response-tool-text-canonicalize.js +27 -3
  23. package/dist/router/virtual-router/classifier.js +4 -2
  24. package/dist/router/virtual-router/engine.d.ts +30 -0
  25. package/dist/router/virtual-router/engine.js +600 -42
  26. package/dist/router/virtual-router/provider-registry.d.ts +15 -0
  27. package/dist/router/virtual-router/provider-registry.js +40 -0
  28. package/dist/router/virtual-router/routing-instructions.d.ts +34 -0
  29. package/dist/router/virtual-router/routing-instructions.js +383 -0
  30. package/dist/router/virtual-router/sticky-session-store.d.ts +3 -0
  31. package/dist/router/virtual-router/sticky-session-store.js +110 -0
  32. package/dist/router/virtual-router/tool-signals.js +0 -22
  33. package/dist/router/virtual-router/types.d.ts +35 -0
  34. package/dist/servertool/engine.js +42 -1
  35. package/dist/servertool/handlers/web-search.js +157 -4
  36. package/dist/servertool/types.d.ts +6 -0
  37. package/package.json +1 -1
@@ -41,12 +41,53 @@ export async function runServerToolOrchestration(options) {
41
41
  const followupBody = followup.body && typeof followup.body === 'object'
42
42
  ? followup.body
43
43
  : engineResult.finalChatResponse;
44
+ const decorated = decorateFinalChatWithServerToolContext(followupBody, engineResult.execution);
44
45
  return {
45
- chat: followupBody,
46
+ chat: decorated,
46
47
  executed: true,
47
48
  flowId: engineResult.execution.flowId
48
49
  };
49
50
  }
51
+ function decorateFinalChatWithServerToolContext(chat, execution) {
52
+ if (!execution || !execution.context) {
53
+ return chat;
54
+ }
55
+ // 目前仅对 web_search flow 附加原文摘要,避免影响其它 ServerTool。
56
+ if (execution.flowId !== 'web_search_flow') {
57
+ return chat;
58
+ }
59
+ const ctx = execution.context;
60
+ const web = ctx.web_search;
61
+ const summary = web && typeof web.summary === 'string' && web.summary.trim().length
62
+ ? web.summary.trim()
63
+ : '';
64
+ if (!summary) {
65
+ return chat;
66
+ }
67
+ const engineId = web && typeof web.engineId === 'string' && web.engineId.trim().length
68
+ ? web.engineId.trim()
69
+ : undefined;
70
+ const label = engineId
71
+ ? `【web_search 原文 | engine: ${engineId}】`
72
+ : '【web_search 原文】';
73
+ const cloned = JSON.parse(JSON.stringify(chat));
74
+ const choices = Array.isArray(cloned.choices) ? cloned.choices : [];
75
+ if (!choices.length) {
76
+ return cloned;
77
+ }
78
+ const first = choices[0] && typeof choices[0] === 'object' ? choices[0] : null;
79
+ if (!first || !first.message || typeof first.message !== 'object') {
80
+ return cloned;
81
+ }
82
+ const message = first.message;
83
+ const baseContent = typeof message.content === 'string' ? message.content : '';
84
+ const suffix = `${label}\n${summary}`;
85
+ message.content =
86
+ baseContent && baseContent.trim().length
87
+ ? `${baseContent}\n\n${suffix}`
88
+ : suffix;
89
+ return cloned;
90
+ }
50
91
  function resolveRouteHint(adapterContext, flowId) {
51
92
  const rawRoute = adapterContext.routeId;
52
93
  const routeId = typeof rawRoute === 'string' && rawRoute.trim() ? rawRoute.trim() : '';
@@ -62,7 +62,14 @@ const handler = async (ctx) => {
62
62
  payload: followupPayload,
63
63
  metadata: buildFollowupMetadata(ctx.adapterContext, 'web_search')
64
64
  }
65
- : undefined
65
+ : undefined,
66
+ context: {
67
+ web_search: {
68
+ engineId: chosenEngine.id,
69
+ providerKey: chosenEngine.providerKey,
70
+ summary: chosenResult.summary
71
+ }
72
+ }
66
73
  };
67
74
  return {
68
75
  chatResponse: patched,
@@ -89,7 +96,9 @@ function getWebSearchConfig(ctx) {
89
96
  const enginesRaw = Array.isArray(record.engines) ? record.engines : [];
90
97
  const engines = [];
91
98
  for (const entry of enginesRaw) {
92
- const obj = entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null;
99
+ const obj = entry && typeof entry === 'object' && !Array.isArray(entry)
100
+ ? entry
101
+ : null;
93
102
  if (!obj)
94
103
  continue;
95
104
  const id = typeof obj.id === 'string' && obj.id.trim() ? obj.id.trim() : undefined;
@@ -104,12 +113,26 @@ function getWebSearchConfig(ctx) {
104
113
  (obj.serverTools &&
105
114
  typeof obj.serverTools === 'object' &&
106
115
  obj.serverTools.enabled === false);
116
+ let searchEngineList;
117
+ const rawSearchList = obj.searchEngineList;
118
+ if (Array.isArray(rawSearchList)) {
119
+ const normalizedList = [];
120
+ for (const item of rawSearchList) {
121
+ if (typeof item === 'string' && item.trim().length) {
122
+ normalizedList.push(item.trim());
123
+ }
124
+ }
125
+ if (normalizedList.length) {
126
+ searchEngineList = normalizedList;
127
+ }
128
+ }
107
129
  engines.push({
108
130
  id,
109
131
  providerKey,
110
132
  description: typeof obj.description === 'string' && obj.description.trim() ? obj.description.trim() : undefined,
111
133
  default: obj.default === true,
112
- ...(serverToolsDisabled ? { serverToolsDisabled: true } : {})
134
+ ...(serverToolsDisabled ? { serverToolsDisabled: true } : {}),
135
+ ...(searchEngineList ? { searchEngineList } : {})
113
136
  });
114
137
  }
115
138
  if (!engines.length) {
@@ -179,6 +202,10 @@ function isGeminiWebSearchEngine(engine) {
179
202
  key.startsWith('antigravity.') ||
180
203
  key.startsWith('gemini.'));
181
204
  }
205
+ function isIflowWebSearchEngine(engine) {
206
+ const key = engine.providerKey.toLowerCase();
207
+ return key.startsWith('iflow.');
208
+ }
182
209
  function normalizeResultCount(value) {
183
210
  if (typeof value === 'number' && Number.isFinite(value)) {
184
211
  const normalized = Math.trunc(value);
@@ -197,7 +224,22 @@ async function executeWebSearchBackend(args) {
197
224
  try {
198
225
  logServerToolWebSearch(engine, ctx.options.requestId, query);
199
226
  const requestSuffix = `:web_search:${engine.id}`;
200
- if (ctx.options.reenterPipeline) {
227
+ // 对于 iFlow,直接通过 providerInvoker 调用 /chat/retrieve,
228
+ // 即使 reenterPipeline 可用,也不走 Chat 模型 + tools。
229
+ if (isIflowWebSearchEngine(engine) && ctx.options.providerInvoker) {
230
+ const backendResult = await executeIflowWebSearchViaProvider({
231
+ ctx,
232
+ engine,
233
+ query,
234
+ recency,
235
+ count: args.resultCount,
236
+ requestSuffix
237
+ });
238
+ summary = backendResult.summary;
239
+ hits = backendResult.hits;
240
+ ok = backendResult.ok;
241
+ }
242
+ else if (ctx.options.reenterPipeline) {
201
243
  const payload = buildWebSearchReenterPayload(engine, query, recency, args.resultCount);
202
244
  const followup = await ctx.options.reenterPipeline({
203
245
  entryEndpoint: '/v1/chat/completions',
@@ -384,6 +426,117 @@ async function executeWebSearchViaProvider(args) {
384
426
  }
385
427
  return extractTextFromChatLike(providerResponse);
386
428
  }
429
+ async function executeIflowWebSearchViaProvider(args) {
430
+ const { ctx, engine, query, count, requestSuffix } = args;
431
+ if (!ctx.options.providerInvoker) {
432
+ return {
433
+ summary: '',
434
+ hits: [],
435
+ ok: false
436
+ };
437
+ }
438
+ const searchEngineList = Array.isArray(engine.searchEngineList) && engine.searchEngineList.length
439
+ ? engine.searchEngineList
440
+ : ['GOOGLE', 'BING', 'SCHOLAR', 'AIPGC', 'PDF'];
441
+ const searchBody = {
442
+ query,
443
+ history: {},
444
+ userId: 2,
445
+ userIp: '42.120.74.197',
446
+ appCode: 'SEARCH_CHATBOT',
447
+ chatId: Date.now(),
448
+ phase: 'UNIFY',
449
+ enableQueryRewrite: false,
450
+ enableRetrievalSecurity: false,
451
+ enableIntention: false,
452
+ searchEngineList
453
+ };
454
+ let providerKey = engine.providerKey;
455
+ try {
456
+ const adapter = ctx.adapterContext && typeof ctx.adapterContext === 'object'
457
+ ? ctx.adapterContext
458
+ : null;
459
+ const target = adapter && adapter.target && typeof adapter.target === 'object'
460
+ ? adapter.target
461
+ : null;
462
+ const targetProviderKey = target && typeof target.providerKey === 'string' && target.providerKey.trim()
463
+ ? target.providerKey.trim()
464
+ : undefined;
465
+ if (targetProviderKey) {
466
+ providerKey = targetProviderKey;
467
+ }
468
+ }
469
+ catch {
470
+ // best-effort: fallback to engine.providerKey
471
+ }
472
+ const payload = {
473
+ data: searchBody,
474
+ metadata: {
475
+ entryEndpoint: '/chat/retrieve',
476
+ iflowWebSearch: true,
477
+ routeName: 'web_search'
478
+ }
479
+ };
480
+ const backend = await ctx.options.providerInvoker({
481
+ providerKey,
482
+ providerType: undefined,
483
+ modelId: undefined,
484
+ providerProtocol: ctx.options.providerProtocol,
485
+ payload,
486
+ entryEndpoint: '/v1/chat/retrieve',
487
+ requestId: `${ctx.options.requestId}${requestSuffix}`,
488
+ routeHint: 'web_search'
489
+ });
490
+ const providerResponse = backend.providerResponse && typeof backend.providerResponse === 'object'
491
+ ? backend.providerResponse
492
+ : null;
493
+ if (!providerResponse) {
494
+ return {
495
+ summary: '',
496
+ hits: [],
497
+ ok: false
498
+ };
499
+ }
500
+ const container = providerResponse;
501
+ const rawHits = Array.isArray(container.data) ? container.data : [];
502
+ const hits = [];
503
+ for (const item of rawHits) {
504
+ if (!item || typeof item !== 'object' || Array.isArray(item))
505
+ continue;
506
+ const record = item;
507
+ const link = typeof record.url === 'string' && record.url.trim() ? record.url.trim() : '';
508
+ if (!link)
509
+ continue;
510
+ const title = typeof record.title === 'string' && record.title.trim() ? record.title.trim() : undefined;
511
+ const publishDate = typeof record.time === 'string' && record.time.trim() ? record.time.trim() : undefined;
512
+ const content = typeof record.abstractInfo === 'string' && record.abstractInfo.trim()
513
+ ? record.abstractInfo.trim()
514
+ : undefined;
515
+ hits.push({
516
+ title,
517
+ link,
518
+ publish_date: publishDate,
519
+ content
520
+ });
521
+ if (hits.length >= count) {
522
+ break;
523
+ }
524
+ }
525
+ let summary = '';
526
+ if (typeof container.message === 'string' && container.message.trim()) {
527
+ summary = container.message.trim();
528
+ }
529
+ if (!summary && hits.length) {
530
+ summary = formatHitsSummary(hits);
531
+ }
532
+ const successField = container.success;
533
+ const ok = typeof successField === 'boolean' ? successField : hits.length > 0;
534
+ return {
535
+ summary,
536
+ hits,
537
+ ok
538
+ };
539
+ }
387
540
  function injectWebSearchToolResult(base, toolCall, engine, query, backendResult) {
388
541
  const cloned = cloneJson(base);
389
542
  const existingOutputs = Array.isArray(cloned.tool_outputs)
@@ -57,6 +57,12 @@ export interface ServerToolFollowupPlan {
57
57
  export interface ServerToolExecution {
58
58
  flowId: string;
59
59
  followup?: ServerToolFollowupPlan;
60
+ /**
61
+ * Optional tool-specific context for the execution result.
62
+ * For example, web_search handler may attach { web_search: { engineId, providerKey, summary } }
63
+ * so that orchestration layer can decorate final Chat response without touching host code.
64
+ */
65
+ context?: JsonObject;
60
66
  }
61
67
  /**
62
68
  * ServerSideToolEngineResult:ServerTool 引擎出参。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.375",
3
+ "version": "0.6.467",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",