pi-smart-router 0.1.0
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/.pi/extensions/smart-router/command-formatters.ts +167 -0
- package/.pi/extensions/smart-router/commands.ts +242 -0
- package/.pi/extensions/smart-router/dataset-export.ts +142 -0
- package/.pi/extensions/smart-router/delegate-stream.ts +105 -0
- package/.pi/extensions/smart-router/delegation-runtime.ts +355 -0
- package/.pi/extensions/smart-router/extension-setup.ts +115 -0
- package/.pi/extensions/smart-router/fleet-bootstrap.ts +244 -0
- package/.pi/extensions/smart-router/index.ts +115 -0
- package/.pi/extensions/smart-router/package.json +3 -0
- package/.pi/extensions/smart-router/pi-model-scope.ts +55 -0
- package/.pi/extensions/smart-router/pricing-lifecycle.ts +43 -0
- package/.pi/extensions/smart-router/route-and-delegate.ts +562 -0
- package/.pi/extensions/smart-router/routing-context.ts +171 -0
- package/.pi/extensions/smart-router/routing-outcomes.ts +44 -0
- package/.pi/extensions/smart-router/session-lifecycle.ts +140 -0
- package/.pi/extensions/smart-router/stream-delegation.ts +75 -0
- package/.pi/extensions/smart-router/types.ts +90 -0
- package/.pi/extensions/smart-router/utils.ts +58 -0
- package/README.md +593 -0
- package/bin/pi-smart-router.mjs +79 -0
- package/config/.gitkeep +0 -0
- package/config/hydra-projection-weights.json.example +1169 -0
- package/config/models.yaml.example +58 -0
- package/config/p-success-weights.json.example +19 -0
- package/config/routing-calibration.json.example +1605 -0
- package/config/routing-centroids.json.example +1570 -0
- package/config/routing-clusters.yaml.example +41 -0
- package/data/contrib/example.json +25 -0
- package/dist/api/explain/router-explain.d.ts +47 -0
- package/dist/api/explain/router-explain.d.ts.map +1 -0
- package/dist/api/explain/router-explain.js +101 -0
- package/dist/api/explain/router-explain.js.map +1 -0
- package/dist/api/middleware/pi-router-middleware.d.ts +86 -0
- package/dist/api/middleware/pi-router-middleware.d.ts.map +1 -0
- package/dist/api/middleware/pi-router-middleware.js +83 -0
- package/dist/api/middleware/pi-router-middleware.js.map +1 -0
- package/dist/cli/smart-router-cli.d.ts +85 -0
- package/dist/cli/smart-router-cli.d.ts.map +1 -0
- package/dist/cli/smart-router-cli.js +293 -0
- package/dist/cli/smart-router-cli.js.map +1 -0
- package/dist/config/defaults.d.ts +7 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +33 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/models-loader.d.ts +22 -0
- package/dist/config/models-loader.d.ts.map +1 -0
- package/dist/config/models-loader.js +51 -0
- package/dist/config/models-loader.js.map +1 -0
- package/dist/config/pi-model-mapper.d.ts +40 -0
- package/dist/config/pi-model-mapper.d.ts.map +1 -0
- package/dist/config/pi-model-mapper.js +216 -0
- package/dist/config/pi-model-mapper.js.map +1 -0
- package/dist/config/routing-clusters-loader.d.ts +35 -0
- package/dist/config/routing-clusters-loader.d.ts.map +1 -0
- package/dist/config/routing-clusters-loader.js +100 -0
- package/dist/config/routing-clusters-loader.js.map +1 -0
- package/dist/domain/delegation/delegation-context.d.ts +23 -0
- package/dist/domain/delegation/delegation-context.d.ts.map +1 -0
- package/dist/domain/delegation/delegation-context.js +73 -0
- package/dist/domain/delegation/delegation-context.js.map +1 -0
- package/dist/domain/delegation/execution-ledger.d.ts +19 -0
- package/dist/domain/delegation/execution-ledger.d.ts.map +1 -0
- package/dist/domain/delegation/execution-ledger.js +23 -0
- package/dist/domain/delegation/execution-ledger.js.map +1 -0
- package/dist/domain/delegation/output-headroom.d.ts +39 -0
- package/dist/domain/delegation/output-headroom.d.ts.map +1 -0
- package/dist/domain/delegation/output-headroom.js +78 -0
- package/dist/domain/delegation/output-headroom.js.map +1 -0
- package/dist/domain/matching/cluster-matcher.d.ts +69 -0
- package/dist/domain/matching/cluster-matcher.d.ts.map +1 -0
- package/dist/domain/matching/cluster-matcher.js +294 -0
- package/dist/domain/matching/cluster-matcher.js.map +1 -0
- package/dist/domain/matching/embedding-provider.d.ts +21 -0
- package/dist/domain/matching/embedding-provider.d.ts.map +1 -0
- package/dist/domain/matching/embedding-provider.js +42 -0
- package/dist/domain/matching/embedding-provider.js.map +1 -0
- package/dist/domain/matching/hydra-input.d.ts +18 -0
- package/dist/domain/matching/hydra-input.d.ts.map +1 -0
- package/dist/domain/matching/hydra-input.js +40 -0
- package/dist/domain/matching/hydra-input.js.map +1 -0
- package/dist/domain/matching/hydra-matcher.d.ts +103 -0
- package/dist/domain/matching/hydra-matcher.d.ts.map +1 -0
- package/dist/domain/matching/hydra-matcher.js +275 -0
- package/dist/domain/matching/hydra-matcher.js.map +1 -0
- package/dist/domain/pinning/cache-economics.d.ts +53 -0
- package/dist/domain/pinning/cache-economics.d.ts.map +1 -0
- package/dist/domain/pinning/cache-economics.js +72 -0
- package/dist/domain/pinning/cache-economics.js.map +1 -0
- package/dist/domain/pinning/loop-escalation.d.ts +41 -0
- package/dist/domain/pinning/loop-escalation.d.ts.map +1 -0
- package/dist/domain/pinning/loop-escalation.js +137 -0
- package/dist/domain/pinning/loop-escalation.js.map +1 -0
- package/dist/domain/pinning/session-pinner.d.ts +81 -0
- package/dist/domain/pinning/session-pinner.d.ts.map +1 -0
- package/dist/domain/pinning/session-pinner.js +237 -0
- package/dist/domain/pinning/session-pinner.js.map +1 -0
- package/dist/domain/pinning/sub-route-policy.d.ts +41 -0
- package/dist/domain/pinning/sub-route-policy.d.ts.map +1 -0
- package/dist/domain/pinning/sub-route-policy.js +72 -0
- package/dist/domain/pinning/sub-route-policy.js.map +1 -0
- package/dist/domain/pipeline/router-pipeline.d.ts +180 -0
- package/dist/domain/pipeline/router-pipeline.d.ts.map +1 -0
- package/dist/domain/pipeline/router-pipeline.js +807 -0
- package/dist/domain/pipeline/router-pipeline.js.map +1 -0
- package/dist/domain/pipeline/safe-default.d.ts +25 -0
- package/dist/domain/pipeline/safe-default.d.ts.map +1 -0
- package/dist/domain/pipeline/safe-default.js +41 -0
- package/dist/domain/pipeline/safe-default.js.map +1 -0
- package/dist/domain/refactor-guardrails.d.ts +48 -0
- package/dist/domain/refactor-guardrails.d.ts.map +1 -0
- package/dist/domain/refactor-guardrails.js +129 -0
- package/dist/domain/refactor-guardrails.js.map +1 -0
- package/dist/domain/routing/context-fit.d.ts +53 -0
- package/dist/domain/routing/context-fit.d.ts.map +1 -0
- package/dist/domain/routing/context-fit.js +142 -0
- package/dist/domain/routing/context-fit.js.map +1 -0
- package/dist/domain/routing/expected-cost.d.ts +63 -0
- package/dist/domain/routing/expected-cost.d.ts.map +1 -0
- package/dist/domain/routing/expected-cost.js +177 -0
- package/dist/domain/routing/expected-cost.js.map +1 -0
- package/dist/domain/routing/p-success-classifier.d.ts +130 -0
- package/dist/domain/routing/p-success-classifier.d.ts.map +1 -0
- package/dist/domain/routing/p-success-classifier.js +350 -0
- package/dist/domain/routing/p-success-classifier.js.map +1 -0
- package/dist/domain/routing/tier-features.d.ts +70 -0
- package/dist/domain/routing/tier-features.d.ts.map +1 -0
- package/dist/domain/routing/tier-features.js +174 -0
- package/dist/domain/routing/tier-features.js.map +1 -0
- package/dist/domain/routing/tool-history-guard.d.ts +32 -0
- package/dist/domain/routing/tool-history-guard.d.ts.map +1 -0
- package/dist/domain/routing/tool-history-guard.js +110 -0
- package/dist/domain/routing/tool-history-guard.js.map +1 -0
- package/dist/domain/scoring/multi-objective.d.ts +48 -0
- package/dist/domain/scoring/multi-objective.d.ts.map +1 -0
- package/dist/domain/scoring/multi-objective.js +116 -0
- package/dist/domain/scoring/multi-objective.js.map +1 -0
- package/dist/domain/triage/triage-engine.d.ts +43 -0
- package/dist/domain/triage/triage-engine.d.ts.map +1 -0
- package/dist/domain/triage/triage-engine.js +317 -0
- package/dist/domain/triage/triage-engine.js.map +1 -0
- package/dist/domain/triage/turn-envelope.d.ts +17 -0
- package/dist/domain/triage/turn-envelope.d.ts.map +1 -0
- package/dist/domain/triage/turn-envelope.js +81 -0
- package/dist/domain/triage/turn-envelope.js.map +1 -0
- package/dist/domain/types/entities.d.ts +303 -0
- package/dist/domain/types/entities.d.ts.map +1 -0
- package/dist/domain/types/entities.js +6 -0
- package/dist/domain/types/entities.js.map +1 -0
- package/dist/domain/types/index.d.ts +4 -0
- package/dist/domain/types/index.d.ts.map +1 -0
- package/dist/domain/types/index.js +2 -0
- package/dist/domain/types/index.js.map +1 -0
- package/dist/domain/types/schemas.d.ts +346 -0
- package/dist/domain/types/schemas.d.ts.map +1 -0
- package/dist/domain/types/schemas.js +241 -0
- package/dist/domain/types/schemas.js.map +1 -0
- package/dist/domain/types/store-port.d.ts +44 -0
- package/dist/domain/types/store-port.d.ts.map +1 -0
- package/dist/domain/types/store-port.js +6 -0
- package/dist/domain/types/store-port.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/infra/gemini-provider.d.ts +28 -0
- package/dist/infra/gemini-provider.d.ts.map +1 -0
- package/dist/infra/gemini-provider.js +112 -0
- package/dist/infra/gemini-provider.js.map +1 -0
- package/dist/infra/telemetry.d.ts +51 -0
- package/dist/infra/telemetry.d.ts.map +1 -0
- package/dist/infra/telemetry.js +100 -0
- package/dist/infra/telemetry.js.map +1 -0
- package/dist/infrastructure/delegation/provider-error.d.ts +44 -0
- package/dist/infrastructure/delegation/provider-error.d.ts.map +1 -0
- package/dist/infrastructure/delegation/provider-error.js +179 -0
- package/dist/infrastructure/delegation/provider-error.js.map +1 -0
- package/dist/infrastructure/gateway/circuit-breaker.d.ts +68 -0
- package/dist/infrastructure/gateway/circuit-breaker.d.ts.map +1 -0
- package/dist/infrastructure/gateway/circuit-breaker.js +150 -0
- package/dist/infrastructure/gateway/circuit-breaker.js.map +1 -0
- package/dist/infrastructure/gateway/gateway-dispatch.d.ts +138 -0
- package/dist/infrastructure/gateway/gateway-dispatch.d.ts.map +1 -0
- package/dist/infrastructure/gateway/gateway-dispatch.js +308 -0
- package/dist/infrastructure/gateway/gateway-dispatch.js.map +1 -0
- package/dist/infrastructure/hardware/hardware-probe.d.ts +32 -0
- package/dist/infrastructure/hardware/hardware-probe.d.ts.map +1 -0
- package/dist/infrastructure/hardware/hardware-probe.js +199 -0
- package/dist/infrastructure/hardware/hardware-probe.js.map +1 -0
- package/dist/infrastructure/local/local-zero-tier.d.ts +47 -0
- package/dist/infrastructure/local/local-zero-tier.d.ts.map +1 -0
- package/dist/infrastructure/local/local-zero-tier.js +92 -0
- package/dist/infrastructure/local/local-zero-tier.js.map +1 -0
- package/dist/infrastructure/persistence/memory-store.d.ts +31 -0
- package/dist/infrastructure/persistence/memory-store.d.ts.map +1 -0
- package/dist/infrastructure/persistence/memory-store.js +87 -0
- package/dist/infrastructure/persistence/memory-store.js.map +1 -0
- package/dist/infrastructure/persistence/sqlite-store.d.ts +81 -0
- package/dist/infrastructure/persistence/sqlite-store.d.ts.map +1 -0
- package/dist/infrastructure/persistence/sqlite-store.js +658 -0
- package/dist/infrastructure/persistence/sqlite-store.js.map +1 -0
- package/dist/infrastructure/pricing/litellm-fetch.d.ts +46 -0
- package/dist/infrastructure/pricing/litellm-fetch.d.ts.map +1 -0
- package/dist/infrastructure/pricing/litellm-fetch.js +145 -0
- package/dist/infrastructure/pricing/litellm-fetch.js.map +1 -0
- package/dist/infrastructure/pricing/price-broker.d.ts +56 -0
- package/dist/infrastructure/pricing/price-broker.d.ts.map +1 -0
- package/dist/infrastructure/pricing/price-broker.js +133 -0
- package/dist/infrastructure/pricing/price-broker.js.map +1 -0
- package/dist/infrastructure/pricing/pricing-monitor.d.ts +27 -0
- package/dist/infrastructure/pricing/pricing-monitor.d.ts.map +1 -0
- package/dist/infrastructure/pricing/pricing-monitor.js +46 -0
- package/dist/infrastructure/pricing/pricing-monitor.js.map +1 -0
- package/dist/infrastructure/telemetry/dataset-limits.d.ts +13 -0
- package/dist/infrastructure/telemetry/dataset-limits.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/dataset-limits.js +28 -0
- package/dist/infrastructure/telemetry/dataset-limits.js.map +1 -0
- package/dist/infrastructure/telemetry/dataset-recorder.d.ts +48 -0
- package/dist/infrastructure/telemetry/dataset-recorder.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/dataset-recorder.js +154 -0
- package/dist/infrastructure/telemetry/dataset-recorder.js.map +1 -0
- package/dist/infrastructure/telemetry/outcome-limits.d.ts +12 -0
- package/dist/infrastructure/telemetry/outcome-limits.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/outcome-limits.js +28 -0
- package/dist/infrastructure/telemetry/outcome-limits.js.map +1 -0
- package/dist/infrastructure/telemetry/outcome-recorder.d.ts +29 -0
- package/dist/infrastructure/telemetry/outcome-recorder.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/outcome-recorder.js +49 -0
- package/dist/infrastructure/telemetry/outcome-recorder.js.map +1 -0
- package/dist/infrastructure/telemetry/routing-telemetry.d.ts +115 -0
- package/dist/infrastructure/telemetry/routing-telemetry.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/routing-telemetry.js +529 -0
- package/dist/infrastructure/telemetry/routing-telemetry.js.map +1 -0
- package/dist/infrastructure/telemetry/telemetry-limits.d.ts +16 -0
- package/dist/infrastructure/telemetry/telemetry-limits.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/telemetry-limits.js +35 -0
- package/dist/infrastructure/telemetry/telemetry-limits.js.map +1 -0
- package/package.json +110 -0
- package/skills/router-backlog-orchestrator/SKILL.md +183 -0
- package/skills/router-backlog-orchestrator/references/github-router-issue-template.md +78 -0
- package/skills/router-backlog-orchestrator/references/packet-from-issue.md +99 -0
- package/skills/router-backlog-orchestrator/references/prioritization-rubric.md +65 -0
- package/skills/router-backlog-orchestrator/scripts/collect-backlog.sh +44 -0
- package/specs/001-build-smart-router/contracts/telemetry-contrib.schema.json +151 -0
- package/src/api/explain/.gitkeep +0 -0
- package/src/api/explain/router-explain.ts +146 -0
- package/src/api/middleware/.gitkeep +0 -0
- package/src/api/middleware/pi-router-middleware.ts +169 -0
- package/src/cli/smart-router-cli.ts +436 -0
- package/src/config/.gitkeep +0 -0
- package/src/config/defaults.ts +35 -0
- package/src/config/models-loader.ts +68 -0
- package/src/config/pi-model-mapper.ts +285 -0
- package/src/config/routing-clusters-loader.ts +148 -0
- package/src/domain/delegation/delegation-context.ts +111 -0
- package/src/domain/delegation/execution-ledger.ts +34 -0
- package/src/domain/delegation/output-headroom.ts +131 -0
- package/src/domain/matching/.gitkeep +0 -0
- package/src/domain/matching/cluster-matcher.ts +474 -0
- package/src/domain/matching/embedding-provider.ts +79 -0
- package/src/domain/matching/hydra-input.ts +53 -0
- package/src/domain/matching/hydra-matcher.ts +454 -0
- package/src/domain/pinning/.gitkeep +0 -0
- package/src/domain/pinning/cache-economics.ts +128 -0
- package/src/domain/pinning/loop-escalation.ts +181 -0
- package/src/domain/pinning/session-pinner.ts +388 -0
- package/src/domain/pinning/sub-route-policy.ts +119 -0
- package/src/domain/pipeline/.gitkeep +0 -0
- package/src/domain/pipeline/router-pipeline.ts +1139 -0
- package/src/domain/pipeline/safe-default.ts +70 -0
- package/src/domain/refactor-guardrails.ts +202 -0
- package/src/domain/routing/context-fit.ts +221 -0
- package/src/domain/routing/expected-cost.ts +320 -0
- package/src/domain/routing/p-success-classifier.ts +511 -0
- package/src/domain/routing/tier-features.ts +290 -0
- package/src/domain/routing/tool-history-guard.ts +159 -0
- package/src/domain/scoring/.gitkeep +0 -0
- package/src/domain/scoring/multi-objective.ts +175 -0
- package/src/domain/triage/.gitkeep +0 -0
- package/src/domain/triage/triage-engine.ts +373 -0
- package/src/domain/triage/turn-envelope.ts +104 -0
- package/src/domain/types/.gitkeep +0 -0
- package/src/domain/types/entities.ts +384 -0
- package/src/domain/types/index.ts +41 -0
- package/src/domain/types/schemas.ts +289 -0
- package/src/domain/types/store-port.ts +59 -0
- package/src/index.ts +103 -0
- package/src/infra/gemini-provider.ts +142 -0
- package/src/infra/telemetry.ts +169 -0
- package/src/infrastructure/delegation/provider-error.ts +251 -0
- package/src/infrastructure/gateway/.gitkeep +0 -0
- package/src/infrastructure/gateway/circuit-breaker.ts +196 -0
- package/src/infrastructure/gateway/gateway-dispatch.ts +444 -0
- package/src/infrastructure/hardware/.gitkeep +0 -0
- package/src/infrastructure/hardware/hardware-probe.ts +276 -0
- package/src/infrastructure/local/.gitkeep +0 -0
- package/src/infrastructure/local/local-zero-tier.ts +151 -0
- package/src/infrastructure/persistence/.gitkeep +0 -0
- package/src/infrastructure/persistence/memory-store.ts +122 -0
- package/src/infrastructure/persistence/sqlite-store.ts +900 -0
- package/src/infrastructure/pricing/.gitkeep +0 -0
- package/src/infrastructure/pricing/litellm-fetch.ts +215 -0
- package/src/infrastructure/pricing/price-broker.ts +183 -0
- package/src/infrastructure/pricing/pricing-monitor.ts +65 -0
- package/src/infrastructure/telemetry/.gitkeep +0 -0
- package/src/infrastructure/telemetry/dataset-limits.ts +39 -0
- package/src/infrastructure/telemetry/dataset-recorder.ts +215 -0
- package/src/infrastructure/telemetry/outcome-limits.ts +42 -0
- package/src/infrastructure/telemetry/outcome-recorder.ts +118 -0
- package/src/infrastructure/telemetry/routing-telemetry.ts +824 -0
- package/src/infrastructure/telemetry/telemetry-limits.ts +50 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session pinner — FR-006, FR-007, FR-008, FR-024.
|
|
3
|
+
*
|
|
4
|
+
* Pins a session to one model after the initial routing decision.
|
|
5
|
+
* Pin holds across subsequent turns until a qualified break event.
|
|
6
|
+
*
|
|
7
|
+
* Break rules (FR-008, exhaustive):
|
|
8
|
+
* 1. History compaction
|
|
9
|
+
* 2. Context overflow (estimated tokens exceed pinned model window)
|
|
10
|
+
* 3. Explicit operator/user override
|
|
11
|
+
* 4. Qualified loop escalation (threshold managed externally)
|
|
12
|
+
* 5. Cache-warmup economics when a cross-provider switch is proposed
|
|
13
|
+
*
|
|
14
|
+
* Sub-routing (FR-024): tool-result turns below the payload threshold
|
|
15
|
+
* may use an economical model on the same provider without breaking the pin.
|
|
16
|
+
*/
|
|
17
|
+
import type { ModelProfile, PinReason, RoutingRequest, SessionPin } from '../types/index.js';
|
|
18
|
+
import type { StorePort } from '../types/store-port.js';
|
|
19
|
+
import { type CacheEconomicsConfig } from './cache-economics.js';
|
|
20
|
+
export type PinAction = 'use_pin' | 'sub_route' | 'break' | 'no_pin';
|
|
21
|
+
export interface PinLookupResult {
|
|
22
|
+
readonly action: PinAction;
|
|
23
|
+
readonly pinnedModel?: ModelProfile;
|
|
24
|
+
readonly subRouteModel?: ModelProfile;
|
|
25
|
+
readonly breakReason?: PinReason;
|
|
26
|
+
}
|
|
27
|
+
export interface SessionPinnerConfig {
|
|
28
|
+
/** FR-024: max payload size (bytes or token estimate) for sub-routing. Default 2048. */
|
|
29
|
+
readonly toolResultSizeThreshold?: number;
|
|
30
|
+
/** Optional persistence — pins survive process restart when set. */
|
|
31
|
+
readonly store?: StorePort;
|
|
32
|
+
/** FR-008 rule #4: cache-warmup economics thresholds. */
|
|
33
|
+
readonly cacheEconomicsConfig?: CacheEconomicsConfig;
|
|
34
|
+
/**
|
|
35
|
+
* Break pin when estimated input tokens exceed the pinned model's
|
|
36
|
+
* max_input_tokens multiplied by this margin. Default 0.90.
|
|
37
|
+
*/
|
|
38
|
+
readonly contextOverflowSafetyMargin?: number;
|
|
39
|
+
}
|
|
40
|
+
export declare class SessionPinner {
|
|
41
|
+
private readonly pins;
|
|
42
|
+
private readonly toolResultSizeThreshold;
|
|
43
|
+
private readonly store;
|
|
44
|
+
private readonly cacheEconomicsConfig;
|
|
45
|
+
private readonly contextOverflowSafetyMargin;
|
|
46
|
+
constructor(config?: SessionPinnerConfig);
|
|
47
|
+
/**
|
|
48
|
+
* Hydrate in-memory pin state for a session from persistent storage.
|
|
49
|
+
* Call on session start after a pi restart so lookupPin stays synchronous.
|
|
50
|
+
*/
|
|
51
|
+
restoreSessionPin(sessionId: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Synchronous pin lookup — must complete in <1ms.
|
|
54
|
+
* All data is in-memory (Map); no I/O.
|
|
55
|
+
*/
|
|
56
|
+
lookupPin(request: RoutingRequest, fleet: readonly ModelProfile[]): PinLookupResult;
|
|
57
|
+
/**
|
|
58
|
+
* Create or update a session pin after a routing decision.
|
|
59
|
+
*/
|
|
60
|
+
recordPin(sessionId: string, modelId: string, reason: PinReason): SessionPin;
|
|
61
|
+
/**
|
|
62
|
+
* Delete a session pin — used by loop escalation or external callers.
|
|
63
|
+
*/
|
|
64
|
+
breakPin(sessionId: string): void;
|
|
65
|
+
/**
|
|
66
|
+
* Hydrate a pin from persistent storage (e.g. SQLite restore).
|
|
67
|
+
*/
|
|
68
|
+
loadPin(pin: SessionPin): void;
|
|
69
|
+
/**
|
|
70
|
+
* Read-only access to the current pin (telemetry, inspection).
|
|
71
|
+
*/
|
|
72
|
+
getPin(sessionId: string): SessionPin | null;
|
|
73
|
+
private evaluateBreakRules;
|
|
74
|
+
private evaluateContextOverflowBreak;
|
|
75
|
+
private evaluateCacheEconomicsBreak;
|
|
76
|
+
private handleForceOverride;
|
|
77
|
+
private persistPin;
|
|
78
|
+
private deletePersistedPin;
|
|
79
|
+
private evaluateSubRouting;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=session-pinner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-pinner.d.ts","sourceRoot":"","sources":["../../../src/domain/pinning/session-pinner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,cAAc,EACd,UAAU,EACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,sBAAsB,CAAC;AAI9B,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,aAAa,CAAC,EAAE,YAAY,CAAC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,mBAAmB;IAClC,wFAAwF;IACxF,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,oEAAoE;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD;;;OAGG;IACH,QAAQ,CAAC,2BAA2B,CAAC,EAAE,MAAM,CAAC;CAC/C;AASD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiC;IACtD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAmC;IACxE,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAS;gBAEzC,MAAM,CAAC,EAAE,mBAAmB;IASxC;;;OAGG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWzD;;;OAGG;IACH,SAAS,CACP,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,SAAS,YAAY,EAAE,GAC7B,eAAe;IAmClB;;OAEG;IACH,SAAS,CACP,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,GAChB,UAAU;IAuBb;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKjC;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAK9B;;OAEG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAM5C,OAAO,CAAC,kBAAkB;IA4C1B,OAAO,CAAC,4BAA4B;IAyBpC,OAAO,CAAC,2BAA2B;IA+CnC,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,kBAAkB;CAuC3B"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session pinner — FR-006, FR-007, FR-008, FR-024.
|
|
3
|
+
*
|
|
4
|
+
* Pins a session to one model after the initial routing decision.
|
|
5
|
+
* Pin holds across subsequent turns until a qualified break event.
|
|
6
|
+
*
|
|
7
|
+
* Break rules (FR-008, exhaustive):
|
|
8
|
+
* 1. History compaction
|
|
9
|
+
* 2. Context overflow (estimated tokens exceed pinned model window)
|
|
10
|
+
* 3. Explicit operator/user override
|
|
11
|
+
* 4. Qualified loop escalation (threshold managed externally)
|
|
12
|
+
* 5. Cache-warmup economics when a cross-provider switch is proposed
|
|
13
|
+
*
|
|
14
|
+
* Sub-routing (FR-024): tool-result turns below the payload threshold
|
|
15
|
+
* may use an economical model on the same provider without breaking the pin.
|
|
16
|
+
*/
|
|
17
|
+
import { evaluateCacheEconomics, } from './cache-economics.js';
|
|
18
|
+
// ─── Defaults ─────────────────────────────────────────────────────────────────
|
|
19
|
+
const DEFAULT_TOOL_RESULT_SIZE_THRESHOLD = 2048;
|
|
20
|
+
const DEFAULT_CONTEXT_OVERFLOW_SAFETY_MARGIN = 0.9;
|
|
21
|
+
// ─── SessionPinner ────────────────────────────────────────────────────────────
|
|
22
|
+
export class SessionPinner {
|
|
23
|
+
pins = new Map();
|
|
24
|
+
toolResultSizeThreshold;
|
|
25
|
+
store;
|
|
26
|
+
cacheEconomicsConfig;
|
|
27
|
+
contextOverflowSafetyMargin;
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.toolResultSizeThreshold =
|
|
30
|
+
config?.toolResultSizeThreshold ?? DEFAULT_TOOL_RESULT_SIZE_THRESHOLD;
|
|
31
|
+
this.store = config?.store;
|
|
32
|
+
this.cacheEconomicsConfig = config?.cacheEconomicsConfig;
|
|
33
|
+
this.contextOverflowSafetyMargin =
|
|
34
|
+
config?.contextOverflowSafetyMargin ?? DEFAULT_CONTEXT_OVERFLOW_SAFETY_MARGIN;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Hydrate in-memory pin state for a session from persistent storage.
|
|
38
|
+
* Call on session start after a pi restart so lookupPin stays synchronous.
|
|
39
|
+
*/
|
|
40
|
+
async restoreSessionPin(sessionId) {
|
|
41
|
+
if (!this.store) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const pin = await this.store.getSessionPin(sessionId);
|
|
45
|
+
if (pin) {
|
|
46
|
+
this.pins.set(sessionId, pin);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Synchronous pin lookup — must complete in <1ms.
|
|
51
|
+
* All data is in-memory (Map); no I/O.
|
|
52
|
+
*/
|
|
53
|
+
lookupPin(request, fleet) {
|
|
54
|
+
const pin = this.pins.get(request.session_id);
|
|
55
|
+
if (!pin) {
|
|
56
|
+
return { action: 'no_pin' };
|
|
57
|
+
}
|
|
58
|
+
// ── Break rule evaluation (FR-008) ──────────────────────────────────────
|
|
59
|
+
const breakResult = this.evaluateBreakRules(request, pin, fleet);
|
|
60
|
+
if (breakResult) {
|
|
61
|
+
return breakResult;
|
|
62
|
+
}
|
|
63
|
+
// ── Sub-routing (FR-024) ────────────────────────────────────────────────
|
|
64
|
+
const subRouteResult = this.evaluateSubRouting(request, pin, fleet);
|
|
65
|
+
if (subRouteResult) {
|
|
66
|
+
return subRouteResult;
|
|
67
|
+
}
|
|
68
|
+
// ── Default: use pinned model (FR-006, FR-007) ──────────────────────────
|
|
69
|
+
const pinnedModel = fleet.find((m) => m.id === pin.pinned_model_id && m.healthy !== false);
|
|
70
|
+
if (!pinnedModel) {
|
|
71
|
+
this.breakPin(request.session_id);
|
|
72
|
+
return { action: 'no_pin' };
|
|
73
|
+
}
|
|
74
|
+
return { action: 'use_pin', pinnedModel };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create or update a session pin after a routing decision.
|
|
78
|
+
*/
|
|
79
|
+
recordPin(sessionId, modelId, reason) {
|
|
80
|
+
const now = new Date().toISOString();
|
|
81
|
+
const existing = this.pins.get(sessionId);
|
|
82
|
+
const pin = {
|
|
83
|
+
session_id: sessionId,
|
|
84
|
+
pinned_model_id: modelId,
|
|
85
|
+
pin_reason: reason,
|
|
86
|
+
has_ever_switched: existing
|
|
87
|
+
? existing.pinned_model_id !== modelId || existing.has_ever_switched
|
|
88
|
+
: false,
|
|
89
|
+
consecutive_upstream_errors: 0,
|
|
90
|
+
consecutive_tool_failures: existing?.consecutive_tool_failures ?? 0,
|
|
91
|
+
last_tool_failure_signature: existing?.last_tool_failure_signature ?? null,
|
|
92
|
+
created_at: existing?.created_at ?? now,
|
|
93
|
+
updated_at: now,
|
|
94
|
+
};
|
|
95
|
+
this.pins.set(sessionId, pin);
|
|
96
|
+
this.persistPin(pin);
|
|
97
|
+
return pin;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Delete a session pin — used by loop escalation or external callers.
|
|
101
|
+
*/
|
|
102
|
+
breakPin(sessionId) {
|
|
103
|
+
this.pins.delete(sessionId);
|
|
104
|
+
this.deletePersistedPin(sessionId);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Hydrate a pin from persistent storage (e.g. SQLite restore).
|
|
108
|
+
*/
|
|
109
|
+
loadPin(pin) {
|
|
110
|
+
this.pins.set(pin.session_id, pin);
|
|
111
|
+
this.persistPin(pin);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Read-only access to the current pin (telemetry, inspection).
|
|
115
|
+
*/
|
|
116
|
+
getPin(sessionId) {
|
|
117
|
+
return this.pins.get(sessionId) ?? null;
|
|
118
|
+
}
|
|
119
|
+
// ─── Break rules (FR-008) ───────────────────────────────────────────────────
|
|
120
|
+
evaluateBreakRules(request, pin, fleet) {
|
|
121
|
+
// 1. History compaction → break pin, allow full re-route
|
|
122
|
+
if (request.compaction_flag) {
|
|
123
|
+
this.breakPin(request.session_id);
|
|
124
|
+
return { action: 'break', breakReason: 'compaction' };
|
|
125
|
+
}
|
|
126
|
+
// 2. Context overflow → break pin when input exceeds pinned model window
|
|
127
|
+
const overflowResult = this.evaluateContextOverflowBreak(request, pin, fleet);
|
|
128
|
+
if (overflowResult) {
|
|
129
|
+
return overflowResult;
|
|
130
|
+
}
|
|
131
|
+
// 3. Explicit operator / user override → pin to forced model
|
|
132
|
+
if (request.force_model_id) {
|
|
133
|
+
return this.handleForceOverride(request, pin, fleet);
|
|
134
|
+
}
|
|
135
|
+
// 4. Loop escalation — threshold tracking is on the pin record;
|
|
136
|
+
// the loop_escalation pipeline stage calls breakPin() externally
|
|
137
|
+
// when the threshold fires. No evaluation here beyond what the
|
|
138
|
+
// pin record already reflects (consecutive_tool_failures).
|
|
139
|
+
// 5. Cache-warmup economics (FR-008 rule #4)
|
|
140
|
+
const cacheEconomicsResult = this.evaluateCacheEconomicsBreak(request, pin, fleet);
|
|
141
|
+
if (cacheEconomicsResult) {
|
|
142
|
+
return cacheEconomicsResult;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
evaluateContextOverflowBreak(request, pin, fleet) {
|
|
147
|
+
const pinnedModel = fleet.find((m) => m.id === pin.pinned_model_id && m.healthy !== false);
|
|
148
|
+
const maxInputTokens = pinnedModel?.limits?.max_input_tokens;
|
|
149
|
+
if (!maxInputTokens) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const tokenEstimate = request.estimated_input_tokens ?? request.prompt_text.length;
|
|
153
|
+
const effectiveLimit = maxInputTokens * this.contextOverflowSafetyMargin;
|
|
154
|
+
if (tokenEstimate > effectiveLimit) {
|
|
155
|
+
this.breakPin(request.session_id);
|
|
156
|
+
return { action: 'break', breakReason: 'context_overflow' };
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
evaluateCacheEconomicsBreak(request, pin, fleet) {
|
|
161
|
+
if (!request.candidate_model_id) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
if (request.candidate_model_id === pin.pinned_model_id) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const pinnedModel = fleet.find((m) => m.id === pin.pinned_model_id && m.healthy !== false);
|
|
168
|
+
const candidate = fleet.find((m) => m.id === request.candidate_model_id && m.healthy !== false);
|
|
169
|
+
if (!pinnedModel || !candidate) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
if (pinnedModel.provider === candidate.provider) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const tokenEstimate = request.estimated_input_tokens ?? request.prompt_text.length;
|
|
176
|
+
const econ = evaluateCacheEconomics(pin, pinnedModel, candidate, tokenEstimate, this.cacheEconomicsConfig);
|
|
177
|
+
if (econ.shouldSwitch) {
|
|
178
|
+
this.breakPin(request.session_id);
|
|
179
|
+
return { action: 'break', breakReason: 'cache_economics' };
|
|
180
|
+
}
|
|
181
|
+
return { action: 'use_pin', pinnedModel };
|
|
182
|
+
}
|
|
183
|
+
handleForceOverride(request, _pin, fleet) {
|
|
184
|
+
const forced = fleet.find((m) => m.id === request.force_model_id && m.healthy !== false);
|
|
185
|
+
if (forced) {
|
|
186
|
+
this.recordPin(request.session_id, forced.id, 'user_forced');
|
|
187
|
+
return { action: 'use_pin', pinnedModel: forced };
|
|
188
|
+
}
|
|
189
|
+
// Forced model unavailable — break pin, allow re-route
|
|
190
|
+
this.breakPin(request.session_id);
|
|
191
|
+
return { action: 'break', breakReason: 'user_forced' };
|
|
192
|
+
}
|
|
193
|
+
// ─── Persistence (StorePort) ────────────────────────────────────────────────
|
|
194
|
+
persistPin(pin) {
|
|
195
|
+
if (!this.store) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
void this.store.putSessionPin(pin).catch((error) => {
|
|
199
|
+
console.warn('Failed to persist session pin', { sessionId: pin.session_id, error });
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
deletePersistedPin(sessionId) {
|
|
203
|
+
if (!this.store) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
void this.store.deleteSessionPin(sessionId).catch((error) => {
|
|
207
|
+
console.warn('Failed to delete persisted session pin', { sessionId, error });
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
// ─── Sub-routing (FR-024) ───────────────────────────────────────────────────
|
|
211
|
+
evaluateSubRouting(request, pin, fleet) {
|
|
212
|
+
if (request.turn_type !== 'tool_result') {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const payloadSize = request.estimated_input_tokens ?? request.prompt_text.length;
|
|
216
|
+
if (payloadSize > this.toolResultSizeThreshold) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
const pinnedModel = fleet.find((m) => m.id === pin.pinned_model_id);
|
|
220
|
+
if (!pinnedModel) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const econModel = fleet.find((m) => m.tier === 'economical-cloud' &&
|
|
224
|
+
m.provider === pinnedModel.provider &&
|
|
225
|
+
m.id !== pin.pinned_model_id &&
|
|
226
|
+
m.healthy !== false);
|
|
227
|
+
if (!econModel) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
action: 'sub_route',
|
|
232
|
+
subRouteModel: econModel,
|
|
233
|
+
pinnedModel,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=session-pinner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-pinner.js","sourceRoot":"","sources":["../../../src/domain/pinning/session-pinner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-route policy — FR-024.
|
|
3
|
+
*
|
|
4
|
+
* Pure policy function that evaluates whether a tool-result turn is
|
|
5
|
+
* eligible for same-provider economical sub-routing without breaking
|
|
6
|
+
* the session pin.
|
|
7
|
+
*
|
|
8
|
+
* Rules (FR-024, exhaustive):
|
|
9
|
+
* 1. Turn must be a tool_result.
|
|
10
|
+
* 2. Payload must be below the configurable size threshold (default 2 KB).
|
|
11
|
+
* 3. An economical-cloud model on the same provider as the pin must exist
|
|
12
|
+
* and be healthy.
|
|
13
|
+
* 4. The economical model must not be the pinned model itself.
|
|
14
|
+
*
|
|
15
|
+
* When all conditions are met the caller may dispatch to the economical
|
|
16
|
+
* model. The session pin record is unchanged — the pin holds.
|
|
17
|
+
*/
|
|
18
|
+
import type { ModelProfile, RoutingRequest, SessionPin } from '../types/index.js';
|
|
19
|
+
export type SubRouteReason = 'eligible' | 'not_tool_result' | 'payload_exceeds_threshold' | 'pinned_model_not_in_fleet' | 'no_same_provider_economical';
|
|
20
|
+
export interface SubRouteEvaluation {
|
|
21
|
+
readonly eligible: boolean;
|
|
22
|
+
readonly subRouteModel?: ModelProfile;
|
|
23
|
+
readonly pinnedModel?: ModelProfile;
|
|
24
|
+
readonly reason: SubRouteReason;
|
|
25
|
+
}
|
|
26
|
+
export interface SubRoutePolicyConfig {
|
|
27
|
+
/** Max payload size (bytes or token estimate) for sub-routing. Default 2048. */
|
|
28
|
+
readonly sizeThreshold?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Select the lowest-cost healthy model from candidates (SP-085).
|
|
32
|
+
*/
|
|
33
|
+
export declare function selectLowestCostModel(candidates: readonly ModelProfile[]): ModelProfile | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Evaluate sub-route eligibility for a single request against a session pin.
|
|
36
|
+
*
|
|
37
|
+
* Designed for the hot path — no allocation beyond the return object,
|
|
38
|
+
* no I/O, deterministic.
|
|
39
|
+
*/
|
|
40
|
+
export declare function evaluateSubRoutePolicy(request: RoutingRequest, pin: SessionPin, fleet: readonly ModelProfile[], config?: SubRoutePolicyConfig): SubRouteEvaluation;
|
|
41
|
+
//# sourceMappingURL=sub-route-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sub-route-policy.d.ts","sourceRoot":"","sources":["../../../src/domain/pinning/sub-route-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIlF,MAAM,MAAM,cAAc,GACtB,UAAU,GACV,iBAAiB,GACjB,2BAA2B,GAC3B,2BAA2B,GAC3B,6BAA6B,CAAC;AAElC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,YAAY,CAAC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,gFAAgF;IAChF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAMD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,SAAS,YAAY,EAAE,GAClC,YAAY,GAAG,SAAS,CAc1B;AAID;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,MAAM,CAAC,EAAE,oBAAoB,GAC5B,kBAAkB,CAuCpB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-route policy — FR-024.
|
|
3
|
+
*
|
|
4
|
+
* Pure policy function that evaluates whether a tool-result turn is
|
|
5
|
+
* eligible for same-provider economical sub-routing without breaking
|
|
6
|
+
* the session pin.
|
|
7
|
+
*
|
|
8
|
+
* Rules (FR-024, exhaustive):
|
|
9
|
+
* 1. Turn must be a tool_result.
|
|
10
|
+
* 2. Payload must be below the configurable size threshold (default 2 KB).
|
|
11
|
+
* 3. An economical-cloud model on the same provider as the pin must exist
|
|
12
|
+
* and be healthy.
|
|
13
|
+
* 4. The economical model must not be the pinned model itself.
|
|
14
|
+
*
|
|
15
|
+
* When all conditions are met the caller may dispatch to the economical
|
|
16
|
+
* model. The session pin record is unchanged — the pin holds.
|
|
17
|
+
*/
|
|
18
|
+
// ─── Defaults ─────────────────────────────────────────────────────────────────
|
|
19
|
+
const DEFAULT_SIZE_THRESHOLD = 2048;
|
|
20
|
+
/**
|
|
21
|
+
* Select the lowest-cost healthy model from candidates (SP-085).
|
|
22
|
+
*/
|
|
23
|
+
export function selectLowestCostModel(candidates) {
|
|
24
|
+
let best;
|
|
25
|
+
let bestCost = Infinity;
|
|
26
|
+
for (const model of candidates) {
|
|
27
|
+
if (model.healthy === false)
|
|
28
|
+
continue;
|
|
29
|
+
const cost = model.pricing.fallback_cost_per_1m;
|
|
30
|
+
if (cost < bestCost) {
|
|
31
|
+
bestCost = cost;
|
|
32
|
+
best = model;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return best;
|
|
36
|
+
}
|
|
37
|
+
// ─── Policy ───────────────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Evaluate sub-route eligibility for a single request against a session pin.
|
|
40
|
+
*
|
|
41
|
+
* Designed for the hot path — no allocation beyond the return object,
|
|
42
|
+
* no I/O, deterministic.
|
|
43
|
+
*/
|
|
44
|
+
export function evaluateSubRoutePolicy(request, pin, fleet, config) {
|
|
45
|
+
const threshold = config?.sizeThreshold ?? DEFAULT_SIZE_THRESHOLD;
|
|
46
|
+
if (request.turn_type !== 'tool_result') {
|
|
47
|
+
return { eligible: false, reason: 'not_tool_result' };
|
|
48
|
+
}
|
|
49
|
+
const payloadSize = request.estimated_input_tokens ?? request.prompt_text.length;
|
|
50
|
+
if (payloadSize > threshold) {
|
|
51
|
+
return { eligible: false, reason: 'payload_exceeds_threshold' };
|
|
52
|
+
}
|
|
53
|
+
const pinnedModel = fleet.find((m) => m.id === pin.pinned_model_id);
|
|
54
|
+
if (!pinnedModel) {
|
|
55
|
+
return { eligible: false, reason: 'pinned_model_not_in_fleet' };
|
|
56
|
+
}
|
|
57
|
+
const candidates = fleet.filter((m) => m.tier === 'economical-cloud' &&
|
|
58
|
+
m.provider === pinnedModel.provider &&
|
|
59
|
+
m.id !== pin.pinned_model_id &&
|
|
60
|
+
m.healthy !== false);
|
|
61
|
+
const econModel = selectLowestCostModel(candidates);
|
|
62
|
+
if (!econModel) {
|
|
63
|
+
return { eligible: false, reason: 'no_same_provider_economical' };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
eligible: true,
|
|
67
|
+
subRouteModel: econModel,
|
|
68
|
+
pinnedModel,
|
|
69
|
+
reason: 'eligible',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=sub-route-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sub-route-policy.js","sourceRoot":"","sources":["../../../src/domain/pinning/sub-route-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAyBH,iFAAiF;AAEjF,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAEpC;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAmC;IAEnC,IAAI,IAA8B,CAAC;IACnC,IAAI,QAAQ,GAAG,QAAQ,CAAC;IAExB,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK;YAAE,SAAS;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,oBAAoB,CAAC;QAChD,IAAI,IAAI,GAAG,QAAQ,EAAE,CAAC;YACpB,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iFAAiF;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAuB,EACvB,GAAe,EACf,KAA8B,EAC9B,MAA6B;IAE7B,MAAM,SAAS,GAAG,MAAM,EAAE,aAAa,IAAI,sBAAsB,CAAC;IAElE,IAAI,OAAO,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;QACxC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IACxD,CAAC;IAED,MAAM,WAAW,GACf,OAAO,CAAC,sBAAsB,IAAI,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC;IAE/D,IAAI,WAAW,GAAG,SAAS,EAAE,CAAC;QAC5B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;IAClE,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,eAAe,CAAC,CAAC;IACpE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;IAClE,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAC7B,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,KAAK,kBAAkB;QAC7B,CAAC,CAAC,QAAQ,KAAK,WAAW,CAAC,QAAQ;QACnC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,eAAe;QAC5B,CAAC,CAAC,OAAO,KAAK,KAAK,CACtB,CAAC;IAEF,MAAM,SAAS,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAEpD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,6BAA6B,EAAE,CAAC;IACpE,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,aAAa,EAAE,SAAS;QACxB,WAAW;QACX,MAAM,EAAE,UAAU;KACnB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline stage orchestrator — FR-001, FR-006, FR-022.
|
|
3
|
+
*
|
|
4
|
+
* Runs stages sequentially with early-exit on decision.
|
|
5
|
+
* Documented order (SP-119, #69):
|
|
6
|
+
* hardware_probe → loop_escalation → turn_envelope → context_fit → low_intensity
|
|
7
|
+
* → session_pin → triage → local_zero → triage_cloud_fallback → hydra_match
|
|
8
|
+
* → safe_default → context_overflow_fallback
|
|
9
|
+
* Any stage failure falls back to safeCloudDefault(); never throws to host.
|
|
10
|
+
*/
|
|
11
|
+
import type { ModelProfile, PriceCatalog, RoutingDecision, RoutingRequest, Tier } from '../types/index.js';
|
|
12
|
+
import type { LowIntensityConfig } from '../types/schemas.js';
|
|
13
|
+
import type { HardwareProbeConfig, SystemInfo } from '../../infrastructure/hardware/hardware-probe.js';
|
|
14
|
+
import type { HttpFetchPort, LocalZeroTierConfig } from '../../infrastructure/local/local-zero-tier.js';
|
|
15
|
+
import type { TriageVerdict } from '../triage/triage-engine.js';
|
|
16
|
+
import { type ContextFitConfig } from '../routing/context-fit.js';
|
|
17
|
+
import type { SessionPinner } from '../pinning/session-pinner.js';
|
|
18
|
+
import type { LoopEscalationConfig } from '../pinning/loop-escalation.js';
|
|
19
|
+
import { RoutingTelemetryEmitter } from '../../infrastructure/telemetry/routing-telemetry.js';
|
|
20
|
+
import type { HydraMatcher as HydraMatcherType } from '../matching/hydra-matcher.js';
|
|
21
|
+
import type { ClusterMatcher, ClusterMatchResult } from '../matching/cluster-matcher.js';
|
|
22
|
+
import { type PSuccessWeights } from '../routing/p-success-classifier.js';
|
|
23
|
+
export interface StageResult {
|
|
24
|
+
readonly decided: boolean;
|
|
25
|
+
readonly decision?: RoutingDecision;
|
|
26
|
+
readonly stage: string;
|
|
27
|
+
}
|
|
28
|
+
export type PipelineStage = (request: RoutingRequest) => Promise<StageResult>;
|
|
29
|
+
/** Canonical pipeline stage order — keep README/specs in sync (SP-119). */
|
|
30
|
+
export declare const PIPELINE_STAGE_ORDER: readonly ["hardware_probe", "loop_escalation", "turn_envelope", "context_fit", "low_intensity", "session_pin", "triage", "local_zero", "triage_cloud_fallback", "hydra_match", "safe_default", "context_overflow_fallback"];
|
|
31
|
+
export type PipelineStageName = (typeof PIPELINE_STAGE_ORDER)[number];
|
|
32
|
+
/** Inputs for local_zero eligibility beyond trivial-only triage (SP-111, #59). */
|
|
33
|
+
export interface LocalEligibleInput {
|
|
34
|
+
readonly triageVerdict: TriageVerdict | null;
|
|
35
|
+
readonly tierHint: Tier | null;
|
|
36
|
+
readonly lowIntensityScore: number | null;
|
|
37
|
+
readonly highThreshold: number;
|
|
38
|
+
readonly clusterMatch: ClusterMatchResult | null;
|
|
39
|
+
}
|
|
40
|
+
export interface LocalEligibleResult {
|
|
41
|
+
readonly eligible: boolean;
|
|
42
|
+
readonly reason: string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Disjunction: triage trivial OR low-intensity zero-tier hint (high confidence)
|
|
46
|
+
* OR high-confidence zero-tier cluster match.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveLocalEligible(input: LocalEligibleInput): LocalEligibleResult;
|
|
49
|
+
export interface PipelineOptions {
|
|
50
|
+
readonly hardwareConfig?: HardwareProbeConfig;
|
|
51
|
+
readonly localConfig?: LocalZeroTierConfig;
|
|
52
|
+
readonly systemInfoProvider?: () => Promise<SystemInfo>;
|
|
53
|
+
readonly httpFetchPort?: HttpFetchPort;
|
|
54
|
+
readonly sessionPinner?: SessionPinner;
|
|
55
|
+
readonly loopEscalationConfig?: LoopEscalationConfig;
|
|
56
|
+
readonly telemetryEmitter?: RoutingTelemetryEmitter;
|
|
57
|
+
readonly hydraMatcher?: HydraMatcherType;
|
|
58
|
+
readonly clusterMatcher?: ClusterMatcher;
|
|
59
|
+
readonly lowIntensityConfig?: LowIntensityConfig;
|
|
60
|
+
readonly priceCatalog?: PriceCatalog | null;
|
|
61
|
+
readonly contextFitConfig?: ContextFitConfig;
|
|
62
|
+
/** Preloaded P(success) weights for tests; lazy-loads artifact when omitted (SP-105). */
|
|
63
|
+
readonly pSuccessWeights?: PSuccessWeights;
|
|
64
|
+
readonly pSuccessWeightsPath?: string;
|
|
65
|
+
}
|
|
66
|
+
export declare class RouterPipeline {
|
|
67
|
+
private readonly stages;
|
|
68
|
+
private readonly fleet;
|
|
69
|
+
private readonly options;
|
|
70
|
+
/** Per-route transient fleet — defaults to constructor fleet. */
|
|
71
|
+
private activeFleet;
|
|
72
|
+
/** Unfiltered fleet for overflow escalation (SP-095). */
|
|
73
|
+
private fullFleet;
|
|
74
|
+
/** Per-route transient state — reset on each route() call. */
|
|
75
|
+
private currentHardwareResult;
|
|
76
|
+
private currentTriageResult;
|
|
77
|
+
private currentHydraResult;
|
|
78
|
+
private currentClusterMatch;
|
|
79
|
+
private currentTierHint;
|
|
80
|
+
private currentTierHintReasonCode;
|
|
81
|
+
private currentLowIntensityScore;
|
|
82
|
+
private currentPSuccessCheap;
|
|
83
|
+
private currentPSuccessAlpha;
|
|
84
|
+
private currentExpectedCostByTier;
|
|
85
|
+
private currentLocalEligibleReason;
|
|
86
|
+
private pSuccessWeightsLoaded;
|
|
87
|
+
private cachedPSuccessWeights;
|
|
88
|
+
private currentContextFitRejected;
|
|
89
|
+
private currentContextFitViableCount;
|
|
90
|
+
private contextOverflowPreferredProvider;
|
|
91
|
+
private contextOverflowTriggered;
|
|
92
|
+
constructor(fleet: readonly ModelProfile[], options?: PipelineOptions);
|
|
93
|
+
route(request: RoutingRequest, fleetOverride?: readonly ModelProfile[]): Promise<RoutingDecision>;
|
|
94
|
+
/**
|
|
95
|
+
* SP-080: move Google/Gemini profiles to the end of the fleet when prior tool
|
|
96
|
+
* calls exist so tier `.find()` passes prefer non-Gemini models first.
|
|
97
|
+
* Honors `force_model_id` by leaving fleet order unchanged.
|
|
98
|
+
*/
|
|
99
|
+
private prioritizeFleetForToolHistory;
|
|
100
|
+
/** Attach privacy-safe dataset features captured during pipeline stages (SP-057, SP-119). */
|
|
101
|
+
private attachFeatures;
|
|
102
|
+
private mergeFeatureCandidates;
|
|
103
|
+
private resolveFailedStage;
|
|
104
|
+
private logPipelineError;
|
|
105
|
+
private redactPromptFromError;
|
|
106
|
+
private emitPipelineErrorTelemetry;
|
|
107
|
+
/** Step 7: emit routing telemetry after decision (T040). */
|
|
108
|
+
private emitTelemetry;
|
|
109
|
+
private withEstimatedCost;
|
|
110
|
+
private buildFallbackDecision;
|
|
111
|
+
private shouldAttemptContextOverflowFallback;
|
|
112
|
+
private buildContextOverflowFallbackDecision;
|
|
113
|
+
/**
|
|
114
|
+
* SP-095: after safe_default, escalate to largest-fit model when economical
|
|
115
|
+
* models cannot fit the current context.
|
|
116
|
+
*/
|
|
117
|
+
private contextOverflowFallback;
|
|
118
|
+
/**
|
|
119
|
+
* SP-022: economical-cloud default when no earlier stage decides.
|
|
120
|
+
* Defers to context_overflow_fallback when economical models were context-rejected.
|
|
121
|
+
*/
|
|
122
|
+
private safeDefaultStage;
|
|
123
|
+
private markContextOverflowFromPin;
|
|
124
|
+
private hardwareProbeStage;
|
|
125
|
+
/**
|
|
126
|
+
* SP-093: filter fleet to models whose context window fits estimated input
|
|
127
|
+
* tokens before session pin and HyDRA matching.
|
|
128
|
+
*/
|
|
129
|
+
private contextFitStage;
|
|
130
|
+
/**
|
|
131
|
+
* SC-007: classification_only MUST NOT dispatch full local.
|
|
132
|
+
* Eligibility: triage trivial OR low-intensity zero-tier hint OR zero-tier cluster (SP-111).
|
|
133
|
+
*/
|
|
134
|
+
private localZeroTierStage;
|
|
135
|
+
private triage;
|
|
136
|
+
/**
|
|
137
|
+
* Economical-cloud fallback for trivial prompts after local zero-tier is skipped
|
|
138
|
+
* or unavailable (PRD Step 4 cloud fallback).
|
|
139
|
+
*/
|
|
140
|
+
private triageCloudFallback;
|
|
141
|
+
private sessionPin;
|
|
142
|
+
/**
|
|
143
|
+
* After a routing decision, persist an initial pin when none exists.
|
|
144
|
+
* Sub-routes and already-pinned decisions skip persistence.
|
|
145
|
+
*/
|
|
146
|
+
private persistPinIfNeeded;
|
|
147
|
+
private static readonly TURN_TIER_MAP;
|
|
148
|
+
private turnEnvelope;
|
|
149
|
+
/**
|
|
150
|
+
* Runs after turn_envelope and context_fit, before session_pin. Computes low-intensity score
|
|
151
|
+
* from structural signals and optional cluster match; sets tier_hint and constrains
|
|
152
|
+
* the active fleet for subsequent HyDRA matching when confidence is high.
|
|
153
|
+
*/
|
|
154
|
+
private lowIntensityGate;
|
|
155
|
+
private selectExpectedCostTierHint;
|
|
156
|
+
private logExpectedCostExplain;
|
|
157
|
+
private resolvePSuccessWeights;
|
|
158
|
+
private resolveTierHint;
|
|
159
|
+
private resolveLowIntensityTierHint;
|
|
160
|
+
private isLocalZeroTierReady;
|
|
161
|
+
private resolveLowIntensityReasonCode;
|
|
162
|
+
private resolveHighIntensityReasonCode;
|
|
163
|
+
private constrainFleetToTierHint;
|
|
164
|
+
/**
|
|
165
|
+
* Observational loop escalation: detects repeated identical tool failures
|
|
166
|
+
* and re-pins the session to a frontier-capable tier.
|
|
167
|
+
*
|
|
168
|
+
* Runs before turn_envelope and session_pin so it can modify pin state.
|
|
169
|
+
* Never returns decided: true — turnEnvelope or sessionPin picks up the
|
|
170
|
+
* (potentially escalated) pin on subsequent stages.
|
|
171
|
+
*/
|
|
172
|
+
private loopEscalation;
|
|
173
|
+
/**
|
|
174
|
+
* Step 5: HyDRA embedding matcher for ambiguous prompts (T050).
|
|
175
|
+
* Scores fleet candidates via embedding cosine similarity with shortfall gate.
|
|
176
|
+
* Pass-through when no matcher is configured.
|
|
177
|
+
*/
|
|
178
|
+
private hydraMatcher;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=router-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router-pipeline.d.ts","sourceRoot":"","sources":["../../../src/domain/pipeline/router-pipeline.ts"],"names":[],"mappings":"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"}
|