pi-smart-router 0.6.1 → 0.8.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 +88 -7
- package/.pi/extensions/smart-router/commands.ts +4 -1
- package/.pi/extensions/smart-router/extension-setup.ts +11 -4
- package/.pi/extensions/smart-router/fleet-bootstrap.ts +53 -4
- package/.pi/extensions/smart-router/index.ts +6 -0
- package/README.md +63 -5
- package/config/benchmark-profiles.json +22 -0
- package/config/operator-config.json.example +8 -0
- package/config/p-success-weights.json +36 -0
- package/config/p-success-weights.json.example +4 -1
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +3 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/pi-model-mapper.d.ts +24 -5
- package/dist/config/pi-model-mapper.d.ts.map +1 -1
- package/dist/config/pi-model-mapper.js +67 -24
- package/dist/config/pi-model-mapper.js.map +1 -1
- package/dist/domain/pinning/loop-escalation.d.ts +23 -1
- package/dist/domain/pinning/loop-escalation.d.ts.map +1 -1
- package/dist/domain/pinning/loop-escalation.js +107 -20
- package/dist/domain/pinning/loop-escalation.js.map +1 -1
- package/dist/domain/pipeline/router-pipeline.d.ts +7 -1
- package/dist/domain/pipeline/router-pipeline.d.ts.map +1 -1
- package/dist/domain/pipeline/router-pipeline.js +63 -7
- package/dist/domain/pipeline/router-pipeline.js.map +1 -1
- package/dist/domain/triage/triage-engine.d.ts.map +1 -1
- package/dist/domain/triage/triage-engine.js +15 -0
- package/dist/domain/triage/triage-engine.js.map +1 -1
- package/dist/domain/triage/turn-envelope.d.ts.map +1 -1
- package/dist/domain/triage/turn-envelope.js +4 -0
- package/dist/domain/triage/turn-envelope.js.map +1 -1
- package/dist/domain/types/schemas.d.ts +15 -0
- package/dist/domain/types/schemas.d.ts.map +1 -1
- package/dist/domain/types/schemas.js +20 -0
- package/dist/domain/types/schemas.js.map +1 -1
- package/dist/infrastructure/telemetry/routing-telemetry.d.ts +4 -0
- package/dist/infrastructure/telemetry/routing-telemetry.d.ts.map +1 -1
- package/dist/infrastructure/telemetry/routing-telemetry.js +13 -0
- package/dist/infrastructure/telemetry/routing-telemetry.js.map +1 -1
- package/package.json +2 -1
- package/src/config/defaults.ts +9 -1
- package/src/config/pi-model-mapper.ts +100 -36
- package/src/domain/pinning/loop-escalation.ts +136 -20
- package/src/domain/pipeline/router-pipeline.ts +88 -11
- package/src/domain/triage/triage-engine.ts +15 -0
- package/src/domain/triage/turn-envelope.ts +4 -0
- package/src/domain/types/schemas.ts +24 -0
- package/src/infrastructure/telemetry/routing-telemetry.ts +15 -0
|
@@ -2,7 +2,11 @@ import {
|
|
|
2
2
|
DEFAULT_HISTORY_LIMIT,
|
|
3
3
|
MAX_HISTORY_LIMIT,
|
|
4
4
|
} from '../../../src/infrastructure/telemetry/telemetry-limits.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
ModelProfile,
|
|
7
|
+
RoutingDecision,
|
|
8
|
+
RoutingTelemetry,
|
|
9
|
+
} from '../../../src/domain/types/index.js';
|
|
6
10
|
import { SMART_ROUTER_USAGE } from './commands.js';
|
|
7
11
|
import {
|
|
8
12
|
DEFAULT_TELEMETRY_CONTRIB_EXPORT_LIMIT,
|
|
@@ -15,6 +19,78 @@ import {
|
|
|
15
19
|
import { formatPricingStalenessLine } from './pricing-lifecycle.js';
|
|
16
20
|
import type { FleetMode, SmartRouterCommand, SmartRouterRuntime } from './types.js';
|
|
17
21
|
|
|
22
|
+
/** Opaque / virtual auto ids that hide the concrete delegated fleet model (SP-178). */
|
|
23
|
+
function isBareOrSmartRouterAuto(modelId: string): boolean {
|
|
24
|
+
return modelId === 'auto' || modelId === 'smart-router/auto';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the operator-facing model id for history/status (SP-178 / #99).
|
|
29
|
+
* Prefer a concrete delegated/primary id over virtual `auto`.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveHistoryModelId(
|
|
32
|
+
entry: Pick<
|
|
33
|
+
RoutingTelemetry,
|
|
34
|
+
| 'selected_model_id'
|
|
35
|
+
| 'planning_delegate_primary_model_id'
|
|
36
|
+
| 'planning_delegate_model_id'
|
|
37
|
+
>,
|
|
38
|
+
fleet?: readonly ModelProfile[],
|
|
39
|
+
): string {
|
|
40
|
+
let modelId = entry.selected_model_id;
|
|
41
|
+
|
|
42
|
+
if (isBareOrSmartRouterAuto(modelId)) {
|
|
43
|
+
const primary = entry.planning_delegate_primary_model_id;
|
|
44
|
+
if (primary && !isBareOrSmartRouterAuto(primary)) {
|
|
45
|
+
modelId = primary;
|
|
46
|
+
} else if (
|
|
47
|
+
entry.planning_delegate_model_id &&
|
|
48
|
+
!isBareOrSmartRouterAuto(entry.planning_delegate_model_id)
|
|
49
|
+
) {
|
|
50
|
+
modelId = entry.planning_delegate_model_id;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return qualifyModelIdForDisplay(modelId, fleet);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Qualify bare `auto` with provider when fleet is available so history never
|
|
59
|
+
* looks like the smart-router virtual model.
|
|
60
|
+
*/
|
|
61
|
+
export function qualifyModelIdForDisplay(
|
|
62
|
+
modelId: string,
|
|
63
|
+
fleet?: readonly ModelProfile[],
|
|
64
|
+
): string {
|
|
65
|
+
if (modelId !== 'auto') {
|
|
66
|
+
return modelId;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const profile = fleet?.find((m) => m.id === 'auto');
|
|
70
|
+
if (profile) {
|
|
71
|
+
return `${profile.provider}/${profile.id}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Cursor opaque auto is the common bare-`auto` fleet id; never leave it unqualified.
|
|
75
|
+
return 'cursor/auto';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolveStatusModelId(
|
|
79
|
+
decision: RoutingDecision,
|
|
80
|
+
fleet?: readonly ModelProfile[],
|
|
81
|
+
): string {
|
|
82
|
+
const primary = decision.features?.planning_delegate?.primary_model_id ?? null;
|
|
83
|
+
const delegate = decision.features?.planning_delegate?.delegate_model_id ?? null;
|
|
84
|
+
return resolveHistoryModelId(
|
|
85
|
+
{
|
|
86
|
+
selected_model_id: decision.selected_model_id,
|
|
87
|
+
planning_delegate_primary_model_id: primary,
|
|
88
|
+
planning_delegate_model_id: delegate,
|
|
89
|
+
},
|
|
90
|
+
fleet,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
18
94
|
export function parseHistoryLimit(raw: string | undefined): number {
|
|
19
95
|
if (raw === undefined) {
|
|
20
96
|
return DEFAULT_HISTORY_LIMIT;
|
|
@@ -142,8 +218,9 @@ export function formatStatusMessage(
|
|
|
142
218
|
return lines.join('\n');
|
|
143
219
|
}
|
|
144
220
|
|
|
221
|
+
const displayModelId = resolveStatusModelId(decision, runtime.streamDeps.fleet);
|
|
145
222
|
lines.push(
|
|
146
|
-
`Model: ${
|
|
223
|
+
`Model: ${displayModelId}`,
|
|
147
224
|
`Stage: ${decision.stage}`,
|
|
148
225
|
`Reason: ${decision.reason_code}`,
|
|
149
226
|
`Latency: ${decision.routing_latency_ms}ms`,
|
|
@@ -151,16 +228,20 @@ export function formatStatusMessage(
|
|
|
151
228
|
return lines.join('\n');
|
|
152
229
|
}
|
|
153
230
|
|
|
154
|
-
export function formatHistoryMessage(
|
|
231
|
+
export function formatHistoryMessage(
|
|
232
|
+
entries: readonly RoutingTelemetry[],
|
|
233
|
+
options?: { fleet?: readonly ModelProfile[] },
|
|
234
|
+
): string {
|
|
155
235
|
if (entries.length === 0) {
|
|
156
236
|
return 'No routing history yet.';
|
|
157
237
|
}
|
|
158
238
|
|
|
239
|
+
const fleet = options?.fleet;
|
|
159
240
|
return entries
|
|
160
|
-
.map(
|
|
161
|
-
(entry)
|
|
162
|
-
|
|
163
|
-
)
|
|
241
|
+
.map((entry) => {
|
|
242
|
+
const modelId = resolveHistoryModelId(entry, fleet);
|
|
243
|
+
return `${entry.timestamp} | ${modelId} | ${entry.stage} | ${entry.turn_type} | ${entry.routing_latency_ms}ms`;
|
|
244
|
+
})
|
|
164
245
|
.join('\n');
|
|
165
246
|
}
|
|
166
247
|
|
|
@@ -160,7 +160,10 @@ export function registerSmartRouterCommand(
|
|
|
160
160
|
throwIfCommandAborted(signal);
|
|
161
161
|
const rows = await runtime.store.listTelemetry({ limit: parsed.limit });
|
|
162
162
|
throwIfCommandAborted(signal);
|
|
163
|
-
ctx.ui.notify(
|
|
163
|
+
ctx.ui.notify(
|
|
164
|
+
formatHistoryMessage(rows, { fleet: runtime.streamDeps.fleet }),
|
|
165
|
+
'info',
|
|
166
|
+
);
|
|
164
167
|
return;
|
|
165
168
|
}
|
|
166
169
|
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
type ExtensionAPI,
|
|
5
5
|
} from '@earendil-works/pi-coding-agent';
|
|
6
6
|
|
|
7
|
+
import { resolveOperatorConfigFromEnv } from '../../../src/config/defaults.js';
|
|
7
8
|
import { ExecutionLedger } from '../../../src/domain/delegation/execution-ledger.js';
|
|
8
|
-
import { SessionPinner } from '../../../src/domain/pinning/session-pinner.js';
|
|
9
9
|
import type { SessionRoutingSnapshot } from '../../../src/infrastructure/telemetry/outcome-recorder.js';
|
|
10
10
|
import { createRouterFromFleet, LifecycleHookState } from '../../../src/index.js';
|
|
11
11
|
|
|
@@ -14,7 +14,11 @@ import {
|
|
|
14
14
|
createExtensionDatasetRecorder,
|
|
15
15
|
createExtensionOutcomeRecorder,
|
|
16
16
|
} from './dataset-export.js';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
createDispatchOptions,
|
|
19
|
+
createOperatorAwareSessionPinner,
|
|
20
|
+
initHydraMatcher,
|
|
21
|
+
} from './fleet-bootstrap.js';
|
|
18
22
|
import { setupSessionHooks } from './session-lifecycle.js';
|
|
19
23
|
import { createStreamSimple } from './stream-delegation.js';
|
|
20
24
|
import type { SmartRouterRuntime } from './types.js';
|
|
@@ -35,7 +39,8 @@ export async function createSmartRouterRuntime(cwd: string): Promise<{
|
|
|
35
39
|
const modelRegistry = ModelRegistry.inMemory(authStorage);
|
|
36
40
|
const hydraMatcher = await initHydraMatcher();
|
|
37
41
|
const store = createExtensionStore(cwd);
|
|
38
|
-
const
|
|
42
|
+
const operatorConfig = resolveOperatorConfigFromEnv();
|
|
43
|
+
const sessionPinner = createOperatorAwareSessionPinner(store, operatorConfig);
|
|
39
44
|
const executionLedger = new ExecutionLedger();
|
|
40
45
|
const lifecycleHookState = new LifecycleHookState();
|
|
41
46
|
const datasetNotify: DatasetNotify = {
|
|
@@ -62,7 +67,9 @@ export async function createSmartRouterRuntime(cwd: string): Promise<{
|
|
|
62
67
|
sessionRouting,
|
|
63
68
|
streamDeps: {
|
|
64
69
|
router: createRouterFromFleet([], {
|
|
65
|
-
...createDispatchOptions(store, sessionPinner, hydraMatcher
|
|
70
|
+
...createDispatchOptions(store, sessionPinner, hydraMatcher, {
|
|
71
|
+
operatorConfig,
|
|
72
|
+
}),
|
|
66
73
|
lifecycleHookState,
|
|
67
74
|
}),
|
|
68
75
|
modelRegistry,
|
|
@@ -6,13 +6,18 @@ import {
|
|
|
6
6
|
} from '@earendil-works/pi-coding-agent';
|
|
7
7
|
|
|
8
8
|
import { mapFleetFromRegistry } from '../../../src/config/pi-model-mapper.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_OPERATOR_CONFIG,
|
|
11
|
+
resolveOperatorConfigFromEnv,
|
|
12
|
+
} from '../../../src/config/defaults.js';
|
|
10
13
|
import {
|
|
11
14
|
HydraMatcher,
|
|
12
15
|
createOnnxEmbeddingProvider,
|
|
13
16
|
} from '../../../src/domain/matching/hydra-matcher.js';
|
|
14
17
|
import { SessionPinner } from '../../../src/domain/pinning/session-pinner.js';
|
|
15
18
|
import type { ModelProfile, PriceCatalog } from '../../../src/domain/types/index.js';
|
|
19
|
+
import type { QuotaWindowPosition } from '../../../src/domain/types/entities.js';
|
|
20
|
+
import type { OperatorConfig } from '../../../src/domain/types/schemas.js';
|
|
16
21
|
import type { StorePort } from '../../../src/domain/types/store-port.js';
|
|
17
22
|
import { getDefaultSystemInfo } from '../../../src/infrastructure/hardware/hardware-probe.js';
|
|
18
23
|
import { DEFAULT_LOCAL_CONFIG } from '../../../src/infrastructure/local/local-zero-tier.js';
|
|
@@ -27,6 +32,16 @@ import { resolveModelScope } from './pi-model-scope.js';
|
|
|
27
32
|
import type { FleetMode, SmartRouterRuntime } from './types.js';
|
|
28
33
|
import { resolveRateLimiter } from './utils.js';
|
|
29
34
|
|
|
35
|
+
/** Optional overrides for extension dispatch wiring (SP-173). */
|
|
36
|
+
export interface CreateDispatchOptionsExtras {
|
|
37
|
+
/** Base operator config before env merge; defaults to DEFAULT_OPERATOR_CONFIG. */
|
|
38
|
+
readonly operatorConfig?: OperatorConfig;
|
|
39
|
+
/** Live price catalog when fleet discovery has loaded one. */
|
|
40
|
+
readonly priceCatalog?: PriceCatalog | null;
|
|
41
|
+
/** Rolling subscription quota position when available. */
|
|
42
|
+
readonly quotaWindowPosition?: QuotaWindowPosition;
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
/** Minimal settings surface used for scoped fleet discovery. */
|
|
31
46
|
export interface ScopedSettingsReader {
|
|
32
47
|
getEnabledModels(): string[] | null | undefined;
|
|
@@ -152,26 +167,58 @@ export function createDispatchOptions(
|
|
|
152
167
|
store: StorePort,
|
|
153
168
|
sessionPinner: SessionPinner,
|
|
154
169
|
hydraMatcher?: HydraMatcher,
|
|
170
|
+
extras?: CreateDispatchOptionsExtras,
|
|
155
171
|
): GatewayDispatchOptions {
|
|
172
|
+
const operatorConfig = resolveOperatorConfigFromEnv(
|
|
173
|
+
extras?.operatorConfig ?? DEFAULT_OPERATOR_CONFIG,
|
|
174
|
+
);
|
|
156
175
|
const telemetryEmitter = new RoutingTelemetryEmitter({
|
|
157
176
|
onRecord: (record) => {
|
|
158
177
|
store.appendTelemetry(record);
|
|
159
178
|
},
|
|
179
|
+
sessionPinner,
|
|
180
|
+
saarConfig: operatorConfig.saar,
|
|
181
|
+
...(extras?.priceCatalog !== undefined ? { priceCatalog: extras.priceCatalog } : {}),
|
|
182
|
+
...(extras?.quotaWindowPosition !== undefined
|
|
183
|
+
? { quotaWindowPosition: extras.quotaWindowPosition }
|
|
184
|
+
: {}),
|
|
160
185
|
});
|
|
161
186
|
const rateLimiter = resolveRateLimiter(store);
|
|
162
187
|
|
|
163
188
|
return {
|
|
164
189
|
sessionPinner,
|
|
165
|
-
hardwareConfig:
|
|
190
|
+
hardwareConfig: operatorConfig.local,
|
|
166
191
|
systemInfoProvider: getDefaultSystemInfo,
|
|
167
192
|
localConfig: DEFAULT_LOCAL_CONFIG,
|
|
168
|
-
loopEscalationConfig:
|
|
193
|
+
loopEscalationConfig: operatorConfig.loop_escalation,
|
|
194
|
+
saarConfig: operatorConfig.saar,
|
|
195
|
+
planningDelegateConfig: operatorConfig.planning_delegate,
|
|
196
|
+
pinOnlyFallback: operatorConfig.pin_only_fallback,
|
|
197
|
+
...(extras?.priceCatalog !== undefined ? { priceCatalog: extras.priceCatalog } : {}),
|
|
198
|
+
...(extras?.quotaWindowPosition !== undefined
|
|
199
|
+
? { quotaWindowPosition: extras.quotaWindowPosition }
|
|
200
|
+
: {}),
|
|
169
201
|
...(hydraMatcher ? { hydraMatcher } : {}),
|
|
170
202
|
...(rateLimiter ? { rateLimiter } : {}),
|
|
171
203
|
telemetryEmitter,
|
|
172
204
|
};
|
|
173
205
|
}
|
|
174
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Build a SessionPinner wired with operator SAAR / pin-only settings (SP-173).
|
|
209
|
+
* No operator-config.json loader exists yet — env + optional base config only.
|
|
210
|
+
*/
|
|
211
|
+
export function createOperatorAwareSessionPinner(
|
|
212
|
+
store: StorePort,
|
|
213
|
+
operatorConfig: OperatorConfig = resolveOperatorConfigFromEnv(),
|
|
214
|
+
): SessionPinner {
|
|
215
|
+
return new SessionPinner({
|
|
216
|
+
store,
|
|
217
|
+
saarConfig: operatorConfig.saar,
|
|
218
|
+
pinOnlyFallback: operatorConfig.pin_only_fallback,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
175
222
|
export async function rebuildFleet(
|
|
176
223
|
runtime: SmartRouterRuntime,
|
|
177
224
|
pi: ExtensionAPI,
|
|
@@ -189,7 +236,9 @@ export async function rebuildFleet(
|
|
|
189
236
|
runtime.priceCatalog = catalog;
|
|
190
237
|
runtime.fleetScopeFingerprint = fingerprint;
|
|
191
238
|
const router = createRouterFromFleet(fleet, {
|
|
192
|
-
...createDispatchOptions(runtime.store, runtime.sessionPinner, runtime.hydraMatcher
|
|
239
|
+
...createDispatchOptions(runtime.store, runtime.sessionPinner, runtime.hydraMatcher, {
|
|
240
|
+
priceCatalog: catalog,
|
|
241
|
+
}),
|
|
193
242
|
lifecycleHookState: runtime.lifecycleHookState,
|
|
194
243
|
});
|
|
195
244
|
router.register(createHooksAdapter(pi));
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from './dataset-export.js';
|
|
25
25
|
import {
|
|
26
26
|
createDispatchOptions,
|
|
27
|
+
createOperatorAwareSessionPinner,
|
|
27
28
|
discoverFleet,
|
|
28
29
|
formatLmuStatus,
|
|
29
30
|
initHydraMatcher,
|
|
@@ -37,6 +38,8 @@ import {
|
|
|
37
38
|
formatHistoryMessage,
|
|
38
39
|
formatStatusMessage,
|
|
39
40
|
parseSmartRouterArgs,
|
|
41
|
+
resolveHistoryModelId,
|
|
42
|
+
qualifyModelIdForDisplay,
|
|
40
43
|
} from './command-formatters.js';
|
|
41
44
|
import { formatPricingStalenessLine, refreshPricingCatalog } from './pricing-lifecycle.js';
|
|
42
45
|
import {
|
|
@@ -62,6 +65,7 @@ export {
|
|
|
62
65
|
createDispatchOptions,
|
|
63
66
|
createExtensionDatasetRecorder,
|
|
64
67
|
createExtensionOutcomeRecorder,
|
|
68
|
+
createOperatorAwareSessionPinner,
|
|
65
69
|
createSmartRouterRuntime,
|
|
66
70
|
createStreamSimple,
|
|
67
71
|
deriveTurnType,
|
|
@@ -85,8 +89,10 @@ export {
|
|
|
85
89
|
getSmartRouterArgumentCompletions,
|
|
86
90
|
mapContextMessages,
|
|
87
91
|
parseSmartRouterArgs,
|
|
92
|
+
qualifyModelIdForDisplay,
|
|
88
93
|
refreshPricingCatalog,
|
|
89
94
|
resolveDelegationOptions,
|
|
95
|
+
resolveHistoryModelId,
|
|
90
96
|
logRoutingDecision,
|
|
91
97
|
toDatasetExportRecord,
|
|
92
98
|
capturePreRouteOutcomes,
|
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ Cursor models bill against your **Cursor Pro subscription quota**, not per-token
|
|
|
197
197
|
|---------|---------|
|
|
198
198
|
| `/smart-router` | Same as `status` (default when no subcommand is given) |
|
|
199
199
|
| `/smart-router status` | Show fleet mode, fleet size, pricing freshness/staleness, and the last routing decision (stage, tier, selected model, latency) |
|
|
200
|
-
| `/smart-router history` | Show recent routing telemetry from SQLite (default limit; optional numeric limit, e.g. `/smart-router history 20`) |
|
|
200
|
+
| `/smart-router history` | Show recent routing telemetry from SQLite (default limit; optional numeric limit, e.g. `/smart-router history 20`). Displays the concrete delegated model id (never bare virtual `auto`) |
|
|
201
201
|
| `/smart-router mode scoped` | Route only among pi's **enabled model patterns** (default) |
|
|
202
202
|
| `/smart-router mode all` | Route among **all authenticated models** in the registry |
|
|
203
203
|
| `/smart-router pricing refresh` | Manually fetch LiteLLM pricing from `LITELLM_PRICING_URL`, persist to SQLite, and rebuild the fleet with updated rates |
|
|
@@ -339,7 +339,7 @@ Cluster IDs are stable reason-code prefixes (`cluster_low_stakes_general`, `clus
|
|
|
339
339
|
| Variable | Default | Purpose |
|
|
340
340
|
|----------|---------|---------|
|
|
341
341
|
| `ROUTER_STATE_DB_PATH` | `./.pi-smart-router/state.db` | Override SQLite state store location (telemetry, pricing catalog, session data) |
|
|
342
|
-
| `SMART_ROUTER_LOG_ROUTING` | (unset) | Set to `1` to log each routing decision to stderr as JSON (debugging dogfood sessions) |
|
|
342
|
+
| `SMART_ROUTER_LOG_ROUTING` | (unset) | Set to `1` to log each routing decision to stderr as JSON (debugging dogfood sessions). Canonical payload builder (`buildRoutingDecisionLogPayload`) includes top-level `stage`, `reason_code`, `low_intensity_score`, `tier_hint`, `local_eligible_reason`, and `cluster_id` (plus nested `cluster_summary` / `features`). The pi extension’s live stderr logger is still a slim subset — see [LOG_ROUTING field checklist](#log_routing-field-checklist) |
|
|
343
343
|
| `SMART_ROUTER_DATASET` | (unset) | Set to `1` to opt in to privacy-safe routing dataset capture (metadata and feature fields only; 30-day / 10k-row retention). Prompt text, messages, and tool arguments are never stored. Required for outcome labels and P(success) training export. See [#8](https://github.com/beettlle/pi-smart-router/issues/8). |
|
|
344
344
|
| `SMART_ROUTER_DATASET_FINGERPRINT` | (unset) | Set to `1` (requires `SMART_ROUTER_DATASET=1`) to store an install-local HMAC-SHA256 fingerprint of each normalized prompt for duplicate detection within this install. The install pepper lives in `.pi-smart-router/.dataset-key` (gitignored) and is never exported. **Warning:** short or common prompts are vulnerable to offline rainbow-table guessing; use only when you accept that tradeoff. See [#10](https://github.com/beettlle/pi-smart-router/issues/10). |
|
|
345
345
|
| `MODELS_YAML_PATH` | `./config/models.yaml` | Fleet catalog path (library API only) |
|
|
@@ -354,6 +354,23 @@ Cluster IDs are stable reason-code prefixes (`cluster_low_stakes_general`, `clus
|
|
|
354
354
|
| `ROUTER_SAFE_DEFAULT_TIER` | `economical-cloud` | Fallback tier on any routing failure |
|
|
355
355
|
| `LITELLM_PRICING_URL` | — | LiteLLM pricing JSON source |
|
|
356
356
|
|
|
357
|
+
### LOG_ROUTING field checklist
|
|
358
|
+
|
|
359
|
+
When `SMART_ROUTER_LOG_ROUTING=1`, prefer the canonical payload from `buildRoutingDecisionLogPayload` (library / tests). Checklist for [#99](https://github.com/beettlle/pi-smart-router/issues/99):
|
|
360
|
+
|
|
361
|
+
| Field | In payload builder? | Notes |
|
|
362
|
+
|-------|---------------------|-------|
|
|
363
|
+
| `stage` | Yes (top-level) | Pipeline stage that decided |
|
|
364
|
+
| `reason_code` | Yes (top-level) | Machine-readable reason |
|
|
365
|
+
| `low_intensity_score` | Yes (top-level + `cluster_summary`) | Null when low-intensity stage did not run |
|
|
366
|
+
| `tier_hint` | Yes (top-level + `cluster_summary`) | Null when no tier hint |
|
|
367
|
+
| `local_eligible_reason` | Yes (top-level + `features`) | Null when local_zero did not evaluate eligibility |
|
|
368
|
+
| `cluster_id` | Yes (top-level + `cluster_summary`) | Null when no cluster match |
|
|
369
|
+
|
|
370
|
+
**Gap:** the pi extension’s live stderr path (`logRoutingDecision` in `.pi/extensions/smart-router`) still emits a slim JSON object (`selected_model_id`, `stage`, `reason_code`, `features`, `delegate`) and does **not** yet call `buildRoutingDecisionLogPayload`. SQLite `/smart-router history` and the payload builder carry the full checklist; wire the extension logger in a follow-up if dogfood needs identical stderr shape.
|
|
371
|
+
|
|
372
|
+
**History model id:** `/smart-router history` resolves bare/`smart-router` virtual `auto` to the concrete planning-delegate primary (or qualifies Cursor opaque `auto` as `cursor/auto`) so operators see the delegated fleet model, not the virtual router id.
|
|
373
|
+
|
|
357
374
|
### SAAR session pin and cache breakeven (v0.2.0 Continuity)
|
|
358
375
|
|
|
359
376
|
v0.2.0 adds **Session-Aware Agentic Routing (SAAR)** pin knobs ([#72](https://github.com/beettlle/pi-smart-router/issues/72)) and a **cache breakeven gate** ([#73](https://github.com/beettlle/pi-smart-router/issues/73)) that blocks tier switches when `marginal_savings + future_cache_value <= cache_reprime_cost` — preventing cheap-turn savings from invalidating a warm prefix cache.
|
|
@@ -464,12 +481,40 @@ When `SMART_ROUTER_DATASET=1`, the router records privacy-safe dataset rows and
|
|
|
464
481
|
|
|
465
482
|
Each JSONL row joins dataset features with `success_label` and `outcome_signals`. Success means no negative outcome signals were recorded for that `request_id` (for example `model_override` or `feedback_bad` mark failure). Prompt plaintext is never included.
|
|
466
483
|
|
|
467
|
-
|
|
484
|
+
**Dogfood artifact (SP-175):** the repo ships a non-example `config/p-success-weights.json` trained on the synthetic fixture at `scripts/fixtures/p-success-synthetic-train.jsonl` (**provenance: synthetic/fixture**, not community contrib — 40 labeled feature-vector rows, no prompt text). With `trained_sample_count ≥ 30`, the low-intensity gate uses trained logistic scores instead of neutral `0.5`. Missing or invalid artifacts still fall back safely to neutral defaults.
|
|
485
|
+
|
|
486
|
+
**Operator train / reload (no prompt text):**
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
# 1) Opt in + dogfood, then export privacy-safe labeled JSONL (features + labels only)
|
|
490
|
+
SMART_ROUTER_DATASET=1
|
|
491
|
+
# …run sessions with /model smart-router/auto and /smart-router feedback…
|
|
492
|
+
/smart-router export dataset --limit 200
|
|
493
|
+
|
|
494
|
+
# 2) Train standalone weights (≥30 labeled rows required)
|
|
495
|
+
npm run routing:train-p-success -- --input path/to/export.jsonl --output config/p-success-weights.json
|
|
496
|
+
|
|
497
|
+
# Or regenerate the checked-in dogfood weights from the synthetic fixture:
|
|
498
|
+
npm run routing:train-p-success
|
|
499
|
+
|
|
500
|
+
# 3) Optional: merge isotonic into an existing calibration bundle (does not rewrite hydra/centroids)
|
|
501
|
+
npm run routing:train-p-success -- --input path/to/export.jsonl \
|
|
502
|
+
--calibration-output config/routing-calibration.json
|
|
503
|
+
|
|
504
|
+
# Full Phase-3 bundle (also refreshes standalone p-success-weights.json when the gate is met):
|
|
505
|
+
npm run routing:train-calibration -- --input path/to/aggregated.jsonl
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Reload is file-based: replace `config/p-success-weights.json` (and optionally `config/routing-calibration.json` for isotonic) and restart the host agent — no prompt text is ever written into training artifacts.
|
|
509
|
+
|
|
510
|
+
**Isotonic gap:** serve-time isotonic calibration loads from `config/routing-calibration.json` (`isotonic_calibrator`). The checked-in dogfood path ships trained **logistic** weights only; isotonic is produced when you pass `--calibration-output` or run `routing:train-calibration` with ≥30 labeled samples. Until that bundle exists, the pipeline uses raw logistic `P(success)` (identity / no-op calibrator) and still exposes `p_success_raw` vs `p_success_calibrated` / `p_success_cheap` on explain and telemetry.
|
|
511
|
+
|
|
512
|
+
Library helpers (see `src/domain/routing/p-success-classifier.ts`):
|
|
468
513
|
|
|
469
514
|
- `trainFromExportJsonl(exportContent)` — fit coefficients from labeled JSONL
|
|
470
515
|
- `predictPSuccessCheap(features, weights)` — returns `P_success_cheap` in `[0, 1]`
|
|
471
516
|
|
|
472
|
-
|
|
517
|
+
**Minimum sample guidance:** collect at least **30** labeled economical-tier rows before relying on non-neutral predictions; below that threshold the classifier returns neutral `P_success_cheap = 0.5`. **Online inference** is active in the low-intensity gate; without trained weights the router uses neutral defaults until you add `config/p-success-weights.json`.
|
|
473
518
|
|
|
474
519
|
### Community telemetry contribution (calibration)
|
|
475
520
|
|
|
@@ -539,8 +584,10 @@ Additional operator defaults:
|
|
|
539
584
|
|
|
540
585
|
| Key | Default | Purpose |
|
|
541
586
|
|-----|---------|---------|
|
|
542
|
-
| `loop_escalation.threshold` | 3 | Consecutive identical failures before escalating to frontier |
|
|
587
|
+
| `loop_escalation.threshold` | 3 | Consecutive identical failures before escalating to frontier. Also used as the default **zero-tier tool-call churn** threshold (SP-178 / [#99](https://github.com/beettlle/pi-smart-router/issues/99)): while pinned to `zero-tier`, unsupported/unknown tool results escalate immediately, and N `tool_result` turns escalate via the same `loop_escalation` pin path (FR-014) — not a cache-breakeven bypass |
|
|
543
588
|
| `pin_only_fallback` | `false` | Emergency pin-on-first-turn mode — see [Pin-only emergency fallback](#pin-only-emergency-fallback) |
|
|
589
|
+
| `local_zero.enabled` | `true` | When `false`, skip `local_zero` dispatch (fall through to later stages). Default keeps the cheap local path for true trivial traffic |
|
|
590
|
+
| `local_zero.max_tool_use_requirement` | `0.25` | Ceiling (0–1) on cheap predicted tool_use for `local_zero`. Effective limit is `min(local model tool_use, this value)`. Skips agentic git/bash/edit/explore/delete/repo cues with telemetry reason `tool_use_capability_shortfall` (SP-177 / [#98](https://github.com/beettlle/pi-smart-router/issues/98)) |
|
|
544
591
|
| `local.min_memory_gb_full` | 16 | Minimum RAM for full local inference |
|
|
545
592
|
| `local.battery_threshold_pct` | 20 | Minimum battery to allow local inference |
|
|
546
593
|
| `pricing.staleness_days` | 14 | Max age before re-fetching pricing data |
|
|
@@ -744,6 +791,7 @@ Contributors must run `npm run build` before publishing or consuming the library
|
|
|
744
791
|
| `npm run routing:bootstrap-centroids` | Regenerate `config/routing-centroids.json` from cluster catalog |
|
|
745
792
|
| `npm run routing:calibration-aggregate` | Aggregate community telemetry for calibration |
|
|
746
793
|
| `npm run routing:train-calibration` | Train routing calibration artifact bundle |
|
|
794
|
+
| `npm run routing:train-p-success` | Train standalone `config/p-success-weights.json` (synthetic fixture by default) |
|
|
747
795
|
| `npm run routing:verify-calibration` | Verify calibration bundle against benchmark prompts |
|
|
748
796
|
| `npm run routing:ingest-benchmarks` | Regenerate `config/benchmark-profiles.json` from leaderboard fixtures |
|
|
749
797
|
| `npm run routing:verify-benchmark-profiles` | CI smoke: assert checked-in profiles match fixture ingest |
|
|
@@ -782,6 +830,16 @@ npm run routing:eval-replay
|
|
|
782
830
|
|
|
783
831
|
Capability scores in `config/benchmark-profiles.json` are grounded from public leaderboard snapshots under `tests/fixtures/benchmark-leaderboards/`. Each artifact records provenance (`source_urls`, `scrape_date`, `catalog_freeze_date`) in its header.
|
|
784
832
|
|
|
833
|
+
**Fleet ID aliases (SP-174):** live pi/Cursor scoped-fleet model IDs often differ from leaderboard `model_id` strings. The artifact’s optional `aliases` map sends those fleet IDs to an existing grounded row (never invents scores). `mapPiModelToProfile` sets `capability_source` to `benchmark` when a direct row or alias hits, otherwise `pattern_default`. Operators can also call `getCapabilitySource(modelId)` / `resolveBenchmarkModelId(modelId)`.
|
|
834
|
+
|
|
835
|
+
**Add a new fleet ID after ingest:**
|
|
836
|
+
|
|
837
|
+
1. Ensure the canonical model has fixture scores (edit `tests/fixtures/benchmark-leaderboards/*.json` if needed).
|
|
838
|
+
2. Run `npm run routing:ingest-benchmarks` (and commit the regenerated `config/benchmark-profiles.json`).
|
|
839
|
+
3. Add `"your-fleet-id": "canonical-model_id"` under `aliases` in `config/benchmark-profiles.json` (target must already appear in `models[].model_id`).
|
|
840
|
+
4. Re-run ingest anytime — the CLI **preserves** existing `aliases` from the output file. Seed defaults live in `DEFAULT_FLEET_BENCHMARK_ALIASES` when no prior artifact exists.
|
|
841
|
+
5. Confirm with `npm run routing:verify-benchmark-profiles` and a mapper unit test that `capability_source === 'benchmark'` for the fleet id.
|
|
842
|
+
|
|
785
843
|
**Operator policy:**
|
|
786
844
|
|
|
787
845
|
1. **PR smoke** — `.github/workflows/benchmark-profile-refresh.yml` runs on PRs that touch fixtures, ingest, or the checked-in artifact. It executes `npm run routing:verify-benchmark-profiles` so fixture edits cannot drift from `config/benchmark-profiles.json`.
|
|
@@ -10,6 +10,28 @@
|
|
|
10
10
|
"scrape_date": "2026-07-09",
|
|
11
11
|
"catalog_freeze_date": "2026-07-09"
|
|
12
12
|
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"claude-3-5-sonnet": "claude-sonnet-4-6",
|
|
15
|
+
"claude-3.5-sonnet": "claude-sonnet-4-6",
|
|
16
|
+
"claude-3.5-sonnet-latest": "claude-sonnet-4-6",
|
|
17
|
+
"claude-opus-4": "claude-opus-4-5",
|
|
18
|
+
"claude-opus-4-20250514": "claude-opus-4-5",
|
|
19
|
+
"claude-sonnet-4": "claude-sonnet-4-6",
|
|
20
|
+
"claude-sonnet-4-20250514": "claude-sonnet-4-6",
|
|
21
|
+
"composer-1": "gpt-5.3-codex",
|
|
22
|
+
"composer-latest": "gpt-5.3-codex",
|
|
23
|
+
"cursor/auto": "gpt-5.3-codex",
|
|
24
|
+
"cursor/composer-latest": "gpt-5.3-codex",
|
|
25
|
+
"default": "gpt-5.3-codex",
|
|
26
|
+
"gemini-2.0-flash": "gemini-2.5-flash",
|
|
27
|
+
"gemini-2.5-flash-lite": "gemini-2.5-flash",
|
|
28
|
+
"gemini-2.5-flash-preview": "gemini-2.5-flash",
|
|
29
|
+
"gemini-flash-latest": "gemini-2.5-flash",
|
|
30
|
+
"gpt-5": "gpt-5.3-codex",
|
|
31
|
+
"gpt-5-codex": "gpt-5.3-codex",
|
|
32
|
+
"gpt-5.3": "gpt-5.3-codex",
|
|
33
|
+
"gpt-5.5": "gpt-5.3-codex"
|
|
34
|
+
},
|
|
13
35
|
"models": [
|
|
14
36
|
{
|
|
15
37
|
"model_id": "claude-3.5-haiku",
|
|
@@ -60,5 +60,13 @@
|
|
|
60
60
|
"exclude_execution_history": true
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
|
+
"local_zero": {
|
|
64
|
+
"enabled": true,
|
|
65
|
+
"max_tool_use_requirement": 0.25,
|
|
66
|
+
"_documentation": {
|
|
67
|
+
"enabled": "When false, skip local_zero entirely (fall through to triage cloud / HyDRA). Default true — does not disable local for trivial traffic.",
|
|
68
|
+
"max_tool_use_requirement": "Ceiling (0–1) on cheap predicted tool_use for local_zero dispatch. Effective limit is min(local model tool_use capability, this value). Default 0.25 keeps format/lint local while skipping agentic git/bash/edit/explore/delete/repo cues (SP-177, #98)."
|
|
69
|
+
}
|
|
70
|
+
},
|
|
63
71
|
"pin_only_fallback": false
|
|
64
72
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"min_training_samples": 30,
|
|
4
|
+
"feature_names": [
|
|
5
|
+
"prompt_length_norm",
|
|
6
|
+
"estimated_input_tokens_norm",
|
|
7
|
+
"triage_cyclomatic_score",
|
|
8
|
+
"requirement_reasoning",
|
|
9
|
+
"requirement_code_gen",
|
|
10
|
+
"requirement_tool_use",
|
|
11
|
+
"has_tool_context",
|
|
12
|
+
"compaction_flag",
|
|
13
|
+
"routing_latency_norm",
|
|
14
|
+
"economical_tier"
|
|
15
|
+
],
|
|
16
|
+
"intercept": 4.101841550841884,
|
|
17
|
+
"coefficients": [
|
|
18
|
+
-3.9458158506645473,
|
|
19
|
+
-3.9458158506645473,
|
|
20
|
+
-4.6221357618419106,
|
|
21
|
+
-4.702014247565688,
|
|
22
|
+
-1.9442887494123497,
|
|
23
|
+
-2.945430358303044,
|
|
24
|
+
-2.8373347243581435,
|
|
25
|
+
0,
|
|
26
|
+
0.09441030879566878,
|
|
27
|
+
4.101841550841884
|
|
28
|
+
],
|
|
29
|
+
"trained_sample_count": 40,
|
|
30
|
+
"provenance": {
|
|
31
|
+
"source": "synthetic_fixture",
|
|
32
|
+
"task": "SP-175",
|
|
33
|
+
"note": "Privacy-safe feature vectors + labels only; no prompt text.",
|
|
34
|
+
"trained_at": "2026-07-10"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -15,5 +15,8 @@
|
|
|
15
15
|
],
|
|
16
16
|
"intercept": 0,
|
|
17
17
|
"coefficients": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
18
|
-
"trained_sample_count": 0
|
|
18
|
+
"trained_sample_count": 0,
|
|
19
|
+
"provenance": {
|
|
20
|
+
"note": "Untrained template. Prefer npm run routing:train-p-success (or copy from config/p-success-weights.json after training). Never commit prompt text."
|
|
21
|
+
}
|
|
19
22
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Values sourced from specs/001-build-smart-router/data-model.md § Configuration (Operator).
|
|
4
4
|
*/
|
|
5
5
|
import { type OperatorConfig } from '../domain/types/schemas.js';
|
|
6
|
-
export { DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv } from '../domain/types/schemas.js';
|
|
6
|
+
export { DEFAULT_LOCAL_ZERO_CONFIG, DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
7
7
|
/** Merge operator env overrides onto defaults (SAAR and planning delegate sections). */
|
|
8
8
|
export declare function resolveOperatorConfigFromEnv(base?: OperatorConfig): OperatorConfig;
|
|
9
9
|
export declare const DEFAULT_OPERATOR_CONFIG: Readonly<OperatorConfig>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAML,KAAK,cAAc,EACpB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AAEpC,wFAAwF;AACxF,wBAAgB,4BAA4B,CAC1C,IAAI,GAAE,cAAwC,GAC7C,cAAc,CAMhB;AAED,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,cAAc,CAgCnD,CAAC"}
|
package/dist/config/defaults.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Operator configuration defaults (FR-021).
|
|
3
3
|
* Values sourced from specs/001-build-smart-router/data-model.md § Configuration (Operator).
|
|
4
4
|
*/
|
|
5
|
-
import { DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
5
|
+
import { DEFAULT_LOCAL_ZERO_CONFIG, DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
6
6
|
import { DEFAULT_LOW_INTENSITY_WEIGHTS } from '../domain/routing/tier-features.js';
|
|
7
|
-
export { DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv } from '../domain/types/schemas.js';
|
|
7
|
+
export { DEFAULT_LOCAL_ZERO_CONFIG, DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
8
8
|
/** Merge operator env overrides onto defaults (SAAR and planning delegate sections). */
|
|
9
9
|
export function resolveOperatorConfigFromEnv(base = DEFAULT_OPERATOR_CONFIG) {
|
|
10
10
|
return {
|
|
@@ -43,6 +43,7 @@ export const DEFAULT_OPERATOR_CONFIG = {
|
|
|
43
43
|
},
|
|
44
44
|
saar: DEFAULT_SAAR_CONFIG,
|
|
45
45
|
planning_delegate: DEFAULT_PLANNING_DELEGATE_CONFIG,
|
|
46
|
+
local_zero: DEFAULT_LOCAL_ZERO_CONFIG,
|
|
46
47
|
pin_only_fallback: false,
|
|
47
48
|
};
|
|
48
49
|
//# sourceMappingURL=defaults.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,gCAAgC,EAChC,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GAEzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAEnF,OAAO,
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GAEzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAEnF,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AAEpC,wFAAwF;AACxF,MAAM,UAAU,4BAA4B,CAC1C,OAAuB,uBAAuB;IAE9C,OAAO;QACL,GAAG,IAAI;QACP,IAAI,EAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,iBAAiB,EAAE,oCAAoC,CAAC,IAAI,CAAC,iBAAiB,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAA6B;IAC/D,SAAS,EAAE;QACT,WAAW,EAAE,GAAG;QAChB,cAAc,EAAE,GAAG;QACnB,gBAAgB,EAAE,IAAI;KACvB;IACD,eAAe,EAAE;QACf,SAAS,EAAE,CAAC;KACb;IACD,OAAO,EAAE;QACP,cAAc,EAAE,EAAE;KACnB;IACD,KAAK,EAAE;QACL,kBAAkB,EAAE,EAAE;QACtB,4BAA4B,EAAE,CAAC;QAC/B,qBAAqB,EAAE,EAAE;KAC1B;IACD,KAAK,EAAE;QACL,mBAAmB,EAAE,0BAA0B;QAC/C,OAAO,EAAE,QAAQ;QACjB,WAAW,EAAE,oBAAoB;KAClC;IACD,aAAa,EAAE;QACb,OAAO,EAAE,6BAA6B;QACtC,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,eAAe,EAAE,GAAG;KACrB;IACD,IAAI,EAAE,mBAAmB;IACzB,iBAAiB,EAAE,gCAAgC;IACnD,UAAU,EAAE,yBAAyB;IACrC,iBAAiB,EAAE,KAAK;CAChB,CAAC"}
|
|
@@ -3,19 +3,37 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Maps pi `Model` objects (provider + id) to router fleet entries using
|
|
5
5
|
* pattern-based lookup for known families. When `config/benchmark-profiles.json`
|
|
6
|
-
* contains a row for the model id (SP-134/136 ingest output),
|
|
7
|
-
* vectors are grounded in benchmark
|
|
8
|
-
* Unknown models or missing benchmark rows
|
|
6
|
+
* contains a row for the model id (SP-134/136 ingest output), or a fleet alias
|
|
7
|
+
* pointing at such a row (SP-174), capability vectors are grounded in benchmark
|
|
8
|
+
* scores instead of regex defaults. Unknown models or missing benchmark rows
|
|
9
|
+
* receive conservative pattern defaults. Mapped profiles include
|
|
10
|
+
* `capability_source` (`benchmark` | `pattern_default`) for explain/telemetry.
|
|
9
11
|
*/
|
|
10
12
|
import type { ModelLimits, ModelProfile, Tier } from '../domain/types/entities.js';
|
|
11
13
|
/** Checked-in ingest artifact from `npm run routing:ingest-benchmarks` (SP-134). */
|
|
12
14
|
export declare const DEFAULT_BENCHMARK_PROFILES_PATH: string;
|
|
15
|
+
/** Whether HyDRA capabilities came from the ingest artifact (or alias) vs regex defaults. */
|
|
16
|
+
export type CapabilitySource = 'benchmark' | 'pattern_default';
|
|
17
|
+
/** ModelProfile plus operator-visible capability provenance (SP-174). */
|
|
18
|
+
export interface MappedModelProfile extends ModelProfile {
|
|
19
|
+
readonly capability_source: CapabilitySource;
|
|
20
|
+
}
|
|
13
21
|
/**
|
|
14
22
|
* Test hook — override benchmark artifact path (null disables benchmark grounding).
|
|
15
23
|
*/
|
|
16
24
|
export declare function setBenchmarkProfilesPathForTests(filePath: string | null): void;
|
|
17
25
|
/** Test hook — clear cached benchmark artifact between cases. */
|
|
18
26
|
export declare function resetBenchmarkProfilesCacheForTests(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a scoped-fleet model id to the canonical ingest `model_id` via aliases.
|
|
29
|
+
* Returns the input id when no alias exists.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveBenchmarkModelId(modelId: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether capabilities for this model id resolve from the benchmark artifact
|
|
34
|
+
* (direct row or fleet alias) vs pattern/family defaults.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getCapabilitySource(modelId: string): CapabilitySource;
|
|
19
37
|
/** Pi registry `Model.cost` shape — per-token USD rates. */
|
|
20
38
|
export interface PiRegistryCost {
|
|
21
39
|
readonly input: number;
|
|
@@ -41,10 +59,11 @@ export declare function getDefaultLimitsForTier(tier: Tier): ModelLimits;
|
|
|
41
59
|
export declare const DEFAULT_CURSOR_QUOTA_COST_PER_1M = 3;
|
|
42
60
|
/**
|
|
43
61
|
* Map a pi model registry entry to a router ModelProfile.
|
|
62
|
+
* Includes `capability_source` for operators (benchmark vs pattern_default).
|
|
44
63
|
*/
|
|
45
|
-
export declare function mapPiModelToProfile(input: PiModelInput):
|
|
64
|
+
export declare function mapPiModelToProfile(input: PiModelInput): MappedModelProfile;
|
|
46
65
|
/**
|
|
47
66
|
* Map an array of pi registry models to a router fleet catalog.
|
|
48
67
|
*/
|
|
49
|
-
export declare function mapFleetFromRegistry(models: readonly PiModelInput[]):
|
|
68
|
+
export declare function mapFleetFromRegistry(models: readonly PiModelInput[]): MappedModelProfile[];
|
|
50
69
|
//# sourceMappingURL=pi-model-mapper.d.ts.map
|