@jsonstudio/llms 0.6.230 → 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 (81) hide show
  1. package/README.md +2 -0
  2. package/dist/conversion/codecs/gemini-openai-codec.js +24 -2
  3. package/dist/conversion/compat/actions/gemini-web-search.d.ts +17 -0
  4. package/dist/conversion/compat/actions/gemini-web-search.js +68 -0
  5. package/dist/conversion/compat/actions/glm-image-content.d.ts +2 -0
  6. package/dist/conversion/compat/actions/glm-image-content.js +83 -0
  7. package/dist/conversion/compat/actions/glm-vision-prompt.d.ts +11 -0
  8. package/dist/conversion/compat/actions/glm-vision-prompt.js +177 -0
  9. package/dist/conversion/compat/actions/glm-web-search.js +25 -28
  10. package/dist/conversion/compat/actions/iflow-web-search.d.ts +18 -0
  11. package/dist/conversion/compat/actions/iflow-web-search.js +87 -0
  12. package/dist/conversion/compat/actions/universal-shape-filter.js +11 -0
  13. package/dist/conversion/compat/profiles/chat-gemini.json +17 -0
  14. package/dist/conversion/compat/profiles/chat-glm.json +194 -184
  15. package/dist/conversion/compat/profiles/chat-iflow.json +199 -195
  16. package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
  17. package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
  18. package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  19. package/dist/conversion/config/sample-config.json +1 -1
  20. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +24 -0
  21. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
  22. package/dist/conversion/hub/pipeline/hub-pipeline.js +32 -1
  23. package/dist/conversion/hub/pipeline/session-identifiers.d.ts +9 -0
  24. package/dist/conversion/hub/pipeline/session-identifiers.js +76 -0
  25. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +31 -2
  26. package/dist/conversion/hub/pipeline/target-utils.js +6 -0
  27. package/dist/conversion/hub/process/chat-process.js +186 -40
  28. package/dist/conversion/hub/response/provider-response.d.ts +13 -1
  29. package/dist/conversion/hub/response/provider-response.js +84 -35
  30. package/dist/conversion/hub/response/server-side-tools.js +61 -4
  31. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +123 -3
  32. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
  33. package/dist/conversion/hub/standardized-bridge.js +14 -0
  34. package/dist/conversion/responses/responses-openai-bridge.js +110 -6
  35. package/dist/conversion/shared/anthropic-message-utils.js +133 -9
  36. package/dist/conversion/shared/bridge-message-utils.js +137 -10
  37. package/dist/conversion/shared/errors.d.ts +20 -0
  38. package/dist/conversion/shared/errors.js +28 -0
  39. package/dist/conversion/shared/responses-conversation-store.js +30 -3
  40. package/dist/conversion/shared/responses-output-builder.js +111 -8
  41. package/dist/conversion/shared/tool-filter-pipeline.js +1 -0
  42. package/dist/filters/special/request-toolcalls-stringify.d.ts +13 -0
  43. package/dist/filters/special/request-toolcalls-stringify.js +103 -3
  44. package/dist/filters/special/response-tool-text-canonicalize.d.ts +16 -0
  45. package/dist/filters/special/response-tool-text-canonicalize.js +27 -3
  46. package/dist/router/virtual-router/bootstrap.js +44 -12
  47. package/dist/router/virtual-router/classifier.js +13 -17
  48. package/dist/router/virtual-router/engine.d.ts +39 -0
  49. package/dist/router/virtual-router/engine.js +755 -55
  50. package/dist/router/virtual-router/features.js +1 -1
  51. package/dist/router/virtual-router/message-utils.js +36 -24
  52. package/dist/router/virtual-router/provider-registry.d.ts +15 -0
  53. package/dist/router/virtual-router/provider-registry.js +42 -1
  54. package/dist/router/virtual-router/routing-instructions.d.ts +34 -0
  55. package/dist/router/virtual-router/routing-instructions.js +383 -0
  56. package/dist/router/virtual-router/sticky-session-store.d.ts +3 -0
  57. package/dist/router/virtual-router/sticky-session-store.js +110 -0
  58. package/dist/router/virtual-router/token-counter.js +14 -3
  59. package/dist/router/virtual-router/tool-signals.js +0 -22
  60. package/dist/router/virtual-router/types.d.ts +80 -0
  61. package/dist/router/virtual-router/types.js +2 -1
  62. package/dist/servertool/engine.d.ts +27 -0
  63. package/dist/servertool/engine.js +101 -0
  64. package/dist/servertool/flow-types.d.ts +40 -0
  65. package/dist/servertool/flow-types.js +1 -0
  66. package/dist/servertool/handlers/vision.d.ts +1 -0
  67. package/dist/servertool/handlers/vision.js +194 -0
  68. package/dist/servertool/handlers/web-search.d.ts +1 -0
  69. package/dist/servertool/handlers/web-search.js +791 -0
  70. package/dist/servertool/orchestration-types.d.ts +33 -0
  71. package/dist/servertool/orchestration-types.js +1 -0
  72. package/dist/servertool/registry.d.ts +18 -0
  73. package/dist/servertool/registry.js +27 -0
  74. package/dist/servertool/server-side-tools.d.ts +8 -0
  75. package/dist/servertool/server-side-tools.js +208 -0
  76. package/dist/servertool/types.d.ts +94 -0
  77. package/dist/servertool/types.js +1 -0
  78. package/dist/servertool/vision-tool.d.ts +2 -0
  79. package/dist/servertool/vision-tool.js +185 -0
  80. package/dist/sse/sse-to-json/builders/response-builder.js +6 -3
  81. package/package.json +1 -1
