@jsonstudio/llms 0.6.3275 → 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 (72) hide show
  1. package/dist/conversion/bridge-message-utils.d.ts +4 -4
  2. package/dist/conversion/bridge-message-utils.js +28 -538
  3. package/dist/conversion/compat/actions/claude-thinking-tools.d.ts +1 -14
  4. package/dist/conversion/compat/actions/claude-thinking-tools.js +3 -71
  5. package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.d.ts +0 -8
  6. package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +2 -57
  7. package/dist/conversion/compat/actions/normalize-tool-call-ids.d.ts +0 -9
  8. package/dist/conversion/compat/actions/normalize-tool-call-ids.js +6 -136
  9. package/dist/conversion/compat/actions/request-rules.js +2 -61
  10. package/dist/conversion/compat/actions/response-blacklist.d.ts +0 -4
  11. package/dist/conversion/compat/actions/response-blacklist.js +2 -77
  12. package/dist/conversion/compat/actions/response-normalize.js +2 -119
  13. package/dist/conversion/compat/actions/response-validate.js +2 -74
  14. package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +2 -150
  15. package/dist/conversion/compat/profiles/responses-crs.json +15 -0
  16. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +24 -1
  17. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +16 -5
  18. package/dist/conversion/hub/pipeline/hub-pipeline.js +91 -0
  19. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +1 -6
  20. package/dist/conversion/hub/response/response-runtime.js +14 -6
  21. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +11 -11
  22. package/dist/conversion/shared/anthropic-message-utils.js +2 -12
  23. package/dist/conversion/shared/chat-request-filters.js +2 -61
  24. package/dist/conversion/shared/reasoning-mapping.js +3 -0
  25. package/dist/conversion/shared/reasoning-normalizer.d.ts +1 -0
  26. package/dist/conversion/shared/reasoning-normalizer.js +35 -388
  27. package/dist/conversion/shared/reasoning-tool-normalizer.js +8 -15
  28. package/dist/conversion/shared/reasoning-tool-parser.js +7 -8
  29. package/dist/conversion/shared/reasoning-utils.js +13 -35
  30. package/dist/conversion/shared/responses-response-utils.js +3 -48
  31. package/dist/conversion/shared/responses-tool-utils.d.ts +1 -1
  32. package/dist/conversion/shared/responses-tool-utils.js +74 -180
  33. package/dist/conversion/shared/streaming-text-extractor.d.ts +0 -5
  34. package/dist/conversion/shared/streaming-text-extractor.js +18 -111
  35. package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +1 -1
  36. package/dist/conversion/shared/text-markup-normalizer/normalize.js +3 -91
  37. package/dist/conversion/shared/thought-signature-validator.js +19 -133
  38. package/dist/conversion/shared/tool-argument-repairer.js +16 -19
  39. package/dist/conversion/shared/tool-call-id-manager.d.ts +1 -5
  40. package/dist/conversion/shared/tool-call-id-manager.js +74 -98
  41. package/dist/conversion/shared/tool-harvester.js +19 -382
  42. package/dist/conversion/shared/tool-mapping.d.ts +2 -3
  43. package/dist/conversion/shared/tool-mapping.js +28 -184
  44. package/dist/conversion/shared/tooling.js +20 -151
  45. package/dist/filters/special/response-tool-arguments-stringify.js +9 -1
  46. package/dist/guidance/index.js +2 -2
  47. package/dist/native/router_hotpath_napi.node +0 -0
  48. package/dist/router/virtual-router/bootstrap/web-search-config.js +25 -0
  49. package/dist/router/virtual-router/bootstrap.js +21 -16
  50. package/dist/router/virtual-router/engine-legacy/helpers.js +1 -1
  51. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +6 -0
  52. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +171 -0
  53. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +39 -0
  54. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +196 -0
  55. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.d.ts +1 -0
  56. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js +27 -0
  57. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +45 -0
  58. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +70 -1
  59. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +993 -55
  60. package/dist/router/virtual-router/engine.js +3 -2
  61. package/dist/router/virtual-router/routing-instructions/parse.js +30 -3
  62. package/dist/router/virtual-router/types.d.ts +23 -0
  63. package/dist/servertool/handlers/web-search.js +26 -1
  64. package/dist/servertool/server-side-tools.js +11 -2
  65. package/dist/servertool/types.d.ts +4 -0
  66. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +28 -3
  67. package/dist/sse/types/anthropic-types.d.ts +3 -1
  68. package/dist/tools/apply-patch/args-normalizer/extract-patch.js +2 -2
  69. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +3 -6
  70. package/dist/tools/apply-patch/patch-text/normalize.js +14 -3
  71. package/dist/tools/tool-registry.js +12 -0
  72. package/package.json +6 -1
