@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.
@@ -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
- 'profile',
41
- 'targetProvider',
42
- 'targetModelId',
43
- 'targetLabel',
44
- 'reasoning',
45
- ].every((key) => typeof value[key] === 'string') &&
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
- export const buildPersistedState = (
132
- routerEnabled: boolean,
133
- selectedProfile: string | undefined,
134
- pinnedTierByProfile: RouterPinByProfile,
135
- thinkingByProfile: RouterThinkingByProfile,
136
- debugEnabled: boolean,
137
- widgetEnabled: boolean,
138
- debugHistory: RoutingDecision[],
139
- lastDecision: RoutingDecision | undefined,
140
- lastNonRouterModel: string | undefined,
141
- accumulatedCost: number,
142
- ): RouterPersistedState => {
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
- pinTier: selectedProfile ? pinnedTierByProfile[selectedProfile] : undefined,
147
- pinByProfile: { ...pinnedTierByProfile },
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(),
@@ -1,60 +1,137 @@
1
1
  import type { ThinkingLevel } from '@earendil-works/pi-agent-core';
2
2
 
3
- export type RouterTier = 'high' | 'medium' | 'low';
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
- thinking?: ThinkingLevel;
32
- fallbacks?: string[];
33
- contextWindow?: number;
34
- maxTokens?: number;
35
- reasoning?: boolean;
36
- thinkingLevels?: ThinkingLevel[];
37
- resolvedContextWindow?: number;
38
- resolvedMaxTokens?: number;
39
- resolvedThinkingLevels?: ThinkingLevel[];
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
- high?: RoutedTierConfig;
44
- medium?: RoutedTierConfig;
45
- low?: RoutedTierConfig;
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
- debug?: boolean;
50
- classifierModel?: ClassifierConfig;
51
- phaseBias?: number;
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
- reasoning: string;
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: Partial<RouterConfig>;
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
- return `${decision.profile}: ${decision.tier} -> ${decision.targetProvider}/${decision.targetModelId} [${decision.thinking}] (${decision.reasoning})`;
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
- routerEnabled: boolean,
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 ? lastDecision?.tier === activePin : true;
78
+ const matchesPin = !activePin || lastDecision?.tier === activePin;
77
79
 
78
80
  let statusText: string;
79
81
  if (lastDecision && matchesProfile && matchesPin) {
80
- const effectiveThinking = getEffectiveThinking(
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
- (currentConfig.maxSessionBudget
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} (${effectiveThinking})`,
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": "low" }
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.5.1",
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": "Smart per-turn model router for Pi that optimizes your AI budget and usage limits by dynamically switching LLM tiers based on task complexity, budget, and phase awareness.",
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@11.18.0",
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 .",