@@ -78,11 +78,22 @@ function encodeContent(content, encoder) {
78
78
  total += encodeText(part, encoder);
79
79
  }
80
80
  else if (part && typeof part === 'object') {
81
- if (typeof part.text === 'string') {
82
- total += encodeText(part.text, encoder);
81
+ const record = part;
82
+ const typeValue = typeof record.type === 'string' ? record.type.toLowerCase() : '';
83
+ // Large binary/image payloads (data URIs, base64, etc.) should not
84
+ // dominate context estimation. For image-like blocks, only count a
85
+ // small textual placeholder instead of the full JSON/body.
86
+ if (typeValue.startsWith('image')) {
87
+ const caption = typeof record.caption === 'string' && record.caption.trim().length
88
+ ? record.caption
89
+ : '[image]';
90
+ total += encodeText(caption, encoder);
91
+ }
92
+ else if (typeof record.text === 'string') {
93
+ total += encodeText(record.text, encoder);
83
94
  }
84
95
  else {
85
- total += encodeText(JSON.stringify(part), encoder);
96
+ total += encodeText(JSON.stringify(record), encoder);
86
97
  }
87
98
  }
88
99
  }
@@ -85,22 +85,6 @@ const SHELL_WRITE_PATTERNS = [
85
85
  'go install',
86
86
  'make install'
87
87
  ];
