@jsonstudio/llms 0.6.1739 → 0.6.1890

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 +359 -39
  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 +15 -1
  40. package/dist/router/virtual-router/routing-instructions.js +110 -151
  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 +5 -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 +19 -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 +386 -257
  85. package/dist/servertool/handlers/stop-message-stage-policy.d.ts +43 -0
  86. package/dist/servertool/handlers/stop-message-stage-policy.js +684 -0
  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,34 +232,68 @@ 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,
172
240
  stopMessageMaxRepeats: sessionState.stopMessageMaxRepeats,
173
241
  stopMessageUsed: sessionState.stopMessageUsed,
174
242
  stopMessageUpdatedAt: sessionState.stopMessageUpdatedAt,
175
- stopMessageLastUsedAt: sessionState.stopMessageLastUsedAt
243
+ stopMessageLastUsedAt: sessionState.stopMessageLastUsedAt,
244
+ stopMessageStage: sessionState.stopMessageStage,
245
+ stopMessageStageMode: sessionState.stopMessageStageMode,
246
+ stopMessageObservationHash: sessionState.stopMessageObservationHash,
247
+ stopMessageObservationStableCount: sessionState.stopMessageObservationStableCount,
248
+ stopMessageBdWorkState: sessionState.stopMessageBdWorkState
249
+ };
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
176
257
  };
177
258
  }
178
259
  }
179
260
  const parsedInstructions = parseRoutingInstructions(request.messages);
261
+ const latestUserHasMarker = hasLatestUserRoutingInstructionMarker(request.messages);
180
262
  let instructions = parsedInstructions;
181
263
  if (stopMessageScope && parsedInstructions.length > 0) {
182
264
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
183
- const hasStopMessageClear = parsedInstructions.some((entry) => entry.type === 'stopMessageClear');
184
- 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');
185
283
  if (!hasStopMessageClear && stopMessageSets.length > 0) {
186
284
  const sessionText = typeof sessionState.stopMessageText === 'string' ? sessionState.stopMessageText.trim() : '';
187
285
  const sessionMax = typeof sessionState.stopMessageMaxRepeats === 'number' && Number.isFinite(sessionState.stopMessageMaxRepeats)
188
286
  ? Math.floor(sessionState.stopMessageMaxRepeats)
189
287
  : undefined;
288
+ const sessionMode = normalizeStopMessageStageMode(sessionState.stopMessageStageMode);
190
289
  const allSame = stopMessageSets.every((entry) => {
191
290
  const entryText = typeof entry.stopMessageText === 'string' ? entry.stopMessageText.trim() : '';
192
291
  const entryMax = typeof entry.stopMessageMaxRepeats === 'number' && Number.isFinite(entry.stopMessageMaxRepeats)
193
292
  ? Math.floor(entry.stopMessageMaxRepeats)
194
293
  : undefined;
195
- 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;
196
297
  });
197
298
  const used = typeof sessionState.stopMessageUsed === 'number' && Number.isFinite(sessionState.stopMessageUsed)
198
299
  ? Math.max(0, Math.floor(sessionState.stopMessageUsed))
@@ -201,19 +302,36 @@ export class VirtualRouterEngine {
201
302
  Number.isFinite(sessionState.stopMessageLastUsedAt);
202
303
  const alreadyArmed = used === 0 && !hasLastUsedAt;
203
304
  if (allSame && alreadyArmed) {
204
- instructions = parsedInstructions.filter((entry) => entry.type !== 'stopMessageSet');
305
+ instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet');
205
306
  }
206
307
  }
207
308
  }
208
309
  // stopMessage must be session-scoped: require explicit sessionId in metadata.
209
310
  // This prevents global/default persistence and ensures the trigger matches the setting sessionId.
210
311
  if (parsedInstructions.length > 0) {
211
- const hasStopMessageInstruction = parsedInstructions.some((entry) => entry.type === 'stopMessageSet' || entry.type === 'stopMessageClear');
212
- if (hasStopMessageInstruction && !stopMessageScope) {
213
- 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 });
214
319
  }
215
320
  }
216
- 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)) {
217
335
  request.messages = cleanMessagesFromRoutingInstructions(request.messages);
218
336
  }
