@jsonstudio/llms 0.6.1749 → 0.6.1892

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 (107) hide show
  1. package/dist/conversion/compat/actions/deepseek-web-request.d.ts +3 -0
  2. package/dist/conversion/compat/actions/deepseek-web-request.js +350 -0
  3. package/dist/conversion/compat/actions/deepseek-web-response.d.ts +3 -0
  4. package/dist/conversion/compat/actions/deepseek-web-response.js +886 -0
  5. package/dist/conversion/compat/actions/gemini-cli-request.js +3 -1
  6. package/dist/conversion/compat/profiles/chat-deepseek-web.json +18 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +166 -2
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +169 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +6 -0
  10. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +12 -0
  11. package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -0
  12. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +4 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.js +365 -144
  14. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +9 -0
  15. package/dist/conversion/hub/policy/policy-engine.d.ts +2 -0
  16. package/dist/conversion/hub/policy/policy-engine.js +8 -0
  17. package/dist/conversion/hub/process/chat-process.js +466 -16
  18. package/dist/conversion/hub/response/provider-response.js +0 -35
  19. package/dist/conversion/responses/responses-openai-bridge.d.ts +2 -0
  20. package/dist/conversion/responses/responses-openai-bridge.js +166 -8
  21. package/dist/conversion/shared/anthropic-message-utils.js +10 -1
  22. package/dist/conversion/shared/protocol-field-allowlists.d.ts +2 -2
  23. package/dist/conversion/shared/protocol-field-allowlists.js +4 -0
  24. package/dist/conversion/shared/tool-governor.js +102 -0
  25. package/dist/guidance/index.js +17 -0
  26. package/dist/router/virtual-router/bootstrap.js +46 -1
  27. package/dist/router/virtual-router/classifier.js +59 -4
  28. package/dist/router/virtual-router/engine/health/index.js +6 -6
  29. package/dist/router/virtual-router/engine/routing-state/store.js +16 -3
  30. package/dist/router/virtual-router/engine-logging.js +62 -24
  31. package/dist/router/virtual-router/engine-selection/route-utils.js +20 -20
  32. package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -2
  33. package/dist/router/virtual-router/engine.d.ts +3 -1
  34. package/dist/router/virtual-router/engine.js +325 -38
  35. package/dist/router/virtual-router/features.js +2 -1
  36. package/dist/router/virtual-router/pre-command-file-resolver.d.ts +2 -0
  37. package/dist/router/virtual-router/pre-command-file-resolver.js +90 -0
  38. package/dist/router/virtual-router/provider-registry.js +3 -1
  39. package/dist/router/virtual-router/routing-instructions.d.ts +11 -1
  40. package/dist/router/virtual-router/routing-instructions.js +101 -183
  41. package/dist/router/virtual-router/routing-pre-command-actions.d.ts +3 -0
  42. package/dist/router/virtual-router/routing-pre-command-actions.js +26 -0
  43. package/dist/router/virtual-router/routing-pre-command-parser.d.ts +2 -0
  44. package/dist/router/virtual-router/routing-pre-command-parser.js +85 -0
  45. package/dist/router/virtual-router/routing-pre-command-state-codec.d.ts +3 -0
  46. package/dist/router/virtual-router/routing-pre-command-state-codec.js +24 -0
  47. package/dist/router/virtual-router/routing-stop-message-actions.d.ts +2 -0
  48. package/dist/router/virtual-router/routing-stop-message-actions.js +96 -0
  49. package/dist/router/virtual-router/routing-stop-message-parser.d.ts +3 -0
  50. package/dist/router/virtual-router/routing-stop-message-parser.js +142 -0
  51. package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +4 -0
  52. package/dist/router/virtual-router/routing-stop-message-state-codec.js +85 -0
  53. package/dist/router/virtual-router/sticky-session-store.js +206 -57
  54. package/dist/router/virtual-router/stop-message-stage-template-files.d.ts +12 -0
  55. package/dist/router/virtual-router/stop-message-stage-template-files.js +67 -0
  56. package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
  57. package/dist/router/virtual-router/stop-message-state-sync.js +1 -0
  58. package/dist/router/virtual-router/token-file-scanner.d.ts +9 -0
  59. package/dist/router/virtual-router/token-file-scanner.js +64 -3
  60. package/dist/router/virtual-router/tool-signals.d.ts +5 -0
  61. package/dist/router/virtual-router/tool-signals.js +42 -3
  62. package/dist/router/virtual-router/types.d.ts +15 -1
  63. package/dist/router/virtual-router/types.js +1 -0
  64. package/dist/servertool/clock/config.d.ts +1 -1
  65. package/dist/servertool/clock/config.js +27 -4
  66. package/dist/servertool/clock/state.js +41 -2
  67. package/dist/servertool/clock/task-store.d.ts +2 -2
  68. package/dist/servertool/clock/task-store.js +1 -1
  69. package/dist/servertool/clock/tasks.d.ts +3 -1
  70. package/dist/servertool/clock/tasks.js +209 -18
  71. package/dist/servertool/clock/types.d.ts +17 -0
  72. package/dist/servertool/continue-execution/log.d.ts +3 -0
  73. package/dist/servertool/continue-execution/log.js +13 -0
  74. package/dist/servertool/engine.js +414 -68
  75. package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +6 -6
  76. package/dist/servertool/handlers/clock-auto.js +54 -71
  77. package/dist/servertool/handlers/clock.js +121 -6
  78. package/dist/servertool/handlers/continue-execution.d.ts +1 -0
  79. package/dist/servertool/handlers/continue-execution.js +91 -0
  80. package/dist/servertool/handlers/followup-request-builder.js +13 -0
  81. package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -1
  82. package/dist/servertool/handlers/iflow-model-error-retry.js +1 -1
  83. package/dist/servertool/handlers/recursive-detection-guard.js +1 -1
  84. package/dist/servertool/handlers/stop-message-auto.js +352 -257
  85. package/dist/servertool/handlers/stop-message-stage-policy.d.ts +22 -1
  86. package/dist/servertool/handlers/stop-message-stage-policy.js +472 -60
  87. package/dist/servertool/handlers/vision.js +1 -1
  88. package/dist/servertool/log/progress-file.d.ts +14 -0
  89. package/dist/servertool/log/progress-file.js +88 -0
  90. package/dist/servertool/pre-command-hooks.d.ts +17 -0
  91. package/dist/servertool/pre-command-hooks.js +491 -0
  92. package/dist/servertool/registry.d.ts +23 -6
  93. package/dist/servertool/registry.js +66 -1
  94. package/dist/servertool/server-side-tools.d.ts +1 -0
  95. package/dist/servertool/server-side-tools.js +216 -14
  96. package/dist/servertool/stop-gateway-context.d.ts +14 -0
  97. package/dist/servertool/stop-gateway-context.js +167 -0
  98. package/dist/servertool/stop-message-compare-context.d.ts +24 -0
  99. package/dist/servertool/stop-message-compare-context.js +133 -0
  100. package/dist/servertool/types.d.ts +12 -0
  101. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
  102. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +36 -1
  103. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +3 -0
  104. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +3 -0
  105. package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +118 -1
  106. package/dist/tools/apply-patch/args-normalizer/default-actions.js +1 -1
  107. package/package.json +1 -1
