@jsonstudio/llms 0.6.3379 → 0.6.3405

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 (30) hide show
  1. package/dist/conversion/compat/actions/claude-thinking-tools.d.ts +1 -14
  2. package/dist/conversion/compat/actions/claude-thinking-tools.js +3 -71
  3. package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.d.ts +0 -8
  4. package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +2 -57
  5. package/dist/conversion/compat/actions/normalize-tool-call-ids.d.ts +0 -9
  6. package/dist/conversion/compat/actions/normalize-tool-call-ids.js +6 -136
  7. package/dist/conversion/compat/actions/request-rules.js +2 -61
  8. package/dist/conversion/compat/actions/response-blacklist.d.ts +0 -4
  9. package/dist/conversion/compat/actions/response-blacklist.js +2 -77
  10. package/dist/conversion/compat/actions/response-normalize.js +2 -119
  11. package/dist/conversion/compat/actions/response-validate.js +2 -74
  12. package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +2 -150
  13. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +24 -1
  14. package/dist/conversion/hub/pipeline/hub-pipeline.js +91 -0
  15. package/dist/conversion/shared/reasoning-tool-parser.js +7 -8
  16. package/dist/conversion/shared/responses-response-utils.js +3 -48
  17. package/dist/conversion/shared/responses-tool-utils.js +22 -126
  18. package/dist/native/router_hotpath_napi.node +0 -0
  19. package/dist/router/virtual-router/bootstrap/web-search-config.js +25 -0
  20. package/dist/router/virtual-router/bootstrap.js +21 -16
  21. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +6 -0
  22. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +171 -0
  23. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +11 -0
  24. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +5 -0
  25. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +137 -0
  26. package/dist/router/virtual-router/types.d.ts +23 -0
  27. package/dist/servertool/handlers/web-search.js +26 -1
  28. package/dist/servertool/server-side-tools.js +11 -2
  29. package/dist/servertool/types.d.ts +4 -0
  30. package/package.json +1 -1
@@ -37,6 +37,27 @@ export function normalizeWebSearch(input, routingSource) {
37
37
  const resolvedProviderKey = resolveWebSearchEngineProviderKey(providerKey, webSearchRouteTargets) ?? providerKey;
38
38
  const description = typeof node.description === 'string' && node.description.trim() ? node.description.trim() : undefined;
39
39
  const isDefault = node.default === true || (typeof node.default === 'string' && node.default.trim().toLowerCase() === 'true');
40
+ const rawExecutionMode = typeof node.executionMode === 'string'
41
+ ? node.executionMode.trim().toLowerCase()
42
+ : typeof node.mode === 'string'
43
+ ? node.mode.trim().toLowerCase()
44
+ : '';
45
+ const executionMode = rawExecutionMode === 'direct' ? 'direct' : 'servertool';
46
+ const rawDirectActivation = typeof node.directActivation === 'string'
47
+ ? node.directActivation.trim().toLowerCase()
48
+ : typeof node.activation === 'string'
49
+ ? node.activation.trim().toLowerCase()
50
+ : '';
51
+ const directActivation = rawDirectActivation === 'builtin'
52
+ ? 'builtin'
53
+ : rawDirectActivation === 'route'
54
+ ? 'route'
55
+ : executionMode === 'direct'
56
+ ? 'route'
57
+ : undefined;
58
+ const modelId = typeof node.modelId === 'string' && node.modelId.trim() ? node.modelId.trim() : undefined;
59
+ const maxUsesRaw = typeof node.maxUses === 'number' ? node.maxUses : Number(node.maxUses);
60
+ const maxUses = Number.isFinite(maxUsesRaw) && maxUsesRaw > 0 ? Math.floor(maxUsesRaw) : undefined;
40
61
  const serverToolsDisabled = node.serverToolsDisabled === true ||
41
62
  (typeof node.serverToolsDisabled === 'string' &&
42
63
  node.serverToolsDisabled.trim().toLowerCase() === 'true') ||
@@ -51,6 +72,10 @@ export function normalizeWebSearch(input, routingSource) {
51
72
  providerKey: resolvedProviderKey,
52
73
  description,
53
74
  default: isDefault,
75
+ executionMode,
76
+ ...(directActivation ? { directActivation } : {}),
77
+ ...(modelId ? { modelId } : {}),
78
+ ...(maxUses ? { maxUses } : {}),
54
79
  ...(serverToolsDisabled ? { serverToolsDisabled: true } : {})
55
80
  });
