@jsonstudio/llms 0.6.2979 → 0.6.3238
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.
- package/dist/conversion/args-mapping.js +8 -0
- package/dist/conversion/{shared/bridge-actions.js → bridge-actions.js} +2 -1
- package/dist/conversion/{shared/bridge-id-utils.js → bridge-id-utils.js} +1 -1
- package/dist/conversion/{shared/bridge-instructions.js → bridge-instructions.js} +1 -1
- package/dist/conversion/{shared/bridge-message-utils.d.ts → bridge-message-utils.d.ts} +1 -1
- package/dist/conversion/{shared/bridge-message-utils.js → bridge-message-utils.js} +5 -149
- package/dist/conversion/{shared/bridge-metadata.js → bridge-metadata.js} +1 -1
- package/dist/conversion/{shared/bridge-policies.js → bridge-policies.js} +1 -1
- package/dist/conversion/codecs/gemini-openai-codec.js +27 -8
- package/dist/conversion/codecs/responses-openai-codec.js +1 -1
- package/dist/conversion/{shared/compaction-detect.d.ts → compaction-detect.d.ts} +1 -1
- package/dist/conversion/compaction-detect.js +4 -0
- package/dist/conversion/compat/actions/apply-patch-fixer.js +2 -2
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +0 -1
- package/dist/conversion/compat/actions/deepseek-web-response.js +15 -405
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +1 -1
- package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +1 -1
- package/dist/conversion/compat/actions/qwen-transform.js +74 -2
- package/dist/conversion/compat/actions/snapshot.js +1 -1
- package/dist/conversion/compat/antigravity-session-signature.js +36 -0
- package/dist/conversion/compat/profiles/chat-deepseek-web.json +0 -22
- package/dist/conversion/compat/profiles/chat-glm.json +251 -72
- package/dist/conversion/compat/profiles/chat-iflow.json +174 -39
- package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -14
- package/dist/conversion/hub/operation-table/operation-table-runner.js +2 -2
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +1 -1
- package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +404 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +5 -381
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +2 -2
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +2 -8
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +50 -3
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +1 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +62 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +3 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/chat-process-semantics-bridge.d.ts +1 -1
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +42 -29
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +12 -0
- package/dist/conversion/hub/policy/protocol-spec.js +1 -1
- package/dist/conversion/hub/process/chat-process-clock-reminders.js +1 -1
- package/dist/conversion/hub/process/chat-process-clock-tools.js +1 -1
- package/dist/conversion/hub/process/chat-process-continue-execution.js +1 -1
- package/dist/conversion/hub/process/chat-process-servertool-orchestration.js +1 -1
- package/dist/conversion/hub/process/chat-process-web-search.js +1 -1
- package/dist/conversion/hub/response/provider-response.js +14 -5
- package/dist/conversion/hub/response/response-mappers.js +23 -1
- package/dist/conversion/hub/response/response-runtime.js +28 -5
- package/dist/conversion/hub/snapshot-recorder.js +3 -92
- package/dist/conversion/hub/tool-governance/engine.d.ts +8 -0
- package/dist/conversion/hub/tool-governance/engine.js +40 -193
- package/dist/conversion/hub/tool-governance/rules.js +73 -69
- package/dist/conversion/hub/tool-surface/tool-surface-engine.js +1 -1
- package/dist/conversion/index.d.ts +1 -2
- package/dist/conversion/index.js +1 -2
- package/dist/conversion/{shared/jsonish.js → jsonish.js} +1 -1
- package/dist/conversion/{shared/mcp-injection.js → mcp-injection.js} +1 -1
- package/dist/conversion/media.js +4 -0
- package/dist/conversion/{shared/metadata-passthrough.d.ts → metadata-passthrough.d.ts} +1 -1
- package/dist/conversion/{shared/metadata-passthrough.js → metadata-passthrough.js} +2 -2
- package/dist/conversion/payload-budget.js +47 -0
- package/dist/conversion/protocol-field-allowlists.d.ts +7 -0
- package/dist/conversion/protocol-field-allowlists.js +9 -0
- package/dist/conversion/{shared/protocol-state.d.ts → protocol-state.d.ts} +2 -2
- package/dist/conversion/{shared/protocol-state.js → protocol-state.js} +2 -2
- package/dist/conversion/{shared/errors.d.ts → provider-protocol-error.d.ts} +0 -3
- package/dist/conversion/provider-protocol-error.js +25 -0
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +8 -5
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -1
- package/dist/conversion/responses/responses-openai-bridge.d.ts +1 -1
- package/dist/conversion/responses/responses-openai-bridge.js +43 -10
- package/dist/conversion/{shared/runtime-metadata.d.ts → runtime-metadata.d.ts} +1 -1
- package/dist/conversion/{shared/runtime-metadata.js → runtime-metadata.js} +2 -2
- package/dist/conversion/shared/anthropic-message-utils.js +19 -8
- package/dist/conversion/shared/chat-request-filters.d.ts +3 -4
- package/dist/conversion/shared/chat-request-filters.js +22 -78
- package/dist/conversion/shared/gemini-tool-utils.d.ts +1 -1
- package/dist/conversion/shared/openai-finalizer.js +1 -0
- package/dist/conversion/shared/openai-message-normalize.js +2 -2
- package/dist/conversion/shared/reasoning-normalizer.js +6 -0
- package/dist/conversion/shared/reasoning-utils.js +5 -2
- package/dist/conversion/shared/responses-conversation-store.js +1 -1
- package/dist/conversion/shared/responses-output-builder.js +55 -11
- package/dist/conversion/shared/responses-reasoning-registry.d.ts +14 -2
- package/dist/conversion/shared/responses-reasoning-registry.js +34 -6
- package/dist/conversion/shared/responses-response-utils.js +99 -9
- package/dist/conversion/shared/responses-tool-utils.js +1 -1
- package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +1 -1
- package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -2
- package/dist/conversion/shared/text-markup-normalizer.js +1 -1
- package/dist/conversion/shared/tool-filter-pipeline.js +1 -1
- package/dist/conversion/shared/tool-governor.js +3 -3
- package/dist/conversion/shared/tool-mapping.d.ts +1 -1
- package/dist/conversion/{shared/snapshot-utils.d.ts → snapshot-utils.d.ts} +11 -0
- package/dist/conversion/{shared/snapshot-utils.js → snapshot-utils.js} +14 -23
- package/dist/conversion/types/text-markup-normalizer.d.ts +13 -0
- package/dist/conversion/types/text-markup-normalizer.js +1 -0
- package/dist/filters/special/request-tools-normalize.js +1 -1
- package/dist/filters/special/response-tool-text-canonicalize.js +2 -2
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/quota/quota-manager.js +31 -59
- package/dist/quota/quota-state.js +14 -7
- package/dist/router/virtual-router/bootstrap/profile-builder.d.ts +1 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.js +13 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +2 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +4 -1
- package/dist/router/virtual-router/bootstrap/streaming-helpers.d.ts +7 -0
- package/dist/router/virtual-router/bootstrap/streaming-helpers.js +44 -0
- package/dist/router/virtual-router/bootstrap.js +2 -0
- package/dist/router/virtual-router/engine/routing-state/store.d.ts +1 -2
- package/dist/router/virtual-router/engine/routing-state/store.js +2 -2
- package/dist/router/virtual-router/engine-legacy/config.d.ts +11 -0
- package/dist/router/virtual-router/engine-legacy/config.js +108 -0
- package/dist/router/virtual-router/engine-legacy/direct-model.d.ts +10 -0
- package/dist/router/virtual-router/engine-legacy/direct-model.js +38 -0
- package/dist/router/virtual-router/engine-legacy/health.d.ts +13 -0
- package/dist/router/virtual-router/engine-legacy/health.js +104 -0
- package/dist/router/virtual-router/engine-legacy/helpers.d.ts +16 -0
- package/dist/router/virtual-router/engine-legacy/helpers.js +226 -0
- package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +9 -0
- package/dist/router/virtual-router/engine-legacy/route-finalize.js +84 -0
- package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +17 -0
- package/dist/router/virtual-router/engine-legacy/route-selection.js +205 -0
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +3 -0
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +36 -0
- package/dist/router/virtual-router/engine-legacy/route-state.d.ts +12 -0
- package/dist/router/virtual-router/engine-legacy/route-state.js +386 -0
- package/dist/router/virtual-router/engine-legacy/route-utils.d.ts +19 -0
- package/dist/router/virtual-router/engine-legacy/route-utils.js +212 -0
- package/dist/router/virtual-router/engine-legacy/routing.d.ts +8 -0
- package/dist/router/virtual-router/engine-legacy/routing.js +8 -0
- package/dist/router/virtual-router/engine-legacy/selection-core.d.ts +28 -0
- package/dist/router/virtual-router/engine-legacy/selection-core.js +112 -0
- package/dist/router/virtual-router/engine-legacy/selection-state.d.ts +16 -0
- package/dist/router/virtual-router/engine-legacy/selection-state.js +187 -0
- package/dist/router/virtual-router/engine-legacy/state-accessors.d.ts +21 -0
- package/dist/router/virtual-router/engine-legacy/state-accessors.js +118 -0
- package/dist/router/virtual-router/engine-legacy.d.ts +123 -0
- package/dist/router/virtual-router/engine-legacy.js +194 -0
- package/dist/router/virtual-router/engine-logging.d.ts +2 -0
- package/dist/router/virtual-router/engine-logging.js +7 -2
- package/dist/router/virtual-router/engine-selection/key-parsing.js +0 -3
- package/dist/router/virtual-router/engine-selection/native-chat-request-filter-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-chat-request-filter-semantics.js +54 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-policy-semantics.d.ts +10 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-policy-semantics.js +67 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.d.ts +30 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.js +202 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.d.ts +2 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.js +83 -0
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +43 -2
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +75 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +205 -0
- package/dist/router/virtual-router/engine-selection/native-snapshot-hooks.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/native-snapshot-hooks.js +109 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-engine-proxy.d.ts +16 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-engine-proxy.js +14 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +2 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +86 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.js +100 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +99 -0
- package/dist/router/virtual-router/engine.d.ts +22 -105
- package/dist/router/virtual-router/engine.js +274 -1641
- package/dist/router/virtual-router/load-balancer.d.ts +8 -0
- package/dist/router/virtual-router/load-balancer.js +65 -2
- package/dist/router/virtual-router/provider-registry.js +2 -0
- package/dist/router/virtual-router/routing-instructions/clean.d.ts +3 -0
- package/dist/router/virtual-router/routing-instructions/clean.js +34 -0
- package/dist/router/virtual-router/routing-instructions/parse.d.ts +18 -0
- package/dist/router/virtual-router/routing-instructions/parse.js +377 -0
- package/dist/router/virtual-router/routing-instructions/state.d.ts +4 -0
- package/dist/router/virtual-router/routing-instructions/state.js +245 -0
- package/dist/router/virtual-router/routing-instructions/types.d.ts +70 -0
- package/dist/router/virtual-router/routing-instructions/types.js +2 -0
- package/dist/router/virtual-router/routing-instructions.d.ts +5 -89
- package/dist/router/virtual-router/routing-instructions.js +4 -655
- package/dist/router/virtual-router/sticky-session-store.d.ts +4 -0
- package/dist/router/virtual-router/sticky-session-store.js +19 -81
- package/dist/router/virtual-router/tool-signals.js +21 -3
- package/dist/router/virtual-router/types.d.ts +4 -0
- package/dist/servertool/clock/session-scope.js +32 -1
- package/dist/servertool/engine.js +79 -8
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +1 -1
- package/dist/servertool/handlers/clock-auto.js +1 -1
- package/dist/servertool/handlers/clock.js +1 -1
- package/dist/servertool/handlers/compaction-detect.d.ts +1 -1
- package/dist/servertool/handlers/compaction-detect.js +1 -1
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -1
- package/dist/servertool/handlers/iflow-model-error-retry.js +1 -1
- package/dist/servertool/handlers/recursive-detection-guard.js +1 -1
- package/dist/servertool/handlers/review.js +1 -1
- package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +1 -1
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +1 -1
- package/dist/servertool/handlers/stop-message-auto.js +1 -1
- package/dist/servertool/handlers/vision.js +1 -1
- package/dist/servertool/handlers/web-search.js +1 -1
- package/dist/servertool/reenter-backend.js +1 -1
- package/dist/servertool/server-side-tools.js +2 -2
- package/dist/servertool/stop-gateway-context.js +1 -1
- package/dist/servertool/stop-message-compare-context.js +1 -1
- package/dist/sse/json-to-sse/event-generators/responses.d.ts +4 -0
- package/dist/sse/json-to-sse/event-generators/responses.js +95 -1
- package/dist/sse/json-to-sse/sequencers/responses-sequencer.js +6 -4
- package/dist/sse/sse-to-json/builders/response-builder.d.ts +8 -0
- package/dist/sse/sse-to-json/builders/response-builder.js +162 -4
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.js +2 -0
- package/dist/sse/types/responses-types.d.ts +6 -2
- package/dist/tools/apply-patch/structured/coercion.js +5 -0
- package/dist/tools/args-json.js +29 -0
- package/package.json +8 -5
- package/dist/conversion/shared/args-mapping.js +0 -77
- package/dist/conversion/shared/compaction-detect.js +0 -4
- package/dist/conversion/shared/errors.js +0 -31
- package/dist/conversion/shared/media.js +0 -4
- package/dist/conversion/shared/payload-budget.js +0 -165
- package/dist/conversion/shared/protocol-field-allowlists.d.ts +0 -7
- package/dist/conversion/shared/protocol-field-allowlists.js +0 -149
- package/dist/conversion/shared/snapshot-hooks.d.ts +0 -11
- package/dist/conversion/shared/snapshot-hooks.js +0 -503
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.d.ts +0 -2
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.js +0 -129
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.d.ts +0 -4
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.js +0 -637
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.d.ts +0 -21
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.js +0 -177
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.d.ts +0 -5
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.js +0 -385
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.d.ts +0 -10
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.js +0 -602
- package/dist/conversion/shared/text-markup-normalizer/extractors.d.ts +0 -5
- package/dist/conversion/shared/text-markup-normalizer/extractors.js +0 -4
- package/dist/conversion/shared/tool-canonicalizer.d.ts +0 -2
- package/dist/conversion/shared/tool-canonicalizer.js +0 -38
- /package/dist/conversion/{shared/args-mapping.d.ts → args-mapping.d.ts} +0 -0
- /package/dist/conversion/{shared/bridge-actions.d.ts → bridge-actions.d.ts} +0 -0
- /package/dist/conversion/{shared/bridge-id-utils.d.ts → bridge-id-utils.d.ts} +0 -0
- /package/dist/conversion/{shared/bridge-instructions.d.ts → bridge-instructions.d.ts} +0 -0
- /package/dist/conversion/{shared/bridge-metadata.d.ts → bridge-metadata.d.ts} +0 -0
- /package/dist/conversion/{shared/bridge-policies.d.ts → bridge-policies.d.ts} +0 -0
- /package/dist/conversion/{shared/jsonish.d.ts → jsonish.d.ts} +0 -0
- /package/dist/conversion/{shared/mcp-injection.d.ts → mcp-injection.d.ts} +0 -0
- /package/dist/conversion/{shared/media.d.ts → media.d.ts} +0 -0
- /package/dist/conversion/{shared/payload-budget.d.ts → payload-budget.d.ts} +0 -0
- /package/dist/conversion/{shared → types}/bridge-message-types.d.ts +0 -0
- /package/dist/conversion/{shared → types}/bridge-message-types.js +0 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { applyRoutingInstructions, cleanMessagesFromRoutingInstructions, parseRoutingInstructions } from '../routing-instructions.js';
|
|
2
|
+
import { buildMetadataInstructions } from '../engine/routing-state/metadata.js';
|
|
3
|
+
import { getRoutingInstructionState, persistRoutingInstructionState, resolveStopMessageScope } from '../engine/routing-state/store.js';
|
|
4
|
+
import { ensureStopMessageModeMaxRepeats } from '../routing-stop-message-state-codec.js';
|
|
5
|
+
import { extractMessageText } from '../message-utils.js';
|
|
6
|
+
import { cleanResponsesContextFromRoutingInstructions, getLatestUserTextFromResponsesContext, hasLatestUserRoutingInstructionMarker, hasRoutingInstructionMarker, hasRoutingInstructionMarkerInResponsesContext, isServerToolFollowupRequest, isStopScopeTraceEnabled, normalizeStopMessageAiMode, normalizeStopMessageStageMode, stripClientInjectScopedFields, stripStopMessageFields } from './helpers.js';
|
|
7
|
+
import { enforceAllowlistIntersection } from './route-state-allowlist.js';
|
|
8
|
+
export function buildRoutingState(engine, request, metadata) {
|
|
9
|
+
const stickyKey = engine.resolveStickyKey(metadata);
|
|
10
|
+
const sessionScope = engine.resolveSessionScope(metadata);
|
|
11
|
+
const stopMessageScope = resolveStopMessageScope(metadata);
|
|
12
|
+
// Route sticky state remains session/request scoped for routing behavior,
|
|
13
|
+
// but stopMessage state is tmux/clockd scoped and merged separately below.
|
|
14
|
+
const stateKey = sessionScope || stickyKey || 'default';
|
|
15
|
+
const baseState = getRoutingInstructionState(stateKey, engine.routingInstructionState, engine.routingStateStore);
|
|
16
|
+
let routingState = stripStopMessageFields(baseState);
|
|
17
|
+
const metadataInstructions = buildMetadataInstructions(metadata);
|
|
18
|
+
if (metadataInstructions.length > 0) {
|
|
19
|
+
routingState = applyRoutingInstructions(metadataInstructions, routingState);
|
|
20
|
+
}
|
|
21
|
+
const disableStickyRoutes = metadata &&
|
|
22
|
+
typeof metadata === 'object' &&
|
|
23
|
+
metadata.disableStickyRoutes === true;
|
|
24
|
+
if (disableStickyRoutes && (routingState.stickyTarget || routingState.preferTarget)) {
|
|
25
|
+
routingState = {
|
|
26
|
+
...routingState,
|
|
27
|
+
stickyTarget: undefined,
|
|
28
|
+
preferTarget: undefined
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (stopMessageScope) {
|
|
32
|
+
const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
|
|
33
|
+
if (ensureStopMessageModeMaxRepeats(sessionState)) {
|
|
34
|
+
engine.routingInstructionState.set(stopMessageScope, sessionState);
|
|
35
|
+
persistRoutingInstructionState(stopMessageScope, sessionState, engine.routingStateStore);
|
|
36
|
+
}
|
|
37
|
+
if (typeof sessionState.stopMessageText === 'string' ||
|
|
38
|
+
typeof sessionState.stopMessageMaxRepeats === 'number' ||
|
|
39
|
+
typeof sessionState.stopMessageStageMode === 'string' ||
|
|
40
|
+
typeof sessionState.stopMessageAiMode === 'string' ||
|
|
41
|
+
typeof sessionState.stopMessageAiSeedPrompt === 'string' ||
|
|
42
|
+
Array.isArray(sessionState.stopMessageAiHistory)) {
|
|
43
|
+
routingState = {
|
|
44
|
+
...routingState,
|
|
45
|
+
stopMessageText: sessionState.stopMessageText,
|
|
46
|
+
stopMessageMaxRepeats: sessionState.stopMessageMaxRepeats,
|
|
47
|
+
stopMessageUsed: sessionState.stopMessageUsed,
|
|
48
|
+
stopMessageUpdatedAt: sessionState.stopMessageUpdatedAt,
|
|
49
|
+
stopMessageLastUsedAt: sessionState.stopMessageLastUsedAt,
|
|
50
|
+
stopMessageStageMode: sessionState.stopMessageStageMode,
|
|
51
|
+
stopMessageAiMode: sessionState.stopMessageAiMode,
|
|
52
|
+
stopMessageAiSeedPrompt: sessionState.stopMessageAiSeedPrompt,
|
|
53
|
+
stopMessageAiHistory: sessionState.stopMessageAiHistory
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()) {
|
|
57
|
+
routingState = {
|
|
58
|
+
...routingState,
|
|
59
|
+
preCommandSource: sessionState.preCommandSource,
|
|
60
|
+
preCommandScriptPath: sessionState.preCommandScriptPath,
|
|
61
|
+
preCommandUpdatedAt: sessionState.preCommandUpdatedAt
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
let parsedInstructions = parseRoutingInstructions(request.messages);
|
|
66
|
+
const responsesContext = request.semantics && typeof request.semantics === 'object'
|
|
67
|
+
? request.semantics.responses?.context
|
|
68
|
+
: undefined;
|
|
69
|
+
const responsesLatestUserText = getLatestUserTextFromResponsesContext(responsesContext);
|
|
70
|
+
const responsesHasMarker = hasRoutingInstructionMarkerInResponsesContext(responsesContext);
|
|
71
|
+
if (!parsedInstructions.length && responsesHasMarker) {
|
|
72
|
+
parsedInstructions = parseRoutingInstructions([
|
|
73
|
+
{ role: 'user', content: responsesLatestUserText }
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
const markerDetected = hasRoutingInstructionMarker(request.messages) || responsesHasMarker;
|
|
77
|
+
const latestMessage = request.messages.length > 0 ? request.messages[request.messages.length - 1] : undefined;
|
|
78
|
+
const latestUserText = latestMessage && latestMessage.role === 'user' ? extractMessageText(latestMessage).trim() : '';
|
|
79
|
+
const stopMessageLogText = responsesHasMarker ? responsesLatestUserText : latestUserText;
|
|
80
|
+
const containsStopMessage = Boolean(stopMessageLogText && /stopmessage/i.test(stopMessageLogText));
|
|
81
|
+
const stopMessageTypes = parsedInstructions
|
|
82
|
+
.filter((entry) => entry.type === 'stopMessageSet' ||
|
|
83
|
+
entry.type === 'stopMessageMode' ||
|
|
84
|
+
entry.type === 'stopMessageClear')
|
|
85
|
+
.map((entry) => entry.type);
|
|
86
|
+
if (containsStopMessage || stopMessageTypes.length > 0) {
|
|
87
|
+
const preview = stopMessageLogText.replace(/\s+/g, ' ').slice(0, 120);
|
|
88
|
+
engine.debug?.log?.(`[virtual-router][stop_message_parse] requestId=${metadata.requestId || 'n/a'} marker=${markerDetected ? 'detected' : 'missing'} parsed=${stopMessageTypes.join(',') || 'none'} preview=${preview}`);
|
|
89
|
+
}
|
|
90
|
+
if (markerDetected && isStopScopeTraceEnabled()) {
|
|
91
|
+
const parsedTypes = parsedInstructions.map((entry) => entry.type);
|
|
92
|
+
engine.debug?.log?.(`[virtual-router][instruction_parse] requestId=${metadata.requestId || 'n/a'} marker=detected parsed=${parsedTypes.join(',') || 'none'}`);
|
|
93
|
+
}
|
|
94
|
+
else if (isStopScopeTraceEnabled()) {
|
|
95
|
+
if (stopMessageLogText && /stopmessage/i.test(stopMessageLogText)) {
|
|
96
|
+
const preview = stopMessageLogText.replace(/\s+/g, ' ').slice(0, 120);
|
|
97
|
+
engine.debug?.log?.(`[virtual-router][instruction_parse] requestId=${metadata.requestId || 'n/a'} marker=missing contains_stopmessage=yes preview=${preview}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const serverToolFollowup = isServerToolFollowupRequest(metadata);
|
|
101
|
+
const latestUserHasMarker = hasLatestUserRoutingInstructionMarker(request.messages) || responsesHasMarker;
|
|
102
|
+
let instructions = parsedInstructions;
|
|
103
|
+
if (serverToolFollowup && instructions.length > 0) {
|
|
104
|
+
instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet' &&
|
|
105
|
+
entry.type !== 'stopMessageMode' &&
|
|
106
|
+
entry.type !== 'stopMessageClear' &&
|
|
107
|
+
entry.type !== 'preCommandSet' &&
|
|
108
|
+
entry.type !== 'preCommandClear');
|
|
109
|
+
}
|
|
110
|
+
if (stopMessageScope && parsedInstructions.length > 0) {
|
|
111
|
+
const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
|
|
112
|
+
const hasStaleStopMessageInstruction = !latestUserHasMarker &&
|
|
113
|
+
parsedInstructions.some((entry) => entry.type === 'stopMessageSet' || entry.type === 'stopMessageMode');
|
|
114
|
+
if (hasStaleStopMessageInstruction) {
|
|
115
|
+
const hasActiveStopState = typeof sessionState.stopMessageText === 'string' ||
|
|
116
|
+
typeof sessionState.stopMessageMaxRepeats === 'number' ||
|
|
117
|
+
typeof sessionState.stopMessageStageMode === 'string' ||
|
|
118
|
+
typeof sessionState.stopMessageAiMode === 'string' ||
|
|
119
|
+
typeof sessionState.stopMessageAiSeedPrompt === 'string' ||
|
|
120
|
+
Array.isArray(sessionState.stopMessageAiHistory);
|
|
121
|
+
const hasStopLifecycleStamp = (typeof sessionState.stopMessageUpdatedAt === 'number' &&
|
|
122
|
+
Number.isFinite(sessionState.stopMessageUpdatedAt)) ||
|
|
123
|
+
(typeof sessionState.stopMessageLastUsedAt === 'number' &&
|
|
124
|
+
Number.isFinite(sessionState.stopMessageLastUsedAt));
|
|
125
|
+
if (hasActiveStopState || hasStopLifecycleStamp) {
|
|
126
|
+
instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet' && entry.type !== 'stopMessageMode');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// stopMessage/precommand require explicit client inject scope (tmux).
|
|
131
|
+
// When scope is missing, drop only these instructions and keep request passthrough.
|
|
132
|
+
if (instructions.length > 0 && !stopMessageScope) {
|
|
133
|
+
const blockedInstructionTypes = instructions
|
|
134
|
+
.filter((entry) => entry.type === 'stopMessageSet' ||
|
|
135
|
+
entry.type === 'stopMessageMode' ||
|
|
136
|
+
entry.type === 'stopMessageClear' ||
|
|
137
|
+
entry.type === 'preCommandSet' ||
|
|
138
|
+
entry.type === 'preCommandClear')
|
|
139
|
+
.map((entry) => entry.type);
|
|
140
|
+
if (blockedInstructionTypes.length > 0 && isStopScopeTraceEnabled()) {
|
|
141
|
+
engine.debug?.log?.(`[virtual-router][stop_scope] requestId=${metadata.requestId || 'n/a'} stage=drop reason=missing_tmux_scope instructions=${blockedInstructionTypes.join(',')}`);
|
|
142
|
+
}
|
|
143
|
+
instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet' &&
|
|
144
|
+
entry.type !== 'stopMessageMode' &&
|
|
145
|
+
entry.type !== 'stopMessageClear' &&
|
|
146
|
+
entry.type !== 'preCommandSet' &&
|
|
147
|
+
entry.type !== 'preCommandClear');
|
|
148
|
+
}
|
|
149
|
+
if (hasRoutingInstructionMarker(request.messages) || responsesHasMarker) {
|
|
150
|
+
request.messages = cleanMessagesFromRoutingInstructions(request.messages);
|
|
151
|
+
cleanResponsesContextFromRoutingInstructions(responsesContext);
|
|
152
|
+
}
|
|
153
|
+
let appliedRoutingState = routingState;
|
|
154
|
+
if (instructions.length > 0) {
|
|
155
|
+
appliedRoutingState = applyRoutingInstructions(instructions, routingState);
|
|
156
|
+
const persistedBaseState = stopMessageScope
|
|
157
|
+
? stripClientInjectScopedFields(appliedRoutingState)
|
|
158
|
+
: appliedRoutingState;
|
|
159
|
+
routingState = persistedBaseState;
|
|
160
|
+
engine.routingInstructionState.set(stateKey, persistedBaseState);
|
|
161
|
+
persistRoutingInstructionState(stateKey, persistedBaseState, engine.routingStateStore);
|
|
162
|
+
// Persist stopMessage under tmux/clockd scope so client injection and trigger matching
|
|
163
|
+
// use the same scope and never fall back to generic session keys.
|
|
164
|
+
if (stopMessageScope) {
|
|
165
|
+
const hasStopMessageSet = instructions.some((entry) => entry.type === 'stopMessageSet');
|
|
166
|
+
const hasStopMessageMode = instructions.some((entry) => entry.type === 'stopMessageMode');
|
|
167
|
+
const hasGlobalClear = instructions.some((entry) => entry.type === 'clear');
|
|
168
|
+
const hasStopMessageClear = hasGlobalClear || instructions.some((entry) => entry.type === 'stopMessageClear');
|
|
169
|
+
if (hasStopMessageSet || hasStopMessageMode || hasStopMessageClear) {
|
|
170
|
+
const activeInstructionTypes = instructions
|
|
171
|
+
.filter((entry) => entry.type === 'stopMessageSet' ||
|
|
172
|
+
entry.type === 'stopMessageMode' ||
|
|
173
|
+
entry.type === 'stopMessageClear')
|
|
174
|
+
.map((entry) => entry.type);
|
|
175
|
+
if (isStopScopeTraceEnabled()) {
|
|
176
|
+
engine.debug?.log?.(`[virtual-router][stop_scope] requestId=${metadata.requestId || 'n/a'} stage=apply scope=${stopMessageScope} instructions=${activeInstructionTypes.join(',') || 'none'}`);
|
|
177
|
+
}
|
|
178
|
+
const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
|
|
179
|
+
let nextSessionState = {
|
|
180
|
+
...sessionState
|
|
181
|
+
};
|
|
182
|
+
let shouldPersistSessionState = false;
|
|
183
|
+
const hasStopMessageStateChanged = () => {
|
|
184
|
+
return (nextSessionState.stopMessageText !== sessionState.stopMessageText ||
|
|
185
|
+
nextSessionState.stopMessageMaxRepeats !== sessionState.stopMessageMaxRepeats ||
|
|
186
|
+
nextSessionState.stopMessageUsed !== sessionState.stopMessageUsed ||
|
|
187
|
+
nextSessionState.stopMessageUpdatedAt !== sessionState.stopMessageUpdatedAt ||
|
|
188
|
+
nextSessionState.stopMessageLastUsedAt !== sessionState.stopMessageLastUsedAt ||
|
|
189
|
+
nextSessionState.stopMessageSource !== sessionState.stopMessageSource ||
|
|
190
|
+
nextSessionState.stopMessageStageMode !== sessionState.stopMessageStageMode ||
|
|
191
|
+
nextSessionState.stopMessageAiMode !== sessionState.stopMessageAiMode ||
|
|
192
|
+
nextSessionState.stopMessageAiSeedPrompt !== sessionState.stopMessageAiSeedPrompt ||
|
|
193
|
+
JSON.stringify(nextSessionState.stopMessageAiHistory || []) !==
|
|
194
|
+
JSON.stringify(sessionState.stopMessageAiHistory || []));
|
|
195
|
+
};
|
|
196
|
+
if (hasStopMessageClear) {
|
|
197
|
+
if (hasGlobalClear) {
|
|
198
|
+
// <**clear**> is a hard reset: clear all session-scoped routing state
|
|
199
|
+
// and let persistRoutingInstructionState delete persistence markers.
|
|
200
|
+
nextSessionState = {
|
|
201
|
+
forcedTarget: undefined,
|
|
202
|
+
stickyTarget: undefined,
|
|
203
|
+
preferTarget: undefined,
|
|
204
|
+
allowedProviders: new Set(),
|
|
205
|
+
disabledProviders: new Set(),
|
|
206
|
+
disabledKeys: new Map(),
|
|
207
|
+
disabledModels: new Map(),
|
|
208
|
+
stopMessageSource: undefined,
|
|
209
|
+
stopMessageText: undefined,
|
|
210
|
+
stopMessageMaxRepeats: undefined,
|
|
211
|
+
stopMessageUsed: undefined,
|
|
212
|
+
stopMessageUpdatedAt: undefined,
|
|
213
|
+
stopMessageLastUsedAt: undefined,
|
|
214
|
+
stopMessageStageMode: undefined,
|
|
215
|
+
stopMessageAiMode: undefined,
|
|
216
|
+
stopMessageAiSeedPrompt: undefined,
|
|
217
|
+
stopMessageAiHistory: undefined,
|
|
218
|
+
preCommandSource: undefined,
|
|
219
|
+
preCommandScriptPath: undefined,
|
|
220
|
+
preCommandUpdatedAt: undefined
|
|
221
|
+
};
|
|
222
|
+
shouldPersistSessionState = true;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
nextSessionState.stopMessageText = undefined;
|
|
226
|
+
nextSessionState.stopMessageMaxRepeats = undefined;
|
|
227
|
+
nextSessionState.stopMessageUsed = undefined;
|
|
228
|
+
nextSessionState.stopMessageUpdatedAt = undefined;
|
|
229
|
+
nextSessionState.stopMessageLastUsedAt = undefined;
|
|
230
|
+
nextSessionState.stopMessageSource = undefined;
|
|
231
|
+
nextSessionState.stopMessageStageMode = undefined;
|
|
232
|
+
nextSessionState.stopMessageAiMode = undefined;
|
|
233
|
+
nextSessionState.stopMessageAiSeedPrompt = undefined;
|
|
234
|
+
nextSessionState.stopMessageAiHistory = undefined;
|
|
235
|
+
shouldPersistSessionState = true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else if (hasStopMessageSet || hasStopMessageMode) {
|
|
239
|
+
nextSessionState.stopMessageText =
|
|
240
|
+
typeof appliedRoutingState.stopMessageText === 'string' &&
|
|
241
|
+
appliedRoutingState.stopMessageText.trim()
|
|
242
|
+
? appliedRoutingState.stopMessageText
|
|
243
|
+
: undefined;
|
|
244
|
+
nextSessionState.stopMessageMaxRepeats =
|
|
245
|
+
typeof appliedRoutingState.stopMessageMaxRepeats === 'number' &&
|
|
246
|
+
Number.isFinite(appliedRoutingState.stopMessageMaxRepeats)
|
|
247
|
+
? Math.floor(appliedRoutingState.stopMessageMaxRepeats)
|
|
248
|
+
: undefined;
|
|
249
|
+
nextSessionState.stopMessageUsed =
|
|
250
|
+
typeof appliedRoutingState.stopMessageUsed === 'number' &&
|
|
251
|
+
Number.isFinite(appliedRoutingState.stopMessageUsed)
|
|
252
|
+
? Math.max(0, Math.floor(appliedRoutingState.stopMessageUsed))
|
|
253
|
+
: undefined;
|
|
254
|
+
nextSessionState.stopMessageUpdatedAt =
|
|
255
|
+
typeof appliedRoutingState.stopMessageUpdatedAt === 'number' &&
|
|
256
|
+
Number.isFinite(appliedRoutingState.stopMessageUpdatedAt)
|
|
257
|
+
? appliedRoutingState.stopMessageUpdatedAt
|
|
258
|
+
: undefined;
|
|
259
|
+
nextSessionState.stopMessageLastUsedAt =
|
|
260
|
+
typeof appliedRoutingState.stopMessageLastUsedAt === 'number' &&
|
|
261
|
+
Number.isFinite(appliedRoutingState.stopMessageLastUsedAt)
|
|
262
|
+
? appliedRoutingState.stopMessageLastUsedAt
|
|
263
|
+
: undefined;
|
|
264
|
+
nextSessionState.stopMessageSource =
|
|
265
|
+
typeof appliedRoutingState.stopMessageSource === 'string' &&
|
|
266
|
+
appliedRoutingState.stopMessageSource.trim()
|
|
267
|
+
? appliedRoutingState.stopMessageSource.trim()
|
|
268
|
+
: undefined;
|
|
269
|
+
nextSessionState.stopMessageStageMode = normalizeStopMessageStageMode(appliedRoutingState.stopMessageStageMode);
|
|
270
|
+
nextSessionState.stopMessageAiMode = normalizeStopMessageAiMode(appliedRoutingState.stopMessageAiMode);
|
|
271
|
+
nextSessionState.stopMessageAiSeedPrompt =
|
|
272
|
+
typeof appliedRoutingState.stopMessageAiSeedPrompt === 'string' &&
|
|
273
|
+
appliedRoutingState.stopMessageAiSeedPrompt.trim()
|
|
274
|
+
? appliedRoutingState.stopMessageAiSeedPrompt.trim()
|
|
275
|
+
: undefined;
|
|
276
|
+
nextSessionState.stopMessageAiHistory = Array.isArray(appliedRoutingState.stopMessageAiHistory)
|
|
277
|
+
? appliedRoutingState.stopMessageAiHistory
|
|
278
|
+
: undefined;
|
|
279
|
+
shouldPersistSessionState = hasStopMessageStateChanged();
|
|
280
|
+
}
|
|
281
|
+
if (shouldPersistSessionState) {
|
|
282
|
+
engine.routingInstructionState.set(stopMessageScope, nextSessionState);
|
|
283
|
+
persistRoutingInstructionState(stopMessageScope, nextSessionState, engine.routingStateStore);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
nextSessionState = sessionState;
|
|
287
|
+
}
|
|
288
|
+
// 日志展示使用 session scope 的 stopMessage 状态,避免每次解析重复刷新时间/次数。
|
|
289
|
+
if (typeof nextSessionState.stopMessageText === 'string' ||
|
|
290
|
+
typeof nextSessionState.stopMessageMaxRepeats === 'number' ||
|
|
291
|
+
typeof nextSessionState.stopMessageStageMode === 'string' ||
|
|
292
|
+
typeof nextSessionState.stopMessageAiMode === 'string' ||
|
|
293
|
+
typeof nextSessionState.stopMessageAiSeedPrompt === 'string' ||
|
|
294
|
+
Array.isArray(nextSessionState.stopMessageAiHistory)) {
|
|
295
|
+
routingState.stopMessageText = nextSessionState.stopMessageText;
|
|
296
|
+
routingState.stopMessageMaxRepeats = nextSessionState.stopMessageMaxRepeats;
|
|
297
|
+
routingState.stopMessageUsed = nextSessionState.stopMessageUsed;
|
|
298
|
+
routingState.stopMessageUpdatedAt = nextSessionState.stopMessageUpdatedAt;
|
|
299
|
+
routingState.stopMessageLastUsedAt = nextSessionState.stopMessageLastUsedAt;
|
|
300
|
+
routingState.stopMessageStageMode = nextSessionState.stopMessageStageMode;
|
|
301
|
+
routingState.stopMessageAiMode = nextSessionState.stopMessageAiMode;
|
|
302
|
+
routingState.stopMessageAiSeedPrompt = nextSessionState.stopMessageAiSeedPrompt;
|
|
303
|
+
routingState.stopMessageAiHistory = nextSessionState.stopMessageAiHistory;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (instructions.length > 0 && stopMessageScope) {
|
|
309
|
+
const hasPreCommandSet = instructions.some((entry) => entry.type === 'preCommandSet');
|
|
310
|
+
const hasPreCommandClear = instructions.some((entry) => entry.type === 'preCommandClear');
|
|
311
|
+
if (hasPreCommandSet || hasPreCommandClear) {
|
|
312
|
+
const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
|
|
313
|
+
const nextSessionState = {
|
|
314
|
+
...sessionState
|
|
315
|
+
};
|
|
316
|
+
let changed = false;
|
|
317
|
+
if (hasPreCommandClear) {
|
|
318
|
+
changed =
|
|
319
|
+
typeof sessionState.preCommandScriptPath === 'string' ||
|
|
320
|
+
typeof sessionState.preCommandSource === 'string' ||
|
|
321
|
+
typeof sessionState.preCommandUpdatedAt === 'number';
|
|
322
|
+
nextSessionState.preCommandScriptPath = undefined;
|
|
323
|
+
nextSessionState.preCommandSource = undefined;
|
|
324
|
+
nextSessionState.preCommandUpdatedAt = Date.now();
|
|
325
|
+
}
|
|
326
|
+
if (hasPreCommandSet) {
|
|
327
|
+
const scriptPath = typeof appliedRoutingState.preCommandScriptPath === 'string'
|
|
328
|
+
? appliedRoutingState.preCommandScriptPath.trim()
|
|
329
|
+
: '';
|
|
330
|
+
if (scriptPath) {
|
|
331
|
+
if (sessionState.preCommandScriptPath !== scriptPath) {
|
|
332
|
+
changed = true;
|
|
333
|
+
}
|
|
334
|
+
nextSessionState.preCommandScriptPath = scriptPath;
|
|
335
|
+
nextSessionState.preCommandSource = 'explicit';
|
|
336
|
+
nextSessionState.preCommandUpdatedAt =
|
|
337
|
+
typeof appliedRoutingState.preCommandUpdatedAt === 'number' &&
|
|
338
|
+
Number.isFinite(appliedRoutingState.preCommandUpdatedAt)
|
|
339
|
+
? appliedRoutingState.preCommandUpdatedAt
|
|
340
|
+
: Date.now();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (changed) {
|
|
344
|
+
engine.routingInstructionState.set(stopMessageScope, nextSessionState);
|
|
345
|
+
persistRoutingInstructionState(stopMessageScope, nextSessionState, engine.routingStateStore);
|
|
346
|
+
routingState.preCommandScriptPath = nextSessionState.preCommandScriptPath;
|
|
347
|
+
routingState.preCommandSource = nextSessionState.preCommandSource;
|
|
348
|
+
routingState.preCommandUpdatedAt = nextSessionState.preCommandUpdatedAt;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (instructions.length === 0 && stopMessageScope) {
|
|
353
|
+
const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
|
|
354
|
+
if (typeof sessionState.stopMessageText === 'string' ||
|
|
355
|
+
typeof sessionState.stopMessageMaxRepeats === 'number' ||
|
|
356
|
+
typeof sessionState.stopMessageStageMode === 'string' ||
|
|
357
|
+
typeof sessionState.stopMessageAiMode === 'string' ||
|
|
358
|
+
typeof sessionState.stopMessageAiSeedPrompt === 'string' ||
|
|
359
|
+
Array.isArray(sessionState.stopMessageAiHistory)) {
|
|
360
|
+
routingState.stopMessageText = sessionState.stopMessageText;
|
|
361
|
+
routingState.stopMessageMaxRepeats = sessionState.stopMessageMaxRepeats;
|
|
362
|
+
routingState.stopMessageUsed = sessionState.stopMessageUsed;
|
|
363
|
+
routingState.stopMessageUpdatedAt = sessionState.stopMessageUpdatedAt;
|
|
364
|
+
routingState.stopMessageLastUsedAt = sessionState.stopMessageLastUsedAt;
|
|
365
|
+
routingState.stopMessageStageMode = sessionState.stopMessageStageMode;
|
|
366
|
+
routingState.stopMessageAiMode = sessionState.stopMessageAiMode;
|
|
367
|
+
routingState.stopMessageAiSeedPrompt = sessionState.stopMessageAiSeedPrompt;
|
|
368
|
+
routingState.stopMessageAiHistory = sessionState.stopMessageAiHistory;
|
|
369
|
+
}
|
|
370
|
+
if (typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()) {
|
|
371
|
+
routingState.preCommandScriptPath = sessionState.preCommandScriptPath;
|
|
372
|
+
routingState.preCommandSource = sessionState.preCommandSource;
|
|
373
|
+
routingState.preCommandUpdatedAt = sessionState.preCommandUpdatedAt;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Guardrail: if a session is restricted to providers that do not exist in any routing pools,
|
|
377
|
+
// we must not hard-fail the request loop. Auto-clear the allowlist and fall back to normal routing.
|
|
378
|
+
routingState = enforceAllowlistIntersection(engine, routingState, stateKey);
|
|
379
|
+
return {
|
|
380
|
+
routingState,
|
|
381
|
+
stateKey,
|
|
382
|
+
stopMessageScope,
|
|
383
|
+
metadataInstructions,
|
|
384
|
+
instructions
|
|
385
|
+
};
|
|
386
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RoutePoolTier } from '../types.js';
|
|
2
|
+
import type { VirtualRouterEngine } from '../engine-legacy.js';
|
|
3
|
+
export declare function normalizeRouteAlias(routeName: string | undefined): string;
|
|
4
|
+
export declare function buildRouteCandidates(engine: VirtualRouterEngine, requestedRoute: string, classificationCandidates: string[] | undefined, features: {
|
|
5
|
+
hasImageAttachment?: boolean;
|
|
6
|
+
hasVideoAttachment?: boolean;
|
|
7
|
+
hasLocalVideoAttachment?: boolean;
|
|
8
|
+
}): string[];
|
|
9
|
+
export declare function reorderForInlineVision(engine: VirtualRouterEngine, routeNames: string[]): string[];
|
|
10
|
+
export declare function reorderForPreferredModel(engine: VirtualRouterEngine, routeNames: string[], modelId: string): string[];
|
|
11
|
+
export declare function routeSupportsModel(engine: VirtualRouterEngine, routeName: string, modelId: string): boolean;
|
|
12
|
+
export declare function routeSupportsInlineVision(engine: VirtualRouterEngine, routeName: string): boolean;
|
|
13
|
+
export declare function sortByPriority(routeNames: string[]): string[];
|
|
14
|
+
export declare function routeWeight(routeName: string): number;
|
|
15
|
+
export declare function routeHasForceFlag(engine: VirtualRouterEngine, routeName: string): boolean;
|
|
16
|
+
export declare function routeHasTargets(engine: VirtualRouterEngine, pools?: RoutePoolTier[]): boolean;
|
|
17
|
+
export declare function hasPrimaryPool(engine: VirtualRouterEngine, pools?: RoutePoolTier[]): boolean;
|
|
18
|
+
export declare function sortRoutePools(_engine: VirtualRouterEngine, pools?: RoutePoolTier[]): RoutePoolTier[];
|
|
19
|
+
export declare function flattenPoolTargets(_engine: VirtualRouterEngine, pools?: RoutePoolTier[]): string[];
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { DEFAULT_ROUTE, ROUTE_PRIORITY } from '../types.js';
|
|
2
|
+
export function normalizeRouteAlias(routeName) {
|
|
3
|
+
const base = routeName && routeName.trim() ? routeName.trim() : DEFAULT_ROUTE;
|
|
4
|
+
return base;
|
|
5
|
+
}
|
|
6
|
+
export function buildRouteCandidates(engine, requestedRoute, classificationCandidates, features) {
|
|
7
|
+
const forceVision = routeHasForceFlag(engine, 'vision');
|
|
8
|
+
const hasMultimodalTargets = routeHasTargets(engine, engine.routing.multimodal);
|
|
9
|
+
const hasVisionTargets = routeHasTargets(engine, engine.routing.vision);
|
|
10
|
+
const hasLocalVideoAttachment = features.hasVideoAttachment === true && features.hasLocalVideoAttachment === true;
|
|
11
|
+
if (features.hasImageAttachment && hasLocalVideoAttachment && hasVisionTargets) {
|
|
12
|
+
return ['vision'];
|
|
13
|
+
}
|
|
14
|
+
const normalized = normalizeRouteAlias(requestedRoute || DEFAULT_ROUTE);
|
|
15
|
+
const baseList = [];
|
|
16
|
+
if (classificationCandidates && classificationCandidates.length) {
|
|
17
|
+
for (const candidate of classificationCandidates) {
|
|
18
|
+
baseList.push(normalizeRouteAlias(candidate));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else if (normalized) {
|
|
22
|
+
baseList.push(normalized);
|
|
23
|
+
}
|
|
24
|
+
if (features.hasImageAttachment) {
|
|
25
|
+
if (hasMultimodalTargets) {
|
|
26
|
+
if (!baseList.includes('multimodal')) {
|
|
27
|
+
baseList.unshift('multimodal');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (hasVisionTargets && (!hasMultimodalTargets || forceVision)) {
|
|
31
|
+
if (!baseList.includes('vision')) {
|
|
32
|
+
baseList.push('vision');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!forceVision && hasMultimodalTargets) {
|
|
36
|
+
const visionAwareRoutes = [DEFAULT_ROUTE, 'thinking'];
|
|
37
|
+
for (const routeName of visionAwareRoutes) {
|
|
38
|
+
if (routeHasTargets(engine, engine.routing[routeName]) && !baseList.includes(routeName)) {
|
|
39
|
+
baseList.push(routeName);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let ordered = sortByPriority(baseList);
|
|
45
|
+
if (features.hasImageAttachment && !forceVision && hasMultimodalTargets) {
|
|
46
|
+
ordered = reorderForInlineVision(engine, ordered);
|
|
47
|
+
}
|
|
48
|
+
if (features.hasImageAttachment && hasMultimodalTargets) {
|
|
49
|
+
ordered = reorderForPreferredModel(engine, ordered, 'kimi-k2.5');
|
|
50
|
+
}
|
|
51
|
+
const deduped = [];
|
|
52
|
+
for (const routeName of ordered) {
|
|
53
|
+
if (routeName && !deduped.includes(routeName)) {
|
|
54
|
+
deduped.push(routeName);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!deduped.includes(DEFAULT_ROUTE)) {
|
|
58
|
+
deduped.push(DEFAULT_ROUTE);
|
|
59
|
+
}
|
|
60
|
+
const filtered = deduped.filter((routeName) => routeHasTargets(engine, engine.routing[routeName]));
|
|
61
|
+
if (!filtered.includes(DEFAULT_ROUTE) && routeHasTargets(engine, engine.routing[DEFAULT_ROUTE])) {
|
|
62
|
+
filtered.push(DEFAULT_ROUTE);
|
|
63
|
+
}
|
|
64
|
+
return filtered.length ? filtered : [DEFAULT_ROUTE];
|
|
65
|
+
}
|
|
66
|
+
export function reorderForInlineVision(engine, routeNames) {
|
|
67
|
+
const unique = Array.from(new Set(routeNames.filter(Boolean)));
|
|
68
|
+
if (!unique.length) {
|
|
69
|
+
return unique;
|
|
70
|
+
}
|
|
71
|
+
// 仅当 default/thinking 中存在 Responses/Gemini 提供方时,才将其提前作为「一次完成」优先级。
|
|
72
|
+
const inlinePreferred = [];
|
|
73
|
+
const inlineRoutes = [DEFAULT_ROUTE, 'thinking'];
|
|
74
|
+
for (const routeName of inlineRoutes) {
|
|
75
|
+
if (routeSupportsInlineVision(engine, routeName) && !inlinePreferred.includes(routeName)) {
|
|
76
|
+
inlinePreferred.push(routeName);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!inlinePreferred.length) {
|
|
80
|
+
return unique;
|
|
81
|
+
}
|
|
82
|
+
const remaining = [];
|
|
83
|
+
for (const routeName of unique) {
|
|
84
|
+
if (!inlinePreferred.includes(routeName)) {
|
|
85
|
+
remaining.push(routeName);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return [...inlinePreferred, ...remaining];
|
|
89
|
+
}
|
|
90
|
+
export function reorderForPreferredModel(engine, routeNames, modelId) {
|
|
91
|
+
const unique = Array.from(new Set(routeNames.filter(Boolean)));
|
|
92
|
+
if (!unique.length) {
|
|
93
|
+
return unique;
|
|
94
|
+
}
|
|
95
|
+
const preferred = unique.filter((routeName) => routeSupportsModel(engine, routeName, modelId));
|
|
96
|
+
if (!preferred.length) {
|
|
97
|
+
return unique;
|
|
98
|
+
}
|
|
99
|
+
const remaining = unique.filter((routeName) => !preferred.includes(routeName));
|
|
100
|
+
return [...preferred, ...remaining];
|
|
101
|
+
}
|
|
102
|
+
export function routeSupportsModel(engine, routeName, modelId) {
|
|
103
|
+
const normalizedModel = modelId.trim().toLowerCase();
|
|
104
|
+
if (!normalizedModel) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const pools = engine.routing[routeName];
|
|
108
|
+
if (!Array.isArray(pools)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
for (const pool of pools) {
|
|
112
|
+
if (!Array.isArray(pool.targets)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const providerKey of pool.targets) {
|
|
116
|
+
try {
|
|
117
|
+
const profile = engine.providerRegistry.get(providerKey);
|
|
118
|
+
const candidate = typeof profile.modelId === 'string' ? profile.modelId.trim().toLowerCase() : '';
|
|
119
|
+
if (candidate === normalizedModel) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// ignore unknown provider keys during capability probing
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
export function routeSupportsInlineVision(engine, routeName) {
|
|
131
|
+
const pools = engine.routing[routeName];
|
|
132
|
+
if (!Array.isArray(pools)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
for (const pool of pools) {
|
|
136
|
+
if (!Array.isArray(pool.targets)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
for (const providerKey of pool.targets) {
|
|
140
|
+
try {
|
|
141
|
+
const profile = engine.providerRegistry.get(providerKey);
|
|
142
|
+
if (profile.providerType === 'responses' || profile.providerType === 'gemini') {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// ignore unknown provider keys during capability probing
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
export function sortByPriority(routeNames) {
|
|
154
|
+
return [...routeNames].sort((a, b) => routeWeight(a) - routeWeight(b));
|
|
155
|
+
}
|
|
156
|
+
export function routeWeight(routeName) {
|
|
157
|
+
const idx = ROUTE_PRIORITY.indexOf(routeName);
|
|
158
|
+
return idx >= 0 ? idx : ROUTE_PRIORITY.length;
|
|
159
|
+
}
|
|
160
|
+
export function routeHasForceFlag(engine, routeName) {
|
|
161
|
+
const pools = engine.routing[routeName];
|
|
162
|
+
if (!Array.isArray(pools)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return pools.some((pool) => pool.force);
|
|
166
|
+
}
|
|
167
|
+
export function routeHasTargets(engine, pools) {
|
|
168
|
+
if (!Array.isArray(pools)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
return pools.some((pool) => Array.isArray(pool.targets) && pool.targets.length > 0);
|
|
172
|
+
}
|
|
173
|
+
export function hasPrimaryPool(engine, pools) {
|
|
174
|
+
if (!Array.isArray(pools)) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return pools.some((pool) => !pool.backup && Array.isArray(pool.targets) && pool.targets.length > 0);
|
|
178
|
+
}
|
|
179
|
+
export function sortRoutePools(_engine, pools) {
|
|
180
|
+
if (!Array.isArray(pools)) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
return pools
|
|
184
|
+
.filter((pool) => Array.isArray(pool.targets) && pool.targets.length > 0)
|
|
185
|
+
.sort((a, b) => {
|
|
186
|
+
if (a.backup && !b.backup)
|
|
187
|
+
return 1;
|
|
188
|
+
if (!a.backup && b.backup)
|
|
189
|
+
return -1;
|
|
190
|
+
if (a.priority !== b.priority) {
|
|
191
|
+
return b.priority - a.priority;
|
|
192
|
+
}
|
|
193
|
+
return a.id.localeCompare(b.id);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
export function flattenPoolTargets(_engine, pools) {
|
|
197
|
+
const flattened = [];
|
|
198
|
+
if (!Array.isArray(pools)) {
|
|
199
|
+
return flattened;
|
|
200
|
+
}
|
|
201
|
+
for (const pool of pools) {
|
|
202
|
+
if (!Array.isArray(pool.targets)) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
for (const target of pool.targets) {
|
|
206
|
+
if (typeof target === 'string' && target && !flattened.includes(target)) {
|
|
207
|
+
flattened.push(target);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return flattened;
|
|
212
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProcessedRequest, StandardizedRequest } from '../../../conversion/hub/types/standardized.js';
|
|
2
|
+
import type { RouterMetadataInput, RoutingDecision, RoutingDiagnostics, TargetMetadata } from '../types.js';
|
|
3
|
+
import type { VirtualRouterEngine } from '../engine-legacy.js';
|
|
4
|
+
export declare function routeRequest(engine: VirtualRouterEngine, request: StandardizedRequest | ProcessedRequest, metadata: RouterMetadataInput): {
|
|
5
|
+
target: TargetMetadata;
|
|
6
|
+
decision: RoutingDecision;
|
|
7
|
+
diagnostics: RoutingDiagnostics;
|
|
8
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { buildRoutingState } from './route-state.js';
|
|
2
|
+
import { selectRoutingTarget } from './route-selection.js';
|
|
3
|
+
import { finalizeRoutingDecision } from './route-finalize.js';
|
|
4
|
+
export function routeRequest(engine, request, metadata) {
|
|
5
|
+
const routingStateResult = buildRoutingState(engine, request, metadata);
|
|
6
|
+
const selectionResult = selectRoutingTarget(engine, request, metadata, routingStateResult.routingState, routingStateResult.stateKey);
|
|
7
|
+
return finalizeRoutingDecision(engine, metadata, selectionResult.routingState, routingStateResult.metadataInstructions, routingStateResult.instructions, selectionResult);
|
|
8
|
+
}
|