@@ -7,6 +7,7 @@ import { ContextAdvisor } from './context-advisor.js';
7
7
  import { DEFAULT_ROUTE, ROUTE_PRIORITY, VirtualRouterError, VirtualRouterErrorCode } from './types.js';
8
8
  import { getStatsCenter } from '../../telemetry/stats-center.js';
9
9
  import { parseRoutingInstructions, applyRoutingInstructions, cleanMessagesFromRoutingInstructions } from './routing-instructions.js';
10
+ import { extractMessageText } from './message-utils.js';
10
11
  import { loadRoutingInstructionStateSync, saveRoutingInstructionStateAsync, saveRoutingInstructionStateSync } from './sticky-session-store.js';
11
12
  import { buildHitReason, formatVirtualRouterHit } from './engine-logging.js';
12
13
  import { selectDirectProviderModel, selectFromStickyPool, selectProviderImpl } from './engine/routing-pools/index.js';
@@ -14,8 +15,74 @@ import { applyQuotaDepletedImpl, applyQuotaRecoveryImpl, applySeriesCooldownImpl
14
15
  import { hydrateAntigravityAliasLeaseStoreIfNeeded, recordAntigravitySessionLease, resolveAntigravityAliasReuseCooldownMs } from './engine/antigravity/alias-lease.js';
15
16
  import { buildMetadataInstructions, resolveRoutingMode } from './engine/routing-state/metadata.js';
16
17
  import { getRoutingInstructionState, persistRoutingInstructionState, resolveStopMessageScope } from './engine/routing-state/store.js';
18
+ import { validateStopMessageStageTemplatesCompleteness } from './stop-message-stage-template-files.js';
17
19
  import { extractKeyAlias, extractKeyIndex, extractProviderId, getProviderModelId } from './engine/provider-key/parse.js';
18
20
  import { resolveSessionScope as resolveSessionScopeImpl, resolveStickyKey as resolveStickyKeyImpl } from './engine/routing-state/keys.js';
21
+ function normalizeStopMessageStageMode(value) {
22
+ if (typeof value !== 'string') {
23
+ return undefined;
24
+ }
25
+ const normalized = value.trim().toLowerCase();
26
+ if (normalized === 'on' || normalized === 'off' || normalized === 'auto') {
27
+ return normalized;
28
+ }
29
+ return undefined;
30
+ }
31
+ function resolveStopMessageStageModeAfterInstructions(instructions, currentMode) {
32
+ let mode = normalizeStopMessageStageMode(currentMode);
33
+ for (const instruction of instructions) {
34
+ if (instruction.type === 'stopMessageClear') {
35
+ mode = undefined;
36
+ continue;
37
+ }
38
+ if (instruction.type === 'stopMessageMode') {
39
+ const incomingMode = normalizeStopMessageStageMode(instruction.stopMessageStageMode);
40
+ if (incomingMode) {
41
+ mode = incomingMode;
42
+ }
43
+ continue;
44
+ }
45
+ if (instruction.type === 'stopMessageSet') {
46
+ const incomingMode = normalizeStopMessageStageMode(instruction.stopMessageStageMode);
47
+ if (incomingMode) {
48
+ mode = incomingMode;
49
+ }
50
+ else if (mode === 'off') {
51
+ mode = 'on';
52
+ }
53
+ }
54
+ }
55
+ return mode;
56
+ }
57
+ function hasRoutingInstructionMarker(messages) {
58
+ for (const message of messages) {
59
+ if (!message || message.role !== 'user') {
60
+ continue;
61
+ }
62
+ const content = extractMessageText(message);
63
+ if (!content) {
64
+ continue;
65
+ }
66
+ if (/<\*\*[^*]+\*\*>/.test(content)) {
67
+ return true;
68
+ }
69
+ }
70
+ return false;
71
+ }
72
+ function hasLatestUserRoutingInstructionMarker(messages) {
73
+ for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
74
+ const message = messages[idx];
75
+ if (!message || message.role !== 'user') {
76
+ continue;
77
+ }
78
+ const content = extractMessageText(message);
79
+ if (!content) {
80
+ return false;
81
+ }
82
+ return /<\*\*[^*]+\*\*>/.test(content);
83
+ }
84
+ return false;
85
+ }
19
86
  export class VirtualRouterEngine {
20
87
  routing = {};
21
88
  providerRegistry = new ProviderRegistry();
@@ -165,7 +232,8 @@ export class VirtualRouterEngine {
165
232
  if (stopMessageScope) {
166
233
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
167
234
  if (typeof sessionState.stopMessageText === 'string' ||
168
- typeof sessionState.stopMessageMaxRepeats === 'number') {
235
+ typeof sessionState.stopMessageMaxRepeats === 'number' ||
236
+ typeof sessionState.stopMessageStageMode === 'string') {
169
237
  routingState = {
170
238
  ...routingState,
171
239
  stopMessageText: sessionState.stopMessageText,
@@ -174,29 +242,58 @@ export class VirtualRouterEngine {
174
242
  stopMessageUpdatedAt: sessionState.stopMessageUpdatedAt,
175
243
  stopMessageLastUsedAt: sessionState.stopMessageLastUsedAt,
176
244
  stopMessageStage: sessionState.stopMessageStage,
245
+ stopMessageStageMode: sessionState.stopMessageStageMode,
177
246
  stopMessageObservationHash: sessionState.stopMessageObservationHash,
178
247
  stopMessageObservationStableCount: sessionState.stopMessageObservationStableCount,
179
248
  stopMessageBdWorkState: sessionState.stopMessageBdWorkState
180
249
  };
181
250
  }
251
+ if (typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()) {
252
+ routingState = {
253
+ ...routingState,
254
+ preCommandSource: sessionState.preCommandSource,
255
+ preCommandScriptPath: sessionState.preCommandScriptPath,
256
+ preCommandUpdatedAt: sessionState.preCommandUpdatedAt
257
+ };
258
+ }
182
259
  }
183
260
  const parsedInstructions = parseRoutingInstructions(request.messages);
261
+ const latestUserHasMarker = hasLatestUserRoutingInstructionMarker(request.messages);
184
262
  let instructions = parsedInstructions;
185
263
  if (stopMessageScope && parsedInstructions.length > 0) {
186
264
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
187
- const hasStopMessageClear = parsedInstructions.some((entry) => entry.type === 'stopMessageClear');
188
- const stopMessageSets = parsedInstructions.filter((entry) => entry.type === 'stopMessageSet');
265
+ const hasStaleStopMessageInstruction = !latestUserHasMarker &&
266
+ parsedInstructions.some((entry) => entry.type === 'stopMessageSet' ||
267
+ entry.type === 'stopMessageMode' ||
268
+ entry.type === 'stopMessageClear');
269
+ if (hasStaleStopMessageInstruction) {
270
+ const hasActiveStopState = typeof sessionState.stopMessageText === 'string' ||
271
+ typeof sessionState.stopMessageMaxRepeats === 'number' ||
272
+ typeof sessionState.stopMessageStageMode === 'string';
273
+ const hasStopLifecycleStamp = (typeof sessionState.stopMessageUpdatedAt === 'number' && Number.isFinite(sessionState.stopMessageUpdatedAt)) ||
274
+ (typeof sessionState.stopMessageLastUsedAt === 'number' && Number.isFinite(sessionState.stopMessageLastUsedAt));
275
+ if (hasActiveStopState || hasStopLifecycleStamp) {
276
+ instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet' &&
277
+ entry.type !== 'stopMessageMode' &&
278
+ entry.type !== 'stopMessageClear');
279
+ }
280
+ }
281
+ const hasStopMessageClear = instructions.some((entry) => entry.type === 'stopMessageClear');
282
+ const stopMessageSets = instructions.filter((entry) => entry.type === 'stopMessageSet');
189
283
  if (!hasStopMessageClear && stopMessageSets.length > 0) {
190
284
  const sessionText = typeof sessionState.stopMessageText === 'string' ? sessionState.stopMessageText.trim() : '';
191
285
  const sessionMax = typeof sessionState.stopMessageMaxRepeats === 'number' && Number.isFinite(sessionState.stopMessageMaxRepeats)
192
286
  ? Math.floor(sessionState.stopMessageMaxRepeats)
193
287
  : undefined;
288
+ const sessionMode = normalizeStopMessageStageMode(sessionState.stopMessageStageMode);
194
289
  const allSame = stopMessageSets.every((entry) => {
195
290
  const entryText = typeof entry.stopMessageText === 'string' ? entry.stopMessageText.trim() : '';
196
291
  const entryMax = typeof entry.stopMessageMaxRepeats === 'number' && Number.isFinite(entry.stopMessageMaxRepeats)
197
292
  ? Math.floor(entry.stopMessageMaxRepeats)
198
293
  : undefined;
199
- return Boolean(entryText) && entryText === sessionText && entryMax === sessionMax;
294
+ const incomingMode = normalizeStopMessageStageMode(entry.stopMessageStageMode);
295
+ const entryMode = incomingMode ?? (sessionMode === 'off' ? 'on' : sessionMode);
296
+ return Boolean(entryText) && entryText === sessionText && entryMax === sessionMax && entryMode === sessionMode;
200
297
  });
201
298
  const used = typeof sessionState.stopMessageUsed === 'number' && Number.isFinite(sessionState.stopMessageUsed)
202
299
  ? Math.max(0, Math.floor(sessionState.stopMessageUsed))
@@ -205,19 +302,36 @@ export class VirtualRouterEngine {
205
302
  Number.isFinite(sessionState.stopMessageLastUsedAt);
206
303
  const alreadyArmed = used === 0 && !hasLastUsedAt;
207
304
  if (allSame && alreadyArmed) {
208
- instructions = parsedInstructions.filter((entry) => entry.type !== 'stopMessageSet');
305
+ instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet');
209
306
  }
210
307
  }
211
308
  }
212
309
  // stopMessage must be session-scoped: require explicit sessionId in metadata.
213
310
  // This prevents global/default persistence and ensures the trigger matches the setting sessionId.
214
311
  if (parsedInstructions.length > 0) {
215
- const hasStopMessageInstruction = parsedInstructions.some((entry) => entry.type === 'stopMessageSet' || entry.type === 'stopMessageClear');
216
- if (hasStopMessageInstruction && !stopMessageScope) {
217
- throw new VirtualRouterError('[stopMessage] requires sessionId (e.g. set x-session-id header or metadata.sessionId).', VirtualRouterErrorCode.CONFIG_ERROR, { requestId: metadata.requestId, entryEndpoint: metadata.entryEndpoint });
312
+ const hasSessionScopedInstruction = parsedInstructions.some((entry) => entry.type === 'stopMessageSet' ||
313
+ entry.type === 'stopMessageMode' ||
314
+ entry.type === 'stopMessageClear' ||
315
+ entry.type === 'preCommandSet' ||
316
+ entry.type === 'preCommandClear');
317
+ if (hasSessionScopedInstruction && !stopMessageScope) {
318
+ throw new VirtualRouterError('[stopMessage/precommand] requires sessionId (e.g. set x-session-id header or metadata.sessionId).', VirtualRouterErrorCode.CONFIG_ERROR, { requestId: metadata.requestId, entryEndpoint: metadata.entryEndpoint });
218
319
  }
219
320
  }
220
- if (parsedInstructions.length > 0) {
321
+ if (stopMessageScope && parsedInstructions.length > 0) {
322
+ const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
323
+ const nextStopMessageMode = resolveStopMessageStageModeAfterInstructions(instructions, sessionState.stopMessageStageMode);
324
+ if (nextStopMessageMode === 'on') {
325
+ const templateCheck = validateStopMessageStageTemplatesCompleteness();
326
+ if (!templateCheck.ok) {
327
+ const detail = templateCheck.missing
328
+ .map((entry) => `${entry.stage}:${entry.ref || '(no-ref)'}:${entry.error || 'invalid'}`)
329
+ .join('; ');
330
+ throw new VirtualRouterError(`[stopMessage] mode=on requires complete stage message files (${detail}).`, VirtualRouterErrorCode.CONFIG_ERROR, { requestId: metadata.requestId, entryEndpoint: metadata.entryEndpoint });
331
+ }
332
+ }
333
+ }
334
+ if (hasRoutingInstructionMarker(request.messages)) {
221
335
  request.messages = cleanMessagesFromRoutingInstructions(request.messages);
222
336
  }
223
337
  if (instructions.length > 0) {
@@ -230,8 +344,9 @@ export class VirtualRouterEngine {
230
344
  // so servertool triggers always match the setting sessionId.
231
345
  if (stopMessageScope) {
232
346
  const hasStopMessageSet = instructions.some((entry) => entry.type === 'stopMessageSet');
347
+ const hasStopMessageMode = instructions.some((entry) => entry.type === 'stopMessageMode');
233
348
  const hasStopMessageClear = instructions.some((entry) => entry.type === 'stopMessageClear');
234
- if (hasStopMessageSet || hasStopMessageClear) {
349
+ if (hasStopMessageSet || hasStopMessageMode || hasStopMessageClear) {
235
350
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
236
351
  let nextSessionState = {
237
352
  ...sessionState
@@ -246,6 +361,7 @@ export class VirtualRouterEngine {
246
361
  nextSessionState.stopMessageLastUsedAt = clearedAt;
247
362
  nextSessionState.stopMessageSource = undefined;
248
363
  nextSessionState.stopMessageStage = undefined;
364
+ nextSessionState.stopMessageStageMode = undefined;
249
365
  nextSessionState.stopMessageObservationHash = undefined;
250
366
  nextSessionState.stopMessageObservationStableCount = undefined;
251
367
  nextSessionState.stopMessageBdWorkState = undefined;
@@ -259,7 +375,10 @@ export class VirtualRouterEngine {
259
375
  const sameMax = typeof sessionState.stopMessageMaxRepeats === 'number' &&
260
376
  typeof maxRepeats === 'number' &&
261
377
  Math.floor(sessionState.stopMessageMaxRepeats) === Math.floor(maxRepeats);
262
- const isSameInstruction = Boolean(text) && sameText && sameMax;
378
+ const currentMode = normalizeStopMessageStageMode(sessionState.stopMessageStageMode);
379
+ const nextMode = normalizeStopMessageStageMode(routingState.stopMessageStageMode) ?? currentMode;
380
+ const isSameMode = currentMode === nextMode;
381
+ const isSameInstruction = Boolean(text) && sameText && sameMax && isSameMode;
263
382
  const used = typeof sessionState.stopMessageUsed === 'number' && Number.isFinite(sessionState.stopMessageUsed)
264
383
  ? Math.max(0, Math.floor(sessionState.stopMessageUsed))
265
384
  : 0;
@@ -269,6 +388,7 @@ export class VirtualRouterEngine {
269
388
  nextSessionState.stopMessageText = text || undefined;
270
389
  nextSessionState.stopMessageMaxRepeats = maxRepeats;
271
390
  nextSessionState.stopMessageSource = 'explicit';
391
+ nextSessionState.stopMessageStageMode = nextMode;
272
392
  if (shouldRearm) {
273
393
  nextSessionState.stopMessageUsed = 0;
274
394
  nextSessionState.stopMessageUpdatedAt =
@@ -283,6 +403,47 @@ export class VirtualRouterEngine {
283
403
  shouldPersistSessionState = true;
284
404
  }
285
405
  }
406
+ else if (hasStopMessageMode) {
407
+ const mode = normalizeStopMessageStageMode(routingState.stopMessageStageMode);
408
+ const modeMaxRepeats = typeof routingState.stopMessageMaxRepeats === 'number' && Number.isFinite(routingState.stopMessageMaxRepeats)
409
+ ? Math.floor(routingState.stopMessageMaxRepeats)
410
+ : 0;
411
+ if (mode === 'off') {
412
+ const changed = typeof nextSessionState.stopMessageText === 'string' ||
413
+ typeof nextSessionState.stopMessageMaxRepeats === 'number' ||
414
+ typeof nextSessionState.stopMessageUsed === 'number' ||
415
+ typeof nextSessionState.stopMessageSource === 'string' ||
416
+ typeof nextSessionState.stopMessageStage === 'string' ||
417
+ typeof nextSessionState.stopMessageObservationHash === 'string' ||
418
+ typeof nextSessionState.stopMessageObservationStableCount === 'number' ||
419
+ typeof nextSessionState.stopMessageBdWorkState === 'string' ||
420
+ normalizeStopMessageStageMode(nextSessionState.stopMessageStageMode) !== 'off';
421
+ nextSessionState.stopMessageText = undefined;
422
+ nextSessionState.stopMessageMaxRepeats = undefined;
423
+ nextSessionState.stopMessageUsed = undefined;
424
+ nextSessionState.stopMessageSource = undefined;
425
+ nextSessionState.stopMessageStage = undefined;
426
+ nextSessionState.stopMessageStageMode = 'off';
427
+ nextSessionState.stopMessageObservationHash = undefined;
428
+ nextSessionState.stopMessageObservationStableCount = undefined;
429
+ nextSessionState.stopMessageBdWorkState = undefined;
430
+ nextSessionState.stopMessageUpdatedAt = Date.now();
431
+ nextSessionState.stopMessageLastUsedAt = undefined;
432
+ shouldPersistSessionState = changed;
433
+ }
434
+ else if (mode) {
435
+ if (normalizeStopMessageStageMode(nextSessionState.stopMessageStageMode) !== mode) {
436
+ nextSessionState.stopMessageStageMode = mode;
437
+ shouldPersistSessionState = true;
438
+ }
439
+ if (modeMaxRepeats > 0 &&
440
+ (typeof nextSessionState.stopMessageMaxRepeats !== 'number' ||
441
+ Math.floor(nextSessionState.stopMessageMaxRepeats) !== modeMaxRepeats)) {
442
+ nextSessionState.stopMessageMaxRepeats = modeMaxRepeats;
443
+ shouldPersistSessionState = true;
444
+ }
445
+ }
446
+ }
286
447
  if (shouldPersistSessionState) {
287
448
  this.routingInstructionState.set(stopMessageScope, nextSessionState);
288
449
  persistRoutingInstructionState(stopMessageScope, nextSessionState, this.routingStateStore);
@@ -292,13 +453,15 @@ export class VirtualRouterEngine {
292
453
  }
293
454
  // 日志展示使用 session scope 的 stopMessage 状态,避免每次解析重复刷新时间/次数。
294
455
  if (typeof nextSessionState.stopMessageText === 'string' ||
295
- typeof nextSessionState.stopMessageMaxRepeats === 'number') {
456
+ typeof nextSessionState.stopMessageMaxRepeats === 'number' ||
457
+ typeof nextSessionState.stopMessageStageMode === 'string') {
296
458
  routingState.stopMessageText = nextSessionState.stopMessageText;
297
459
  routingState.stopMessageMaxRepeats = nextSessionState.stopMessageMaxRepeats;
298
460
  routingState.stopMessageUsed = nextSessionState.stopMessageUsed;
299
461
  routingState.stopMessageUpdatedAt = nextSessionState.stopMessageUpdatedAt;
300
462
  routingState.stopMessageLastUsedAt = nextSessionState.stopMessageLastUsedAt;
301
463
  routingState.stopMessageStage = nextSessionState.stopMessageStage;
464
+ routingState.stopMessageStageMode = nextSessionState.stopMessageStageMode;
302
465
  routingState.stopMessageObservationHash = nextSessionState.stopMessageObservationHash;
303
466
  routingState.stopMessageObservationStableCount = nextSessionState.stopMessageObservationStableCount;
304
467
  routingState.stopMessageBdWorkState = nextSessionState.stopMessageBdWorkState;
@@ -306,20 +469,68 @@ export class VirtualRouterEngine {
306
469
  }
307
470
  }
308
471
  }
472
+ if (instructions.length > 0 && stopMessageScope) {
473
+ const hasPreCommandSet = instructions.some((entry) => entry.type === 'preCommandSet');
474
+ const hasPreCommandClear = instructions.some((entry) => entry.type === 'preCommandClear');
475
+ if (hasPreCommandSet || hasPreCommandClear) {
476
+ const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
477
+ const nextSessionState = {
478
+ ...sessionState
479
+ };
480
+ let changed = false;
481
+ if (hasPreCommandClear) {
482
+ changed =
483
+ typeof sessionState.preCommandScriptPath === 'string' ||
484
+ typeof sessionState.preCommandSource === 'string' ||
485
+ typeof sessionState.preCommandUpdatedAt === 'number';
486
+ nextSessionState.preCommandScriptPath = undefined;
487
+ nextSessionState.preCommandSource = undefined;
488
+ nextSessionState.preCommandUpdatedAt = Date.now();
489
+ }
490
+ if (hasPreCommandSet) {
491
+ const scriptPath = typeof routingState.preCommandScriptPath === 'string' ? routingState.preCommandScriptPath.trim() : '';
492
+ if (scriptPath) {
493
+ if (sessionState.preCommandScriptPath !== scriptPath) {
494
+ changed = true;
495
+ }
496
+ nextSessionState.preCommandScriptPath = scriptPath;
497
+ nextSessionState.preCommandSource = 'explicit';
498
+ nextSessionState.preCommandUpdatedAt =
499
+ typeof routingState.preCommandUpdatedAt === 'number' && Number.isFinite(routingState.preCommandUpdatedAt)
500
+ ? routingState.preCommandUpdatedAt
501
+ : Date.now();
502
+ }
503
+ }
504
+ if (changed) {
505
+ this.routingInstructionState.set(stopMessageScope, nextSessionState);
506
+ persistRoutingInstructionState(stopMessageScope, nextSessionState, this.routingStateStore);
507
+ routingState.preCommandScriptPath = nextSessionState.preCommandScriptPath;
508
+ routingState.preCommandSource = nextSessionState.preCommandSource;
509
+ routingState.preCommandUpdatedAt = nextSessionState.preCommandUpdatedAt;
510
+ }
511
+ }
512
+ }
309
513
  if (instructions.length === 0 && stopMessageScope) {
310
514
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
311
515
  if (typeof sessionState.stopMessageText === 'string' ||
312
- typeof sessionState.stopMessageMaxRepeats === 'number') {
516
+ typeof sessionState.stopMessageMaxRepeats === 'number' ||
517
+ typeof sessionState.stopMessageStageMode === 'string') {
313
518
  routingState.stopMessageText = sessionState.stopMessageText;
314
519
  routingState.stopMessageMaxRepeats = sessionState.stopMessageMaxRepeats;
315
520
  routingState.stopMessageUsed = sessionState.stopMessageUsed;
316
521
  routingState.stopMessageUpdatedAt = sessionState.stopMessageUpdatedAt;
317
522
  routingState.stopMessageLastUsedAt = sessionState.stopMessageLastUsedAt;
318
523
  routingState.stopMessageStage = sessionState.stopMessageStage;
524
+ routingState.stopMessageStageMode = sessionState.stopMessageStageMode;
319
525
  routingState.stopMessageObservationHash = sessionState.stopMessageObservationHash;
320
526
  routingState.stopMessageObservationStableCount = sessionState.stopMessageObservationStableCount;
321
527
  routingState.stopMessageBdWorkState = sessionState.stopMessageBdWorkState;
322
528
  }
529
+ if (typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()) {
530
+ routingState.preCommandScriptPath = sessionState.preCommandScriptPath;
531
+ routingState.preCommandSource = sessionState.preCommandSource;
532
+ routingState.preCommandUpdatedAt = sessionState.preCommandUpdatedAt;
533
+ }
323
534
  }
324
535
  // Guardrail: if a session is restricted to providers that do not exist in any routing pools,
325
536
  // we must not hard-fail the request loop. Auto-clear the allowlist and fall back to normal routing.
@@ -537,12 +748,16 @@ export class VirtualRouterEngine {
537
748
  }
538
749
  }
539
750
  const baseTarget = this.providerRegistry.buildTarget(selection.providerKey);
540
- const forceVision = this.routeHasForceFlag('vision');
751
+ const forceVision = selection.routeUsed === 'vision' && this.routeHasForceFlag('vision');
541
752
  const target = {
542
753
  ...baseTarget,
543
754
  ...(this.webSearchForce ? { forceWebSearch: true } : {}),
544
755
  ...(forceVision ? { forceVision: true } : {})
545
756
  };
757
+ const instructionProcessMode = this.resolveInstructionProcessModeForSelection(selection.providerKey, routingState);
758
+ if (instructionProcessMode) {
759
+ target.processMode = instructionProcessMode;
760
+ }
546
761
  recordAntigravitySessionLease({
547
762
  metadata: features.metadata,
548
763
  providerKey: selection.providerKey,
@@ -605,6 +820,20 @@ export class VirtualRouterEngine {
605
820
  };
606
821
  }
607
822
  getStopMessageState(metadata) {
823
+ const hasArmedStopState = (candidate) => {
824
+ if (!candidate) {
825
+ return false;
826
+ }
827
+ const text = typeof candidate.stopMessageText === 'string' ? candidate.stopMessageText.trim() : '';
828
+ const maxRepeats = typeof candidate.stopMessageMaxRepeats === 'number' && Number.isFinite(candidate.stopMessageMaxRepeats)
829
+ ? Math.max(1, Math.floor(candidate.stopMessageMaxRepeats))
830
+ : 0;
831
+ const mode = typeof candidate.stopMessageStageMode === 'string'
832
+ ? candidate.stopMessageStageMode.trim().toLowerCase()
833
+ : '';
834
+ const allowModeOnly = !text && maxRepeats > 0 && (mode === 'on' || mode === 'auto');
835
+ return (Boolean(text) || allowModeOnly) && maxRepeats > 0;
836
+ };
608
837
  const sessionScope = this.resolveSessionScope(metadata);
609
838
  const sessionState = sessionScope
610
839
  ? getRoutingInstructionState(sessionScope, this.routingInstructionState, this.routingStateStore)
@@ -613,9 +842,11 @@ export class VirtualRouterEngine {
613
842
  const stickyState = stickyKey
614
843
  ? getRoutingInstructionState(stickyKey, this.routingInstructionState, this.routingStateStore)
615
844
  : null;
616
- const effectiveState = sessionState && typeof sessionState.stopMessageText === 'string' && sessionState.stopMessageText.trim()
845
+ const effectiveState = hasArmedStopState(sessionState)
617
846
  ? sessionState
618
- : stickyState;
847
+ : hasArmedStopState(stickyState)
848
+ ? stickyState
849
+ : null;
619
850
  if (!effectiveState) {
620
851
  return null;
621
852
  }
@@ -624,11 +855,18 @@ export class VirtualRouterEngine {
624
855
  Number.isFinite(effectiveState.stopMessageMaxRepeats)
625
856
  ? Math.max(1, Math.floor(effectiveState.stopMessageMaxRepeats))
626
857
  : 0;
627
- if (!text || maxRepeats <= 0) {
858
+ const stageModeRaw = typeof effectiveState.stopMessageStageMode === 'string'
859
+ ? effectiveState.stopMessageStageMode.trim().toLowerCase()
860
+ : '';
861
+ const stageModeNormalized = stageModeRaw === 'on' || stageModeRaw === 'off' || stageModeRaw === 'auto'
862
+ ? stageModeRaw
863
+ : undefined;
864
+ const allowModeOnly = !text && maxRepeats > 0 && (stageModeNormalized === 'on' || stageModeNormalized === 'auto');
865
+ if ((!text && !allowModeOnly) || maxRepeats <= 0) {
628
866
  return null;
629
867
  }
630
868
  return {
631
- stopMessageText: text,
869
+ ...(text ? { stopMessageText: text } : {}),
632
870
  stopMessageMaxRepeats: maxRepeats,
633
871
  ...(typeof effectiveState.stopMessageSource === 'string' && effectiveState.stopMessageSource.trim()
634
872
  ? { stopMessageSource: effectiveState.stopMessageSource.trim() }
@@ -647,6 +885,7 @@ export class VirtualRouterEngine {
647
885
  ...(typeof effectiveState.stopMessageStage === 'string' && effectiveState.stopMessageStage.trim()
648
886
  ? { stopMessageStage: effectiveState.stopMessageStage.trim() }
649
887
  : {}),
888
+ ...(stageModeNormalized ? { stopMessageStageMode: stageModeNormalized } : {}),
650
889
  ...(typeof effectiveState.stopMessageObservationHash === 'string' && effectiveState.stopMessageObservationHash.trim()
651
890
  ? { stopMessageObservationHash: effectiveState.stopMessageObservationHash.trim() }
652
891
  : {}),
@@ -659,6 +898,35 @@ export class VirtualRouterEngine {
659
898
  : {})
660
899
  };
661
900
  }
901
+ getPreCommandState(metadata) {
902
+ const sessionScope = this.resolveSessionScope(metadata);
903
+ const sessionState = sessionScope
904
+ ? getRoutingInstructionState(sessionScope, this.routingInstructionState, this.routingStateStore)
905
+ : null;
906
+ const stickyKey = this.resolveStickyKey(metadata);
907
+ const stickyState = stickyKey
908
+ ? getRoutingInstructionState(stickyKey, this.routingInstructionState, this.routingStateStore)
909
+ : null;
910
+ const effectiveState = sessionState && typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()
911
+ ? sessionState
912
+ : stickyState;
913
+ if (!effectiveState) {
914
+ return null;
915
+ }
916
+ const scriptPath = typeof effectiveState.preCommandScriptPath === 'string' ? effectiveState.preCommandScriptPath.trim() : '';
917
+ if (!scriptPath) {
918
+ return null;
919
+ }
920
+ return {
921
+ preCommandScriptPath: scriptPath,
922
+ ...(typeof effectiveState.preCommandSource === 'string' && effectiveState.preCommandSource.trim()
923
+ ? { preCommandSource: effectiveState.preCommandSource.trim() }
924
+ : {}),
925
+ ...(typeof effectiveState.preCommandUpdatedAt === 'number' && Number.isFinite(effectiveState.preCommandUpdatedAt)
926
+ ? { preCommandUpdatedAt: effectiveState.preCommandUpdatedAt }
927
+ : {})
928
+ };
929
+ }
662
930
  handleProviderFailure(event) {
663
931
  handleProviderFailureImpl(event, this.healthManager, this.providerHealthConfig(), (key, ttl) => this.markProviderCooldown(key, ttl));
664
932
  }
@@ -820,6 +1088,27 @@ export class VirtualRouterEngine {
820
1088
  resolveSessionScope(metadata) {
821
1089
  return resolveSessionScopeImpl(metadata);
822
1090
  }
1091
+ resolveInstructionProcessModeForSelection(providerKey, routingState) {
1092
+ const candidates = [
1093
+ routingState.forcedTarget,
1094
+ routingState.stickyTarget,
1095
+ routingState.preferTarget
1096
+ ];
1097
+ for (const candidate of candidates) {
1098
+ const processMode = candidate?.processMode;
1099
+ if (!processMode) {
1100
+ continue;
1101
+ }
1102
+ const resolved = this.resolveInstructionTarget(candidate);
1103
+ if (!resolved) {
1104
+ continue;
1105
+ }
1106
+ if (resolved.keys.includes(providerKey)) {
1107
+ return processMode;
1108
+ }
1109
+ }
1110
+ return undefined;
1111
+ }
823
1112
  resolveInstructionTarget(target) {
824
1113
  if (!target || !target.provider) {
825
1114
  return null;
@@ -1067,6 +1356,8 @@ export class VirtualRouterEngine {
1067
1356
  }
1068
1357
  buildRouteCandidates(requestedRoute, classificationCandidates, features) {
1069
1358
  const forceVision = this.routeHasForceFlag('vision');
1359
+ const hasMultimodalTargets = this.routeHasTargets(this.routing.multimodal);
1360
+ const hasVisionTargets = this.routeHasTargets(this.routing.vision);
1070
1361
  const normalized = this.normalizeRouteAlias(requestedRoute || DEFAULT_ROUTE);
1071
1362
  const baseList = [];
1072
1363
  if (classificationCandidates && classificationCandidates.length) {
@@ -1077,35 +1368,31 @@ export class VirtualRouterEngine {
1077
1368
  else if (normalized) {
1078
1369
  baseList.push(normalized);
1079
1370
  }
1080
- // 当检测到当前请求包含图片时,确保 default/thinking 也参与候选集,
1081
- // 以便优先尝试内建多模态模型(Responses/Gemini),再回落到 vision 路由池。
1082
- if (features.hasImageAttachment && !forceVision) {
1083
- const visionAwareRoutes = [DEFAULT_ROUTE, 'thinking'];
1084
- for (const routeName of visionAwareRoutes) {
1085
- if (this.routeHasTargets(this.routing[routeName]) && !baseList.includes(routeName)) {
1086
- baseList.push(routeName);
1087
- }
1088
- }
1089
- }
1090
1371
  if (features.hasImageAttachment) {
1091
- const allRouteNames = Object.keys(this.routing);
1092
- for (const routeName of allRouteNames) {
1093
- if (!this.routeHasTargets(this.routing[routeName])) {
1094
- continue;
1372
+ if (hasMultimodalTargets) {
1373
+ if (!baseList.includes('multimodal')) {
1374
+ baseList.unshift('multimodal');
1095
1375
  }
1096
- if (!this.routeSupportsModel(routeName, 'kimi-k2.5')) {
1097
- continue;
1376
+ }
1377
+ else if (hasVisionTargets) {
1378
+ if (!baseList.includes('vision')) {
1379
+ baseList.unshift('vision');
1098
1380
  }
1099
- if (!baseList.includes(routeName)) {
1100
- baseList.push(routeName);
1381
+ }
1382
+ if (!forceVision && hasMultimodalTargets) {
1383
+ const visionAwareRoutes = [DEFAULT_ROUTE, 'thinking'];
1384
+ for (const routeName of visionAwareRoutes) {
1385
+ if (this.routeHasTargets(this.routing[routeName]) && !baseList.includes(routeName)) {
1386
+ baseList.push(routeName);
1387
+ }
1101
1388
  }
1102
1389
  }
1103
1390
  }
1104
1391
  let ordered = this.sortByPriority(baseList);
1105
- if (features.hasImageAttachment && !forceVision) {
1392
+ if (features.hasImageAttachment && !forceVision && hasMultimodalTargets) {
1106
1393
  ordered = this.reorderForInlineVision(ordered);
1107
1394
  }
1108
- if (features.hasImageAttachment) {
1395
+ if (features.hasImageAttachment && hasMultimodalTargets) {
1109
1396
  ordered = this.reorderForPreferredModel(ordered, 'kimi-k2.5');
1110
1397
  }
1111
1398
  const deduped = [];
@@ -1,6 +1,6 @@
1
1
  import { detectExtendedThinkingKeyword, detectImageAttachment, detectKeyword, extractMessageText, getLatestMessageRole, getLatestUserMessage } from './message-utils.js';
2
2
  import { extractAntigravityGeminiSessionId } from '../../conversion/compat/antigravity-session-signature.js';
3
- import { detectCodingTool, detectLastAssistantToolCategory, detectVisionTool, detectWebTool, extractMeaningfulDeclaredToolNames } from './tool-signals.js';
3
+ import { detectCodingTool, detectLastAssistantToolCategory, detectVisionTool, detectWebSearchToolDeclared, detectWebTool, extractMeaningfulDeclaredToolNames } from './tool-signals.js';
4
4
  import { computeRequestTokens } from './token-estimator.js';
5
5
  const THINKING_KEYWORDS = ['let me think', 'chain of thought', 'cot', 'reason step', 'deliberate'];
6
6
  export function buildRoutingFeatures(request, metadata) {
@@ -59,6 +59,7 @@ export function buildRoutingFeatures(request, metadata) {
59
59
  hasVisionTool,
60
60
  hasImageAttachment,
61
61
  hasWebTool,
62
+ hasWebSearchToolDeclared: detectWebSearchToolDeclared(request),
62
63
  hasCodingTool,
63
64
  hasThinkingKeyword,
64
65
  estimatedTokens,
@@ -0,0 +1,2 @@
1
+ export declare function resolvePreCommandScriptPath(raw: string): string;
2
+ export declare function isPreCommandScriptPathAllowed(rawPath: string): boolean;