@jsonstudio/llms 0.6.230 → 0.6.375

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 (63) hide show
  1. package/README.md +2 -0
  2. package/dist/conversion/codecs/gemini-openai-codec.js +9 -1
  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/universal-shape-filter.js +11 -0
  11. package/dist/conversion/compat/profiles/chat-gemini.json +17 -0
  12. package/dist/conversion/compat/profiles/chat-glm.json +190 -184
  13. package/dist/conversion/compat/profiles/chat-iflow.json +195 -195
  14. package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
  15. package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
  16. package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  17. package/dist/conversion/config/sample-config.json +1 -1
  18. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +18 -0
  19. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +6 -0
  20. package/dist/conversion/hub/pipeline/hub-pipeline.js +28 -1
  21. package/dist/conversion/hub/pipeline/target-utils.js +6 -0
  22. package/dist/conversion/hub/process/chat-process.js +100 -18
  23. package/dist/conversion/hub/response/provider-response.d.ts +13 -1
  24. package/dist/conversion/hub/response/provider-response.js +84 -35
  25. package/dist/conversion/hub/response/server-side-tools.js +61 -4
  26. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +123 -3
  27. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
  28. package/dist/conversion/hub/standardized-bridge.js +14 -0
  29. package/dist/conversion/responses/responses-openai-bridge.js +35 -2
  30. package/dist/conversion/shared/anthropic-message-utils.js +92 -3
  31. package/dist/conversion/shared/bridge-message-utils.js +137 -10
  32. package/dist/conversion/shared/responses-output-builder.js +43 -2
  33. package/dist/conversion/shared/tool-filter-pipeline.js +1 -0
  34. package/dist/router/virtual-router/bootstrap.js +44 -12
  35. package/dist/router/virtual-router/classifier.js +11 -17
  36. package/dist/router/virtual-router/engine.d.ts +9 -0
  37. package/dist/router/virtual-router/engine.js +160 -18
  38. package/dist/router/virtual-router/features.js +1 -1
  39. package/dist/router/virtual-router/message-utils.js +36 -24
  40. package/dist/router/virtual-router/provider-registry.js +2 -1
  41. package/dist/router/virtual-router/token-counter.js +14 -3
  42. package/dist/router/virtual-router/types.d.ts +45 -0
  43. package/dist/router/virtual-router/types.js +2 -1
  44. package/dist/servertool/engine.d.ts +27 -0
  45. package/dist/servertool/engine.js +60 -0
  46. package/dist/servertool/flow-types.d.ts +40 -0
  47. package/dist/servertool/flow-types.js +1 -0
  48. package/dist/servertool/handlers/vision.d.ts +1 -0
  49. package/dist/servertool/handlers/vision.js +194 -0
  50. package/dist/servertool/handlers/web-search.d.ts +1 -0
  51. package/dist/servertool/handlers/web-search.js +638 -0
  52. package/dist/servertool/orchestration-types.d.ts +33 -0
  53. package/dist/servertool/orchestration-types.js +1 -0
  54. package/dist/servertool/registry.d.ts +18 -0
  55. package/dist/servertool/registry.js +27 -0
  56. package/dist/servertool/server-side-tools.d.ts +8 -0
  57. package/dist/servertool/server-side-tools.js +208 -0
  58. package/dist/servertool/types.d.ts +88 -0
  59. package/dist/servertool/types.js +1 -0
  60. package/dist/servertool/vision-tool.d.ts +2 -0
  61. package/dist/servertool/vision-tool.js +185 -0
  62. package/dist/sse/sse-to-json/builders/response-builder.js +6 -3
  63. package/package.json +1 -1
@@ -10,6 +10,13 @@ export interface RoutePoolTier {
10
10
  targets: string[];
11
11
  priority: number;
12
12
  backup?: boolean;
13
+ /**
14
+ * Optional force flag for this route pool.
15
+ * Currently interpreted for:
16
+ * - routing.vision: force dedicated vision backend handling.
17
+ * - routing.web_search / routing.search: force server-side web_search flow.
18
+ */
19
+ force?: boolean;
13
20
  }
14
21
  export type RoutingPools = Record<string, RoutePoolTier[]>;
15
22
  export type StreamingPreference = 'auto' | 'always' | 'never';
