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,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway dispatch — T020, T055, T056, FR-001, FR-017, FR-018, FR-022, FR-023.
|
|
3
|
+
*
|
|
4
|
+
* Infrastructure entry point that delegates routing to the domain pipeline.
|
|
5
|
+
* Integrates circuit breaker for resilient failover (FR-018: infra errors only),
|
|
6
|
+
* weighted distribution across same-tier model endpoints (T056), and
|
|
7
|
+
* per-key rate limiting with 429 + Retry-After responses (T057, FR-017).
|
|
8
|
+
*
|
|
9
|
+
* FR-023: preserves provider context-caching semantics on same-provider
|
|
10
|
+
* request paths by tracking the last-used provider per session.
|
|
11
|
+
*
|
|
12
|
+
* SP-097: Cursor subscription quota exhaustion detection and failover to
|
|
13
|
+
* `cursor/auto` or economical API models with `cursor_quota_exhausted` telemetry.
|
|
14
|
+
*/
|
|
15
|
+
import { RouterPipeline } from '../../domain/pipeline/router-pipeline.js';
|
|
16
|
+
import { CircuitBreaker, isInfraError } from './circuit-breaker.js';
|
|
17
|
+
const CURSOR_QUOTA_MESSAGE_PATTERNS = [
|
|
18
|
+
/usage\s+limit/i,
|
|
19
|
+
/hit your usage/i,
|
|
20
|
+
/quota\s+exhaust/i,
|
|
21
|
+
/subscription.*limit/i,
|
|
22
|
+
/switch to auto/i,
|
|
23
|
+
];
|
|
24
|
+
const CURSOR_QUOTA_ERROR_CODES = new Set([
|
|
25
|
+
'usage_limit_exceeded',
|
|
26
|
+
'resource_exhausted',
|
|
27
|
+
'quota_exceeded',
|
|
28
|
+
'insufficient_quota',
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* Classify Cursor subscription quota / usage-limit errors from dogfood evidence
|
|
32
|
+
* (#70: "You've hit your usage limit… Switch to Auto for more usage…").
|
|
33
|
+
*/
|
|
34
|
+
export function isCursorQuotaExhaustedError(error) {
|
|
35
|
+
const code = error.code?.trim().toLowerCase();
|
|
36
|
+
if (code && CURSOR_QUOTA_ERROR_CODES.has(code)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const message = error.message ?? '';
|
|
40
|
+
if (message.length > 0 && CURSOR_QUOTA_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (error.statusCode === 429 && /usage|quota|limit/i.test(message)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
/** Cursor frontier models billed against subscription quota (composer-*, cursor/*). */
|
|
49
|
+
export function isCursorSubscriptionModel(model) {
|
|
50
|
+
const provider = model.provider.trim().toLowerCase();
|
|
51
|
+
if (provider === 'cursor') {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const id = model.id.trim().toLowerCase();
|
|
55
|
+
return id.startsWith('composer-') || id.startsWith('cursor/');
|
|
56
|
+
}
|
|
57
|
+
/** Cursor opaque-auto models (`cursor/auto`, `cursor/*`). */
|
|
58
|
+
export function isCursorAutoModel(model) {
|
|
59
|
+
const id = model.id.trim().toLowerCase();
|
|
60
|
+
return id === 'cursor/auto' || id === 'auto' || id.startsWith('cursor/');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Whether a provider error should trigger model failover (infra or Cursor quota).
|
|
64
|
+
* Quota exhaustion on Cursor subscription models must not trip the circuit breaker.
|
|
65
|
+
*/
|
|
66
|
+
export function shouldFailoverOnProviderError(error, failedModel) {
|
|
67
|
+
if (isCursorQuotaExhaustedError(error)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (failedModel && isCursorSubscriptionModel(failedModel) && error.statusCode === 429) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return isInfraError(error);
|
|
74
|
+
}
|
|
75
|
+
// ─── Weighted model selection (T056) ─────────────────────────────────────────
|
|
76
|
+
/**
|
|
77
|
+
* Select a model from same-tier candidates using inverse-cost weighting.
|
|
78
|
+
* Models with lower cost get proportionally more traffic. Falls back to
|
|
79
|
+
* uniform random when all costs are equal.
|
|
80
|
+
*/
|
|
81
|
+
export function weightedSelect(candidates) {
|
|
82
|
+
if (candidates.length === 0)
|
|
83
|
+
return undefined;
|
|
84
|
+
if (candidates.length === 1)
|
|
85
|
+
return candidates[0];
|
|
86
|
+
const costs = candidates.map((c) => c.pricing.fallback_cost_per_1m);
|
|
87
|
+
const maxCost = Math.max(...costs);
|
|
88
|
+
if (maxCost === 0) {
|
|
89
|
+
return candidates[Math.floor(Math.random() * candidates.length)];
|
|
90
|
+
}
|
|
91
|
+
const weights = costs.map((c) => (maxCost + 1) / (c + 1));
|
|
92
|
+
const totalWeight = weights.reduce((sum, w) => sum + w, 0);
|
|
93
|
+
let random = Math.random() * totalWeight;
|
|
94
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
95
|
+
random -= weights[i];
|
|
96
|
+
if (random <= 0)
|
|
97
|
+
return candidates[i];
|
|
98
|
+
}
|
|
99
|
+
return candidates[candidates.length - 1];
|
|
100
|
+
}
|
|
101
|
+
export class GatewayDispatch {
|
|
102
|
+
pipeline;
|
|
103
|
+
fleet;
|
|
104
|
+
cacheMarkers = new Map();
|
|
105
|
+
circuitBreaker;
|
|
106
|
+
rateLimiter;
|
|
107
|
+
quotaExhaustedModels = new Set();
|
|
108
|
+
constructor(fleet, options) {
|
|
109
|
+
this.fleet = fleet;
|
|
110
|
+
this.pipeline = new RouterPipeline(fleet, options);
|
|
111
|
+
this.circuitBreaker = new CircuitBreaker(options?.circuitBreakerConfig);
|
|
112
|
+
this.rateLimiter = options?.rateLimiter;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Route a single request through the pipeline.
|
|
116
|
+
*
|
|
117
|
+
* 1. Run pipeline to get routing decision.
|
|
118
|
+
* 2. Verify circuit breaker allows dispatch to the selected model.
|
|
119
|
+
* 3. If the selected model's circuit is open, attempt failover to a
|
|
120
|
+
* same-tier alternative (T056).
|
|
121
|
+
* 4. Update FR-023 cache marker.
|
|
122
|
+
*
|
|
123
|
+
* Never throws.
|
|
124
|
+
*/
|
|
125
|
+
async dispatch(request, options) {
|
|
126
|
+
const effectiveFleet = options?.effectiveFleet ?? this.fleet;
|
|
127
|
+
const decision = await this.pipeline.route(request, effectiveFleet);
|
|
128
|
+
const finalDecision = this.applyCircuitBreaker(decision, effectiveFleet);
|
|
129
|
+
this.updateCacheMarker(request.session_id, finalDecision);
|
|
130
|
+
return finalDecision;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Route with per-key rate limiting (T057, FR-017).
|
|
134
|
+
*
|
|
135
|
+
* Checks the token bucket before routing. Returns a RateLimitResult with
|
|
136
|
+
* 429-compatible body `{ error, retry_after_seconds }` when the bucket is
|
|
137
|
+
* exhausted. Otherwise delegates to dispatch().
|
|
138
|
+
*/
|
|
139
|
+
async dispatchWithRateLimit(request, rateLimitKey) {
|
|
140
|
+
if (this.rateLimiter) {
|
|
141
|
+
const bucket = this.rateLimiter.consumeToken(rateLimitKey);
|
|
142
|
+
if (!bucket.allowed) {
|
|
143
|
+
return {
|
|
144
|
+
limited: true,
|
|
145
|
+
error: 'rate_limit_exceeded',
|
|
146
|
+
retry_after_seconds: bucket.retryAfterSeconds ?? 0,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return this.dispatch(request);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Record an upstream response outcome for circuit breaker tracking.
|
|
154
|
+
* Only infra errors trip the breaker; policy/safety rejections are ignored (FR-018).
|
|
155
|
+
* Cursor quota exhaustion is tracked for SP-097 failover without opening circuits.
|
|
156
|
+
*/
|
|
157
|
+
recordOutcome(modelId, error) {
|
|
158
|
+
if (!error) {
|
|
159
|
+
this.circuitBreaker.recordSuccess(modelId);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (isCursorQuotaExhaustedError(error)) {
|
|
163
|
+
this.quotaExhaustedModels.add(modelId);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const failedModel = this.fleet.find((m) => m.id === modelId);
|
|
167
|
+
if (failedModel && isCursorSubscriptionModel(failedModel) && error.statusCode === 429) {
|
|
168
|
+
this.quotaExhaustedModels.add(modelId);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (isInfraError(error)) {
|
|
172
|
+
this.circuitBreaker.recordFailure(modelId);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Expose circuit breaker for observability and testing. */
|
|
176
|
+
getCircuitBreaker() {
|
|
177
|
+
return this.circuitBreaker;
|
|
178
|
+
}
|
|
179
|
+
/** Whether a model recently returned a Cursor quota exhaustion error (SP-097). */
|
|
180
|
+
hasQuotaExhaustion(modelId) {
|
|
181
|
+
return this.quotaExhaustedModels.has(modelId);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Retrieve the current cache marker for a session (FR-023 inspection).
|
|
185
|
+
*/
|
|
186
|
+
getCacheMarker(sessionId) {
|
|
187
|
+
return this.cacheMarkers.get(sessionId) ?? null;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Select an alternate model when the current target is unavailable or failed.
|
|
191
|
+
* Prefers same-tier healthy models with closed circuits, then any tier.
|
|
192
|
+
* When the failed model hit Cursor quota limits, prefers `cursor/auto` then
|
|
193
|
+
* economical API models with `cursor_quota_exhausted`.
|
|
194
|
+
*/
|
|
195
|
+
selectFailover(decision, excludeModelIds = [], fleetOverride) {
|
|
196
|
+
const excluded = new Set(excludeModelIds);
|
|
197
|
+
const fleet = fleetOverride ?? this.fleet;
|
|
198
|
+
const quotaFailover = this.selectCursorQuotaFailover(decision, excluded, fleet);
|
|
199
|
+
if (quotaFailover) {
|
|
200
|
+
return quotaFailover;
|
|
201
|
+
}
|
|
202
|
+
const sameTier = fleet.filter((m) => m.tier === decision.tier &&
|
|
203
|
+
m.id !== decision.selected_model_id &&
|
|
204
|
+
!excluded.has(m.id) &&
|
|
205
|
+
m.healthy !== false &&
|
|
206
|
+
this.circuitBreaker.canDispatch(m.id));
|
|
207
|
+
const sameTierAlternative = weightedSelect(sameTier);
|
|
208
|
+
if (sameTierAlternative) {
|
|
209
|
+
return {
|
|
210
|
+
...decision,
|
|
211
|
+
selected_model_id: sameTierAlternative.id,
|
|
212
|
+
reason_code: 'circuit_breaker_failover',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const anyHealthy = fleet.filter((m) => m.id !== decision.selected_model_id &&
|
|
216
|
+
!excluded.has(m.id) &&
|
|
217
|
+
m.healthy !== false &&
|
|
218
|
+
this.circuitBreaker.canDispatch(m.id));
|
|
219
|
+
const fallback = weightedSelect(anyHealthy);
|
|
220
|
+
if (fallback) {
|
|
221
|
+
return {
|
|
222
|
+
...decision,
|
|
223
|
+
selected_model_id: fallback.id,
|
|
224
|
+
tier: fallback.tier,
|
|
225
|
+
reason_code: 'circuit_breaker_failover',
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* If the selected model's circuit is open, attempt to fail over to
|
|
232
|
+
* an alternative model on the same tier (T056 weighted distribution).
|
|
233
|
+
* Falls back to any healthy model on any tier if no same-tier alternative exists.
|
|
234
|
+
*/
|
|
235
|
+
applyCircuitBreaker(decision, fleet = this.fleet) {
|
|
236
|
+
if (this.circuitBreaker.canDispatch(decision.selected_model_id)) {
|
|
237
|
+
return decision;
|
|
238
|
+
}
|
|
239
|
+
return this.selectFailover(decision, [], fleet) ?? decision;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* SP-097: fail over from quota-exhausted Cursor models to `cursor/auto` or
|
|
243
|
+
* the cheapest viable economical API model in the scoped fleet.
|
|
244
|
+
*/
|
|
245
|
+
selectCursorQuotaFailover(decision, excluded, fleet) {
|
|
246
|
+
const failedIds = [decision.selected_model_id, ...excluded];
|
|
247
|
+
const quotaFailed = failedIds.some((id) => this.quotaExhaustedModels.has(id));
|
|
248
|
+
if (!quotaFailed) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
const failedModel = fleet.find((m) => m.id === decision.selected_model_id);
|
|
252
|
+
if (failedModel && !isCursorSubscriptionModel(failedModel)) {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
const cursorAuto = fleet.find((m) => isCursorAutoModel(m) &&
|
|
256
|
+
!excluded.has(m.id) &&
|
|
257
|
+
m.id !== decision.selected_model_id &&
|
|
258
|
+
m.healthy !== false &&
|
|
259
|
+
this.circuitBreaker.canDispatch(m.id));
|
|
260
|
+
if (cursorAuto) {
|
|
261
|
+
return {
|
|
262
|
+
...decision,
|
|
263
|
+
selected_model_id: cursorAuto.id,
|
|
264
|
+
tier: cursorAuto.tier,
|
|
265
|
+
reason_code: 'cursor_quota_exhausted',
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const economical = fleet.filter((m) => m.tier === 'economical-cloud' &&
|
|
269
|
+
!excluded.has(m.id) &&
|
|
270
|
+
m.id !== decision.selected_model_id &&
|
|
271
|
+
m.healthy !== false &&
|
|
272
|
+
!isCursorSubscriptionModel(m) &&
|
|
273
|
+
this.circuitBreaker.canDispatch(m.id));
|
|
274
|
+
const economicalFallback = weightedSelect(economical);
|
|
275
|
+
if (economicalFallback) {
|
|
276
|
+
return {
|
|
277
|
+
...decision,
|
|
278
|
+
selected_model_id: economicalFallback.id,
|
|
279
|
+
tier: economicalFallback.tier,
|
|
280
|
+
reason_code: 'cursor_quota_exhausted',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* FR-023: track the provider and cache-friendly status for the session.
|
|
287
|
+
* Sub-routed requests on the same provider preserve the existing marker
|
|
288
|
+
* rather than replacing it — the pin model's cache state is authoritative.
|
|
289
|
+
*/
|
|
290
|
+
updateCacheMarker(sessionId, decision) {
|
|
291
|
+
const model = this.fleet.find((m) => m.id === decision.selected_model_id);
|
|
292
|
+
if (!model)
|
|
293
|
+
return;
|
|
294
|
+
const existing = this.cacheMarkers.get(sessionId);
|
|
295
|
+
if (existing &&
|
|
296
|
+
existing.provider === model.provider &&
|
|
297
|
+
decision.reason_code === 'tool_result_sub_route') {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
this.cacheMarkers.set(sessionId, {
|
|
301
|
+
sessionId,
|
|
302
|
+
provider: model.provider,
|
|
303
|
+
modelId: model.id,
|
|
304
|
+
cacheFriendly: model.performance?.cache_friendly ?? false,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=gateway-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-dispatch.js","sourceRoot":"","sources":["../../../src/infrastructure/gateway/gateway-dispatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardware probe — T044, FR-012.
|
|
3
|
+
*
|
|
4
|
+
* Three-state gate: inspects platform/arch, unified memory, and battery
|
|
5
|
+
* to return `full_local`, `classification_only`, or `disabled`.
|
|
6
|
+
*
|
|
7
|
+
* Pure function of SystemInfo × config; side-effect-free for testability.
|
|
8
|
+
* Default SystemInfo provider reads from Node.js `os` and platform-specific
|
|
9
|
+
* power sources (macOS pmset, Linux /sys/class/power_supply, Windows WMI).
|
|
10
|
+
*/
|
|
11
|
+
export type HardwareProbeResult = 'full_local' | 'classification_only' | 'disabled';
|
|
12
|
+
export interface HardwareProbeConfig {
|
|
13
|
+
readonly min_memory_gb_full: number;
|
|
14
|
+
readonly min_memory_gb_classification: number;
|
|
15
|
+
readonly battery_threshold_pct: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SystemInfo {
|
|
18
|
+
readonly totalMemoryGb: number;
|
|
19
|
+
readonly arch: string;
|
|
20
|
+
readonly platform: NodeJS.Platform;
|
|
21
|
+
readonly batteryLevel: number | null;
|
|
22
|
+
readonly isOnAcPower: boolean | null;
|
|
23
|
+
}
|
|
24
|
+
/** Port for dependency injection in tests. */
|
|
25
|
+
export interface SystemInfoPort {
|
|
26
|
+
getSystemInfo(): Promise<SystemInfo>;
|
|
27
|
+
}
|
|
28
|
+
export declare function probeHardware(config: HardwareProbeConfig, info: SystemInfo): HardwareProbeResult;
|
|
29
|
+
export declare function getDefaultSystemInfoPort(): SystemInfoPort;
|
|
30
|
+
export declare function getDefaultSystemInfo(): Promise<SystemInfo>;
|
|
31
|
+
export declare function probeHardwareDefault(config: HardwareProbeConfig): Promise<HardwareProbeResult>;
|
|
32
|
+
//# sourceMappingURL=hardware-probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hardware-probe.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/hardware/hardware-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,qBAAqB,GAAG,UAAU,CAAC;AAEpF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,4BAA4B,EAAE,MAAM,CAAC;IAC9C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC;CACtC;AAED,8CAA8C;AAC9C,MAAM,WAAW,cAAc;IAC7B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CACtC;AAiBD,wBAAgB,aAAa,CAC3B,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,UAAU,GACf,mBAAmB,CAsBrB;AA4KD,wBAAgB,wBAAwB,IAAI,cAAc,CAWzD;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,UAAU,CAAC,CAEhE;AAID,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,mBAAmB,CAAC,CAG9B"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardware probe — T044, FR-012.
|
|
3
|
+
*
|
|
4
|
+
* Three-state gate: inspects platform/arch, unified memory, and battery
|
|
5
|
+
* to return `full_local`, `classification_only`, or `disabled`.
|
|
6
|
+
*
|
|
7
|
+
* Pure function of SystemInfo × config; side-effect-free for testability.
|
|
8
|
+
* Default SystemInfo provider reads from Node.js `os` and platform-specific
|
|
9
|
+
* power sources (macOS pmset, Linux /sys/class/power_supply, Windows WMI).
|
|
10
|
+
*/
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
// ─── Pure probe logic ────────────────────────────────────────────────────────
|
|
16
|
+
function isSupportedPlatform(info) {
|
|
17
|
+
if (info.platform === 'darwin' && info.arch === 'arm64') {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (info.platform === 'linux' && (info.arch === 'x64' || info.arch === 'arm64')) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (info.platform === 'win32' && (info.arch === 'x64' || info.arch === 'arm64')) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
export function probeHardware(config, info) {
|
|
29
|
+
if (!isSupportedPlatform(info)) {
|
|
30
|
+
return 'disabled';
|
|
31
|
+
}
|
|
32
|
+
if (info.isOnAcPower === false &&
|
|
33
|
+
info.batteryLevel !== null &&
|
|
34
|
+
info.batteryLevel < config.battery_threshold_pct) {
|
|
35
|
+
return 'disabled';
|
|
36
|
+
}
|
|
37
|
+
if (info.totalMemoryGb >= config.min_memory_gb_full) {
|
|
38
|
+
return 'full_local';
|
|
39
|
+
}
|
|
40
|
+
if (info.totalMemoryGb >= config.min_memory_gb_classification) {
|
|
41
|
+
return 'classification_only';
|
|
42
|
+
}
|
|
43
|
+
return 'disabled';
|
|
44
|
+
}
|
|
45
|
+
// ─── Platform-specific system info providers ───────────────────────────────
|
|
46
|
+
function parseBatteryInfo(output) {
|
|
47
|
+
const pctMatch = /(\d+)%/.exec(output);
|
|
48
|
+
const batteryLevel = pctMatch?.[1] !== undefined
|
|
49
|
+
? parseInt(pctMatch[1], 10)
|
|
50
|
+
: null;
|
|
51
|
+
const isOnAcPower = output.includes('AC Power');
|
|
52
|
+
return { batteryLevel, isOnAcPower };
|
|
53
|
+
}
|
|
54
|
+
function readLinuxPowerInfo() {
|
|
55
|
+
const powerSupplyDir = '/sys/class/power_supply';
|
|
56
|
+
if (!existsSync(powerSupplyDir)) {
|
|
57
|
+
return { batteryLevel: null, isOnAcPower: true };
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const entries = readdirSync(powerSupplyDir);
|
|
61
|
+
let batteryLevel = null;
|
|
62
|
+
let isOnAcPower = null;
|
|
63
|
+
for (const name of entries) {
|
|
64
|
+
const base = join(powerSupplyDir, name);
|
|
65
|
+
const typePath = join(base, 'type');
|
|
66
|
+
if (!existsSync(typePath)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const type = readFileSync(typePath, 'utf8').trim();
|
|
70
|
+
if (type !== 'Battery') {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const capacityPath = join(base, 'capacity');
|
|
74
|
+
if (existsSync(capacityPath)) {
|
|
75
|
+
const capacity = parseInt(readFileSync(capacityPath, 'utf8').trim(), 10);
|
|
76
|
+
if (!Number.isNaN(capacity)) {
|
|
77
|
+
batteryLevel = capacity;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const statusPath = join(base, 'status');
|
|
81
|
+
if (existsSync(statusPath)) {
|
|
82
|
+
const status = readFileSync(statusPath, 'utf8').trim();
|
|
83
|
+
isOnAcPower = status === 'Charging' || status === 'Full' || status === 'Not charging';
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
if (batteryLevel === null && isOnAcPower === null) {
|
|
88
|
+
return { batteryLevel: null, isOnAcPower: true };
|
|
89
|
+
}
|
|
90
|
+
return { batteryLevel, isOnAcPower };
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { batteryLevel: null, isOnAcPower: true };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function buildBaseSystemInfo() {
|
|
97
|
+
return {
|
|
98
|
+
totalMemoryGb: os.totalmem() / 1024 ** 3,
|
|
99
|
+
arch: os.arch(),
|
|
100
|
+
platform: os.platform(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const macOsSystemInfoPort = {
|
|
104
|
+
async getSystemInfo() {
|
|
105
|
+
let batteryLevel = null;
|
|
106
|
+
let isOnAcPower = null;
|
|
107
|
+
try {
|
|
108
|
+
const output = execSync('pmset -g batt', {
|
|
109
|
+
timeout: 2000,
|
|
110
|
+
encoding: 'utf8',
|
|
111
|
+
});
|
|
112
|
+
const parsed = parseBatteryInfo(output);
|
|
113
|
+
batteryLevel = parsed.batteryLevel;
|
|
114
|
+
isOnAcPower = parsed.isOnAcPower;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
isOnAcPower = true;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
...buildBaseSystemInfo(),
|
|
121
|
+
batteryLevel,
|
|
122
|
+
isOnAcPower,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
const linuxSystemInfoPort = {
|
|
127
|
+
async getSystemInfo() {
|
|
128
|
+
const power = readLinuxPowerInfo();
|
|
129
|
+
return {
|
|
130
|
+
...buildBaseSystemInfo(),
|
|
131
|
+
batteryLevel: power.batteryLevel,
|
|
132
|
+
isOnAcPower: power.isOnAcPower,
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
function readWindowsPowerInfo() {
|
|
137
|
+
try {
|
|
138
|
+
const output = execSync('powershell -NoProfile -Command "Get-CimInstance -ClassName Win32_Battery -ErrorAction SilentlyContinue | Select-Object -First 1 EstimatedChargeRemaining, BatteryStatus | ConvertTo-Json -Compress"', { timeout: 3000, encoding: 'utf8' }).trim();
|
|
139
|
+
if (!output) {
|
|
140
|
+
return { batteryLevel: null, isOnAcPower: true };
|
|
141
|
+
}
|
|
142
|
+
const parsed = JSON.parse(output);
|
|
143
|
+
const batteryLevel = typeof parsed.EstimatedChargeRemaining === 'number'
|
|
144
|
+
? parsed.EstimatedChargeRemaining
|
|
145
|
+
: null;
|
|
146
|
+
let isOnAcPower = null;
|
|
147
|
+
if (typeof parsed.BatteryStatus === 'number') {
|
|
148
|
+
// 1 = discharging; other values indicate AC/charging/full states.
|
|
149
|
+
isOnAcPower = parsed.BatteryStatus !== 1;
|
|
150
|
+
}
|
|
151
|
+
if (batteryLevel === null && isOnAcPower === null) {
|
|
152
|
+
return { batteryLevel: null, isOnAcPower: true };
|
|
153
|
+
}
|
|
154
|
+
return { batteryLevel, isOnAcPower };
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return { batteryLevel: null, isOnAcPower: true };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const windowsSystemInfoPort = {
|
|
161
|
+
async getSystemInfo() {
|
|
162
|
+
const power = readWindowsPowerInfo();
|
|
163
|
+
return {
|
|
164
|
+
...buildBaseSystemInfo(),
|
|
165
|
+
batteryLevel: power.batteryLevel,
|
|
166
|
+
isOnAcPower: power.isOnAcPower,
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
const genericSystemInfoPort = {
|
|
171
|
+
async getSystemInfo() {
|
|
172
|
+
return {
|
|
173
|
+
...buildBaseSystemInfo(),
|
|
174
|
+
batteryLevel: null,
|
|
175
|
+
isOnAcPower: null,
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
export function getDefaultSystemInfoPort() {
|
|
180
|
+
switch (os.platform()) {
|
|
181
|
+
case 'darwin':
|
|
182
|
+
return macOsSystemInfoPort;
|
|
183
|
+
case 'linux':
|
|
184
|
+
return linuxSystemInfoPort;
|
|
185
|
+
case 'win32':
|
|
186
|
+
return windowsSystemInfoPort;
|
|
187
|
+
default:
|
|
188
|
+
return genericSystemInfoPort;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export async function getDefaultSystemInfo() {
|
|
192
|
+
return getDefaultSystemInfoPort().getSystemInfo();
|
|
193
|
+
}
|
|
194
|
+
// ─── Convenience: probe with real system info ────────────────────────────────
|
|
195
|
+
export async function probeHardwareDefault(config) {
|
|
196
|
+
const info = await getDefaultSystemInfo();
|
|
197
|
+
return probeHardware(config, info);
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=hardware-probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hardware-probe.js","sourceRoot":"","sources":["../../../src/infrastructure/hardware/hardware-probe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local zero-tier readiness pings — T045, FR-012, FR-013.
|
|
3
|
+
*
|
|
4
|
+
* Pings LM Studio and Ollama HTTP endpoints in parallel to determine
|
|
5
|
+
* whether a local model is loaded and ready. Combined latency target <15ms.
|
|
6
|
+
*
|
|
7
|
+
* Never throws — unreachable services return `available: false`.
|
|
8
|
+
* Port-based HTTP for testability; default uses Node.js global fetch.
|
|
9
|
+
*/
|
|
10
|
+
export interface ServicePingResult {
|
|
11
|
+
readonly available: boolean;
|
|
12
|
+
readonly hasLoadedModel: boolean;
|
|
13
|
+
readonly latencyMs: number;
|
|
14
|
+
}
|
|
15
|
+
export interface LocalReadinessResult {
|
|
16
|
+
readonly lmStudio: ServicePingResult;
|
|
17
|
+
readonly ollama: ServicePingResult;
|
|
18
|
+
readonly anyModelReady: boolean;
|
|
19
|
+
readonly combinedLatencyMs: number;
|
|
20
|
+
}
|
|
21
|
+
export interface LocalZeroTierConfig {
|
|
22
|
+
readonly lmStudioBaseUrl: string;
|
|
23
|
+
readonly ollamaBaseUrl: string;
|
|
24
|
+
readonly pingTimeoutMs: number;
|
|
25
|
+
}
|
|
26
|
+
/** Port for HTTP fetch — allows stubbing in tests without network. */
|
|
27
|
+
export interface HttpFetchPort {
|
|
28
|
+
fetch(url: string, init: {
|
|
29
|
+
signal: AbortSignal;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
ok: boolean;
|
|
32
|
+
json(): Promise<unknown>;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export declare const DEFAULT_LOCAL_CONFIG: LocalZeroTierConfig;
|
|
36
|
+
/**
|
|
37
|
+
* Default HTTP fetch port using Node.js global fetch.
|
|
38
|
+
*/
|
|
39
|
+
export declare const defaultHttpFetch: HttpFetchPort;
|
|
40
|
+
/**
|
|
41
|
+
* Ping LM Studio and Ollama in parallel. Returns readiness state for each
|
|
42
|
+
* service and whether any local model is loaded and ready for inference.
|
|
43
|
+
*
|
|
44
|
+
* Combined latency target: <15ms on a healthy system.
|
|
45
|
+
*/
|
|
46
|
+
export declare function pingLocalServices(config?: LocalZeroTierConfig, httpFetch?: HttpFetchPort): Promise<LocalReadinessResult>;
|
|
47
|
+
//# sourceMappingURL=local-zero-tier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-zero-tier.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/local/local-zero-tier.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,sEAAsE;AACtE,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC;QACzD,EAAE,EAAE,OAAO,CAAC;QACZ,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;KAC1B,CAAC,CAAC;CACJ;AAID,eAAO,MAAM,oBAAoB,EAAE,mBAIlC,CAAC;AA4DF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,aAE9B,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,GAAE,mBAA0C,EAClD,SAAS,GAAE,aAAgC,GAC1C,OAAO,CAAC,oBAAoB,CAAC,CA6B/B"}
|