219
337
  if (instructions.length > 0) {
@@ -226,8 +344,9 @@ export class VirtualRouterEngine {
226
344
  // so servertool triggers always match the setting sessionId.
227
345
  if (stopMessageScope) {
228
346
  const hasStopMessageSet = instructions.some((entry) => entry.type === 'stopMessageSet');
347
+ const hasStopMessageMode = instructions.some((entry) => entry.type === 'stopMessageMode');
229
348
  const hasStopMessageClear = instructions.some((entry) => entry.type === 'stopMessageClear');
230
- if (hasStopMessageSet || hasStopMessageClear) {
349
+ if (hasStopMessageSet || hasStopMessageMode || hasStopMessageClear) {
231
350
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
232
351
  let nextSessionState = {
233
352
  ...sessionState
@@ -241,6 +360,11 @@ export class VirtualRouterEngine {
241
360
  nextSessionState.stopMessageUpdatedAt = clearedAt;
242
361
  nextSessionState.stopMessageLastUsedAt = clearedAt;
243
362
  nextSessionState.stopMessageSource = undefined;
363
+ nextSessionState.stopMessageStage = undefined;
364
+ nextSessionState.stopMessageStageMode = undefined;
365
+ nextSessionState.stopMessageObservationHash = undefined;
366
+ nextSessionState.stopMessageObservationStableCount = undefined;
367
+ nextSessionState.stopMessageBdWorkState = undefined;
244
368
  shouldPersistSessionState = true;
245
369
  }
246
370
  else if (hasStopMessageSet) {
@@ -251,7 +375,10 @@ export class VirtualRouterEngine {
251
375
  const sameMax = typeof sessionState.stopMessageMaxRepeats === 'number' &&
252
376
  typeof maxRepeats === 'number' &&
253
377
  Math.floor(sessionState.stopMessageMaxRepeats) === Math.floor(maxRepeats);
254
- 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;
255
382
  const used = typeof sessionState.stopMessageUsed === 'number' && Number.isFinite(sessionState.stopMessageUsed)
256
383
  ? Math.max(0, Math.floor(sessionState.stopMessageUsed))
257
384
  : 0;
@@ -261,16 +388,62 @@ export class VirtualRouterEngine {
261
388
  nextSessionState.stopMessageText = text || undefined;
262
389
  nextSessionState.stopMessageMaxRepeats = maxRepeats;
263
390
  nextSessionState.stopMessageSource = 'explicit';
391
+ nextSessionState.stopMessageStageMode = nextMode;
264
392
  if (shouldRearm) {
265
393
  nextSessionState.stopMessageUsed = 0;
266
394
  nextSessionState.stopMessageUpdatedAt =
267
395
  typeof routingState.stopMessageUpdatedAt === 'number'
268
396
  ? routingState.stopMessageUpdatedAt
269
397
  : Date.now();
398
+ nextSessionState.stopMessageStage = undefined;
399
+ nextSessionState.stopMessageObservationHash = undefined;
400
+ nextSessionState.stopMessageObservationStableCount = 0;
401
+ nextSessionState.stopMessageBdWorkState = undefined;
270
402
  nextSessionState.stopMessageLastUsedAt = undefined;
271
403
  shouldPersistSessionState = true;
272
404
  }
273
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
+ }
274
447
  if (shouldPersistSessionState) {
275
448
  this.routingInstructionState.set(stopMessageScope, nextSessionState);
276
449
  persistRoutingInstructionState(stopMessageScope, nextSessionState, this.routingStateStore);
@@ -280,25 +453,83 @@ export class VirtualRouterEngine {
280
453
  }
281
454
  // 日志展示使用 session scope 的 stopMessage 状态,避免每次解析重复刷新时间/次数。
282
455
  if (typeof nextSessionState.stopMessageText === 'string' ||
283
- typeof nextSessionState.stopMessageMaxRepeats === 'number') {
456
+ typeof nextSessionState.stopMessageMaxRepeats === 'number' ||
457
+ typeof nextSessionState.stopMessageStageMode === 'string') {
284
458
  routingState.stopMessageText = nextSessionState.stopMessageText;
285
459
  routingState.stopMessageMaxRepeats = nextSessionState.stopMessageMaxRepeats;
286
460
  routingState.stopMessageUsed = nextSessionState.stopMessageUsed;
287
461
  routingState.stopMessageUpdatedAt = nextSessionState.stopMessageUpdatedAt;
288
462
  routingState.stopMessageLastUsedAt = nextSessionState.stopMessageLastUsedAt;
463
+ routingState.stopMessageStage = nextSessionState.stopMessageStage;
464
+ routingState.stopMessageStageMode = nextSessionState.stopMessageStageMode;
465
+ routingState.stopMessageObservationHash = nextSessionState.stopMessageObservationHash;
466
+ routingState.stopMessageObservationStableCount = nextSessionState.stopMessageObservationStableCount;
467
+ routingState.stopMessageBdWorkState = nextSessionState.stopMessageBdWorkState;
289
468
  }
290
469
  }
291
470
  }
292
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
+ }
293
513
  if (instructions.length === 0 && stopMessageScope) {
294
514
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
295
515
  if (typeof sessionState.stopMessageText === 'string' ||
296
- typeof sessionState.stopMessageMaxRepeats === 'number') {
516
+ typeof sessionState.stopMessageMaxRepeats === 'number' ||
517
+ typeof sessionState.stopMessageStageMode === 'string') {
297
518
  routingState.stopMessageText = sessionState.stopMessageText;
298
519
  routingState.stopMessageMaxRepeats = sessionState.stopMessageMaxRepeats;
299
520
  routingState.stopMessageUsed = sessionState.stopMessageUsed;
300
521
  routingState.stopMessageUpdatedAt = sessionState.stopMessageUpdatedAt;
301
522
  routingState.stopMessageLastUsedAt = sessionState.stopMessageLastUsedAt;
523
+ routingState.stopMessageStage = sessionState.stopMessageStage;
524
+ routingState.stopMessageStageMode = sessionState.stopMessageStageMode;
525
+ routingState.stopMessageObservationHash = sessionState.stopMessageObservationHash;
526
+ routingState.stopMessageObservationStableCount = sessionState.stopMessageObservationStableCount;
527
+ routingState.stopMessageBdWorkState = sessionState.stopMessageBdWorkState;
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;
302
533
  }
303
534
  }
304
535
  // Guardrail: if a session is restricted to providers that do not exist in any routing pools,
@@ -517,12 +748,16 @@ export class VirtualRouterEngine {
517
748
  }
518
749
  }
519
750
  const baseTarget = this.providerRegistry.buildTarget(selection.providerKey);
520
- const forceVision = this.routeHasForceFlag('vision');
751
+ const forceVision = selection.routeUsed === 'vision' && this.routeHasForceFlag('vision');
521
752
  const target = {
522
753
  ...baseTarget,
523
754
  ...(this.webSearchForce ? { forceWebSearch: true } : {}),
524
755
  ...(forceVision ? { forceVision: true } : {})
525
756
  };
757
+ const instructionProcessMode = this.resolveInstructionProcessModeForSelection(selection.providerKey, routingState);
758
+ if (instructionProcessMode) {
759
+ target.processMode = instructionProcessMode;
760
+ }
526
761
  recordAntigravitySessionLease({
527
762
  metadata: features.metadata,
528
763
  providerKey: selection.providerKey,
@@ -585,6 +820,20 @@ export class VirtualRouterEngine {
585
820
  };
586
821
  }
587
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
+ };
588
837
  const sessionScope = this.resolveSessionScope(metadata);
589
838
  const sessionState = sessionScope
590
839
  ? getRoutingInstructionState(sessionScope, this.routingInstructionState, this.routingStateStore)
@@ -593,9 +842,11 @@ export class VirtualRouterEngine {
593
842
  const stickyState = stickyKey
594
843
  ? getRoutingInstructionState(stickyKey, this.routingInstructionState, this.routingStateStore)
595
844
  : null;
596
- const effectiveState = sessionState && typeof sessionState.stopMessageText === 'string' && sessionState.stopMessageText.trim()
845
+ const effectiveState = hasArmedStopState(sessionState)
597
846
  ? sessionState
598
- : stickyState;
847
+ : hasArmedStopState(stickyState)
848
+ ? stickyState
849
+ : null;
599
850
  if (!effectiveState) {
600
851
  return null;
601
852
  }
@@ -604,11 +855,18 @@ export class VirtualRouterEngine {
604
855
  Number.isFinite(effectiveState.stopMessageMaxRepeats)
605
856
  ? Math.max(1, Math.floor(effectiveState.stopMessageMaxRepeats))
606
857
  : 0;
607
- 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) {
608
866
  return null;
609
867
  }
610
868
  return {
611
- stopMessageText: text,
869
+ ...(text ? { stopMessageText: text } : {}),
612
870
  stopMessageMaxRepeats: maxRepeats,
613
871
  ...(typeof effectiveState.stopMessageSource === 'string' && effectiveState.stopMessageSource.trim()
614
872
  ? { stopMessageSource: effectiveState.stopMessageSource.trim() }
@@ -623,6 +881,49 @@ export class VirtualRouterEngine {
623
881
  ...(typeof effectiveState.stopMessageLastUsedAt === 'number' &&
624
882
  Number.isFinite(effectiveState.stopMessageLastUsedAt)
625
883
  ? { stopMessageLastUsedAt: effectiveState.stopMessageLastUsedAt }
884
+ : {}),
885
+ ...(typeof effectiveState.stopMessageStage === 'string' && effectiveState.stopMessageStage.trim()
886
+ ? { stopMessageStage: effectiveState.stopMessageStage.trim() }
887
+ : {}),
888
+ ...(stageModeNormalized ? { stopMessageStageMode: stageModeNormalized } : {}),
889
+ ...(typeof effectiveState.stopMessageObservationHash === 'string' && effectiveState.stopMessageObservationHash.trim()
890
+ ? { stopMessageObservationHash: effectiveState.stopMessageObservationHash.trim() }
891
+ : {}),
892
+ ...(typeof effectiveState.stopMessageObservationStableCount === 'number' &&
893
+ Number.isFinite(effectiveState.stopMessageObservationStableCount)
894
+ ? { stopMessageObservationStableCount: Math.max(0, Math.floor(effectiveState.stopMessageObservationStableCount)) }
895
+ : {}),
896
+ ...(typeof effectiveState.stopMessageBdWorkState === 'string' && effectiveState.stopMessageBdWorkState.trim()
897
+ ? { stopMessageBdWorkState: effectiveState.stopMessageBdWorkState.trim() }
898
+ : {})
899
+ };
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 }
626
927
  : {})
627
928
  };
628
929
  }
@@ -787,6 +1088,27 @@ export class VirtualRouterEngine {
787
1088
  resolveSessionScope(metadata) {
788
1089
  return resolveSessionScopeImpl(metadata);
789
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
+ }
790
1112
  resolveInstructionTarget(target) {
791
1113
  if (!target || !target.provider) {
792
1114
  return null;
@@ -1034,6 +1356,8 @@ export class VirtualRouterEngine {
1034
1356
  }
1035
1357
  buildRouteCandidates(requestedRoute, classificationCandidates, features) {
1036
1358
  const forceVision = this.routeHasForceFlag('vision');
1359
+ const hasMultimodalTargets = this.routeHasTargets(this.routing.multimodal);
1360
+ const hasVisionTargets = this.routeHasTargets(this.routing.vision);
1037
1361
  const normalized = this.normalizeRouteAlias(requestedRoute || DEFAULT_ROUTE);
1038
1362
  const baseList = [];
1039
1363
  if (classificationCandidates && classificationCandidates.length) {
@@ -1044,35 +1368,31 @@ export class VirtualRouterEngine {
1044
1368
  else if (normalized) {
1045
1369
  baseList.push(normalized);
1046
1370
  }
1047
- // 当检测到当前请求包含图片时,确保 default/thinking 也参与候选集,
1048
- // 以便优先尝试内建多模态模型(Responses/Gemini),再回落到 vision 路由池。
1049
- if (features.hasImageAttachment && !forceVision) {
1050
- const visionAwareRoutes = [DEFAULT_ROUTE, 'thinking'];
1051
- for (const routeName of visionAwareRoutes) {
1052
- if (this.routeHasTargets(this.routing[routeName]) && !baseList.includes(routeName)) {
1053
- baseList.push(routeName);
1054
- }
1055
- }
1056
- }
1057
1371
  if (features.hasImageAttachment) {
1058
- const allRouteNames = Object.keys(this.routing);
1059
- for (const routeName of allRouteNames) {
1060
- if (!this.routeHasTargets(this.routing[routeName])) {
1061
- continue;
1372
+ if (hasMultimodalTargets) {
1373
+ if (!baseList.includes('multimodal')) {
1374
+ baseList.unshift('multimodal');
1062
1375
  }
1063
- if (!this.routeSupportsModel(routeName, 'kimi-k2.5')) {
1064
- continue;
1376
+ }
1377
+ else if (hasVisionTargets) {
1378
+ if (!baseList.includes('vision')) {
1379
+ baseList.unshift('vision');
1065
1380
  }
1066
- if (!baseList.includes(routeName)) {
1067
- 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
+ }
1068
1388
  }
1069
1389
  }
1070
1390
  }
1071
1391
  let ordered = this.sortByPriority(baseList);
1072
- if (features.hasImageAttachment && !forceVision) {
1392
+ if (features.hasImageAttachment && !forceVision && hasMultimodalTargets) {
1073
1393
  ordered = this.reorderForInlineVision(ordered);
1074
1394
  }
1075
- if (features.hasImageAttachment) {
1395
+ if (features.hasImageAttachment && hasMultimodalTargets) {
1076
1396
  ordered = this.reorderForPreferredModel(ordered, 'kimi-k2.5');
1077
1397
  }
1078
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;