@@ -42,6 +49,13 @@ export interface ProviderProfile {
42
49
  responsesConfig?: ResponsesProviderConfig;
43
50
  streaming?: StreamingPreference;
44
51
  maxContextTokens?: number;
52
+ /**
53
+ * When true, this provider must be skipped for any request that
54
+ * requires server-side tool orchestration (e.g. web_search).
55
+ * Normal chat routing (without servertool injection) may still
56
+ * use this provider as usual.
57
+ */
58
+ serverToolsDisabled?: boolean;
45
59
  }
46
60
  export interface ProviderRuntimeProfile {
47
61
  runtimeKey: string;
@@ -61,6 +75,12 @@ export interface ProviderRuntimeProfile {
61
75
  modelContextTokens?: Record<string, number>;
62
76
  defaultContextTokens?: number;
63
77
  maxContextTokens?: number;
78
+ /**
79
+ * Provider-level flag propagated from virtualrouter.providers[*].
80
+ * When true, VirtualRouterEngine will skip this runtime for any
81
+ * request that declares serverToolRequired=true in routing metadata.
82
+ */
83
+ serverToolsDisabled?: boolean;
64
84
  }
65
85
  export interface VirtualRouterClassifierConfig {
66
86
  longContextThresholdTokens?: number;
@@ -83,10 +103,21 @@ export interface VirtualRouterWebSearchEngineConfig {
83
103
  providerKey: string;
84
104
  description?: string;
85
105
  default?: boolean;
106
+ /**
107
+ * When true, this engine will never be used by server-side tools
108
+ * (e.g. web_search). It will also be omitted from injected tool
109
+ * schemas so main models cannot select it for servertool flows.
110
+ */
111
+ serverToolsDisabled?: boolean;
86
112
  }
87
113
  export interface VirtualRouterWebSearchConfig {
88
114
  engines: VirtualRouterWebSearchEngineConfig[];
89
115
  injectPolicy?: 'always' | 'selective';
116
+ /**
117
+ * When true, always prefer server-side web_search orchestration
118
+ * over upstream builtin behaviours (e.g. OpenAI Responses builtin web_search).
119
+ */
120
+ force?: boolean;
90
121
  }
91
122
  export interface VirtualRouterConfig {
92
123
  routing: RoutingPools;
@@ -129,6 +160,13 @@ export interface RouterMetadataInput {
129
160
  providerProtocol?: string;
130
161
  stage?: 'inbound' | 'outbound' | 'response';
131
162
  routeHint?: string;
163
+ /**
164
+ * Indicates that current routing decision is for a request which
165
+ * expects server-side tools orchestration (e.g. web_search).
166
+ * Virtual Router should skip providers that opt out via
167
+ * serverToolsDisabled when this flag is true.
168
+ */
169
+ serverToolRequired?: boolean;
132
170
  responsesResume?: {
133
171
  previousRequestId?: string;
134
172
  restoredFromResponseId?: string;
@@ -182,6 +220,13 @@ export interface TargetMetadata {
182
220
  responsesConfig?: ResponsesProviderConfig;
183
221
  streaming?: StreamingPreference;
184
222
  maxContextTokens?: number;
223
+ /**
224
+ * Route-level flags propagated from the virtual router.
225
+ * These are derived from routing pools and webSearch config and
226
+ * are used by hub pipeline/process layers (web_search / vision).
227
+ */
228
+ forceWebSearch?: boolean;
229
+ forceVision?: boolean;
185
230
  }
186
231
  export interface ResponsesProviderConfig {
187
232
  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,60 @@
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
+ return {
45
+ chat: followupBody,
46
+ executed: true,
47
+ flowId: engineResult.execution.flowId
48
+ };
49
+ }
50
+ function resolveRouteHint(adapterContext, flowId) {
51
+ const rawRoute = adapterContext.routeId;
52
+ const routeId = typeof rawRoute === 'string' && rawRoute.trim() ? rawRoute.trim() : '';
53
+ if (!routeId) {
54
+ return undefined;
55
+ }
56
+ if (flowId && routeId.toLowerCase() === flowId.toLowerCase()) {
57
+ return undefined;
58
+ }
59
+ return routeId;
60
+ }
@@ -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 {};