@@ -4,6 +4,7 @@ import { parseRoutingInstructions } from './routing-instructions/parse.js';
4
4
  import { createVirtualRouterEngineProxy } from './engine-selection/native-virtual-router-engine-proxy.js';
5
5
  import { cleanRoutingInstructionMarkersWithNative, parseRoutingInstructionKindsWithNative } from './engine-selection/native-virtual-router-routing-instructions-semantics.js';
6
6
  import { getLatestUserTextFromResponsesContext, hasLatestUserRoutingInstructionMarker, hasRoutingInstructionMarkerInResponsesContext } from './engine-legacy/helpers.js';
7
+ import { extractMessageText, getLatestUserMessage } from './message-utils.js';
7
8
  import { ProviderRegistry } from './provider-registry.js';
8
9
  import { resolveStopMessageScope } from './engine/routing-state/store.js';
9
10
  import { loadRoutingInstructionStateSync } from './sticky-session-store.js';
@@ -225,8 +226,8 @@ function buildRoutingInstructionParseLog(request, metadata) {
225
226
  if (!messages.length) {
226
227
  return null;
227
228
  }
228
- const latest = messages[messages.length - 1];
229
- const latestText = typeof latest?.content === 'string' ? latest.content.trim() : '';
229
+ const latest = getLatestUserMessage(messages);
230
+ const latestText = latest ? extractMessageText(latest).trim() : '';
230
231
  const parsedKinds = parseRoutingInstructionKindsWithNative(request);
231
232
  const stopMessageTypes = parsedKinds.filter((type) => type === 'stopMessageSet' || type === 'stopMessageMode' || type === 'stopMessageClear');
232
233
  const scopedTypes = parsedKinds.filter((type) => type === 'stopMessageSet' ||
@@ -1,4 +1,4 @@
1
- import { extractMessageText } from '../message-utils.js';
1
+ import { extractMessageText, getLatestUserMessage } from '../message-utils.js';
2
2
  import { parseStopMessageInstruction } from '../routing-stop-message-parser.js';
3
3
  import { parsePreCommandInstruction } from '../routing-pre-command-parser.js';
4
4
  import { stripCodeSegments } from './clean.js';
@@ -6,8 +6,8 @@ import { ROUTING_INSTRUCTION_MARKER_PATTERN } from './types.js';
6
6
  export function parseRoutingInstructions(messages) {
7
7
  const instructions = [];
8
8
  // 只解析“当前最新一条消息”中的 marker,避免历史 user marker 在后续轮次被重复回放。
9
- const latestMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
10
- if (!latestMessage || latestMessage.role !== 'user') {
9
+ const latestMessage = getLatestUserMessage(messages);
10
+ if (!latestMessage) {
11
11
  return instructions;
12
12
  }
13
13
  const content = extractMessageText(latestMessage);
@@ -87,6 +87,10 @@ function expandInstructionSegments(instruction) {
87
87
  if (/^precommand(?:\s*:|$)/i.test(normalizedLeading)) {
88
88
  return [normalizedLeading];
89
89
  }
90
+ const quotedStopMessage = normalizeQuotedStopMessageShorthand(normalizedLeading);
91
+ if (quotedStopMessage) {
92
+ return [normalizeStopMessageCommandPrefix(quotedStopMessage)];
93
+ }
90
94
  const prefix = trimmed[0];
91
95
  if (prefix === '!' || prefix === '#' || prefix === '@') {
92
96
  const tokens = splitInstructionTargets(trimmed.substring(1));
@@ -121,6 +125,29 @@ function normalizeSplitStopMessageHeadToken(token) {
121
125
  .replace(/^["']+|["']+$/g, '')
122
126
  .trim();
123
127
  }
128
+ function normalizeQuotedStopMessageShorthand(content) {
129
+ const normalized = normalizeInstructionLeading(content);
130
+ const quote = normalized[0];
131
+ if (quote !== '"' && quote !== "'") {
132
+ return null;
133
+ }
134
+ let escaped = false;
135
+ for (let idx = 1; idx < normalized.length; idx += 1) {
136
+ const ch = normalized[idx];
137
+ if (escaped) {
138
+ escaped = false;
139
+ continue;
140
+ }
141
+ if (ch === '\\') {
142
+ escaped = true;
143
+ continue;
144
+ }
145
+ if (ch === quote) {
146
+ return `stopMessage:${normalized}`;
147
+ }
148
+ }
149
+ return null;
150
+ }
124
151
  function recoverSplitStopMessageInstruction(tokens) {
125
152
  if (!Array.isArray(tokens) || tokens.length < 2) {
126
153
  return null;
@@ -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
  }[];
@@ -1,4 +1,23 @@
1
1
  import { dispatchReasoning } from '../../shared/reasoning-dispatcher.js';
2
+ function mergeAnthropicUsage(current, incoming) {
3
+ if (!incoming || typeof incoming !== 'object') {
4
+ return current;
5
+ }
6
+ const incomingRecord = incoming;
7
+ const base = current && typeof current === 'object' ? { ...current } : {};
8
+ for (const [key, value] of Object.entries(incomingRecord)) {
9
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
10
+ const prev = base[key];
11
+ base[key] = {
12
+ ...(prev && typeof prev === 'object' && !Array.isArray(prev) ? prev : {}),
13
+ ...value
14
+ };
15
+ continue;
16
+ }
17
+ base[key] = value;
18
+ }
19
+ return base;
20
+ }
2
21
  export function createAnthropicResponseBuilder(options) {
3
22
  const state = {
4
23
  content: [],
@@ -58,6 +77,7 @@ export function createAnthropicResponseBuilder(options) {
58
77
  state.id = payload.id || state.id;
59
78
  state.model = payload.model || state.model;
60
79
  state.role = payload.role || state.role;
80
+ state.usage = mergeAnthropicUsage(state.usage, payload.usage);
61
81
  }
62
82
  break;
63
83
  }
@@ -116,11 +136,14 @@ export function createAnthropicResponseBuilder(options) {
116
136
  if (delta?.stop_reason) {
117
137
  state.stopReason = delta.stop_reason;
118
138
  }
139
+ if (typeof delta?.stop_sequence === 'string' || delta?.stop_sequence === null) {
140
+ state.stopSequence = delta.stop_sequence;
141
+ }
119
142
  // 部分实现将 usage 挂在 delta.usage,部分实现挂在顶层 event.data.usage,
120
143
  // 这里统一优先读取 delta.usage,缺失时回退到 data.usage。
121
144
  const usageNode = (delta && delta.usage) ?? data.usage;
122
145
  if (usageNode) {
123
- state.usage = usageNode;
146
+ state.usage = mergeAnthropicUsage(state.usage, usageNode);
124
147
  }
125
148
  break;
126
149
  }
@@ -151,7 +174,8 @@ export function createAnthropicResponseBuilder(options) {
151
174
  model: state.model || 'unknown',
152
175
  content: state.content,
153
176
  usage: state.usage,
154
- stop_reason: state.stopReason ?? 'end_turn'
177
+ stop_reason: state.stopReason ?? 'end_turn',
178
+ stop_sequence: state.stopSequence
155
179
  }
156
180
  };
157
181
  }
@@ -166,7 +190,8 @@ export function createAnthropicResponseBuilder(options) {
166
190
  model: state.model || 'unknown',
167
191
  content: state.content,
168
192
  usage: state.usage,
169
- stop_reason: state.stopReason ?? 'end_turn'
193
+ stop_reason: state.stopReason ?? 'end_turn',
194
+ stop_sequence: state.stopSequence
170
195
  }
171
196
  };
172
197
  }
@@ -30,11 +30,12 @@ export interface AnthropicMessageResponse {
30
30
  role: 'assistant' | 'user';
31
31
  model: string;
32
32
  content: AnthropicContentBlock[];
33
- usage?: {
33
+ usage?: Record<string, unknown> & {
34
34
  input_tokens?: number;
35
35
  output_tokens?: number;
36
36
  };
37
37
  stop_reason?: 'end_turn' | 'max_tokens' | 'stop_sequence' | 'tool_use';
38
+ stop_sequence?: string | null;
38
39
  }
39
40
  export interface AnthropicEventStats {
40
41
  totalEvents: number;
@@ -157,6 +158,7 @@ export interface AnthropicSseEventMessageDelta extends AnthropicSseEventBase<'me
157
158
  type: 'message_delta';
158
159
  delta?: {
159
160
  stop_reason?: AnthropicMessageResponse['stop_reason'];
161
+ stop_sequence?: string | null;
160
162
  usage?: AnthropicMessageResponse['usage'];
161
163
  };
162
164
  };
@@ -4,11 +4,11 @@ export function extractNormalizedPatch(value) {
4
4
  return {};
5
5
  }
6
6
  const normalized = normalizeApplyPatchText(value);
7
- const patchLike = looksLikePatch(value) || looksLikePatch(normalized) || normalized.includes('*** Begin Patch');
7
+ const patchLike = looksLikePatch(value) || looksLikePatch(normalized);
8
8
  if (!patchLike) {
9
9
  return {};
10
10
  }
11
- if (!normalized.includes('*** Begin Patch')) {
11
+ if (!/^(?:\s*)\*\*\*\s*Begin Patch\b/m.test(normalized)) {
12
12
  return { failureReason: 'unsupported_patch_format' };
13
13
  }
14
14
  return { patchText: normalized };
@@ -4,11 +4,8 @@ export const looksLikePatch = (text) => {
4
4
  const t = text.trim();
5
5
  if (!t)
6
6
  return false;
7
- return (t.includes('*** Begin Patch') ||
8
- t.includes('*** Update File:') ||
9
- t.includes('*** Add File:') ||
10
- t.includes('*** Create File:') ||
11
- t.includes('*** Delete File:') ||
12
- t.includes('diff --git') ||
7
+ return (/^(?:\s*)\*\*\*\s*Begin Patch\b/m.test(t) ||
8
+ /^(?:\s*)\*\*\*\s*(?:Update|Add|Create|Delete)\s+File:/m.test(t) ||
9
+ /^diff --git\s/m.test(t) ||
13
10
  /^(?:@@|\+\+\+\s|---\s)/m.test(t));
14
11
  };
@@ -109,18 +109,29 @@ const convertStarHeaderDiffToApplyPatchIfPossible = (text) => {
109
109
  return null;
110
110
  }
111
111
  };
112
+ const normalizeApplyPatchHeaderPath = (raw) => {
113
+ let out = String(raw ?? '').trim();
114
+ if (!out)
115
+ return out;
116
+ if ((out.startsWith('"') && out.endsWith('"')) ||
117
+ (out.startsWith("'") && out.endsWith("'")) ||
118
+ (out.startsWith('`') && out.endsWith('`'))) {
119
+ out = out.slice(1, -1).trim();
120
+ }
121
+ return out;
122
+ };
112
123
  const normalizeApplyPatchFileHeader = (line) => {
113
124
  const addMatch = line.match(/^\*\*\* Add File:\s*(.+?)(?:\s+\*\*\*)?\s*$/);
114
125
  if (addMatch && addMatch[1]) {
115
- return `*** Add File: ${addMatch[1].trim()}`;
126
+ return `*** Add File: ${normalizeApplyPatchHeaderPath(addMatch[1])}`;
116
127
  }
117
128
  const updateMatch = line.match(/^\*\*\* Update File:\s*(.+?)(?:\s+\*\*\*)?\s*$/);
118
129
  if (updateMatch && updateMatch[1]) {
119
- return `*** Update File: ${updateMatch[1].trim()}`;
130
+ return `*** Update File: ${normalizeApplyPatchHeaderPath(updateMatch[1])}`;
120
131
  }
121
132
  const deleteMatch = line.match(/^\*\*\* Delete File:\s*(.+?)(?:\s+\*\*\*)?\s*$/);
122
133
  if (deleteMatch && deleteMatch[1]) {
123
- return `*** Delete File: ${deleteMatch[1].trim()}`;
134
+ return `*** Delete File: ${normalizeApplyPatchHeaderPath(deleteMatch[1])}`;
124
135
  }
125
136
  return line;
126
137
  };
@@ -72,6 +72,8 @@ const isImagePath = (value) => {
72
72
  export function getAllowedToolNames() {
73
73
  return [
74
74
  'shell',
75
+ 'shell_command',
76
+ 'bash',
75
77
  'exec_command',
76
78
  'apply_patch',
77
79
  'update_plan',
@@ -108,6 +110,16 @@ export function validateToolCall(name, argsString, options) {
108
110
  }
109
111
  return { ok: true, normalizedArgs: validation.normalizedArgs };
110
112
  }
113
+ case 'shell_command':
114
+ case 'bash': {
115
+ const rawArgs = isRecord(rawArgsAny) ? rawArgsAny : {};
116
+ const command = asString(rawArgs.command) ??
117
+ asString(rawArgs.cmd);
118
+ if (!command) {
119
+ return { ok: false, reason: 'missing_command' };
120
+ }
121
+ return { ok: true, normalizedArgs: toJson(rawArgs) };
122
+ }
111
123
  case 'apply_patch': {
112
124
  const validation = validateApplyPatchArgs(argsString, rawArgsAny);
113
125
  if (!validation.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.3275",
3
+ "version": "0.6.3405",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -78,6 +78,7 @@
78
78
  "test:coverage:hub-resp-outbound-client-remap-switch": "npm run build:ci && node ./node_modules/c8/bin/c8.js --reporter=json-summary --reporter=text --report-dir=coverage/module-hub-resp-outbound-client-remap-switch --include=dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.js --exclude=scripts/** node scripts/tests/coverage-hub-resp-outbound-client-remap-protocol-switch.mjs",
79
79
  "test:coverage:virtual-router-tier-antigravity-session-lease": "npm run build:ci && node ./node_modules/c8/bin/c8.js --reporter=json-summary --reporter=text --report-dir=coverage/module-virtual-router-tier-antigravity-session-lease --include=dist/router/virtual-router/engine-selection/tier-selection-antigravity-session-lease.js --exclude=scripts/** node scripts/tests/coverage-virtual-router-tier-antigravity-session-lease.mjs",
80
80
  "test:coverage:virtual-router-tier-antigravity-target-split": "npm run build:ci && node ./node_modules/c8/bin/c8.js --reporter=json-summary --reporter=text --report-dir=coverage/module-virtual-router-tier-antigravity-target-split --include=dist/router/virtual-router/engine-selection/tier-selection-antigravity-target-split.js --exclude=scripts/** node scripts/tests/coverage-virtual-router-tier-antigravity-target-split.mjs",
81
+ "test:coverage:text-markup-normalizer": "npm run build:ci && C8_COVERAGE=1 node ./node_modules/c8/bin/c8.js --lines 95 --branches 95 --functions 95 --statements 95 --reporter=json-summary --reporter=text --report-dir=coverage/module-text-markup-normalizer --include=dist/conversion/shared/text-markup-normalizer.js --exclude=**/node_modules/** node scripts/tests/coverage-text-markup-normalizer.mjs",
81
82
  "test:coverage": "node scripts/run-ci-coverage.mjs",
82
83
  "verify:module-blackbox": "node scripts/tests/module-blackbox-gate.mjs",
83
84
  "verify:shadow-gate": "node scripts/check-shadow-coverage-gate.mjs",
@@ -141,6 +142,7 @@
141
142
  "verify:shadow-gate:hub-resp-outbound-sse-stream": "npm run test:coverage:hub-resp-outbound-sse-stream && node scripts/check-shadow-coverage-gate.mjs --summary coverage/module-hub-resp-outbound-sse-stream/coverage-summary.json --module hub.resp-outbound.sse-stream && node scripts/promote-shadow-module.mjs --module hub.resp-outbound.sse-stream",
142
143
  "verify:shadow-gate:hub-native-batch": "npm run test:coverage:hub-native-batch && node scripts/check-shadow-coverage-gate.mjs --summary coverage/module-hub-native-batch/coverage-summary.json --module hub.native-batch.adapters-snapshot-governance && node scripts/promote-shadow-module.mjs --module hub.native-batch.adapters-snapshot-governance",
143
144
  "test:coverage:hub-chat-request-filters": "npm run build:ci && node ./node_modules/c8/bin/c8.js --reporter=json-summary --reporter=text --report-dir=coverage/module-hub-chat-request-filters --include=dist/conversion/shared/chat-request-filters.js --include=dist/router/virtual-router/engine-selection/native-chat-request-filter-semantics.js --exclude=scripts/** node scripts/tests/coverage-chat-request-filters.mjs",
145
+ "test:coverage:router-hotpath-napi": "cd rust-core && cargo llvm-cov -p router-hotpath-napi --summary-only --json --output-path /tmp/router-hotpath-napi-cov.json && node -e \"const fs=require('fs');const data=JSON.parse(fs.readFileSync('/tmp/router-hotpath-napi-cov.json','utf8'));const files=(data.data&&data.data[0]&&data.data[0].files)||[];const target=files.find(f=>String(f.filename||'').endsWith('resp_process_stage1_tool_governance.rs'));if(!target){console.error('missing resp_process_stage1_tool_governance.rs in coverage');process.exit(1);}const sum=target.summary||{};const lines=sum.lines?sum.lines.percent:0;const funcs=sum.functions?sum.functions.percent:0;const regions=sum.regions?sum.regions.percent:0;const branches=sum.branches?sum.branches.percent:'n/a';const statements=sum.statements?sum.statements.percent:'n/a';console.log('[coverage router-hotpath-napi] lines',lines,'functions',funcs,'regions',regions,'branches',branches,'statements',statements);const min=95;if(lines<min||funcs<min||regions<min){process.exit(1);}\"",
144
146
  "verify:shadow-gate:hub-chat-request-filters": "npm run test:coverage:hub-chat-request-filters && node scripts/check-shadow-coverage-gate.mjs --summary coverage/module-hub-chat-request-filters/coverage-summary.json --module hub.shared.chat-request-filters && node scripts/promote-shadow-module.mjs --module hub.shared.chat-request-filters"
145
147
  },
146
148
  "dependencies": {
@@ -167,11 +169,14 @@
167
169
  }
168
170
  },
169
171
  "devDependencies": {
172
+ "@types/jest": "^29.5.14",
170
173
  "@types/node": "^20.11.25",
171
174
  "@typescript-eslint/eslint-plugin": "^8.56.1",
172
175
  "@typescript-eslint/parser": "^8.56.1",
173
176
  "c8": "^11.0.0",
174
177
  "eslint": "^8.50.0",
178
+ "jest": "^29.7.0",
179
+ "ts-jest": "^29.2.6",
175
180
  "tsx": "^4.21.0",
176
181
  "typescript": "^5.4.0"
177
182
  },