@jsonstudio/llms 0.6.3271 → 0.6.3379

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 (49) 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/anthropic-claude-code-system-prompt.js +38 -0
  4. package/dist/conversion/compat/profiles/responses-crs.json +15 -0
  5. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +16 -5
  6. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +1 -6
  7. package/dist/conversion/hub/response/response-runtime.js +14 -6
  8. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +11 -11
  9. package/dist/conversion/shared/anthropic-message-utils.js +2 -12
  10. package/dist/conversion/shared/chat-request-filters.js +2 -61
  11. package/dist/conversion/shared/reasoning-mapping.js +3 -0
  12. package/dist/conversion/shared/reasoning-normalizer.d.ts +1 -0
  13. package/dist/conversion/shared/reasoning-normalizer.js +35 -388
  14. package/dist/conversion/shared/reasoning-tool-normalizer.js +8 -15
  15. package/dist/conversion/shared/reasoning-utils.js +13 -35
  16. package/dist/conversion/shared/responses-tool-utils.d.ts +1 -1
  17. package/dist/conversion/shared/responses-tool-utils.js +63 -65
  18. package/dist/conversion/shared/streaming-text-extractor.d.ts +0 -5
  19. package/dist/conversion/shared/streaming-text-extractor.js +18 -111
  20. package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +1 -1
  21. package/dist/conversion/shared/text-markup-normalizer/normalize.js +3 -91
  22. package/dist/conversion/shared/thought-signature-validator.js +19 -133
  23. package/dist/conversion/shared/tool-argument-repairer.js +16 -19
  24. package/dist/conversion/shared/tool-call-id-manager.d.ts +1 -5
  25. package/dist/conversion/shared/tool-call-id-manager.js +74 -98
  26. package/dist/conversion/shared/tool-harvester.js +19 -382
  27. package/dist/conversion/shared/tool-mapping.d.ts +2 -3
  28. package/dist/conversion/shared/tool-mapping.js +28 -184
  29. package/dist/conversion/shared/tooling.js +20 -151
  30. package/dist/filters/special/response-tool-arguments-stringify.js +9 -1
  31. package/dist/guidance/index.js +2 -2
  32. package/dist/native/router_hotpath_napi.node +0 -0
  33. package/dist/router/virtual-router/engine-legacy/helpers.js +1 -1
  34. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +39 -0
  35. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +196 -0
  36. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.d.ts +1 -0
  37. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js +27 -0
  38. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +34 -0
  39. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +65 -1
  40. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +836 -35
  41. package/dist/router/virtual-router/engine.js +3 -2
  42. package/dist/router/virtual-router/routing-instructions/parse.js +30 -3
  43. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +28 -3
  44. package/dist/sse/types/anthropic-types.d.ts +3 -1
  45. package/dist/tools/apply-patch/args-normalizer/extract-patch.js +2 -2
  46. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +3 -6
  47. package/dist/tools/apply-patch/patch-text/normalize.js +14 -3
  48. package/dist/tools/tool-registry.js +12 -0
  49. 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;
@@ -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.3271",
3
+ "version": "0.6.3379",
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
  },