88
- const SHELL_SEARCH_PATTERNS = [
89
- 'rg ',
90
- 'rg-',
91
- 'grep ',
92
- 'grep-',
93
- 'ripgrep',
94
- 'find ',
95
- 'fd ',
96
- 'locate ',
97
- 'search ',
98
- 'ack ',
99
- 'ag ',
100
- 'where ',
101
- 'which ',
102
- 'codesearch'
103
- ];
104
88
  const SHELL_READ_PATTERNS = [
105
89
  'ls',
106
90
  'dir ',
@@ -362,9 +346,6 @@ function classifyShellCommand(command) {
362
346
  if (segments.some((segment) => matchesAnyPattern(segment, SHELL_WRITE_PATTERNS))) {
363
347
  return 'write';
364
348
  }
365
- if (segments.some((segment) => matchesAnyPattern(segment, SHELL_SEARCH_PATTERNS))) {
366
- return 'search';
367
- }
368
349
  if (segments.some((segment) => matchesAnyPattern(segment, SHELL_READ_PATTERNS))) {
369
350
  return 'read';
370
351
  }
@@ -372,9 +353,6 @@ function classifyShellCommand(command) {
372
353
  if (matchesAnyPattern(stripped, SHELL_WRITE_PATTERNS)) {
373
354
  return 'write';
374
355
  }
375
- if (matchesAnyPattern(stripped, SHELL_SEARCH_PATTERNS)) {
376
- return 'search';
377
- }
378
356
  if (matchesAnyPattern(stripped, SHELL_READ_PATTERNS)) {
379
357
  return 'read';
380
358
  }
@@ -5,11 +5,19 @@ import type { StandardizedRequest } from '../../conversion/hub/types/standardize
5
5
  export declare const DEFAULT_MODEL_CONTEXT_TOKENS = 200000;
6
6
  export declare const DEFAULT_ROUTE = "default";
7
7
  export declare const ROUTE_PRIORITY: string[];
8
+ export type RoutingInstructionMode = 'force' | 'sticky' | 'none';
8
9
  export interface RoutePoolTier {
9
10
  id: string;
10
11
  targets: string[];
11
12
  priority: number;
12
13
  backup?: boolean;
14
+ /**
15
+ * Optional force flag for this route pool.
16
+ * Currently interpreted for:
17
+ * - routing.vision: force dedicated vision backend handling.
18
+ * - routing.web_search / routing.search: force server-side web_search flow.
19
+ */
20
+ force?: boolean;
13
21
  }
14
22
  export type RoutingPools = Record<string, RoutePoolTier[]>;
15
23
  export type StreamingPreference = 'auto' | 'always' | 'never';
@@ -42,6 +50,13 @@ export interface ProviderProfile {
42
50
  responsesConfig?: ResponsesProviderConfig;
43
51
  streaming?: StreamingPreference;
44
52
  maxContextTokens?: number;
53
+ /**
54
+ * When true, this provider must be skipped for any request that
55
+ * requires server-side tool orchestration (e.g. web_search).
56
+ * Normal chat routing (without servertool injection) may still
57
+ * use this provider as usual.
58
+ */
59
+ serverToolsDisabled?: boolean;
45
60
  }
46
61
  export interface ProviderRuntimeProfile {
47
62
  runtimeKey: string;
@@ -61,6 +76,12 @@ export interface ProviderRuntimeProfile {
61
76
  modelContextTokens?: Record<string, number>;
62
77
  defaultContextTokens?: number;
63
78
  maxContextTokens?: number;
79
+ /**
80
+ * Provider-level flag propagated from virtualrouter.providers[*].
81
+ * When true, VirtualRouterEngine will skip this runtime for any
82
+ * request that declares serverToolRequired=true in routing metadata.
83
+ */
84
+ serverToolsDisabled?: boolean;
64
85
  }
65
86
  export interface VirtualRouterClassifierConfig {
66
87
  longContextThresholdTokens?: number;
@@ -83,10 +104,21 @@ export interface VirtualRouterWebSearchEngineConfig {
83
104
  providerKey: string;
84
105
  description?: string;
85
106
  default?: boolean;
107
+ /**
108
+ * When true, this engine will never be used by server-side tools
109
+ * (e.g. web_search). It will also be omitted from injected tool
110
+ * schemas so main models cannot select it for servertool flows.
111
+ */
112
+ serverToolsDisabled?: boolean;
86
113
  }
87
114
  export interface VirtualRouterWebSearchConfig {
88
115
  engines: VirtualRouterWebSearchEngineConfig[];
89
116
  injectPolicy?: 'always' | 'selective';
117
+ /**
118
+ * When true, always prefer server-side web_search orchestration
119
+ * over upstream builtin behaviours (e.g. OpenAI Responses builtin web_search).
120
+ */
121
+ force?: boolean;
90
122
  }
91
123
  export interface VirtualRouterConfig {
92
124
  routing: RoutingPools;
@@ -129,6 +161,47 @@ export interface RouterMetadataInput {
129
161
  providerProtocol?: string;
130
162
  stage?: 'inbound' | 'outbound' | 'response';
131
163
  routeHint?: string;
164
+ /**
165
+ * Indicates that current routing decision is for a request which
166
+ * expects server-side tools orchestration (e.g. web_search).
167
+ * Virtual Router should skip providers that opt out via
168
+ * serverToolsDisabled when this flag is true.
169
+ */
170
+ serverToolRequired?: boolean;
171
+ /**
172
+ * 强制路由模式,从消息中的 <**...**> 指令解析得出
173
+ */
174
+ routingMode?: RoutingInstructionMode;
175
+ /**
176
+ * 允许的 provider 白名单
177
+ */
178
+ allowedProviders?: string[];
179
+ /**
180
+ * 强制使用的 provider model (格式: provider.model)
181
+ */
182
+ forcedProviderModel?: string;
183
+ /**
184
+ * 强制使用的 provider keyAlias
185
+ */
186
+ forcedProviderKeyAlias?: string;
187
+ /**
188
+ * 强制使用的 provider keyIndex (从 1 开始)
189
+ */
190
+ forcedProviderKeyIndex?: number;
191
+ /**
192
+ * 禁用的 provider model 列表
193
+ */
194
+ disabledProviderModels?: string[];
195
+ /**
196
+ * 禁用的 provider keyAlias 列表
197
+ */
198
+ disabledProviderKeyAliases?: string[];
199
+ /**
200
+ * 禁用的 provider keyIndex 列表 (从 1 开始)
201
+ */
202
+ disabledProviderKeyIndexes?: number[];
203
+ sessionId?: string;
204
+ conversationId?: string;
132
205
  responsesResume?: {
133
206
  previousRequestId?: string;
134
207
  restoredFromResponseId?: string;
@@ -182,6 +255,13 @@ export interface TargetMetadata {
182
255
  responsesConfig?: ResponsesProviderConfig;
183
256
  streaming?: StreamingPreference;
184
257
  maxContextTokens?: number;
258
+ /**
259
+ * Route-level flags propagated from the virtual router.
260
+ * These are derived from routing pools and webSearch config and
261
+ * are used by hub pipeline/process layers (web_search / vision).
262
+ */
263
+ forceWebSearch?: boolean;
264
+ forceVision?: boolean;
185
265
  }
186
266
  export interface ResponsesProviderConfig {
187
267
  toolCallIdStyle?: 'fc' | 'preserve';
@@ -6,7 +6,8 @@ export const DEFAULT_ROUTE = 'default';
6
6
  export const ROUTE_PRIORITY = [
7
7
  'vision',
8
8
  'longcontext',
9
- 'websearch',
9
+ 'web_search',
10
+ 'search',
10
11
  'coding',
11
12
  'thinking',
12
13
  'tools',
@@ -0,0 +1,27 @@
1
+ import type { AdapterContext } from '../conversion/hub/types/chat-envelope.js';
2
+ import type { JsonObject } from '../conversion/hub/types/json.js';
3
+ import type { ProviderInvoker } from './types.js';
4
+ export interface ServerToolOrchestrationOptions {
5
+ chat: JsonObject;
6
+ adapterContext: AdapterContext;
7
+ requestId: string;
8
+ entryEndpoint: string;
9
+ providerProtocol: string;
10
+ reenterPipeline?: (options: {
11
+ entryEndpoint: string;
12
+ requestId: string;
13
+ body: JsonObject;
14
+ metadata?: JsonObject;
15
+ }) => Promise<{
16
+ body?: JsonObject;
17
+ __sse_responses?: unknown;
18
+ format?: string;
19
+ }>;
20
+ providerInvoker?: ProviderInvoker;
21
+ }
22
+ export interface ServerToolOrchestrationResult {
23
+ chat: JsonObject;
24
+ executed: boolean;
25
+ flowId?: string;
26
+ }
27
+ export declare function runServerToolOrchestration(options: ServerToolOrchestrationOptions): Promise<ServerToolOrchestrationResult>;
@@ -0,0 +1,101 @@
1
+ import { runServerSideToolEngine } from './server-side-tools.js';
2
+ export async function runServerToolOrchestration(options) {
3
+ const engineOptions = {
4
+ chatResponse: options.chat,
5
+ adapterContext: options.adapterContext,
6
+ entryEndpoint: options.entryEndpoint,
7
+ requestId: options.requestId,
8
+ providerProtocol: options.providerProtocol,
9
+ providerInvoker: options.providerInvoker,
10
+ reenterPipeline: options.reenterPipeline
11
+ };
12
+ const engineResult = await runServerSideToolEngine(engineOptions);
13
+ if (engineResult.mode === 'passthrough' || !engineResult.execution) {
14
+ return {
15
+ chat: engineResult.finalChatResponse,
16
+ executed: false
17
+ };
18
+ }
19
+ if (!engineResult.execution.followup || !options.reenterPipeline) {
20
+ return {
21
+ chat: engineResult.finalChatResponse,
22
+ executed: true,
23
+ flowId: engineResult.execution.flowId
24
+ };
25
+ }
26
+ const routeHint = resolveRouteHint(options.adapterContext, engineResult.execution.flowId);
27
+ const metadata = {
28
+ serverToolFollowup: true,
29
+ stream: false,
30
+ ...(engineResult.execution.followup.metadata ?? {})
31
+ };
32
+ if (routeHint && typeof metadata.routeHint !== 'string') {
33
+ metadata.routeHint = routeHint;
34
+ }
35
+ const followup = await options.reenterPipeline({
36
+ entryEndpoint: '/v1/chat/completions',
37
+ requestId: `${options.requestId}${engineResult.execution.followup.requestIdSuffix}`,
38
+ body: engineResult.execution.followup.payload,
39
+ metadata
40
+ });
41
+ const followupBody = followup.body && typeof followup.body === 'object'
42
+ ? followup.body
43
+ : engineResult.finalChatResponse;
44
+ const decorated = decorateFinalChatWithServerToolContext(followupBody, engineResult.execution);
45
+ return {
46
+ chat: decorated,
47
+ executed: true,
48
+ flowId: engineResult.execution.flowId
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
+ }
91
+ function resolveRouteHint(adapterContext, flowId) {
92
+ const rawRoute = adapterContext.routeId;
93
+ const routeId = typeof rawRoute === 'string' && rawRoute.trim() ? rawRoute.trim() : '';
94
+ if (!routeId) {
95
+ return undefined;
96
+ }
97
+ if (flowId && routeId.toLowerCase() === flowId.toLowerCase()) {
98
+ return undefined;
99
+ }
100
+ return routeId;
101
+ }
@@ -0,0 +1,40 @@
1
+ import type { JsonObject } from '../conversion/hub/types/json.js';
2
+ import type { ServerToolOrchestrationOptions, ServerToolOrchestrationResult } from './orchestration-types.js';
3
+ export interface ServerToolFlowContext {
4
+ options: ServerToolOrchestrationOptions;
5
+ baseChatResponse: JsonObject;
6
+ capturedChatRequest?: JsonObject;
7
+ routeId?: string;
8
+ cache: Record<string, unknown>;
9
+ }
10
+ export interface ServerToolHopResult {
11
+ body?: JsonObject;
12
+ __sse_responses?: unknown;
13
+ format?: string;
14
+ }
15
+ export interface ServerToolReenterCallOptions {
16
+ requestIdSuffix: string;
17
+ body: JsonObject;
18
+ entryEndpoint?: string;
19
+ metadata?: JsonObject;
20
+ }
21
+ export interface ServerToolProviderCallOptions {
22
+ requestIdSuffix: string;
23
+ providerKey: string;
24
+ providerProtocol: string;
25
+ entryEndpoint: string;
26
+ payload: JsonObject;
27
+ modelId?: string;
28
+ routeHint?: string;
29
+ }
30
+ export interface ServerToolFlowHelpers {
31
+ makeRequestId: (suffix: string) => string;
32
+ callReenterHop: (options: ServerToolReenterCallOptions) => Promise<ServerToolHopResult | null>;
33
+ callProviderHop: (options: ServerToolProviderCallOptions) => Promise<ServerToolHopResult | null>;
34
+ getRouteHintForFollowup: (exclude?: string) => string | undefined;
35
+ }
36
+ export interface ServerToolFlow {
37
+ id: string;
38
+ shouldRun: (context: ServerToolFlowContext) => Promise<boolean> | boolean;
39
+ run: (context: ServerToolFlowContext, helpers: ServerToolFlowHelpers) => Promise<ServerToolOrchestrationResult | null>;
40
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,194 @@
1
+ import { registerServerToolHandler } from '../registry.js';
2
+ import { cloneJson, extractTextFromChatLike } from '../server-side-tools.js';
3
+ const FLOW_ID = 'vision_flow';
4
+ const handler = async (ctx) => {
5
+ if (!ctx.options.reenterPipeline) {
6
+ return null;
7
+ }
8
+ if (!shouldRunVisionFlow(ctx)) {
9
+ return null;
10
+ }
11
+ const captured = getCapturedRequest(ctx.adapterContext);
12
+ if (!captured) {
13
+ return null;
14
+ }
15
+ const analysisPayload = buildVisionAnalysisPayload(captured);
16
+ if (!analysisPayload) {
17
+ return null;
18
+ }
19
+ const visionResponse = await ctx.options.reenterPipeline({
20
+ entryEndpoint: '/v1/chat/completions',
21
+ requestId: `${ctx.options.requestId}:vision`,
22
+ body: analysisPayload,
23
+ metadata: {
24
+ routeHint: 'vision',
25
+ serverToolFollowup: true,
26
+ stream: false
27
+ }
28
+ });
29
+ const visionBody = visionResponse.body && typeof visionResponse.body === 'object'
30
+ ? visionResponse.body
31
+ : null;
32
+ if (!visionBody) {
33
+ return null;
34
+ }
35
+ const visionSummary = extractTextFromChatLike(visionBody);
36
+ if (!visionSummary) {
37
+ return null;
38
+ }
39
+ const followupPayload = buildVisionFollowupPayload(captured, visionSummary);
40
+ if (!followupPayload) {
41
+ return null;
42
+ }
43
+ const execution = {
44
+ flowId: FLOW_ID,
45
+ followup: {
46
+ requestIdSuffix: ':vision_followup',
47
+ payload: followupPayload,
48
+ metadata: buildFollowupMetadata(ctx.adapterContext, 'vision')
49
+ }
50
+ };
51
+ return {
52
+ chatResponse: ctx.base,
53
+ execution
54
+ };
55
+ };
56
+ registerServerToolHandler('vision_auto', handler, { trigger: 'auto' });
57
+ function shouldRunVisionFlow(ctx) {
58
+ const record = ctx.adapterContext;
59
+ const followupFlag = record.serverToolFollowup === true || record.serverToolFollowup === 'true';
60
+ if (followupFlag) {
61
+ return false;
62
+ }
63
+ return record.hasImageAttachment === true || record.hasImageAttachment === 'true';
64
+ }
65
+ function getCapturedRequest(adapterContext) {
66
+ if (!adapterContext || typeof adapterContext !== 'object') {
67
+ return null;
68
+ }
69
+ const captured = adapterContext.capturedChatRequest;
70
+ if (!captured || typeof captured !== 'object' || Array.isArray(captured)) {
71
+ return null;
72
+ }
73
+ return captured;
74
+ }
75
+ function buildVisionAnalysisPayload(source) {
76
+ if (!source || typeof source !== 'object') {
77
+ return null;
78
+ }
79
+ const payload = {};
80
+ if (typeof source.model === 'string' && source.model.trim()) {
81
+ payload.model = source.model.trim();
82
+ }
83
+ if (Array.isArray(source.messages)) {
84
+ payload.messages = cloneJson(source.messages);
85
+ }
86
+ else {
87
+ return null;
88
+ }
89
+ if (Array.isArray(source.tools) && source.tools.length) {
90
+ payload.tools = cloneJson(source.tools);
91
+ }
92
+ const parameters = source.parameters;
93
+ if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
94
+ const params = cloneJson(parameters);
95
+ Object.assign(payload, params);
96
+ }
97
+ return payload;
98
+ }
99
+ function buildVisionFollowupPayload(source, summary) {
100
+ if (!source || typeof source !== 'object') {
101
+ return null;
102
+ }
103
+ const payload = {};
104
+ if (typeof source.model === 'string' && source.model.trim()) {
105
+ payload.model = source.model.trim();
106
+ }
107
+ payload.messages = injectVisionSummary(source.messages, summary);
108
+ if (Array.isArray(source.tools) && source.tools.length) {
109
+ payload.tools = cloneJson(source.tools);
110
+ }
111
+ const parameters = source.parameters;
112
+ if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
113
+ const params = cloneJson(parameters);
114
+ Object.assign(payload, params);
115
+ }
116
+ return payload;
117
+ }
118
+ function injectVisionSummary(source, summary) {
119
+ const messages = Array.isArray(source) ? cloneJson(source) : [];
120
+ let injected = false;
121
+ for (const message of messages) {
122
+ if (!message || typeof message !== 'object')
123
+ continue;
124
+ const content = message.content;
125
+ if (!Array.isArray(content))
126
+ continue;
127
+ const nextParts = [];
128
+ let removed = false;
129
+ for (const part of content) {
130
+ if (part && typeof part === 'object') {
131
+ const typeValue = typeof part.type === 'string'
132
+ ? part.type.toLowerCase()
133
+ : '';
134
+ if (typeValue.includes('image')) {
135
+ removed = true;
136
+ continue;
137
+ }
138
+ }
139
+ nextParts.push(part);
140
+ }
141
+ if (removed) {
142
+ nextParts.push({
143
+ type: 'text',
144
+ text: `[Vision] ${summary}`
145
+ });
146
+ message.content = nextParts;
147
+ injected = true;
148
+ }
149
+ }
150
+ if (!injected) {
151
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
152
+ const msg = messages[i];
153
+ if (!msg || typeof msg !== 'object')
154
+ continue;
155
+ const role = typeof msg.role === 'string'
156
+ ? msg.role.toLowerCase()
157
+ : '';
158
+ if (role !== 'user')
159
+ continue;
160
+ const content = msg.content;
161
+ if (Array.isArray(content)) {
162
+ content.push({
163
+ type: 'text',
164
+ text: `[Vision] ${summary}`
165
+ });
166
+ injected = true;
167
+ break;
168
+ }
169
+ if (typeof content === 'string' && content.length) {
170
+ msg.content = `${content}\n[Vision] ${summary}`;
171
+ }
172
+ else {
173
+ msg.content = `[Vision] ${summary}`;
174
+ }
175
+ injected = true;
176
+ break;
177
+ }
178
+ }
179
+ if (!injected) {
180
+ messages.push({
181
+ role: 'system',
182
+ content: `[Vision] ${summary}`
183
+ });
184
+ }
185
+ return messages;
186
+ }
187
+ function buildFollowupMetadata(adapterContext, toolName) {
188
+ const ctx = adapterContext && typeof adapterContext === 'object' ? adapterContext : null;
189
+ const routeId = ctx && typeof ctx.routeId === 'string' && ctx.routeId.trim() ? ctx.routeId.trim() : '';
190
+ if (!routeId || routeId.toLowerCase() === toolName.toLowerCase()) {
191
+ return undefined;
192
+ }
193
+ return { routeHint: routeId };
194
+ }
@@ -0,0 +1 @@
1
+ export {};