56
81
  }
@@ -140,26 +140,31 @@ function buildProviderRuntimeEntries(providers) {
140
140
  }
141
141
  return { runtimeEntries, aliasIndex, modelIndex };
142
142
  }
143
- function collectProviderModels(providerRaw, normalizedProvider) {
143
+ function collectProviderModels(providerRaw, _normalizedProvider) {
144
144
  const rawModelsNode = providerRaw.models;
145
145
  const modelsDeclared = rawModelsNode !== undefined;
146
146
  const modelsNode = asRecord(rawModelsNode);
147
- const baseModels = Object.keys(modelsNode).filter(Boolean);
148
- if (normalizedProvider.compatibilityProfile !== 'chat:deepseek-web') {
149
- return { declared: modelsDeclared, models: baseModels };
150
- }
151
- const withAliases = new Set(baseModels);
152
- const hasChatBase = withAliases.has('deepseek-chat') || withAliases.has('deepseek-v3');
153
- const hasReasonerBase = withAliases.has('deepseek-reasoner') || withAliases.has('deepseek-r1');
154
- if (hasChatBase) {
155
- withAliases.add('deepseek-chat-search');
156
- withAliases.add('deepseek-v3-search');
157
- }
158
- if (hasReasonerBase) {
159
- withAliases.add('deepseek-reasoner-search');
160
- withAliases.add('deepseek-r1-search');
147
+ const collected = new Set();
148
+ for (const [modelName, modelConfigRaw] of Object.entries(modelsNode)) {
149
+ const normalizedModelName = typeof modelName === 'string' ? modelName.trim() : '';
150
+ if (normalizedModelName) {
151
+ collected.add(normalizedModelName);
152
+ }
153
+ const modelConfig = asRecord(modelConfigRaw);
154
+ const aliasesNode = Array.isArray(modelConfig.aliases)
155
+ ? modelConfig.aliases
156
+ : [];
157
+ for (const alias of aliasesNode) {
158
+ if (typeof alias !== 'string') {
159
+ continue;
160
+ }
161
+ const normalizedAlias = alias.trim();
162
+ if (normalizedAlias) {
163
+ collected.add(normalizedAlias);
164
+ }
165
+ }
161
166
  }
162
- return { declared: modelsDeclared, models: Array.from(withAliases) };
167
+ return { declared: modelsDeclared, models: Array.from(collected) };
163
168
  }
164
169
  function normalizeClassifier(input) {
165
170
  const normalized = asRecord(input);
@@ -0,0 +1,6 @@
1
+ export declare function normalizeResponsePayloadWithNative(payload: Record<string, unknown>, config?: Record<string, unknown>): Record<string, unknown>;
2
+ export declare function validateResponsePayloadWithNative(payload: Record<string, unknown>): void;
3
+ export declare function applyRequestRulesWithNative(payload: Record<string, unknown>, config?: Record<string, unknown>): Record<string, unknown>;
4
+ export declare function applyResponseBlacklistWithNative(payload: Record<string, unknown>, config?: Record<string, unknown>): Record<string, unknown>;
5
+ export declare function normalizeToolCallIdsWithNative(payload: Record<string, unknown>): Record<string, unknown>;
6
+ export declare function enforceLmstudioResponsesFcToolCallIdsWithNative(payload: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,171 @@
1
+ import { failNativeRequired, isNativeDisabledByEnv } from './native-router-hotpath-policy.js';
2
+ import { loadNativeRouterHotpathBindingForInternalUse } from './native-router-hotpath.js';
3
+ function readNativeFunction(name) {
4
+ const binding = loadNativeRouterHotpathBindingForInternalUse();
5
+ const fn = binding?.[name];
6
+ return typeof fn === 'function' ? fn : null;
7
+ }
8
+ function safeStringify(value) {
9
+ try {
10
+ return JSON.stringify(value);
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ function parseRecord(raw) {
17
+ try {
18
+ const parsed = JSON.parse(raw);
19
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
20
+ return null;
21
+ }
22
+ return parsed;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ export function normalizeResponsePayloadWithNative(payload, config) {
29
+ const capability = 'normalizeResponsePayloadJson';
30
+ const fail = (reason) => failNativeRequired(capability, reason);
31
+ if (isNativeDisabledByEnv()) {
32
+ return fail('native disabled');
33
+ }
34
+ const fn = readNativeFunction(capability);
35
+ if (!fn) {
36
+ return fail();
37
+ }
38
+ const payloadJson = safeStringify(payload);
39
+ const configJson = config ? safeStringify(config) : '{}';
40
+ if (!payloadJson || !configJson) {
41
+ return fail('json stringify failed');
42
+ }
43
+ try {
44
+ const raw = fn(payloadJson, configJson);
45
+ if (typeof raw !== 'string' || !raw) {
46
+ return fail('empty result');
47
+ }
48
+ const parsed = parseRecord(raw);
49
+ return parsed ?? fail('invalid payload');
50
+ }
51
+ catch (error) {
52
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
53
+ return fail(reason);
54
+ }
55
+ }
56
+ export function validateResponsePayloadWithNative(payload) {
57
+ const capability = 'validateResponsePayloadJson';
58
+ const fail = (reason) => failNativeRequired(capability, reason);
59
+ if (isNativeDisabledByEnv()) {
60
+ return fail('native disabled');
61
+ }
62
+ const fn = readNativeFunction(capability);
63
+ if (!fn) {
64
+ return fail();
65
+ }
66
+ const payloadJson = safeStringify(payload);
67
+ if (!payloadJson) {
68
+ return fail('json stringify failed');
69
+ }
70
+ try {
71
+ fn(payloadJson);
72
+ }
73
+ catch (error) {
74
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
75
+ throw new Error(reason);
76
+ }
77
+ }
78
+ export function applyRequestRulesWithNative(payload, config) {
79
+ const capability = 'applyRequestRulesJson';
80
+ const fail = (reason) => failNativeRequired(capability, reason);
81
+ if (isNativeDisabledByEnv())
82
+ return fail('native disabled');
83
+ const fn = readNativeFunction(capability);
84
+ if (!fn)
85
+ return fail();
86
+ const payloadJson = safeStringify(payload);
87
+ const configJson = config ? safeStringify(config) : '{}';
88
+ if (!payloadJson || !configJson)
89
+ return fail('json stringify failed');
90
+ try {
91
+ const raw = fn(payloadJson, configJson);
92
+ if (typeof raw !== 'string' || !raw)
93
+ return fail('empty result');
94
+ const parsed = parseRecord(raw);
95
+ return parsed ?? fail('invalid payload');
96
+ }
97
+ catch (error) {
98
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
99
+ return fail(reason);
100
+ }
101
+ }
102
+ export function applyResponseBlacklistWithNative(payload, config) {
103
+ const capability = 'applyResponseBlacklistJson';
104
+ const fail = (reason) => failNativeRequired(capability, reason);
105
+ if (isNativeDisabledByEnv())
106
+ return fail('native disabled');
107
+ const fn = readNativeFunction(capability);
108
+ if (!fn)
109
+ return fail();
110
+ const payloadJson = safeStringify(payload);
111
+ const configJson = config ? safeStringify(config) : '{}';
112
+ if (!payloadJson || !configJson)
113
+ return fail('json stringify failed');
114
+ try {
115
+ const raw = fn(payloadJson, configJson);
116
+ if (typeof raw !== 'string' || !raw)
117
+ return fail('empty result');
118
+ const parsed = parseRecord(raw);
119
+ return parsed ?? fail('invalid payload');
120
+ }
121
+ catch (error) {
122
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
123
+ return fail(reason);
124
+ }
125
+ }
126
+ export function normalizeToolCallIdsWithNative(payload) {
127
+ const capability = 'normalizeToolCallIdsJson';
128
+ const fail = (reason) => failNativeRequired(capability, reason);
129
+ if (isNativeDisabledByEnv())
130
+ return fail('native disabled');
131
+ const fn = readNativeFunction(capability);
132
+ if (!fn)
133
+ return fail();
134
+ const payloadJson = safeStringify(payload);
135
+ if (!payloadJson)
136
+ return fail('json stringify failed');
137
+ try {
138
+ const raw = fn(payloadJson);
139
+ if (typeof raw !== 'string' || !raw)
140
+ return fail('empty result');
141
+ const parsed = parseRecord(raw);
142
+ return parsed ?? fail('invalid payload');
143
+ }
144
+ catch (error) {
145
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
146
+ return fail(reason);
147
+ }
148
+ }
149
+ export function enforceLmstudioResponsesFcToolCallIdsWithNative(payload) {
150
+ const capability = 'enforceLmstudioResponsesFcToolCallIdsJson';
151
+ const fail = (reason) => failNativeRequired(capability, reason);
152
+ if (isNativeDisabledByEnv())
153
+ return fail('native disabled');
154
+ const fn = readNativeFunction(capability);
155
+ if (!fn)
156
+ return fail();
157
+ const payloadJson = safeStringify(payload);
158
+ if (!payloadJson)
159
+ return fail('json stringify failed');
160
+ try {
161
+ const raw = fn(payloadJson);
162
+ if (typeof raw !== 'string' || !raw)
163
+ return fail('empty result');
164
+ const parsed = parseRecord(raw);
165
+ return parsed ?? fail('invalid payload');
166
+ }
167
+ catch (error) {
168
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
169
+ return fail(reason);
170
+ }
171
+ }
@@ -98,6 +98,8 @@ const REQUIRED_NATIVE_EXPORTS = [
98
98
  'chatToolToBridgeDefinitionJson',
99
99
  'mapBridgeToolsToChatWithOptionsJson',
100
100
  'mapChatToolsToBridgeWithOptionsJson',
101
+ 'collectToolCallsFromResponsesJson',
102
+ 'resolveFinishReasonJson',
101
103
  'captureReqInboundResponsesContextSnapshotJson',
102
104
  'computeQuotaBucketsJson',
103
105
  'extractReasoningSegmentsJson',
@@ -167,6 +169,15 @@ const REQUIRED_NATIVE_EXPORTS = [
167
169
  'normalizeChatResponseReasoningToolsJson',
168
170
  'normalizeBridgeToolCallIdsJson',
169
171
  'normalizeChatMessageContentJson',
172
+ 'validateResponsePayloadJson',
173
+ 'normalizeResponsePayloadJson',
174
+ 'applyResponseBlacklistJson',
175
+ 'normalizeToolCallIdsJson',
176
+ 'normalizeResponsesToolCallIdsJson',
177
+ 'resolveToolCallIdStyleJson',
178
+ 'stripInternalToolingMetadataJson',
179
+ 'enforceLmstudioResponsesFcToolCallIdsJson',
180
+ 'applyRequestRulesJson',
170
181
  'normalizeArgsBySchemaJson',
171
182
  'normalizeToolsJson',
172
183
  'normalizeFunctionCallIdJson',
@@ -26,6 +26,8 @@ export declare function mapBridgeToolsToChatWithNative(rawTools: unknown, option
26
26
  export declare function mapChatToolsToBridgeWithNative(rawTools: unknown, options?: {
27
27
  sanitizeMode?: string;
28
28
  }): Array<Record<string, unknown>>;
29
+ export declare function collectToolCallsFromResponsesWithNative(response: Record<string, unknown>): Array<Record<string, unknown>>;
30
+ export declare function resolveFinishReasonWithNative(response: Record<string, unknown>, toolCalls: Array<Record<string, unknown>>): string;
29
31
  export declare function hasValidThoughtSignatureWithNative(block: unknown, options?: Record<string, unknown>): boolean;
30
32
  export declare function sanitizeThinkingBlockWithNative(block: unknown): Record<string, unknown>;
31
33
  export declare function filterInvalidThinkingBlocksWithNative(messages: unknown, options?: Record<string, unknown>): unknown[];
@@ -49,6 +51,9 @@ export declare function enforceToolCallIdStyleWithNative(messages: unknown[], st
49
51
  messages: unknown[];
50
52
  state: Record<string, unknown>;
51
53
  };
54
+ export declare function normalizeResponsesToolCallIdsWithNative(payload: unknown): Record<string, unknown> | null;
55
+ export declare function resolveToolCallIdStyleWithNative(metadata: unknown): string;
56
+ export declare function stripInternalToolingMetadataWithNative(metadata: unknown): Record<string, unknown> | null;
52
57
  export declare function buildProviderProtocolErrorWithNative(input: {
53
58
  message: string;
54
59
  code: string;
@@ -123,6 +123,10 @@ function parseToolDefinitionArray(raw) {
123
123
  }
124
124
  return output;
125
125
  }
126
+ function parseString(raw) {
127
+ const parsed = parseJson(raw);
128
+ return typeof parsed === 'string' ? parsed : null;
129
+ }
126
130
  function parseStringArray(raw) {
127
131
  const parsed = parseArray(raw);
128
132
  if (!parsed)
@@ -510,6 +514,60 @@ export function mapChatToolsToBridgeWithNative(rawTools, options) {
510
514
  return fail(reason);
511
515
  }
512
516
  }
517
+ export function collectToolCallsFromResponsesWithNative(response) {
518
+ const capability = 'collectToolCallsFromResponsesJson';
519
+ const fail = (reason) => failNativeRequired(capability, reason);
520
+ if (isNativeDisabledByEnv()) {
521
+ return fail('native disabled');
522
+ }
523
+ const fn = readNativeFunction(capability);
524
+ if (!fn) {
525
+ return fail();
526
+ }
527
+ const payloadJson = safeStringify(response ?? {});
528
+ if (!payloadJson) {
529
+ return fail('json stringify failed');
530
+ }
531
+ try {
532
+ const raw = fn(payloadJson);
533
+ if (typeof raw !== 'string' || !raw) {
534
+ return fail('empty result');
535
+ }
536
+ const parsed = parseToolDefinitionArray(raw);
537
+ return parsed ?? fail('invalid payload');
538
+ }
539
+ catch (error) {
540
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
541
+ return fail(reason);
542
+ }
543
+ }
544
+ export function resolveFinishReasonWithNative(response, toolCalls) {
545
+ const capability = 'resolveFinishReasonJson';
546
+ const fail = (reason) => failNativeRequired(capability, reason);
547
+ if (isNativeDisabledByEnv()) {
548
+ return fail('native disabled');
549
+ }
550
+ const fn = readNativeFunction(capability);
551
+ if (!fn) {
552
+ return fail();
553
+ }
554
+ const responseJson = safeStringify(response ?? {});
555
+ const toolCallsJson = safeStringify(Array.isArray(toolCalls) ? toolCalls : []);
556
+ if (!responseJson || !toolCallsJson) {
557
+ return fail('json stringify failed');
558
+ }
559
+ try {
560
+ const raw = fn(responseJson, toolCallsJson);
561
+ if (typeof raw !== 'string' || !raw) {
562
+ return fail('empty result');
563
+ }
564
+ return parseString(raw) ?? fail('invalid payload');
565
+ }
566
+ catch (error) {
567
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
568
+ return fail(reason);
569
+ }
570
+ }
513
571
  export function hasValidThoughtSignatureWithNative(block, options) {
514
572
  const capability = 'hasValidThoughtSignatureJson';
515
573
  const fail = (reason) => failNativeRequired(capability, reason);
@@ -973,6 +1031,85 @@ export function enforceToolCallIdStyleWithNative(messages, state) {
973
1031
  return fail(reason);
974
1032
  }
975
1033
  }
1034
+ export function normalizeResponsesToolCallIdsWithNative(payload) {
1035
+ const capability = 'normalizeResponsesToolCallIdsJson';
1036
+ const fail = (reason) => failNativeRequired(capability, reason);
1037
+ if (isNativeDisabledByEnv()) {
1038
+ return fail('native disabled');
1039
+ }
1040
+ const fn = readNativeFunction(capability);
1041
+ if (!fn) {
1042
+ return fail();
1043
+ }
1044
+ const payloadJson = safeStringify(payload ?? null);
1045
+ if (!payloadJson) {
1046
+ return fail('json stringify failed');
1047
+ }
1048
+ try {
1049
+ const raw = fn(payloadJson);
1050
+ if (typeof raw !== 'string' || !raw) {
1051
+ return fail('empty result');
1052
+ }
1053
+ return parseRecord(raw) ?? fail('invalid payload');
1054
+ }
1055
+ catch (error) {
1056
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
1057
+ return fail(reason);
1058
+ }
1059
+ }
1060
+ export function resolveToolCallIdStyleWithNative(metadata) {
1061
+ const capability = 'resolveToolCallIdStyleJson';
1062
+ const fail = (reason) => failNativeRequired(capability, reason);
1063
+ if (isNativeDisabledByEnv()) {
1064
+ return fail('native disabled');
1065
+ }
1066
+ const fn = readNativeFunction(capability);
1067
+ if (!fn) {
1068
+ return fail();
1069
+ }
1070
+ const metadataJson = safeStringify(metadata ?? null);
1071
+ if (!metadataJson) {
1072
+ return fail('json stringify failed');
1073
+ }
1074
+ try {
1075
+ const raw = fn(metadataJson);
1076
+ if (typeof raw !== 'string' || !raw) {
1077
+ return fail('empty result');
1078
+ }
1079
+ const parsed = parseJson(raw);
1080
+ return typeof parsed === 'string' ? parsed : fail('invalid payload');
1081
+ }
1082
+ catch (error) {
1083
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
1084
+ return fail(reason);
1085
+ }
1086
+ }
1087
+ export function stripInternalToolingMetadataWithNative(metadata) {
1088
+ const capability = 'stripInternalToolingMetadataJson';
1089
+ const fail = (reason) => failNativeRequired(capability, reason);
1090
+ if (isNativeDisabledByEnv()) {
1091
+ return fail('native disabled');
1092
+ }
1093
+ const fn = readNativeFunction(capability);
1094
+ if (!fn) {
1095
+ return fail();
1096
+ }
1097
+ const metadataJson = safeStringify(metadata ?? null);
1098
+ if (!metadataJson) {
1099
+ return fail('json stringify failed');
1100
+ }
1101
+ try {
1102
+ const raw = fn(metadataJson);
1103
+ if (typeof raw !== 'string' || !raw) {
1104
+ return fail('empty result');
1105
+ }
1106
+ return parseRecord(raw) ?? fail('invalid payload');
1107
+ }
1108
+ catch (error) {
1109
+ const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
1110
+ return fail(reason);
1111
+ }
1112
+ }
976
1113
  export function buildProviderProtocolErrorWithNative(input) {
977
1114
  const capability = 'buildProviderProtocolErrorJson';
978
1115
  const fail = (reason) => failNativeRequired(capability, reason);
@@ -215,11 +215,34 @@ export interface ProviderHealthConfig {
215
215
  cooldownMs: number;
216
216
  fatalCooldownMs?: number;
217
217
  }
218
+ export type VirtualRouterWebSearchExecutionMode = 'servertool' | 'direct';
219
+ export type VirtualRouterWebSearchDirectActivation = 'route' | 'builtin';
218
220
  export interface VirtualRouterWebSearchEngineConfig {
219
221
  id: string;
220
222
  providerKey: string;
221
223
  description?: string;
222
224
  default?: boolean;
225
+ /**
226
+ * Search execution mode:
227
+ * - servertool: expose canonical web_search tool and execute through servertool engine.
228
+ * - direct: route to a search-capable model/provider directly; servertool injection must skip it.
229
+ */
230
+ executionMode?: VirtualRouterWebSearchExecutionMode;
231
+ /**
232
+ * When executionMode=direct, controls how the upstream search capability is activated.
233
+ * - route: route selection itself enables native search behavior (e.g. deepseek-web search route).
234
+ * - builtin: upstream requires a provider-native builtin search tool/schema.
235
+ */
236
+ directActivation?: VirtualRouterWebSearchDirectActivation;
237
+ /**
238
+ * Optional target model id for direct-mode matching when request/compat layers need to detect
239
+ * which routed provider payload should receive native web search activation.
240
+ */
241
+ modelId?: string;
242
+ /**
243
+ * Optional builtin max-uses hint for providers that support builtin web search tools.
244
+ */
245
+ maxUses?: number;
223
246
  /**
224
247
  * When true, this engine will never be used by server-side tools
225
248
  * (e.g. web_search). It will also be omitted from injected tool
@@ -118,6 +118,27 @@ function getWebSearchConfig(ctx) {
118
118
  : undefined;
119
119
  if (!id || !providerKey)
120
120
  continue;
121
+ const rawExecutionMode = typeof obj.executionMode === 'string'
122
+ ? obj.executionMode.trim().toLowerCase()
123
+ : typeof obj.mode === 'string'
124
+ ? obj.mode.trim().toLowerCase()
125
+ : '';
126
+ const executionMode = rawExecutionMode === 'direct' ? 'direct' : 'servertool';
127
+ const rawDirectActivation = typeof obj.directActivation === 'string'
128
+ ? obj.directActivation.trim().toLowerCase()
129
+ : typeof obj.activation === 'string'
130
+ ? obj.activation.trim().toLowerCase()
131
+ : '';
132
+ const directActivation = rawDirectActivation === 'builtin'
133
+ ? 'builtin'
134
+ : rawDirectActivation === 'route'
135
+ ? 'route'
136
+ : executionMode === 'direct'
137
+ ? 'route'
138
+ : undefined;
139
+ const modelId = typeof obj.modelId === 'string' && obj.modelId.trim() ? obj.modelId.trim() : undefined;
140
+ const rawMaxUses = typeof obj.maxUses === 'number' ? obj.maxUses : Number(obj.maxUses);
141
+ const maxUses = Number.isFinite(rawMaxUses) && rawMaxUses > 0 ? Math.floor(rawMaxUses) : undefined;
121
142
  const serverToolsDisabled = obj.serverToolsDisabled === true ||
122
143
  (typeof obj.serverToolsDisabled === 'string' &&
123
144
  obj.serverToolsDisabled.trim().toLowerCase() === 'true') ||
@@ -142,6 +163,10 @@ function getWebSearchConfig(ctx) {
142
163
  providerKey,
143
164
  description: typeof obj.description === 'string' && obj.description.trim() ? obj.description.trim() : undefined,
144
165
  default: obj.default === true,
166
+ executionMode,
167
+ ...(directActivation ? { directActivation } : {}),
168
+ ...(modelId ? { modelId } : {}),
169
+ ...(maxUses ? { maxUses } : {}),
145
170
  ...(serverToolsDisabled ? { serverToolsDisabled: true } : {}),
146
171
  ...(searchEngineList ? { searchEngineList } : {})
147
172
  });
@@ -181,7 +206,7 @@ function resolveWebSearchEngine(config, engineId) {
181
206
  return undefined;
182
207
  }
183
208
  function buildEnginePriorityList(config, engineId) {
184
- const engines = (Array.isArray(config.engines) ? config.engines : []).filter((engine) => !engine.serverToolsDisabled);
209
+ const engines = (Array.isArray(config.engines) ? config.engines : []).filter((engine) => !engine.serverToolsDisabled && (engine.executionMode ?? 'servertool') === 'servertool');
185
210
  if (!engines.length) {
186
211
  return [];
187
212
  }
@@ -100,7 +100,7 @@ function normalizeFilterTokenSet(values) {
100
100
  return normalized.size > 0 ? normalized : null;
101
101
  }
102
102
  function isNameIncluded(name, includeSet, excludeSet) {
103
- const normalized = name.trim().toLowerCase();
103
+ const normalized = normalizeServerToolCallName(name);
104
104
  if (includeSet && !includeSet.has(normalized)) {
105
105
  return false;
106
106
  }
@@ -109,6 +109,13 @@ function isNameIncluded(name, includeSet, excludeSet) {
109
109
  }
110
110
  return true;
111
111
  }
112
+ function normalizeServerToolCallName(name) {
113
+ const normalized = name.trim().toLowerCase();
114
+ if (normalized === 'websearch' || normalized === 'web-search') {
115
+ return 'web_search';
116
+ }
117
+ return normalized;
118
+ }
112
119
  function extractToolCallsFromMessage(message) {
113
120
  const toolCalls = getArray(message.tool_calls);
114
121
  const out = [];
@@ -120,7 +127,9 @@ function extractToolCallsFromMessage(message) {
120
127
  const fn = asObject(tc.function) ??
121
128
  asObject(tc.functionCall) ??
122
129
  asObject(tc.function_call);
123
- const name = fn && typeof fn.name === 'string' && String(fn.name).trim() ? String(fn.name).trim() : '';
130
+ const name = fn && typeof fn.name === 'string' && String(fn.name).trim()
131
+ ? normalizeServerToolCallName(String(fn.name))
132
+ : '';
124
133
  const rawArgs = (fn ? fn.arguments : undefined) ??
125
134
  (fn ? fn.args : undefined) ??
126
135
  (fn ? fn.input : undefined) ??
@@ -137,6 +137,10 @@ export type ServerToolBackendPlan = {
137
137
  providerKey: string;
138
138
  description?: string;
139
139
  default?: boolean;
140
+ executionMode?: 'servertool' | 'direct';
141
+ directActivation?: 'route' | 'builtin';
142
+ modelId?: string;
143
+ maxUses?: number;
140
144
  serverToolsDisabled?: boolean;
141
145
  searchEngineList?: string[];
142
146
  }[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.3379",
3
+ "version": "0.6.3405",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",