@alexeiled/pi-model-router 0.5.1 → 0.6.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/CHANGELOG.md +21 -0
- package/README.md +145 -24
- package/extensions/classifier.ts +119 -0
- package/extensions/commands.ts +76 -40
- package/extensions/config.ts +240 -146
- package/extensions/context.ts +91 -0
- package/extensions/index.ts +120 -132
- package/extensions/jev.ts +223 -0
- package/extensions/provider.ts +392 -178
- package/extensions/routing.ts +233 -436
- package/extensions/state.ts +74 -25
- package/extensions/types.ts +148 -52
- package/extensions/ui.ts +32 -34
- package/model-router.example.json +17 -10
- package/package.json +4 -4
package/extensions/state.ts
CHANGED
|
@@ -8,12 +8,13 @@ import {
|
|
|
8
8
|
parseCanonicalModelRef,
|
|
9
9
|
} from './config';
|
|
10
10
|
import type {
|
|
11
|
+
PersistedStateInput,
|
|
11
12
|
RouterLastProfileState,
|
|
12
13
|
RouterPersistedState,
|
|
13
14
|
RouterPinByProfile,
|
|
14
|
-
RouterThinkingByProfile,
|
|
15
15
|
RoutingDecision,
|
|
16
16
|
} from './types';
|
|
17
|
+
import { isRoutingReasonCode } from './types';
|
|
17
18
|
|
|
18
19
|
const LAST_PROFILE_STATE_FILE = 'model-router-state.json';
|
|
19
20
|
|
|
@@ -30,20 +31,33 @@ const isModelRef = (value: unknown) => {
|
|
|
30
31
|
return false;
|
|
31
32
|
}
|
|
32
33
|
};
|
|
34
|
+
|
|
35
|
+
// Historical snapshots remain readable, but obsolete prompt-derived sources
|
|
36
|
+
// are sanitized to `legacy` and never become live routing behavior again.
|
|
37
|
+
const OBSOLETE_REASON_CODES = new Set([
|
|
38
|
+
'custom-rule',
|
|
39
|
+
'micro-mechanical',
|
|
40
|
+
'heuristic',
|
|
41
|
+
'safety-floor',
|
|
42
|
+
'budget-floor-conflict',
|
|
43
|
+
]);
|
|
44
|
+
const isPersistedReasonCode = (value: unknown): boolean =>
|
|
45
|
+
isRoutingReasonCode(value) ||
|
|
46
|
+
(typeof value === 'string' && OBSOLETE_REASON_CODES.has(value));
|
|
47
|
+
|
|
33
48
|
const isDecision = (value: unknown): value is RoutingDecision =>
|
|
34
49
|
isObjectRecord(value) &&
|
|
35
50
|
isRouterTier(value.tier) &&
|
|
36
51
|
isPhase(value.phase) &&
|
|
37
52
|
isThinkingLevel(value.thinking) &&
|
|
38
53
|
isFiniteNumber(value.timestamp) &&
|
|
39
|
-
[
|
|
40
|
-
'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
'
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
['isClassifier', 'isFallback', 'isBudgetForced', 'isRuleMatched'].every(
|
|
54
|
+
['profile', 'targetProvider', 'targetModelId', 'targetLabel'].every(
|
|
55
|
+
(key) => typeof value[key] === 'string',
|
|
56
|
+
) &&
|
|
57
|
+
(value.reasonCode === undefined
|
|
58
|
+
? typeof value.reasoning === 'string'
|
|
59
|
+
: isPersistedReasonCode(value.reasonCode)) &&
|
|
60
|
+
['isClassifier', 'isFallback', 'isBudgetForced'].every(
|
|
47
61
|
(key) => value[key] === undefined || typeof value[key] === 'boolean',
|
|
48
62
|
);
|
|
49
63
|
const isMap = (value: unknown, validate: (entry: unknown) => boolean) =>
|
|
@@ -128,29 +142,64 @@ export const isRouterPersistedState = (
|
|
|
128
142
|
);
|
|
129
143
|
};
|
|
130
144
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
// Copy only the decision contract, never incidental runtime properties.
|
|
146
|
+
export const snapshotDecision = (
|
|
147
|
+
decision: RoutingDecision,
|
|
148
|
+
): RoutingDecision => ({
|
|
149
|
+
profile: decision.profile,
|
|
150
|
+
tier: decision.tier,
|
|
151
|
+
phase: decision.phase,
|
|
152
|
+
targetProvider: decision.targetProvider,
|
|
153
|
+
targetModelId: decision.targetModelId,
|
|
154
|
+
targetLabel: decision.targetLabel,
|
|
155
|
+
reasonCode: isRoutingReasonCode(decision.reasonCode)
|
|
156
|
+
? decision.reasonCode
|
|
157
|
+
: 'legacy',
|
|
158
|
+
routingLatencyMs:
|
|
159
|
+
isFiniteNumber(decision.routingLatencyMs) && decision.routingLatencyMs >= 0
|
|
160
|
+
? decision.routingLatencyMs
|
|
161
|
+
: undefined,
|
|
162
|
+
errorClass:
|
|
163
|
+
decision.errorClass === 'advisor-unavailable' ||
|
|
164
|
+
decision.errorClass === 'deadline'
|
|
165
|
+
? decision.errorClass
|
|
166
|
+
: undefined,
|
|
167
|
+
thinking: decision.thinking,
|
|
168
|
+
timestamp: decision.timestamp,
|
|
169
|
+
isClassifier: decision.isClassifier,
|
|
170
|
+
isFallback: decision.isFallback,
|
|
171
|
+
isBudgetForced: decision.isBudgetForced,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
export const buildPersistedState = ({
|
|
175
|
+
routerEnabled,
|
|
176
|
+
selectedProfile,
|
|
177
|
+
pinnedTierByProfile,
|
|
178
|
+
thinkingByProfile,
|
|
179
|
+
debugEnabled,
|
|
180
|
+
widgetEnabled,
|
|
181
|
+
debugHistory,
|
|
182
|
+
lastDecision,
|
|
183
|
+
lastNonRouterModel,
|
|
184
|
+
accumulatedCost,
|
|
185
|
+
}: PersistedStateInput): RouterPersistedState => {
|
|
186
|
+
const pinByProfile: RouterPinByProfile = {};
|
|
187
|
+
for (const [profile, tier] of Object.entries(pinnedTierByProfile)) {
|
|
188
|
+
if (tier) pinByProfile[profile] = tier;
|
|
189
|
+
}
|
|
143
190
|
return structuredClone({
|
|
144
191
|
enabled: routerEnabled,
|
|
145
192
|
selectedProfile: selectedProfile ?? '',
|
|
146
|
-
|
|
147
|
-
|
|
193
|
+
...(selectedProfile && pinnedTierByProfile[selectedProfile]
|
|
194
|
+
? { pinTier: pinnedTierByProfile[selectedProfile] }
|
|
195
|
+
: {}),
|
|
196
|
+
pinByProfile,
|
|
148
197
|
thinkingByProfile: { ...thinkingByProfile },
|
|
149
198
|
debugEnabled,
|
|
150
199
|
widgetEnabled,
|
|
151
|
-
debugHistory,
|
|
200
|
+
debugHistory: debugHistory.map(snapshotDecision),
|
|
152
201
|
lastPhase: lastDecision?.phase,
|
|
153
|
-
lastDecision,
|
|
202
|
+
lastDecision: lastDecision ? snapshotDecision(lastDecision) : undefined,
|
|
154
203
|
lastNonRouterModel,
|
|
155
204
|
accumulatedCost,
|
|
156
205
|
timestamp: Date.now(),
|
package/extensions/types.ts
CHANGED
|
@@ -1,60 +1,137 @@
|
|
|
1
1
|
import type { ThinkingLevel } from '@earendil-works/pi-agent-core';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Descending routing complexity; all tier iteration and ranking derives here.
|
|
4
|
+
export const ROUTER_TIERS = ['high', 'medium', 'low', 'micro'] as const;
|
|
5
|
+
export type RouterTier = (typeof ROUTER_TIERS)[number];
|
|
6
|
+
export type ClassifierTier = RouterTier;
|
|
4
7
|
export type RouterPin = RouterTier | 'auto';
|
|
5
8
|
export type RouterPhase = 'planning' | 'implementation' | 'lightweight';
|
|
6
9
|
export type RouterPinByProfile = Partial<Record<string, RouterTier>>;
|
|
7
10
|
export type RouterThinkingByTier = Partial<Record<RouterTier, ThinkingLevel>>;
|
|
8
11
|
export type RouterThinkingByProfile = Record<string, RouterThinkingByTier>;
|
|
9
12
|
|
|
10
|
-
export interface RoutingRule {
|
|
11
|
-
matches: string | string[];
|
|
12
|
-
tier: RouterTier;
|
|
13
|
-
reason?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
13
|
export interface ModelDefinition {
|
|
17
14
|
model: string;
|
|
18
|
-
contextWindow?: number;
|
|
19
|
-
maxTokens?: number;
|
|
20
|
-
reasoning?: boolean;
|
|
21
|
-
thinkingLevels?: ThinkingLevel[];
|
|
15
|
+
contextWindow?: number | undefined;
|
|
16
|
+
maxTokens?: number | undefined;
|
|
17
|
+
reasoning?: boolean | undefined;
|
|
18
|
+
thinkingLevels?: ThinkingLevel[] | undefined;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
export interface ClassifierConfig {
|
|
25
22
|
model: string;
|
|
26
|
-
thinking?: ThinkingLevel;
|
|
23
|
+
thinking?: ThinkingLevel | undefined;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export interface RoutedTierConfig {
|
|
30
27
|
model: string;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
/** False for normalization defaults; omitted on legacy in-memory profiles. */
|
|
29
|
+
thinkingExplicit?: boolean | undefined;
|
|
30
|
+
thinking?: ThinkingLevel | undefined;
|
|
31
|
+
fallbacks?: string[] | undefined;
|
|
32
|
+
/** Canonical targets and exact alias metadata, in the same order as fallbacks. */
|
|
33
|
+
resolvedFallbacks?: ModelDefinition[] | undefined;
|
|
34
|
+
contextWindow?: number | undefined;
|
|
35
|
+
maxTokens?: number | undefined;
|
|
36
|
+
reasoning?: boolean | undefined;
|
|
37
|
+
thinkingLevels?: ThinkingLevel[] | undefined;
|
|
38
|
+
resolvedContextWindow?: number | undefined;
|
|
39
|
+
resolvedMaxTokens?: number | undefined;
|
|
40
|
+
resolvedThinkingLevels?: ThinkingLevel[] | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface JevConfig {
|
|
44
|
+
enabled: boolean;
|
|
45
|
+
apiKey: string;
|
|
46
|
+
endpoint: string;
|
|
47
|
+
model: string;
|
|
48
|
+
timeoutMs: number;
|
|
49
|
+
confidenceThreshold: number;
|
|
50
|
+
maxStateChars: number;
|
|
51
|
+
mode: 'advisory';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface JevProfileConfig {
|
|
55
|
+
enabled: boolean;
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
export interface RouterProfile {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
baselineTier?: RouterTier | undefined;
|
|
60
|
+
jev?: JevProfileConfig | undefined;
|
|
61
|
+
high?: RoutedTierConfig | undefined;
|
|
62
|
+
medium?: RoutedTierConfig | undefined;
|
|
63
|
+
low?: RoutedTierConfig | undefined;
|
|
64
|
+
micro?: RoutedTierConfig | undefined;
|
|
46
65
|
}
|
|
47
66
|
|
|
48
67
|
export interface RouterConfig {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
maxSessionBudget?: number;
|
|
53
|
-
rules?: RoutingRule[];
|
|
68
|
+
jev?: JevConfig | undefined;
|
|
69
|
+
debug?: boolean | undefined;
|
|
70
|
+
classifierModel?: ClassifierConfig | undefined;
|
|
71
|
+
maxSessionBudget?: number | undefined;
|
|
54
72
|
profiles: Record<string, RouterProfile>;
|
|
55
|
-
models?: Record<string, ModelDefinition
|
|
73
|
+
models?: Record<string, ModelDefinition> | undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RouterStatusState {
|
|
77
|
+
routerEnabled: boolean;
|
|
78
|
+
selectedProfile: string | undefined;
|
|
79
|
+
pinnedTierByProfile: RouterPinByProfile;
|
|
80
|
+
lastDecision: RoutingDecision | undefined;
|
|
81
|
+
lastNonRouterModel: string | undefined;
|
|
82
|
+
accumulatedCost: number;
|
|
83
|
+
widgetEnabled: boolean;
|
|
84
|
+
maxSessionBudget: number | undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface RoutePair {
|
|
88
|
+
tier: RouterTier;
|
|
89
|
+
model: string;
|
|
90
|
+
thinking: ThinkingLevel;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface JevRouteCandidate extends RoutePair {
|
|
94
|
+
id: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface JevDependencies {
|
|
98
|
+
fetch?: typeof fetch;
|
|
99
|
+
now?: () => number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface JevRequest {
|
|
103
|
+
taskSummary: string;
|
|
104
|
+
candidates: readonly JevRouteCandidate[];
|
|
105
|
+
profile: JevProfileConfig | undefined;
|
|
106
|
+
/** Absolute monotonic deadline supplied by the routing orchestrator. */
|
|
107
|
+
routingDeadline: number;
|
|
108
|
+
signal?: AbortSignal | undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Only allowlisted local identity and numeric diagnostics cross the adapter boundary. */
|
|
112
|
+
export interface JevAdvice {
|
|
113
|
+
candidateId: string;
|
|
114
|
+
confidence: number;
|
|
115
|
+
latencyMs: number;
|
|
56
116
|
}
|
|
57
117
|
|
|
118
|
+
export const ROUTING_REASON_CODES = [
|
|
119
|
+
'baseline',
|
|
120
|
+
'pinned',
|
|
121
|
+
'continuation',
|
|
122
|
+
'classifier',
|
|
123
|
+
'jev',
|
|
124
|
+
'fallback',
|
|
125
|
+
'budget',
|
|
126
|
+
'legacy',
|
|
127
|
+
] as const;
|
|
128
|
+
export type RoutingReasonCode = (typeof ROUTING_REASON_CODES)[number];
|
|
129
|
+
export const isRoutingReasonCode = (
|
|
130
|
+
value: unknown,
|
|
131
|
+
): value is RoutingReasonCode =>
|
|
132
|
+
ROUTING_REASON_CODES.some((code) => code === value);
|
|
133
|
+
export type RoutingErrorClass = 'advisor-unavailable' | 'deadline';
|
|
134
|
+
|
|
58
135
|
export interface RoutingDecision {
|
|
59
136
|
profile: string;
|
|
60
137
|
tier: RouterTier;
|
|
@@ -62,13 +139,14 @@ export interface RoutingDecision {
|
|
|
62
139
|
targetProvider: string;
|
|
63
140
|
targetModelId: string;
|
|
64
141
|
targetLabel: string;
|
|
65
|
-
|
|
142
|
+
reasonCode: RoutingReasonCode;
|
|
143
|
+
routingLatencyMs?: number | undefined;
|
|
144
|
+
errorClass?: RoutingErrorClass | undefined;
|
|
66
145
|
thinking: ThinkingLevel;
|
|
67
146
|
timestamp: number;
|
|
68
|
-
isClassifier?: boolean;
|
|
69
|
-
isFallback?: boolean;
|
|
70
|
-
isBudgetForced?: boolean;
|
|
71
|
-
isRuleMatched?: boolean;
|
|
147
|
+
isClassifier?: boolean | undefined;
|
|
148
|
+
isFallback?: boolean | undefined;
|
|
149
|
+
isBudgetForced?: boolean | undefined;
|
|
72
150
|
}
|
|
73
151
|
|
|
74
152
|
export interface RouterLastProfileState {
|
|
@@ -76,34 +154,52 @@ export interface RouterLastProfileState {
|
|
|
76
154
|
timestamp: number;
|
|
77
155
|
}
|
|
78
156
|
|
|
157
|
+
export interface PersistedStateInput {
|
|
158
|
+
routerEnabled: boolean;
|
|
159
|
+
selectedProfile: string | undefined;
|
|
160
|
+
pinnedTierByProfile: RouterPinByProfile;
|
|
161
|
+
thinkingByProfile: RouterThinkingByProfile;
|
|
162
|
+
debugEnabled: boolean;
|
|
163
|
+
widgetEnabled: boolean;
|
|
164
|
+
debugHistory: RoutingDecision[];
|
|
165
|
+
lastDecision: RoutingDecision | undefined;
|
|
166
|
+
lastNonRouterModel: string | undefined;
|
|
167
|
+
accumulatedCost: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
79
170
|
export interface RouterPersistedState {
|
|
80
171
|
enabled: boolean;
|
|
81
172
|
selectedProfile: string;
|
|
82
|
-
pinTier?: RouterTier;
|
|
83
|
-
pinByProfile?: RouterPinByProfile;
|
|
84
|
-
thinkingByProfile?: RouterThinkingByProfile;
|
|
85
|
-
debugEnabled?: boolean;
|
|
86
|
-
widgetEnabled?: boolean;
|
|
87
|
-
debugHistory?: RoutingDecision[];
|
|
88
|
-
lastPhase?: RouterPhase;
|
|
89
|
-
lastDecision?: RoutingDecision;
|
|
90
|
-
lastNonRouterModel?: string;
|
|
91
|
-
accumulatedCost?: number;
|
|
173
|
+
pinTier?: RouterTier | undefined;
|
|
174
|
+
pinByProfile?: RouterPinByProfile | undefined;
|
|
175
|
+
thinkingByProfile?: RouterThinkingByProfile | undefined;
|
|
176
|
+
debugEnabled?: boolean | undefined;
|
|
177
|
+
widgetEnabled?: boolean | undefined;
|
|
178
|
+
debugHistory?: RoutingDecision[] | undefined;
|
|
179
|
+
lastPhase?: RouterPhase | undefined;
|
|
180
|
+
lastDecision?: RoutingDecision | undefined;
|
|
181
|
+
lastNonRouterModel?: string | undefined;
|
|
182
|
+
accumulatedCost?: number | undefined;
|
|
92
183
|
timestamp: number;
|
|
93
184
|
}
|
|
94
185
|
|
|
186
|
+
export interface RawRouterConfig {
|
|
187
|
+
jev?: unknown;
|
|
188
|
+
debug?: unknown;
|
|
189
|
+
classifierModel?: unknown;
|
|
190
|
+
phaseBias?: unknown;
|
|
191
|
+
maxSessionBudget?: unknown;
|
|
192
|
+
rules?: unknown;
|
|
193
|
+
profiles?: unknown;
|
|
194
|
+
models?: unknown;
|
|
195
|
+
}
|
|
196
|
+
|
|
95
197
|
export interface ConfigLoadResult {
|
|
96
198
|
config: RouterConfig;
|
|
97
199
|
warnings: string[];
|
|
98
200
|
}
|
|
99
201
|
|
|
100
202
|
export interface ParsedConfigFile {
|
|
101
|
-
config:
|
|
203
|
+
config: RawRouterConfig;
|
|
102
204
|
warnings: string[];
|
|
103
205
|
}
|
|
104
|
-
|
|
105
|
-
export interface CustomSessionEntry {
|
|
106
|
-
type: string;
|
|
107
|
-
customType?: string;
|
|
108
|
-
data?: unknown;
|
|
109
|
-
}
|
package/extensions/ui.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
import type {
|
|
3
|
-
RouterConfig,
|
|
4
3
|
RouterPinByProfile,
|
|
4
|
+
RouterStatusState,
|
|
5
5
|
RouterThinkingByProfile,
|
|
6
6
|
RoutingDecision,
|
|
7
7
|
} from './types';
|
|
8
|
-
|
|
9
|
-
const getEffectiveThinking = (
|
|
10
|
-
thinkingByProfile: RouterThinkingByProfile,
|
|
11
|
-
profileName: string,
|
|
12
|
-
decision: RoutingDecision,
|
|
13
|
-
) => thinkingByProfile[profileName]?.[decision.tier] ?? decision.thinking;
|
|
8
|
+
import { isRoutingReasonCode } from './types';
|
|
14
9
|
|
|
15
10
|
const getDecisionFlags = (decision: RoutingDecision): string[] => {
|
|
16
11
|
const flags: string[] = [];
|
|
17
12
|
if (decision.isFallback) flags.push('fallback');
|
|
18
13
|
if (decision.isBudgetForced) flags.push('budget-limit');
|
|
19
|
-
if (decision.isRuleMatched) flags.push('rule');
|
|
20
14
|
return flags;
|
|
21
15
|
};
|
|
22
16
|
|
|
17
|
+
export const formatDecisionSource = (decision: RoutingDecision): string =>
|
|
18
|
+
isRoutingReasonCode(decision.reasonCode) && decision.reasonCode !== 'legacy'
|
|
19
|
+
? decision.reasonCode
|
|
20
|
+
: '';
|
|
21
|
+
|
|
23
22
|
export const formatDecision = (decision: RoutingDecision): string => {
|
|
24
|
-
|
|
23
|
+
const source = formatDecisionSource(decision);
|
|
24
|
+
return `${decision.profile}: ${decision.tier} -> ${decision.targetProvider}/${decision.targetModelId} [${decision.thinking}]${source ? ` (${source})` : ''}`;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
export const formatPinSummary = (
|
|
@@ -53,16 +53,18 @@ export const formatModelRef = (ref: string | undefined): string => {
|
|
|
53
53
|
|
|
54
54
|
export const updateStatus = (
|
|
55
55
|
ctx: ExtensionContext,
|
|
56
|
-
|
|
57
|
-
selectedProfile: string | undefined,
|
|
58
|
-
pinnedTierByProfile: RouterPinByProfile,
|
|
59
|
-
thinkingByProfile: RouterThinkingByProfile,
|
|
60
|
-
lastDecision: RoutingDecision | undefined,
|
|
61
|
-
lastNonRouterModel: string | undefined,
|
|
62
|
-
accumulatedCost: number,
|
|
63
|
-
widgetEnabled: boolean,
|
|
64
|
-
currentConfig: RouterConfig,
|
|
56
|
+
state: RouterStatusState,
|
|
65
57
|
) => {
|
|
58
|
+
const {
|
|
59
|
+
routerEnabled,
|
|
60
|
+
selectedProfile,
|
|
61
|
+
pinnedTierByProfile,
|
|
62
|
+
lastDecision,
|
|
63
|
+
lastNonRouterModel,
|
|
64
|
+
accumulatedCost,
|
|
65
|
+
widgetEnabled,
|
|
66
|
+
maxSessionBudget,
|
|
67
|
+
} = state;
|
|
66
68
|
const activeRouterProfile = routerEnabled ? selectedProfile : undefined;
|
|
67
69
|
const statusProfile = selectedProfile ?? 'none';
|
|
68
70
|
const activePin = selectedProfile
|
|
@@ -73,16 +75,11 @@ export const updateStatus = (
|
|
|
73
75
|
if (activeRouterProfile) {
|
|
74
76
|
const matchesProfile =
|
|
75
77
|
lastDecision && lastDecision.profile === activeRouterProfile;
|
|
76
|
-
const matchesPin = activePin
|
|
78
|
+
const matchesPin = !activePin || lastDecision?.tier === activePin;
|
|
77
79
|
|
|
78
80
|
let statusText: string;
|
|
79
81
|
if (lastDecision && matchesProfile && matchesPin) {
|
|
80
|
-
|
|
81
|
-
thinkingByProfile,
|
|
82
|
-
activeRouterProfile,
|
|
83
|
-
lastDecision,
|
|
84
|
-
);
|
|
85
|
-
statusText = `router:${activeRouterProfile}${pinLabel} -> ${lastDecision.tier} -> ${lastDecision.targetProvider}/${lastDecision.targetModelId} (${effectiveThinking})`;
|
|
82
|
+
statusText = `router:${activeRouterProfile}${pinLabel} -> ${lastDecision.tier} -> ${lastDecision.targetProvider}/${lastDecision.targetModelId} (${lastDecision.thinking})`;
|
|
86
83
|
} else {
|
|
87
84
|
statusText = `router:${activeRouterProfile}${pinLabel} -> waiting`;
|
|
88
85
|
}
|
|
@@ -101,22 +98,23 @@ export const updateStatus = (
|
|
|
101
98
|
`Profile: ${statusProfile}${activeRouterProfile ? ' (active)' : ''}`,
|
|
102
99
|
`Pin: ${activePin ?? 'auto'}`,
|
|
103
100
|
`Cost: $${accumulatedCost.toFixed(4)}` +
|
|
104
|
-
(
|
|
105
|
-
? ` / $${currentConfig.maxSessionBudget.toFixed(2)}`
|
|
106
|
-
: ''),
|
|
101
|
+
(maxSessionBudget ? ` / $${maxSessionBudget.toFixed(2)}` : ''),
|
|
107
102
|
];
|
|
108
103
|
if (lastDecision && lastDecision.profile === statusProfile) {
|
|
109
|
-
const effectiveThinking = getEffectiveThinking(
|
|
110
|
-
thinkingByProfile,
|
|
111
|
-
statusProfile,
|
|
112
|
-
lastDecision,
|
|
113
|
-
);
|
|
114
104
|
const flags = getDecisionFlags(lastDecision);
|
|
115
105
|
const flagsStr = flags.length > 0 ? ` [${flags.join(',')}]` : '';
|
|
116
106
|
|
|
117
107
|
widgetLines.push(
|
|
118
|
-
`Route: ${lastDecision.tier}${flagsStr} -> ${lastDecision.targetProvider}/${lastDecision.targetModelId} (${
|
|
108
|
+
`Route: ${lastDecision.tier}${flagsStr} -> ${lastDecision.targetProvider}/${lastDecision.targetModelId} (${lastDecision.thinking})`,
|
|
119
109
|
`Phase: ${lastDecision.phase}`,
|
|
110
|
+
`Source: ${formatDecisionSource(lastDecision) || 'unknown'}`,
|
|
111
|
+
...(Number.isFinite(lastDecision.routingLatencyMs)
|
|
112
|
+
? [`Routing: ${Math.round(lastDecision.routingLatencyMs ?? 0)}ms`]
|
|
113
|
+
: []),
|
|
114
|
+
...(lastDecision.errorClass === 'deadline' ||
|
|
115
|
+
lastDecision.errorClass === 'advisor-unavailable'
|
|
116
|
+
? [`Routing error: ${lastDecision.errorClass}`]
|
|
117
|
+
: []),
|
|
120
118
|
);
|
|
121
119
|
} else if (!routerEnabled && lastNonRouterModel) {
|
|
122
120
|
widgetLines.push(`Fallback: ${lastNonRouterModel}`);
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"debug": false,
|
|
3
|
+
"jev": {
|
|
4
|
+
"enabled": false,
|
|
5
|
+
"apiKey": "<rendered by chezmoi/1Password in user config only>",
|
|
6
|
+
"endpoint": "https://api.typesafe.ai/v1/systemone",
|
|
7
|
+
"model": "jev-1.13.0",
|
|
8
|
+
"timeoutMs": 750,
|
|
9
|
+
"confidenceThreshold": 0.65,
|
|
10
|
+
"maxStateChars": 12000,
|
|
11
|
+
"mode": "advisory"
|
|
12
|
+
},
|
|
3
13
|
"classifierModel": "flash",
|
|
4
|
-
"phaseBias": 0.5,
|
|
5
14
|
"maxSessionBudget": 1.0,
|
|
6
15
|
"models": {
|
|
7
16
|
"gpt-pro": {
|
|
@@ -25,30 +34,27 @@
|
|
|
25
34
|
"contextWindow": 200000
|
|
26
35
|
}
|
|
27
36
|
},
|
|
28
|
-
"rules": [
|
|
29
|
-
{
|
|
30
|
-
"matches": ["deploy", "production", "release"],
|
|
31
|
-
"tier": "high",
|
|
32
|
-
"reason": "Safety check for production tasks"
|
|
33
|
-
},
|
|
34
|
-
{ "matches": "changelog", "tier": "low" }
|
|
35
|
-
],
|
|
36
37
|
"profiles": {
|
|
37
38
|
"auto": {
|
|
39
|
+
"baselineTier": "medium",
|
|
40
|
+
"jev": { "enabled": false },
|
|
38
41
|
"high": {
|
|
39
42
|
"model": "gpt-pro",
|
|
40
43
|
"thinking": "high",
|
|
41
44
|
"fallbacks": ["sonnet"]
|
|
42
45
|
},
|
|
43
46
|
"medium": { "model": "flash", "thinking": "medium" },
|
|
44
|
-
"low": { "model": "nano", "thinking": "
|
|
47
|
+
"low": { "model": "nano", "thinking": "off" },
|
|
48
|
+
"micro": { "model": "nano", "thinking": "off" }
|
|
45
49
|
},
|
|
46
50
|
"cheap": {
|
|
51
|
+
"jev": { "enabled": false },
|
|
47
52
|
"high": { "model": "flash", "thinking": "low" },
|
|
48
53
|
"medium": { "model": "nano", "thinking": "off" },
|
|
49
54
|
"low": { "model": "google/gemini-flash-lite-latest", "thinking": "off" }
|
|
50
55
|
},
|
|
51
56
|
"deep": {
|
|
57
|
+
"jev": { "enabled": false },
|
|
52
58
|
"high": {
|
|
53
59
|
"model": "openai/o1-preview",
|
|
54
60
|
"thinking": "xhigh",
|
|
@@ -58,6 +64,7 @@
|
|
|
58
64
|
"low": { "model": "flash", "thinking": "low" }
|
|
59
65
|
},
|
|
60
66
|
"anthropic": {
|
|
67
|
+
"jev": { "enabled": false },
|
|
61
68
|
"high": {
|
|
62
69
|
"model": "sonnet",
|
|
63
70
|
"thinking": "high"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexeiled/pi-model-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"extensions",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"README.md",
|
|
12
12
|
"CHANGELOG.md"
|
|
13
13
|
],
|
|
14
|
-
"description": "
|
|
14
|
+
"description": "Independently maintained Pi model router with tiered routing, soft budget controls, classifier fallback, and custom-provider support.",
|
|
15
15
|
"keywords": [
|
|
16
16
|
"pi-package",
|
|
17
17
|
"pi",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=22.19.0"
|
|
26
26
|
},
|
|
27
|
-
"packageManager": "npm@
|
|
27
|
+
"packageManager": "npm@12.0.2",
|
|
28
28
|
"author": "Alexei Ledenev",
|
|
29
29
|
"contributors": [
|
|
30
30
|
"Ye Liu"
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"tsc": "tsc --noEmit",
|
|
53
53
|
"build": "tsc",
|
|
54
54
|
"prepublishOnly": "npm run check && npm test",
|
|
55
|
-
"test": "vitest run",
|
|
55
|
+
"test": "vitest run --pool=threads",
|
|
56
56
|
"check": "biome check --error-on-warnings . && npm run tsc",
|
|
57
57
|
"lint": "biome lint --error-on-warnings .",
|
|
58
58
|
"lint:fix": "biome lint --write --error-on-warnings .",
|