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,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expected-cost tier selection — SP-106, #68.
|
|
3
|
+
*
|
|
4
|
+
* E[cost_T] = P(success | T) × directCost(T)
|
|
5
|
+
* + (1 - P(success | T)) × E[cost_escalation]
|
|
6
|
+
*
|
|
7
|
+
* Selects the tier minimizing adjusted expected cost subject to context-fit,
|
|
8
|
+
* local readiness, and pin/cache economics (FR-008).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ModelProfile,
|
|
13
|
+
PriceCatalog,
|
|
14
|
+
SessionPin,
|
|
15
|
+
Tier,
|
|
16
|
+
} from '../types/index.js';
|
|
17
|
+
import { resolveFrugalityCostPer1M } from '../../infrastructure/pricing/price-broker.js';
|
|
18
|
+
import {
|
|
19
|
+
evaluateCacheEconomics,
|
|
20
|
+
type CacheEconomicsConfig,
|
|
21
|
+
} from '../pinning/cache-economics.js';
|
|
22
|
+
import { selectLowestCostModel } from '../pinning/sub-route-policy.js';
|
|
23
|
+
|
|
24
|
+
/** Frontier-tier success probability when evaluating escalation terminal cost. */
|
|
25
|
+
export const FRONTIER_P_SUCCESS = 1;
|
|
26
|
+
|
|
27
|
+
/** Minimum per-1M-token spread required before economical tiers compete. */
|
|
28
|
+
export const MIN_PRICE_DELTA_PER_1M = 0.25;
|
|
29
|
+
|
|
30
|
+
export interface ExpectedCostBreakdown {
|
|
31
|
+
readonly tier: Tier;
|
|
32
|
+
readonly pSuccess: number;
|
|
33
|
+
readonly costPer1M: number;
|
|
34
|
+
readonly directCostUsd: number;
|
|
35
|
+
readonly escalationCostUsd: number;
|
|
36
|
+
readonly expectedCostUsd: number;
|
|
37
|
+
readonly adjustedExpectedCostUsd: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SelectTierByExpectedCostInput {
|
|
41
|
+
readonly fleet: readonly ModelProfile[];
|
|
42
|
+
readonly priceCatalog: PriceCatalog | null;
|
|
43
|
+
readonly estTokens: number;
|
|
44
|
+
readonly pSuccessCheap: number;
|
|
45
|
+
/** Cost-quality tradeoff in [0, 1]; higher favors economical tiers (SP-106). */
|
|
46
|
+
readonly alpha: number;
|
|
47
|
+
readonly localZeroReady: boolean;
|
|
48
|
+
readonly pinnedModel?: ModelProfile;
|
|
49
|
+
readonly sessionPin?: SessionPin;
|
|
50
|
+
readonly cacheEconomicsConfig?: CacheEconomicsConfig;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SelectTierByExpectedCostResult {
|
|
54
|
+
readonly tierHint: Tier | null;
|
|
55
|
+
readonly reasonCode: string;
|
|
56
|
+
readonly tierCosts: readonly ExpectedCostBreakdown[];
|
|
57
|
+
readonly rationale: string;
|
|
58
|
+
readonly blockedByPinEconomics: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function clamp01(value: number): number {
|
|
62
|
+
if (value <= 0) return 0;
|
|
63
|
+
if (value >= 1) return 1;
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function directCostUsd(costPer1M: number, estTokens: number): number {
|
|
68
|
+
return (estTokens / 1_000_000) * costPer1M;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveCheapestModelForTier(
|
|
72
|
+
fleet: readonly ModelProfile[],
|
|
73
|
+
tier: Tier,
|
|
74
|
+
): ModelProfile | undefined {
|
|
75
|
+
return selectLowestCostModel(fleet.filter((model) => model.tier === tier));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve representative per-1M cost for a tier using subscription-aware pricing (SP-096).
|
|
80
|
+
*/
|
|
81
|
+
export function resolveTierCostPer1M(
|
|
82
|
+
tier: Tier,
|
|
83
|
+
fleet: readonly ModelProfile[],
|
|
84
|
+
priceCatalog: PriceCatalog | null,
|
|
85
|
+
): number {
|
|
86
|
+
const model = resolveCheapestModelForTier(fleet, tier);
|
|
87
|
+
if (!model) {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
return resolveFrugalityCostPer1M(model, priceCatalog);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolvePSuccessForTier(tier: Tier, pSuccessCheap: number): number {
|
|
94
|
+
if (tier === 'frontier-cloud') {
|
|
95
|
+
return FRONTIER_P_SUCCESS;
|
|
96
|
+
}
|
|
97
|
+
return clamp01(pSuccessCheap);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function applyCostQualityAlpha(
|
|
101
|
+
expectedCostUsd: number,
|
|
102
|
+
pSuccess: number,
|
|
103
|
+
escalationCostUsd: number,
|
|
104
|
+
alpha: number,
|
|
105
|
+
): number {
|
|
106
|
+
const riskPenalty = (1 - clamp01(alpha)) * (1 - clamp01(pSuccess)) * escalationCostUsd;
|
|
107
|
+
return expectedCostUsd + riskPenalty;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Compute per-tier expected routing cost under uncertainty.
|
|
112
|
+
*
|
|
113
|
+
* `priceCatalog` drives subscription virtual cost resolution; pass `costPer1M`
|
|
114
|
+
* via options when the caller already resolved tier pricing.
|
|
115
|
+
*/
|
|
116
|
+
export function computeExpectedCost(
|
|
117
|
+
tier: Tier,
|
|
118
|
+
pSuccess: number,
|
|
119
|
+
priceCatalog: PriceCatalog | null,
|
|
120
|
+
estTokens: number,
|
|
121
|
+
escalationCostUsd: number,
|
|
122
|
+
options?: {
|
|
123
|
+
readonly alpha?: number;
|
|
124
|
+
readonly costPer1M?: number;
|
|
125
|
+
readonly fleet?: readonly ModelProfile[];
|
|
126
|
+
},
|
|
127
|
+
): ExpectedCostBreakdown {
|
|
128
|
+
const alpha = options?.alpha ?? 1;
|
|
129
|
+
const fleet = options?.fleet ?? [];
|
|
130
|
+
const costPer1M =
|
|
131
|
+
options?.costPer1M ?? resolveTierCostPer1M(tier, fleet, priceCatalog);
|
|
132
|
+
const direct = directCostUsd(costPer1M, estTokens);
|
|
133
|
+
const boundedPSuccess = resolvePSuccessForTier(tier, pSuccess);
|
|
134
|
+
const expectedCostUsd =
|
|
135
|
+
boundedPSuccess * direct + (1 - boundedPSuccess) * escalationCostUsd;
|
|
136
|
+
const adjustedExpectedCostUsd = applyCostQualityAlpha(
|
|
137
|
+
expectedCostUsd,
|
|
138
|
+
boundedPSuccess,
|
|
139
|
+
escalationCostUsd,
|
|
140
|
+
alpha,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
tier,
|
|
145
|
+
pSuccess: boundedPSuccess,
|
|
146
|
+
costPer1M,
|
|
147
|
+
directCostUsd: direct,
|
|
148
|
+
escalationCostUsd,
|
|
149
|
+
expectedCostUsd,
|
|
150
|
+
adjustedExpectedCostUsd,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function listViableTiers(
|
|
155
|
+
fleet: readonly ModelProfile[],
|
|
156
|
+
localZeroReady: boolean,
|
|
157
|
+
): Tier[] {
|
|
158
|
+
const tiers = new Set<Tier>();
|
|
159
|
+
|
|
160
|
+
for (const model of fleet) {
|
|
161
|
+
if (model.healthy === false) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (model.tier === 'zero-tier' && !localZeroReady) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
tiers.add(model.tier);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return [...tiers];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildEscalationCostUsd(
|
|
174
|
+
tier: Tier,
|
|
175
|
+
cheapDirectUsd: number,
|
|
176
|
+
frontierDirectUsd: number,
|
|
177
|
+
): number {
|
|
178
|
+
if (tier === 'frontier-cloud') {
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
return cheapDirectUsd + frontierDirectUsd;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function hasSignificantPriceDelta(
|
|
185
|
+
economicalCostPer1M: number,
|
|
186
|
+
frontierCostPer1M: number,
|
|
187
|
+
): boolean {
|
|
188
|
+
return frontierCostPer1M - economicalCostPer1M >= MIN_PRICE_DELTA_PER_1M;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function pickCheaperTierModel(
|
|
192
|
+
fleet: readonly ModelProfile[],
|
|
193
|
+
tier: Tier,
|
|
194
|
+
): ModelProfile | undefined {
|
|
195
|
+
return resolveCheapestModelForTier(fleet, tier);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function shouldKeepPinnedTier(
|
|
199
|
+
input: SelectTierByExpectedCostInput,
|
|
200
|
+
selectedTier: Tier,
|
|
201
|
+
): boolean {
|
|
202
|
+
const pinnedModel = input.pinnedModel;
|
|
203
|
+
const sessionPin = input.sessionPin;
|
|
204
|
+
if (!pinnedModel || !sessionPin) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const candidate = pickCheaperTierModel(input.fleet, selectedTier);
|
|
209
|
+
if (!candidate || candidate.id === pinnedModel.id) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const economics = evaluateCacheEconomics(
|
|
214
|
+
sessionPin,
|
|
215
|
+
pinnedModel,
|
|
216
|
+
candidate,
|
|
217
|
+
input.estTokens,
|
|
218
|
+
input.cacheEconomicsConfig,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
return !economics.shouldSwitch;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Compare expected cost across context-fit-viable tiers and return argmin tier hint.
|
|
226
|
+
*/
|
|
227
|
+
export function selectTierByExpectedCost(
|
|
228
|
+
input: SelectTierByExpectedCostInput,
|
|
229
|
+
): SelectTierByExpectedCostResult {
|
|
230
|
+
const viableTiers = listViableTiers(input.fleet, input.localZeroReady);
|
|
231
|
+
const frontierCostPer1M = resolveTierCostPer1M(
|
|
232
|
+
'frontier-cloud',
|
|
233
|
+
input.fleet,
|
|
234
|
+
input.priceCatalog,
|
|
235
|
+
);
|
|
236
|
+
const economicalCostPer1M = Math.min(
|
|
237
|
+
resolveTierCostPer1M('economical-cloud', input.fleet, input.priceCatalog),
|
|
238
|
+
resolveTierCostPer1M('zero-tier', input.fleet, input.priceCatalog) || Infinity,
|
|
239
|
+
);
|
|
240
|
+
const frontierDirectUsd = directCostUsd(frontierCostPer1M, input.estTokens);
|
|
241
|
+
const priceDeltaSignificant = hasSignificantPriceDelta(
|
|
242
|
+
economicalCostPer1M,
|
|
243
|
+
frontierCostPer1M,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const tierCosts = viableTiers.map((tier) => {
|
|
247
|
+
const costPer1M = resolveTierCostPer1M(tier, input.fleet, input.priceCatalog);
|
|
248
|
+
const direct = directCostUsd(costPer1M, input.estTokens);
|
|
249
|
+
const escalationCostUsd = buildEscalationCostUsd(tier, direct, frontierDirectUsd);
|
|
250
|
+
|
|
251
|
+
return computeExpectedCost(
|
|
252
|
+
tier,
|
|
253
|
+
input.pSuccessCheap,
|
|
254
|
+
input.priceCatalog,
|
|
255
|
+
input.estTokens,
|
|
256
|
+
escalationCostUsd,
|
|
257
|
+
{
|
|
258
|
+
alpha: input.alpha,
|
|
259
|
+
costPer1M,
|
|
260
|
+
fleet: input.fleet,
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (tierCosts.length === 0) {
|
|
266
|
+
return {
|
|
267
|
+
tierHint: null,
|
|
268
|
+
reasonCode: 'expected_cost_no_viable_tier',
|
|
269
|
+
tierCosts,
|
|
270
|
+
rationale: 'No viable tiers after context-fit and local readiness filters',
|
|
271
|
+
blockedByPinEconomics: false,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let best = tierCosts[0]!;
|
|
276
|
+
for (const candidate of tierCosts.slice(1)) {
|
|
277
|
+
if (candidate.adjustedExpectedCostUsd < best.adjustedExpectedCostUsd) {
|
|
278
|
+
best = candidate;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (
|
|
283
|
+
(best.tier === 'economical-cloud' || best.tier === 'zero-tier') &&
|
|
284
|
+
!priceDeltaSignificant
|
|
285
|
+
) {
|
|
286
|
+
return {
|
|
287
|
+
tierHint: null,
|
|
288
|
+
reasonCode: 'expected_cost_price_delta_insufficient',
|
|
289
|
+
tierCosts,
|
|
290
|
+
rationale:
|
|
291
|
+
'Economical tier expected cost is lowest but frontier–economical price delta is below threshold',
|
|
292
|
+
blockedByPinEconomics: false,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (shouldKeepPinnedTier(input, best.tier)) {
|
|
297
|
+
const pinnedTier = input.pinnedModel!.tier;
|
|
298
|
+
return {
|
|
299
|
+
tierHint: pinnedTier,
|
|
300
|
+
reasonCode: 'expected_cost_pin_cache_economics',
|
|
301
|
+
tierCosts,
|
|
302
|
+
rationale:
|
|
303
|
+
'Expected-cost tier switch blocked because cache reprime exceeds projected savings (FR-008)',
|
|
304
|
+
blockedByPinEconomics: true,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const rationale =
|
|
309
|
+
best.tier === 'frontier-cloud'
|
|
310
|
+
? `Frontier minimizes E[cost]=${best.adjustedExpectedCostUsd.toFixed(6)} with P(success)=${best.pSuccess.toFixed(3)}`
|
|
311
|
+
: `Economical tier minimizes E[cost]=${best.adjustedExpectedCostUsd.toFixed(6)} with P(success)=${best.pSuccess.toFixed(3)}`;
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
tierHint: best.tier,
|
|
315
|
+
reasonCode: `expected_cost_${best.tier.replace('-', '_')}`,
|
|
316
|
+
tierCosts,
|
|
317
|
+
rationale,
|
|
318
|
+
blockedByPinEconomics: false,
|
|
319
|
+
};
|
|
320
|
+
}
|