@robhowley/pi-openrouter 0.8.0 → 0.8.2

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.
@@ -27,11 +27,15 @@ import {
27
27
  groupSkipReasons,
28
28
  } from './models/sync.js';
29
29
  import { loadCache, getCacheAgeMs, formatDuration } from './models/cache.js';
30
+ import { mapOpenRouterModels } from './models/mapper.js';
30
31
 
31
32
  // Store the current session state for use in command handlers
32
33
  let currentSessionState: OpenRouterSessionState | null = null;
33
34
  let sessionTrackingInstalled = false;
34
35
 
36
+ // Store startup cache state for notifications
37
+ let startupCacheInfo: { count: number; age: string } | undefined;
38
+
35
39
  // =============================================================================
36
40
  // Utility Functions
37
41
  // =============================================================================
@@ -87,7 +91,36 @@ function getCurrentSessionId(ctx: { sessionManager: { getSessionId(): string } }
87
91
  }
88
92
  }
89
93
 
90
- export default function (pi: ExtensionAPI) {
94
+ export default async function (pi: ExtensionAPI) {
95
+ // Eager cache load on extension startup (before any sessions)
96
+ startupCacheInfo = undefined;
97
+ let startupCacheWarning: string | undefined;
98
+ if (isSyncEnabled()) {
99
+ const cache = await loadCache().catch(() => null);
100
+
101
+ if (cache?.models.length) {
102
+ try {
103
+ const { configs } = await mapOpenRouterModels(cache.models);
104
+
105
+ // Register models directly with Pi's OpenRouter provider
106
+ pi.registerProvider('openrouter', {
107
+ baseUrl: 'https://openrouter.ai/api/v1',
108
+ apiKey: 'OPENROUTER_API_KEY',
109
+ api: 'openai-completions',
110
+ models: configs,
111
+ authHeader: true,
112
+ });
113
+
114
+ // Store for session_start notification
115
+ const age = formatDuration(getCacheAgeMs(cache));
116
+ startupCacheInfo = { count: configs.length, age };
117
+ } catch (error) {
118
+ startupCacheInfo = undefined;
119
+ startupCacheWarning = `OpenRouter: cached models found but failed to register: ${error instanceof Error ? error.message : String(error)}`;
120
+ }
121
+ }
122
+ }
123
+
91
124
  // Install before_provider_request hook once
92
125
  if (!sessionTrackingInstalled) {
93
126
  sessionTrackingInstalled = true;
@@ -198,6 +231,27 @@ export default function (pi: ExtensionAPI) {
198
231
  stopBackgroundRefresh();
199
232
  });
200
233
 
234
+ // Notify on first session start after extension load
235
+ pi.on('session_start', (event, ctx) => {
236
+ if (!ctx.hasUI) return;
237
+
238
+ // Show a persistent status indicator
239
+ if (startupCacheInfo) {
240
+ const statusText = `OpenRouter ${startupCacheInfo.count} models`;
241
+ ctx.ui.setStatus('openrouter', ctx.ui.theme.fg('dim', statusText));
242
+ }
243
+
244
+ // Show a one-time notification on startup
245
+ if (event.reason === 'startup' && startupCacheInfo) {
246
+ const notice = `OpenRouter: ${startupCacheInfo.count} models loaded from cache (${startupCacheInfo.age} old). Run /openrouter models-sync to refresh.`;
247
+ ctx.ui.notify(notice, 'info');
248
+ }
249
+
250
+ if (event.reason === 'startup' && startupCacheWarning) {
251
+ ctx.ui.notify(startupCacheWarning, 'warning');
252
+ }
253
+ });
254
+
201
255
  pi.registerCommand('openrouter-usage', {
202
256
  description: 'Show OpenRouter usage: caps, spend, burn rate, and model breakdowns',
203
257
  getArgumentCompletions: () => null,
@@ -4,9 +4,9 @@ import { createValidModel } from '../../__tests__/fixtures.js';
4
4
  import type { OpenRouterModel, MapResult } from '../types.js';
5
5
 
6
6
  describe('mapOpenRouterModel', () => {
7
- it('should map a valid model correctly', () => {
7
+ it('should map a valid model correctly', async () => {
8
8
  const model = createValidModel();
9
- const result = mapOpenRouterModel(model);
9
+ const result = await mapOpenRouterModel(model);
10
10
 
11
11
  expect(result).not.toBeNull();
12
12
  expect(result!.id).toBe('test/model');
@@ -17,11 +17,11 @@ describe('mapOpenRouterModel', () => {
17
17
  expect(result!.cost.output).toBe(1.5); // 0.0000015 * 1M
18
18
  });
19
19
 
20
- it('should use name fallback to id when name missing', () => {
20
+ it('should use name fallback to id when name missing', async () => {
21
21
  const model = createValidModel();
22
22
  // @ts-expect-error: intentionally setting name to undefined for test
23
23
  model.name = undefined;
24
- const result = mapOpenRouterModel(model);
24
+ const result = await mapOpenRouterModel(model);
25
25
 
26
26
  expect(result!.name).toBe('test/model');
27
27
  });
@@ -46,78 +46,78 @@ describe('mapOpenRouterModel', () => {
46
46
  ];
47
47
 
48
48
  skipCases.forEach(({ name, overrides }) => {
49
- it(`should skip model with ${name}`, () => {
49
+ it(`should skip model with ${name}`, async () => {
50
50
  const model = createValidModel();
51
51
  if (typeof overrides === 'function') {
52
52
  overrides(model);
53
53
  } else {
54
54
  Object.assign(model, overrides);
55
55
  }
56
- expect(mapOpenRouterModel(model)).toBeNull();
56
+ expect(await mapOpenRouterModel(model)).toBeNull();
57
57
  });
58
58
  });
59
59
  });
60
60
 
61
61
  describe('reasoning detection', () => {
62
- it("should detect reasoning from 'reasoning' parameter", () => {
62
+ it("should detect reasoning from 'reasoning' parameter", async () => {
63
63
  const model = createValidModel({
64
64
  supported_parameters: ['temperature', 'reasoning', 'max_tokens'],
65
65
  });
66
- expect(mapOpenRouterModel(model)!.reasoning).toBe(true);
66
+ expect((await mapOpenRouterModel(model))!.reasoning).toBe(true);
67
67
  });
68
68
 
69
- it("should detect reasoning from 'include_reasoning' parameter", () => {
69
+ it("should detect reasoning from 'include_reasoning' parameter", async () => {
70
70
  const model = createValidModel({
71
71
  supported_parameters: ['include_reasoning'],
72
72
  });
73
- expect(mapOpenRouterModel(model)!.reasoning).toBe(true);
73
+ expect((await mapOpenRouterModel(model))!.reasoning).toBe(true);
74
74
  });
75
75
 
76
- it('should not detect reasoning when neither parameter present', () => {
76
+ it('should not detect reasoning when neither parameter present', async () => {
77
77
  const model = createValidModel({
78
78
  supported_parameters: ['temperature', 'max_tokens'],
79
79
  });
80
- expect(mapOpenRouterModel(model)!.reasoning).toBe(false);
80
+ expect((await mapOpenRouterModel(model))!.reasoning).toBe(false);
81
81
  });
82
82
 
83
- it('should handle missing supported_parameters', () => {
83
+ it('should handle missing supported_parameters', async () => {
84
84
  const model = createValidModel({ supported_parameters: [] as any });
85
- expect(mapOpenRouterModel(model)!.reasoning).toBe(false);
85
+ expect((await mapOpenRouterModel(model))!.reasoning).toBe(false);
86
86
  });
87
87
  });
88
88
 
89
89
  describe('input modality detection', () => {
90
- it('should detect image support from input_modalities', () => {
90
+ it('should detect image support from input_modalities', async () => {
91
91
  const model = createValidModel({
92
92
  architecture: { input_modalities: ['text', 'image'] } as any,
93
93
  });
94
- expect(mapOpenRouterModel(model)!.input).toEqual(['text', 'image']);
94
+ expect((await mapOpenRouterModel(model))!.input).toEqual(['text', 'image']);
95
95
  });
96
96
 
97
- it('should default to text-only without image in modalities', () => {
97
+ it('should default to text-only without image in modalities', async () => {
98
98
  const model = createValidModel({
99
99
  architecture: { input_modalities: ['text'] } as any,
100
100
  });
101
- expect(mapOpenRouterModel(model)!.input).toEqual(['text']);
101
+ expect((await mapOpenRouterModel(model))!.input).toEqual(['text']);
102
102
  });
103
103
 
104
- it('should default to text-only when architecture missing', () => {
104
+ it('should default to text-only when architecture missing', async () => {
105
105
  const model = createValidModel({ architecture: null as any });
106
- expect(mapOpenRouterModel(model)!.input).toEqual(['text']);
106
+ expect((await mapOpenRouterModel(model))!.input).toEqual(['text']);
107
107
  });
108
108
  });
109
109
 
110
110
  describe('cost calculation', () => {
111
- it('should convert per-token to per-1M-tokens', () => {
111
+ it('should convert per-token to per-1M-tokens', async () => {
112
112
  const model = createValidModel({
113
113
  pricing: { prompt: '0.000002', completion: '0.000006' },
114
114
  });
115
- const result = mapOpenRouterModel(model);
115
+ const result = await mapOpenRouterModel(model);
116
116
  expect(result!.cost.input).toBe(2.0); // 0.000002 * 1,000,000
117
117
  expect(result!.cost.output).toBe(6.0); // 0.000006 * 1,000,000
118
118
  });
119
119
 
120
- it('should handle cache pricing when present', () => {
120
+ it('should handle cache pricing when present', async () => {
121
121
  const model = createValidModel({
122
122
  pricing: {
123
123
  prompt: '0.000001',
@@ -126,71 +126,90 @@ describe('mapOpenRouterModel', () => {
126
126
  input_cache_write: '0.000001',
127
127
  },
128
128
  });
129
- const result = mapOpenRouterModel(model);
129
+ const result = await mapOpenRouterModel(model);
130
130
  expect(result!.cost.cacheRead).toBe(0.5);
131
131
  expect(result!.cost.cacheWrite).toBe(1.0);
132
132
  });
133
133
 
134
- it('should default cache pricing to 0 when missing', () => {
134
+ it('should default cache pricing to 0 when missing', async () => {
135
135
  const model = createValidModel({
136
136
  pricing: { prompt: '0.000001', completion: '0.000003' },
137
137
  });
138
- const result = mapOpenRouterModel(model);
138
+ const result = await mapOpenRouterModel(model);
139
139
  expect(result!.cost.cacheRead).toBe(0);
140
140
  expect(result!.cost.cacheWrite).toBe(0);
141
141
  });
142
142
  });
143
143
 
144
144
  describe('contextWindow fallback', () => {
145
- it('should prefer top_provider.context_length', () => {
145
+ it('should prefer top_provider.context_length', async () => {
146
146
  const model = createValidModel({
147
147
  context_length: 8000,
148
148
  top_provider: { context_length: 128000 },
149
149
  });
150
- expect(mapOpenRouterModel(model)!.contextWindow).toBe(128000);
150
+ expect((await mapOpenRouterModel(model))!.contextWindow).toBe(128000);
151
151
  });
152
152
 
153
- it('should fall back to context_length', () => {
153
+ it('should fall back to context_length', async () => {
154
154
  const model = createValidModel({
155
155
  context_length: 32000,
156
156
  top_provider: null as any,
157
157
  });
158
- expect(mapOpenRouterModel(model)!.contextWindow).toBe(32000);
158
+ expect((await mapOpenRouterModel(model))!.contextWindow).toBe(32000);
159
159
  });
160
160
  });
161
161
 
162
162
  describe('maxTokens fallback', () => {
163
- it('should prefer top_provider.max_completion_tokens', () => {
163
+ it('should prefer top_provider.max_completion_tokens', async () => {
164
164
  const model = createValidModel({
165
165
  top_provider: { max_completion_tokens: 8192 },
166
166
  per_request_limits: { completion_tokens: 4096 },
167
167
  });
168
- expect(mapOpenRouterModel(model)!.maxTokens).toBe(8192);
168
+ expect((await mapOpenRouterModel(model))!.maxTokens).toBe(8192);
169
169
  });
170
170
 
171
- it('should fall back to per_request_limits.completion_tokens', () => {
171
+ it('should fall back to per_request_limits.completion_tokens', async () => {
172
172
  const model = createValidModel({
173
173
  per_request_limits: { completion_tokens: 8192 },
174
174
  });
175
- expect(mapOpenRouterModel(model)!.maxTokens).toBe(8192);
175
+ expect((await mapOpenRouterModel(model))!.maxTokens).toBe(8192);
176
176
  });
177
177
 
178
- it('should use default when neither present', () => {
178
+ it('should use default when neither present', async () => {
179
179
  const model = createValidModel();
180
- expect(mapOpenRouterModel(model)!.maxTokens).toBe(4096);
180
+ expect((await mapOpenRouterModel(model))!.maxTokens).toBe(4096);
181
+ });
182
+ });
183
+
184
+ describe('thinkingLevelMap', () => {
185
+ it('should include thinkingLevelMap when available from built-in registry', async () => {
186
+ // This test documents the expected behavior when the model exists
187
+ // in Pi's built-in registry. The actual lookup happens via dynamic import
188
+ // of @mariozechner/pi-ai or @earendil-works/pi-ai at runtime.
189
+ const model = createValidModel({
190
+ id: 'deepseek/deepseek-v4-pro',
191
+ supported_parameters: ['reasoning'],
192
+ });
193
+ const result = await mapOpenRouterModel(model);
194
+ expect(result).not.toBeNull();
195
+ // If pi-ai is available and has this model, thinkingLevelMap will be set
196
+ // Otherwise it will be undefined (which is valid)
197
+ expect(
198
+ result!.thinkingLevelMap === undefined || typeof result!.thinkingLevelMap === 'object',
199
+ ).toBe(true);
181
200
  });
182
201
  });
183
202
  });
184
203
 
185
204
  describe('mapOpenRouterModels', () => {
186
- it('should map multiple models and track skips', () => {
205
+ it('should map multiple models and track skips', async () => {
187
206
  const models: OpenRouterModel[] = [
188
207
  createValidModel({ id: 'model/valid-1', name: 'Valid 1' }),
189
208
  createValidModel({ id: '', name: 'Invalid (no id)' }), // will skip
190
209
  createValidModel({ id: 'model/valid-2', name: 'Valid 2' }),
191
210
  ];
192
211
 
193
- const result: MapResult = mapOpenRouterModels(models);
212
+ const result: MapResult = await mapOpenRouterModels(models);
194
213
 
195
214
  expect(result.configs).toHaveLength(2);
196
215
  expect(result.skipped).toBe(1);
@@ -198,13 +217,13 @@ describe('mapOpenRouterModels', () => {
198
217
  expect(result.configs[1]!.id).toBe('model/valid-2');
199
218
  });
200
219
 
201
- it('should handle empty array', () => {
202
- const result = mapOpenRouterModels([] as OpenRouterModel[]);
220
+ it('should handle empty array', async () => {
221
+ const result = await mapOpenRouterModels([] as OpenRouterModel[]);
203
222
  expect(result.configs).toHaveLength(0);
204
223
  expect(result.skipped).toBe(0);
205
224
  });
206
225
 
207
- it('should skip all invalid models', () => {
226
+ it('should skip all invalid models', async () => {
208
227
  const models: OpenRouterModel[] = [
209
228
  createValidModel({ id: '' }),
210
229
  createValidModel({ pricing: { prompt: '0.000001' } as any }),
@@ -214,7 +233,7 @@ describe('mapOpenRouterModels', () => {
214
233
  }),
215
234
  ];
216
235
 
217
- const result = mapOpenRouterModels(models);
236
+ const result = await mapOpenRouterModels(models);
218
237
  expect(result.configs).toHaveLength(0);
219
238
  expect(result.skipped).toBe(3);
220
239
  });
@@ -2,6 +2,58 @@ import type { OpenRouterModel, PiModelConfig, SkipReason, MapResult } from './ty
2
2
  import { ROUTER_ALIASES } from './types.js';
3
3
  import type { Model as SDKModel } from '@openrouter/sdk/models/index.js';
4
4
 
5
+ // Cache for built-in OpenRouter models from pi-ai
6
+ // Populated lazily on first access
7
+ let builtInOpenRouterModels: Map<string, PiModelConfig> | undefined;
8
+
9
+ /**
10
+ * Load built-in OpenRouter models from pi-ai package if available.
11
+ * This allows us to preserve thinkingLevelMap and other metadata from
12
+ * Pi's built-in registry when syncing models from OpenRouter API.
13
+ */
14
+ async function loadBuiltInOpenRouterModels(): Promise<Map<string, PiModelConfig>> {
15
+ if (builtInOpenRouterModels !== undefined) {
16
+ return builtInOpenRouterModels;
17
+ }
18
+
19
+ const models = new Map<string, PiModelConfig>();
20
+
21
+ try {
22
+ // Import from pi-ai to get built-in model registry
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ const { getModels } = (await import('@earendil-works/pi-ai')) as {
25
+ getModels: (provider: string) => unknown[];
26
+ };
27
+
28
+ const openrouterModels = getModels('openrouter');
29
+ if (Array.isArray(openrouterModels)) {
30
+ for (const model of openrouterModels) {
31
+ // Extract thinkingLevelMap from built-in model if present
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ const modelWithThinking = model as { id: string; thinkingLevelMap?: unknown };
34
+ if (modelWithThinking.id) {
35
+ models.set(modelWithThinking.id, model as PiModelConfig);
36
+ }
37
+ }
38
+ }
39
+ } catch {
40
+ // Ignore - built-in registry not available, will sync without merging
41
+ }
42
+
43
+ builtInOpenRouterModels = models;
44
+ return models;
45
+ }
46
+
47
+ /**
48
+ * Get thinkingLevelMap from built-in registry for a model, if available.
49
+ */
50
+ async function getBuiltInThinkingLevelMap(
51
+ modelId: string,
52
+ ): Promise<PiModelConfig['thinkingLevelMap'] | undefined> {
53
+ const builtIn = await loadBuiltInOpenRouterModels();
54
+ return builtIn.get(modelId)?.thinkingLevelMap;
55
+ }
56
+
5
57
  const COST_PER_MILLION = 1_000_000;
6
58
  const DEFAULT_MAX_TOKENS = 4096;
7
59
 
@@ -103,15 +155,22 @@ function validateModel(model: OpenRouterModel): ValidationResult {
103
155
 
104
156
  /**
105
157
  * Build PiModelConfig from a validated OpenRouterModel.
158
+ * Merges thinkingLevelMap from Pi's built-in registry if available.
106
159
  */
107
- function buildPiConfig(model: OpenRouterModel, contextWindow: number): PiModelConfig {
160
+ async function buildPiConfig(
161
+ model: OpenRouterModel,
162
+ contextWindow: number,
163
+ ): Promise<PiModelConfig> {
108
164
  const supportedParams = model.supported_parameters ?? [];
109
165
  const hasReasoning =
110
166
  supportedParams.includes('reasoning') || supportedParams.includes('include_reasoning');
111
167
  const inputModalities = model.architecture?.input_modalities;
112
168
  const supportsImages = inputModalities?.includes('image') ?? false;
113
169
 
114
- return {
170
+ // Fetch thinkingLevelMap from built-in registry if this is a reasoning model
171
+ const thinkingLevelMap = hasReasoning ? await getBuiltInThinkingLevelMap(model.id) : undefined;
172
+
173
+ const config: PiModelConfig = {
115
174
  id: model.id,
116
175
  name: model.name ?? model.id,
117
176
  reasoning: hasReasoning,
@@ -128,12 +187,25 @@ function buildPiConfig(model: OpenRouterModel, contextWindow: number): PiModelCo
128
187
  model.per_request_limits?.completion_tokens ??
129
188
  DEFAULT_MAX_TOKENS,
130
189
  };
190
+
191
+ // Only add thinkingLevelMap if it's defined for exactOptionalPropertyTypes compatibility
192
+ if (thinkingLevelMap !== undefined) {
193
+ config.thinkingLevelMap = thinkingLevelMap;
194
+ }
195
+
196
+ return config;
131
197
  }
132
198
 
133
199
  /**
134
200
  * Maps multiple OpenRouter models, tracking skips.
201
+ * Async to allow fetching thinkingLevelMap from built-in registry.
135
202
  */
136
- export function mapOpenRouterModels(models: OpenRouterModel[] | SDKModel[]): MapResult {
203
+ export async function mapOpenRouterModels(
204
+ models: OpenRouterModel[] | SDKModel[],
205
+ ): Promise<MapResult> {
206
+ // Pre-load built-in models for efficient lookup during mapping
207
+ await loadBuiltInOpenRouterModels();
208
+
137
209
  const configs: PiModelConfig[] = [];
138
210
  let skipped = 0;
139
211
  const skippedDetails: SkipReason[] = [];
@@ -154,7 +226,7 @@ export function mapOpenRouterModels(models: OpenRouterModel[] | SDKModel[]): Map
154
226
  continue;
155
227
  }
156
228
 
157
- configs.push(buildPiConfig(model, validation.contextWindow));
229
+ configs.push(await buildPiConfig(model, validation.contextWindow));
158
230
  }
159
231
 
160
232
  return { configs, skipped, skippedDetails };
@@ -163,8 +235,13 @@ export function mapOpenRouterModels(models: OpenRouterModel[] | SDKModel[]): Map
163
235
  /**
164
236
  * Maps a single OpenRouter model to Pi model config.
165
237
  * Returns null if the model should be skipped.
238
+ * Async to allow fetching thinkingLevelMap from built-in registry.
166
239
  */
167
- export function mapOpenRouterModel(model: OpenRouterModel | SDKModel): PiModelConfig | null {
240
+ export async function mapOpenRouterModel(
241
+ model: OpenRouterModel | SDKModel,
242
+ ): Promise<PiModelConfig | null> {
243
+ await loadBuiltInOpenRouterModels();
244
+
168
245
  const normalized = normalizeModel(model);
169
246
 
170
247
  // Router aliases are handled separately, skip them here
@@ -66,7 +66,7 @@ export function getSyncState(): SyncResult | null {
66
66
  * Uses modelRegistry.registerProvider() to add models to the built-in openrouter provider.
67
67
  * The models array replaces all existing models for the provider.
68
68
  */
69
- async function registerModelsWithProvider(
69
+ export async function registerModelsWithProvider(
70
70
  ctx: ExtensionContext,
71
71
  configs: PiModelConfig[],
72
72
  ): Promise<void> {
@@ -79,8 +79,6 @@ async function registerModelsWithProvider(
79
79
  models: configs,
80
80
  authHeader: true,
81
81
  });
82
-
83
- console.log(`[pi-openrouter] Registered ${configs.length} models with OpenRouter provider`);
84
82
  }
85
83
 
86
84
  /**
@@ -134,7 +132,7 @@ export async function syncModels(_ctx: ExtensionContext): Promise<SyncResult> {
134
132
  // Attempt 1: Fetch from API
135
133
  try {
136
134
  const response = await fetchUserModels();
137
- const { configs, skipped, skippedDetails } = mapOpenRouterModels(response.data);
135
+ const { configs, skipped, skippedDetails } = await mapOpenRouterModels(response.data);
138
136
 
139
137
  // Add built-in router aliases that don't appear in /models/user endpoint
140
138
  const configsWithRouters = [...configs, ...BUILTIN_ROUTER_MODELS];
@@ -174,7 +172,7 @@ export async function syncModels(_ctx: ExtensionContext): Promise<SyncResult> {
174
172
 
175
173
  if (cache) {
176
174
  // Attempt 2: Use cached models
177
- const { configs, skipped } = mapOpenRouterModels(cache.models);
175
+ const { configs, skipped } = await mapOpenRouterModels(cache.models);
178
176
 
179
177
  await registerModelsWithProvider(_ctx, configs);
180
178
 
@@ -32,6 +32,20 @@ export interface OpenRouterModelsResponse {
32
32
  data: OpenRouterModel[];
33
33
  }
34
34
 
35
+ /**
36
+ * Pi thinking level map - copied from pi-ai's Model type.
37
+ * Levels mapped to null are hidden in Pi's UI.
38
+ * Levels mapped to strings are sent to the provider API.
39
+ */
40
+ export interface ThinkingLevelMap {
41
+ off?: string | null;
42
+ minimal?: string | null;
43
+ low?: string | null;
44
+ medium?: string | null;
45
+ high?: string | null;
46
+ xhigh?: string | null;
47
+ }
48
+
35
49
  /**
36
50
  * Mapped Pi model configuration for provider registration
37
51
  */
@@ -48,6 +62,7 @@ export interface PiModelConfig {
48
62
  };
49
63
  contextWindow: number;
50
64
  maxTokens: number;
65
+ thinkingLevelMap?: ThinkingLevelMap;
51
66
  }
52
67
 
53
68
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robhowley/pi-openrouter",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "type": "module",
5
5
  "description": "Live OpenRouter spend/account TUI overlays, user-scoped model sync, and session tagging for Pi.",
6
6
  "license": "MIT",
@@ -40,10 +40,13 @@
40
40
  "@openrouter/sdk": "^0.12.21"
41
41
  },
42
42
  "peerDependencies": {
43
- "@mariozechner/pi-ai": "*",
44
- "@mariozechner/pi-coding-agent": "*"
43
+ "@earendil-works/pi-ai": "*",
44
+ "@earendil-works/pi-coding-agent": "*"
45
45
  },
46
46
  "devDependencies": {
47
+ "@earendil-works/pi-ai": "*",
48
+ "@earendil-works/pi-coding-agent": "*",
49
+ "@mariozechner/pi-coding-agent": "*",
47
50
  "@types/node": "^22.15.17"
48
51
  }
49
52
  }