@oh-my-pi/pi-coding-agent 11.8.2 → 11.9.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.
Files changed (141) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/docs/tui.md +9 -9
  3. package/package.json +7 -7
  4. package/src/capability/mcp.ts +9 -0
  5. package/src/cli/file-processor.ts +8 -13
  6. package/src/cli/oclif-help.ts +1 -1
  7. package/src/cli.ts +14 -0
  8. package/src/commit/git/index.ts +16 -16
  9. package/src/config/file-lock.ts +1 -1
  10. package/src/config/keybindings.ts +11 -11
  11. package/src/config/model-registry.ts +31 -66
  12. package/src/config/settings.ts +88 -95
  13. package/src/config.ts +2 -2
  14. package/src/cursor.ts +4 -4
  15. package/src/debug/index.ts +28 -28
  16. package/src/discovery/builtin.ts +48 -0
  17. package/src/discovery/codex.ts +5 -13
  18. package/src/discovery/cursor.ts +2 -7
  19. package/src/discovery/mcp-json.ts +33 -0
  20. package/src/exa/mcp-client.ts +2 -2
  21. package/src/exa/websets.ts +2 -2
  22. package/src/export/html/index.ts +3 -3
  23. package/src/export/ttsr.ts +27 -27
  24. package/src/extensibility/custom-tools/loader.ts +9 -9
  25. package/src/extensibility/extensions/runner.ts +64 -64
  26. package/src/extensibility/hooks/runner.ts +46 -46
  27. package/src/extensibility/plugins/manager.ts +49 -49
  28. package/src/extensibility/slash-commands.ts +1 -0
  29. package/src/index.ts +0 -3
  30. package/src/internal-urls/router.ts +5 -5
  31. package/src/ipy/kernel.ts +61 -57
  32. package/src/lsp/client.ts +1 -1
  33. package/src/lsp/clients/biome-client.ts +2 -2
  34. package/src/lsp/clients/lsp-linter-client.ts +7 -7
  35. package/src/lsp/index.ts +9 -9
  36. package/src/mcp/config-writer.ts +194 -0
  37. package/src/mcp/config.ts +20 -6
  38. package/src/mcp/index.ts +4 -0
  39. package/src/mcp/loader.ts +6 -0
  40. package/src/mcp/manager.ts +139 -50
  41. package/src/mcp/oauth-discovery.ts +274 -0
  42. package/src/mcp/oauth-flow.ts +229 -0
  43. package/src/mcp/tool-bridge.ts +20 -20
  44. package/src/mcp/transports/http.ts +107 -66
  45. package/src/mcp/transports/stdio.ts +74 -59
  46. package/src/mcp/types.ts +15 -1
  47. package/src/modes/components/assistant-message.ts +25 -25
  48. package/src/modes/components/bash-execution.ts +51 -51
  49. package/src/modes/components/bordered-loader.ts +7 -7
  50. package/src/modes/components/branch-summary-message.ts +7 -7
  51. package/src/modes/components/compaction-summary-message.ts +7 -7
  52. package/src/modes/components/countdown-timer.ts +15 -15
  53. package/src/modes/components/custom-editor.ts +22 -22
  54. package/src/modes/components/custom-message.ts +21 -21
  55. package/src/modes/components/dynamic-border.ts +3 -3
  56. package/src/modes/components/extensions/extension-dashboard.ts +72 -72
  57. package/src/modes/components/extensions/extension-list.ts +99 -97
  58. package/src/modes/components/extensions/inspector-panel.ts +26 -26
  59. package/src/modes/components/footer.ts +36 -36
  60. package/src/modes/components/history-search.ts +52 -52
  61. package/src/modes/components/hook-editor.ts +20 -20
  62. package/src/modes/components/hook-input.ts +20 -20
  63. package/src/modes/components/hook-message.ts +22 -22
  64. package/src/modes/components/hook-selector.ts +52 -52
  65. package/src/modes/components/index.ts +0 -1
  66. package/src/modes/components/login-dialog.ts +57 -57
  67. package/src/modes/components/mcp-add-wizard.ts +1286 -0
  68. package/src/modes/components/model-selector.ts +173 -173
  69. package/src/modes/components/oauth-selector.ts +45 -45
  70. package/src/modes/components/plugin-settings.ts +52 -52
  71. package/src/modes/components/python-execution.ts +53 -53
  72. package/src/modes/components/queue-mode-selector.ts +7 -7
  73. package/src/modes/components/read-tool-group.ts +23 -23
  74. package/src/modes/components/session-selector.ts +40 -37
  75. package/src/modes/components/settings-selector.ts +80 -80
  76. package/src/modes/components/show-images-selector.ts +7 -7
  77. package/src/modes/components/skill-message.ts +27 -27
  78. package/src/modes/components/status-line-segment-editor.ts +81 -81
  79. package/src/modes/components/status-line.ts +73 -73
  80. package/src/modes/components/theme-selector.ts +11 -11
  81. package/src/modes/components/thinking-selector.ts +7 -7
  82. package/src/modes/components/todo-display.ts +19 -19
  83. package/src/modes/components/todo-reminder.ts +9 -9
  84. package/src/modes/components/tool-execution.ts +212 -216
  85. package/src/modes/components/tree-selector.ts +144 -144
  86. package/src/modes/components/ttsr-notification.ts +17 -17
  87. package/src/modes/components/user-message-selector.ts +18 -18
  88. package/src/modes/components/welcome.ts +10 -10
  89. package/src/modes/controllers/command-controller.ts +0 -7
  90. package/src/modes/controllers/event-controller.ts +23 -23
  91. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  92. package/src/modes/controllers/input-controller.ts +12 -9
  93. package/src/modes/controllers/mcp-command-controller.ts +1223 -0
  94. package/src/modes/interactive-mode.ts +240 -241
  95. package/src/modes/rpc/rpc-client.ts +77 -77
  96. package/src/modes/rpc/rpc-mode.ts +5 -5
  97. package/src/modes/theme/theme.ts +113 -113
  98. package/src/modes/types.ts +1 -1
  99. package/src/patch/index.ts +45 -45
  100. package/src/prompts/tools/task.md +22 -2
  101. package/src/sdk.ts +1 -0
  102. package/src/session/agent-session.ts +512 -476
  103. package/src/session/agent-storage.ts +72 -75
  104. package/src/session/auth-storage.ts +186 -252
  105. package/src/session/history-storage.ts +36 -38
  106. package/src/session/session-manager.ts +300 -299
  107. package/src/session/session-storage.ts +65 -90
  108. package/src/ssh/connection-manager.ts +9 -9
  109. package/src/system-prompt.ts +2 -3
  110. package/src/task/agents.ts +1 -1
  111. package/src/task/executor.ts +28 -40
  112. package/src/task/index.ts +13 -12
  113. package/src/task/subprocess-tool-registry.ts +5 -5
  114. package/src/task/worktree.ts +8 -5
  115. package/src/tools/ask.ts +7 -7
  116. package/src/tools/bash.ts +15 -10
  117. package/src/tools/browser.ts +130 -127
  118. package/src/tools/calculator.ts +46 -46
  119. package/src/tools/context.ts +9 -9
  120. package/src/tools/exit-plan-mode.ts +5 -5
  121. package/src/tools/fetch.ts +5 -5
  122. package/src/tools/find.ts +16 -16
  123. package/src/tools/grep.ts +12 -24
  124. package/src/tools/index.ts +1 -1
  125. package/src/tools/notebook.ts +6 -6
  126. package/src/tools/output-meta.ts +10 -2
  127. package/src/tools/python.ts +12 -11
  128. package/src/tools/read.ts +17 -17
  129. package/src/tools/ssh.ts +9 -9
  130. package/src/tools/submit-result.ts +13 -13
  131. package/src/tools/todo-write.ts +6 -6
  132. package/src/tools/write.ts +10 -10
  133. package/src/tui/output-block.ts +6 -6
  134. package/src/tui/utils.ts +9 -9
  135. package/src/utils/event-bus.ts +13 -11
  136. package/src/utils/frontmatter.ts +1 -1
  137. package/src/utils/ignore-files.ts +1 -1
  138. package/src/web/search/index.ts +5 -5
  139. package/src/web/search/providers/anthropic.ts +7 -2
  140. package/examples/hooks/snake.ts +0 -342
  141. package/src/modes/components/armin.ts +0 -379
@@ -33,7 +33,6 @@ import {
33
33
  zaiUsageProvider,
34
34
  } from "@oh-my-pi/pi-ai";
35
35
  import { logger } from "@oh-my-pi/pi-utils";
36
- import { getAgentDbPath } from "../config";
37
36
  import { resolveConfigValue } from "../config/resolve-config-value";
38
37
  import { AgentStorage } from "./agent-storage";
39
38
 
@@ -141,33 +140,33 @@ class AuthStorageUsageCache implements UsageCache {
141
140
  * Reads from SQLite (agent.db).
142
141
  */
143
142
  export class AuthStorage {
144
- private static readonly defaultBackoffMs = 60_000; // Default backoff when no reset time available
143
+ static readonly #defaultBackoffMs = 60_000; // Default backoff when no reset time available
145
144
 
146
145
  /** Provider -> credentials cache, populated from agent.db on reload(). */
147
- private data: Map<string, StoredCredential[]> = new Map();
148
- private runtimeOverrides: Map<string, string> = new Map();
146
+ #data: Map<string, StoredCredential[]> = new Map();
147
+ #runtimeOverrides: Map<string, string> = new Map();
149
148
  /** Tracks next credential index per provider:type key for round-robin distribution (non-session use). */
150
- private providerRoundRobinIndex: Map<string, number> = new Map();
149
+ #providerRoundRobinIndex: Map<string, number> = new Map();
151
150
  /** Tracks the last used credential per provider for a session (used for rate-limit switching). */
152
- private sessionLastCredential: Map<string, Map<string, { type: AuthCredential["type"]; index: number }>> = new Map();
151
+ #sessionLastCredential: Map<string, Map<string, { type: AuthCredential["type"]; index: number }>> = new Map();
153
152
  /** Maps provider:type -> credentialIndex -> blockedUntilMs for temporary backoff. */
154
- private credentialBackoff: Map<string, Map<number, number>> = new Map();
155
- private usageProviderResolver?: (provider: Provider) => UsageProvider | undefined;
156
- private usageCache?: UsageCache;
157
- private usageFetch: typeof fetch;
158
- private usageNow: () => number;
159
- private usageLogger?: UsageLogger;
160
- private fallbackResolver?: (provider: string) => string | undefined;
153
+ #credentialBackoff: Map<string, Map<number, number>> = new Map();
154
+ #usageProviderResolver?: (provider: Provider) => UsageProvider | undefined;
155
+ #usageCache?: UsageCache;
156
+ #usageFetch: typeof fetch;
157
+ #usageNow: () => number;
158
+ #usageLogger?: UsageLogger;
159
+ #fallbackResolver?: (provider: string) => string | undefined;
161
160
 
162
161
  private constructor(
163
162
  private storage: AgentStorage,
164
163
  options: AuthStorageOptions = {},
165
164
  ) {
166
- this.usageProviderResolver = options.usageProviderResolver ?? resolveDefaultUsageProvider;
167
- this.usageCache = options.usageCache ?? new AuthStorageUsageCache(this.storage);
168
- this.usageFetch = options.usageFetch ?? fetch;
169
- this.usageNow = options.usageNow ?? Date.now;
170
- this.usageLogger =
165
+ this.#usageProviderResolver = options.usageProviderResolver ?? resolveDefaultUsageProvider;
166
+ this.#usageCache = options.usageCache ?? new AuthStorageUsageCache(this.storage);
167
+ this.#usageFetch = options.usageFetch ?? fetch;
168
+ this.#usageNow = options.usageNow ?? Date.now;
169
+ this.#usageLogger =
171
170
  options.usageLogger ??
172
171
  ({
173
172
  debug: (message, meta) => logger.debug(message, meta),
@@ -184,89 +183,19 @@ export class AuthStorage {
184
183
  return new AuthStorage(storage, options);
185
184
  }
186
185
 
187
- /**
188
- * Create an in-memory AuthStorage instance from serialized data.
189
- * Used by subagent workers to bypass discovery and use parent's credentials.
190
- */
191
- static async fromSerialized(data: SerializedAuthStorage, options: AuthStorageOptions = {}): Promise<AuthStorage> {
192
- const dbPath = data.dbPath ?? getAgentDbPath();
193
- const storage = await AgentStorage.open(dbPath);
194
-
195
- const instance = Object.create(AuthStorage.prototype) as AuthStorage;
196
- instance.storage = storage;
197
- instance.data = new Map();
198
- instance.runtimeOverrides = new Map();
199
- instance.providerRoundRobinIndex = new Map();
200
- instance.sessionLastCredential = new Map();
201
- instance.credentialBackoff = new Map();
202
- instance.usageProviderResolver = options.usageProviderResolver ?? resolveDefaultUsageProvider;
203
- instance.usageCache = options.usageCache ?? new AuthStorageUsageCache(instance.storage);
204
- instance.usageFetch = options.usageFetch ?? fetch;
205
- instance.usageNow = options.usageNow ?? Date.now;
206
- instance.usageLogger =
207
- options.usageLogger ??
208
- ({
209
- debug: (message, meta) => logger.debug(message, meta),
210
- warn: (message, meta) => logger.warn(message, meta),
211
- } satisfies UsageLogger);
212
-
213
- for (const [provider, creds] of Object.entries(data.credentials)) {
214
- instance.data.set(
215
- provider,
216
- creds.map(c => ({
217
- id: c.id,
218
- credential:
219
- c.type === "api_key"
220
- ? ({ type: "api_key", key: c.data.key as string } satisfies ApiKeyCredential)
221
- : ({ type: "oauth", ...c.data } as OAuthCredential),
222
- })),
223
- );
224
- }
225
- if (data.runtimeOverrides) {
226
- for (const [k, v] of Object.entries(data.runtimeOverrides)) {
227
- instance.runtimeOverrides.set(k, v);
228
- }
229
- }
230
-
231
- return instance;
232
- }
233
-
234
- /**
235
- * Serialize AuthStorage for passing to subagent workers.
236
- * Excludes runtime state (round-robin, backoff, usage cache).
237
- */
238
- serialize(): SerializedAuthStorage {
239
- const credentials: SerializedAuthStorage["credentials"] = {};
240
- for (const [provider, creds] of this.data.entries()) {
241
- credentials[provider] = creds.map(c => ({
242
- id: c.id,
243
- type: c.credential.type,
244
- data: c.credential.type === "api_key" ? { key: c.credential.key } : { ...c.credential },
245
- }));
246
- }
247
- const runtimeOverrides: Record<string, string> = {};
248
- for (const [k, v] of this.runtimeOverrides.entries()) {
249
- runtimeOverrides[k] = v;
250
- }
251
- return {
252
- credentials,
253
- runtimeOverrides: Object.keys(runtimeOverrides).length > 0 ? runtimeOverrides : undefined,
254
- };
255
- }
256
-
257
186
  /**
258
187
  * Set a runtime API key override (not persisted to disk).
259
188
  * Used for CLI --api-key flag.
260
189
  */
261
190
  setRuntimeApiKey(provider: string, apiKey: string): void {
262
- this.runtimeOverrides.set(provider, apiKey);
191
+ this.#runtimeOverrides.set(provider, apiKey);
263
192
  }
264
193
 
265
194
  /**
266
195
  * Remove a runtime API key override.
267
196
  */
268
197
  removeRuntimeApiKey(provider: string): void {
269
- this.runtimeOverrides.delete(provider);
198
+ this.#runtimeOverrides.delete(provider);
270
199
  }
271
200
 
272
201
  /**
@@ -274,7 +203,7 @@ export class AuthStorage {
274
203
  * Used for custom provider keys from models.json.
275
204
  */
276
205
  setFallbackResolver(resolver: (provider: string) => string | undefined): void {
277
- this.fallbackResolver = resolver;
206
+ this.#fallbackResolver = resolver;
278
207
  }
279
208
 
280
209
  /**
@@ -292,12 +221,12 @@ export class AuthStorage {
292
221
 
293
222
  const dedupedGrouped = new Map<string, StoredCredential[]>();
294
223
  for (const [provider, entries] of grouped.entries()) {
295
- const deduped = this.pruneDuplicateStoredCredentials(provider, entries);
224
+ const deduped = this.#pruneDuplicateStoredCredentials(provider, entries);
296
225
  if (deduped.length > 0) {
297
226
  dedupedGrouped.set(provider, deduped);
298
227
  }
299
228
  }
300
- this.data = dedupedGrouped;
229
+ this.#data = dedupedGrouped;
301
230
  }
302
231
 
303
232
  /**
@@ -305,8 +234,8 @@ export class AuthStorage {
305
234
  * @param provider - Provider name (e.g., "anthropic", "openai")
306
235
  * @returns Array of stored credentials, empty if none exist
307
236
  */
308
- private getStoredCredentials(provider: string): StoredCredential[] {
309
- return this.data.get(provider) ?? [];
237
+ #getStoredCredentials(provider: string): StoredCredential[] {
238
+ return this.#data.get(provider) ?? [];
310
239
  }
311
240
 
312
241
  /**
@@ -315,34 +244,34 @@ export class AuthStorage {
315
244
  * @param provider - Provider name (e.g., "anthropic", "openai")
316
245
  * @param credentials - Array of stored credentials to cache
317
246
  */
318
- private setStoredCredentials(provider: string, credentials: StoredCredential[]): void {
247
+ #setStoredCredentials(provider: string, credentials: StoredCredential[]): void {
319
248
  if (credentials.length === 0) {
320
- this.data.delete(provider);
249
+ this.#data.delete(provider);
321
250
  } else {
322
- this.data.set(provider, credentials);
251
+ this.#data.set(provider, credentials);
323
252
  }
324
253
  }
325
254
 
326
- private getOAuthIdentifiers(credential: OAuthCredential): string[] {
255
+ #getOAuthIdentifiers(credential: OAuthCredential): string[] {
327
256
  const identifiers: string[] = [];
328
257
  const accountId = credential.accountId?.trim();
329
258
  if (accountId) identifiers.push(`account:${accountId}`);
330
259
  const email = credential.email?.trim().toLowerCase();
331
260
  if (email) identifiers.push(`email:${email}`);
332
261
  if (identifiers.length > 0) return identifiers;
333
- const tokenIdentifiers = this.getOAuthIdentifiersFromToken(credential.access) ?? [];
262
+ const tokenIdentifiers = this.#getOAuthIdentifiersFromToken(credential.access) ?? [];
334
263
  for (const identifier of tokenIdentifiers) {
335
264
  identifiers.push(identifier);
336
265
  }
337
266
  if (identifiers.length > 0) return identifiers;
338
- const refreshIdentifiers = this.getOAuthIdentifiersFromToken(credential.refresh) ?? [];
267
+ const refreshIdentifiers = this.#getOAuthIdentifiersFromToken(credential.refresh) ?? [];
339
268
  for (const identifier of refreshIdentifiers) {
340
269
  identifiers.push(identifier);
341
270
  }
342
271
  return identifiers;
343
272
  }
344
273
 
345
- private getOAuthIdentifiersFromToken(token: string | undefined): string[] | undefined {
274
+ #getOAuthIdentifiersFromToken(token: string | undefined): string[] | undefined {
346
275
  if (!token) return undefined;
347
276
  const parts = token.split(".");
348
277
  if (parts.length !== 3) return undefined;
@@ -374,7 +303,7 @@ export class AuthStorage {
374
303
  }
375
304
  }
376
305
 
377
- private dedupeOAuthCredentials(credentials: AuthCredential[]): AuthCredential[] {
306
+ #dedupeOAuthCredentials(credentials: AuthCredential[]): AuthCredential[] {
378
307
  const seen = new Set<string>();
379
308
  const deduped: AuthCredential[] = [];
380
309
  for (let index = credentials.length - 1; index >= 0; index -= 1) {
@@ -383,7 +312,7 @@ export class AuthStorage {
383
312
  deduped.push(credential);
384
313
  continue;
385
314
  }
386
- const identifiers = this.getOAuthIdentifiers(credential);
315
+ const identifiers = this.#getOAuthIdentifiers(credential);
387
316
  if (identifiers.length === 0) {
388
317
  deduped.push(credential);
389
318
  continue;
@@ -399,7 +328,7 @@ export class AuthStorage {
399
328
  return deduped.reverse();
400
329
  }
401
330
 
402
- private pruneDuplicateStoredCredentials(provider: string, entries: StoredCredential[]): StoredCredential[] {
331
+ #pruneDuplicateStoredCredentials(provider: string, entries: StoredCredential[]): StoredCredential[] {
403
332
  const seen = new Set<string>();
404
333
  const kept: StoredCredential[] = [];
405
334
  const removed: StoredCredential[] = [];
@@ -410,7 +339,7 @@ export class AuthStorage {
410
339
  kept.push(entry);
411
340
  continue;
412
341
  }
413
- const identifiers = this.getOAuthIdentifiers(credential);
342
+ const identifiers = this.#getOAuthIdentifiers(credential);
414
343
  if (identifiers.length === 0) {
415
344
  kept.push(entry);
416
345
  continue;
@@ -428,18 +357,18 @@ export class AuthStorage {
428
357
  for (const entry of removed) {
429
358
  this.storage.deleteAuthCredential(entry.id);
430
359
  }
431
- this.resetProviderAssignments(provider);
360
+ this.#resetProviderAssignments(provider);
432
361
  }
433
362
  return kept.reverse();
434
363
  }
435
364
 
436
365
  /** Returns all credentials for a provider as an array */
437
- private getCredentialsForProvider(provider: string): AuthCredential[] {
438
- return this.getStoredCredentials(provider).map(entry => entry.credential);
366
+ #getCredentialsForProvider(provider: string): AuthCredential[] {
367
+ return this.#getStoredCredentials(provider).map(entry => entry.credential);
439
368
  }
440
369
 
441
370
  /** Composite key for round-robin tracking: "anthropic:oauth" or "openai:api_key" */
442
- private getProviderTypeKey(provider: string, type: AuthCredential["type"]): string {
371
+ #getProviderTypeKey(provider: string, type: AuthCredential["type"]): string {
443
372
  return `${provider}:${type}`;
444
373
  }
445
374
 
@@ -447,11 +376,11 @@ export class AuthStorage {
447
376
  * Returns next index in round-robin sequence for load distribution.
448
377
  * Increments stored counter and wraps at total.
449
378
  */
450
- private getNextRoundRobinIndex(providerKey: string, total: number): number {
379
+ #getNextRoundRobinIndex(providerKey: string, total: number): number {
451
380
  if (total <= 1) return 0;
452
- const current = this.providerRoundRobinIndex.get(providerKey) ?? -1;
381
+ const current = this.#providerRoundRobinIndex.get(providerKey) ?? -1;
453
382
  const next = (current + 1) % total;
454
- this.providerRoundRobinIndex.set(providerKey, next);
383
+ this.#providerRoundRobinIndex.set(providerKey, next);
455
384
  return next;
456
385
  }
457
386
 
@@ -459,7 +388,7 @@ export class AuthStorage {
459
388
  * FNV-1a hash for deterministic session-to-credential mapping.
460
389
  * Ensures the same session always starts with the same credential.
461
390
  */
462
- private getHashedIndex(sessionId: string, total: number): number {
391
+ #getHashedIndex(sessionId: string, total: number): number {
463
392
  if (total <= 1) return 0;
464
393
  let hash = 2166136261; // FNV offset basis
465
394
  for (let i = 0; i < sessionId.length; i++) {
@@ -475,9 +404,11 @@ export class AuthStorage {
475
404
  * Without sessionId: starts from round-robin index (load balancing).
476
405
  * Order wraps around so all credentials are tried if earlier ones are blocked.
477
406
  */
478
- private getCredentialOrder(providerKey: string, sessionId: string | undefined, total: number): number[] {
407
+ #getCredentialOrder(providerKey: string, sessionId: string | undefined, total: number): number[] {
479
408
  if (total <= 1) return [0];
480
- const start = sessionId ? this.getHashedIndex(sessionId, total) : this.getNextRoundRobinIndex(providerKey, total);
409
+ const start = sessionId
410
+ ? this.#getHashedIndex(sessionId, total)
411
+ : this.#getNextRoundRobinIndex(providerKey, total);
481
412
  const order: number[] = [];
482
413
  for (let i = 0; i < total; i++) {
483
414
  order.push((start + i) % total);
@@ -486,15 +417,15 @@ export class AuthStorage {
486
417
  }
487
418
 
488
419
  /** Checks if a credential is temporarily blocked due to usage limits. */
489
- private isCredentialBlocked(providerKey: string, credentialIndex: number): boolean {
490
- const backoffMap = this.credentialBackoff.get(providerKey);
420
+ #isCredentialBlocked(providerKey: string, credentialIndex: number): boolean {
421
+ const backoffMap = this.#credentialBackoff.get(providerKey);
491
422
  if (!backoffMap) return false;
492
423
  const blockedUntil = backoffMap.get(credentialIndex);
493
424
  if (!blockedUntil) return false;
494
425
  if (blockedUntil <= Date.now()) {
495
426
  backoffMap.delete(credentialIndex);
496
427
  if (backoffMap.size === 0) {
497
- this.credentialBackoff.delete(providerKey);
428
+ this.#credentialBackoff.delete(providerKey);
498
429
  }
499
430
  return false;
500
431
  }
@@ -502,33 +433,33 @@ export class AuthStorage {
502
433
  }
503
434
 
504
435
  /** Marks a credential as blocked until the specified time. */
505
- private markCredentialBlocked(providerKey: string, credentialIndex: number, blockedUntilMs: number): void {
506
- const backoffMap = this.credentialBackoff.get(providerKey) ?? new Map<number, number>();
436
+ #markCredentialBlocked(providerKey: string, credentialIndex: number, blockedUntilMs: number): void {
437
+ const backoffMap = this.#credentialBackoff.get(providerKey) ?? new Map<number, number>();
507
438
  const existing = backoffMap.get(credentialIndex) ?? 0;
508
439
  backoffMap.set(credentialIndex, Math.max(existing, blockedUntilMs));
509
- this.credentialBackoff.set(providerKey, backoffMap);
440
+ this.#credentialBackoff.set(providerKey, backoffMap);
510
441
  }
511
442
 
512
443
  /** Records which credential was used for a session (for rate-limit switching). */
513
- private recordSessionCredential(
444
+ #recordSessionCredential(
514
445
  provider: string,
515
446
  sessionId: string | undefined,
516
447
  type: AuthCredential["type"],
517
448
  index: number,
518
449
  ): void {
519
450
  if (!sessionId) return;
520
- const sessionMap = this.sessionLastCredential.get(provider) ?? new Map();
451
+ const sessionMap = this.#sessionLastCredential.get(provider) ?? new Map();
521
452
  sessionMap.set(sessionId, { type, index });
522
- this.sessionLastCredential.set(provider, sessionMap);
453
+ this.#sessionLastCredential.set(provider, sessionMap);
523
454
  }
524
455
 
525
456
  /** Retrieves the last credential used by a session. */
526
- private getSessionCredential(
457
+ #getSessionCredential(
527
458
  provider: string,
528
459
  sessionId: string | undefined,
529
460
  ): { type: AuthCredential["type"]; index: number } | undefined {
530
461
  if (!sessionId) return undefined;
531
- return this.sessionLastCredential.get(provider)?.get(sessionId);
462
+ return this.#sessionLastCredential.get(provider)?.get(sessionId);
532
463
  }
533
464
 
534
465
  /**
@@ -536,12 +467,12 @@ export class AuthStorage {
536
467
  * Returns both the credential and its index in the original array (for updates/removal).
537
468
  * Uses deterministic hashing for session stickiness and skips blocked credentials when possible.
538
469
  */
539
- private selectCredentialByType<T extends AuthCredential["type"]>(
470
+ #selectCredentialByType<T extends AuthCredential["type"]>(
540
471
  provider: string,
541
472
  type: T,
542
473
  sessionId?: string,
543
474
  ): { credential: Extract<AuthCredential, { type: T }>; index: number } | undefined {
544
- const credentials = this.getCredentialsForProvider(provider)
475
+ const credentials = this.#getCredentialsForProvider(provider)
545
476
  .map((credential, index) => ({ credential, index }))
546
477
  .filter(
547
478
  (entry): entry is { credential: Extract<AuthCredential, { type: T }>; index: number } =>
@@ -551,13 +482,13 @@ export class AuthStorage {
551
482
  if (credentials.length === 0) return undefined;
552
483
  if (credentials.length === 1) return credentials[0];
553
484
 
554
- const providerKey = this.getProviderTypeKey(provider, type);
555
- const order = this.getCredentialOrder(providerKey, sessionId, credentials.length);
485
+ const providerKey = this.#getProviderTypeKey(provider, type);
486
+ const order = this.#getCredentialOrder(providerKey, sessionId, credentials.length);
556
487
  const fallback = credentials[order[0]];
557
488
 
558
489
  for (const idx of order) {
559
490
  const candidate = credentials[idx];
560
- if (!this.isCredentialBlocked(providerKey, candidate.index)) {
491
+ if (!this.#isCredentialBlocked(providerKey, candidate.index)) {
561
492
  return candidate;
562
493
  }
563
494
  }
@@ -569,49 +500,49 @@ export class AuthStorage {
569
500
  * Clears round-robin and session assignment state for a provider.
570
501
  * Called when credentials are added/removed to prevent stale index references.
571
502
  */
572
- private resetProviderAssignments(provider: string): void {
573
- for (const key of this.providerRoundRobinIndex.keys()) {
503
+ #resetProviderAssignments(provider: string): void {
504
+ for (const key of this.#providerRoundRobinIndex.keys()) {
574
505
  if (key.startsWith(`${provider}:`)) {
575
- this.providerRoundRobinIndex.delete(key);
506
+ this.#providerRoundRobinIndex.delete(key);
576
507
  }
577
508
  }
578
- this.sessionLastCredential.delete(provider);
579
- for (const key of this.credentialBackoff.keys()) {
509
+ this.#sessionLastCredential.delete(provider);
510
+ for (const key of this.#credentialBackoff.keys()) {
580
511
  if (key.startsWith(`${provider}:`)) {
581
- this.credentialBackoff.delete(key);
512
+ this.#credentialBackoff.delete(key);
582
513
  }
583
514
  }
584
515
  }
585
516
 
586
517
  /** Updates credential at index in-place (used for OAuth token refresh) */
587
- private replaceCredentialAt(provider: string, index: number, credential: AuthCredential): void {
588
- const entries = this.getStoredCredentials(provider);
518
+ #replaceCredentialAt(provider: string, index: number, credential: AuthCredential): void {
519
+ const entries = this.#getStoredCredentials(provider);
589
520
  if (index < 0 || index >= entries.length) return;
590
521
  const target = entries[index];
591
522
  this.storage.updateAuthCredential(target.id, credential);
592
523
  const updated = [...entries];
593
524
  updated[index] = { id: target.id, credential };
594
- this.setStoredCredentials(provider, updated);
525
+ this.#setStoredCredentials(provider, updated);
595
526
  }
596
527
 
597
528
  /**
598
529
  * Removes credential at index (used when OAuth refresh fails).
599
530
  * Cleans up provider entry if last credential removed.
600
531
  */
601
- private removeCredentialAt(provider: string, index: number): void {
602
- const entries = this.getStoredCredentials(provider);
532
+ #removeCredentialAt(provider: string, index: number): void {
533
+ const entries = this.#getStoredCredentials(provider);
603
534
  if (index < 0 || index >= entries.length) return;
604
535
  this.storage.deleteAuthCredential(entries[index].id);
605
536
  const updated = entries.filter((_value, idx) => idx !== index);
606
- this.setStoredCredentials(provider, updated);
607
- this.resetProviderAssignments(provider);
537
+ this.#setStoredCredentials(provider, updated);
538
+ this.#resetProviderAssignments(provider);
608
539
  }
609
540
 
610
541
  /**
611
542
  * Get credential for a provider (first entry if multiple).
612
543
  */
613
544
  get(provider: string): AuthCredential | undefined {
614
- return this.getCredentialsForProvider(provider)[0];
545
+ return this.#getCredentialsForProvider(provider)[0];
615
546
  }
616
547
 
617
548
  /**
@@ -619,13 +550,13 @@ export class AuthStorage {
619
550
  */
620
551
  async set(provider: string, credential: AuthCredentialEntry): Promise<void> {
621
552
  const normalized = Array.isArray(credential) ? credential : [credential];
622
- const deduped = this.dedupeOAuthCredentials(normalized);
553
+ const deduped = this.#dedupeOAuthCredentials(normalized);
623
554
  const stored = this.storage.replaceAuthCredentialsForProvider(provider, deduped);
624
- this.setStoredCredentials(
555
+ this.#setStoredCredentials(
625
556
  provider,
626
557
  stored.map(record => ({ id: record.id, credential: record.credential })),
627
558
  );
628
- this.resetProviderAssignments(provider);
559
+ this.#resetProviderAssignments(provider);
629
560
  }
630
561
 
631
562
  /**
@@ -633,22 +564,22 @@ export class AuthStorage {
633
564
  */
634
565
  async remove(provider: string): Promise<void> {
635
566
  this.storage.deleteAuthCredentialsForProvider(provider);
636
- this.data.delete(provider);
637
- this.resetProviderAssignments(provider);
567
+ this.#data.delete(provider);
568
+ this.#resetProviderAssignments(provider);
638
569
  }
639
570
 
640
571
  /**
641
572
  * List all providers with credentials.
642
573
  */
643
574
  list(): string[] {
644
- return [...this.data.keys()];
575
+ return [...this.#data.keys()];
645
576
  }
646
577
 
647
578
  /**
648
579
  * Check if credentials exist for a provider in agent.db.
649
580
  */
650
581
  has(provider: string): boolean {
651
- return this.getCredentialsForProvider(provider).length > 0;
582
+ return this.#getCredentialsForProvider(provider).length > 0;
652
583
  }
653
584
 
654
585
  /**
@@ -656,10 +587,10 @@ export class AuthStorage {
656
587
  * Unlike getApiKey(), this doesn't refresh OAuth tokens.
657
588
  */
658
589
  hasAuth(provider: string): boolean {
659
- if (this.runtimeOverrides.has(provider)) return true;
660
- if (this.getCredentialsForProvider(provider).length > 0) return true;
590
+ if (this.#runtimeOverrides.has(provider)) return true;
591
+ if (this.#getCredentialsForProvider(provider).length > 0) return true;
661
592
  if (getEnvApiKey(provider)) return true;
662
- if (this.fallbackResolver?.(provider)) return true;
593
+ if (this.#fallbackResolver?.(provider)) return true;
663
594
  return false;
664
595
  }
665
596
 
@@ -667,14 +598,14 @@ export class AuthStorage {
667
598
  * Check if OAuth credentials are configured for a provider.
668
599
  */
669
600
  hasOAuth(provider: string): boolean {
670
- return this.getCredentialsForProvider(provider).some(credential => credential.type === "oauth");
601
+ return this.#getCredentialsForProvider(provider).some(credential => credential.type === "oauth");
671
602
  }
672
603
 
673
604
  /**
674
605
  * Get OAuth credentials for a provider.
675
606
  */
676
607
  getOAuthCredential(provider: string): OAuthCredential | undefined {
677
- return this.getCredentialsForProvider(provider).find(
608
+ return this.#getCredentialsForProvider(provider).find(
678
609
  (credential): credential is OAuthCredential => credential.type === "oauth",
679
610
  );
680
611
  }
@@ -684,7 +615,7 @@ export class AuthStorage {
684
615
  */
685
616
  getAll(): AuthStorageData {
686
617
  const result: AuthStorageData = {};
687
- for (const [provider, entries] of this.data.entries()) {
618
+ for (const [provider, entries] of this.#data.entries()) {
688
619
  const credentials = entries.map(entry => entry.credential);
689
620
  if (credentials.length === 1) {
690
621
  result[provider] = credentials[0];
@@ -753,7 +684,7 @@ export class AuthStorage {
753
684
  }
754
685
 
755
686
  const newCredential: OAuthCredential = { type: "oauth", ...credentials };
756
- const existing = this.getCredentialsForProvider(provider);
687
+ const existing = this.#getCredentialsForProvider(provider);
757
688
  if (existing.length === 0) {
758
689
  await this.set(provider, newCredential);
759
690
  return;
@@ -774,7 +705,7 @@ export class AuthStorage {
774
705
  // Queries provider usage endpoints to detect rate limits before they occur.
775
706
  // ─────────────────────────────────────────────────────────────────────────────
776
707
 
777
- private buildUsageCredential(credential: OAuthCredential): UsageCredential {
708
+ #buildUsageCredential(credential: OAuthCredential): UsageCredential {
778
709
  return {
779
710
  type: "oauth",
780
711
  accessToken: credential.access,
@@ -787,14 +718,14 @@ export class AuthStorage {
787
718
  };
788
719
  }
789
720
 
790
- private getUsageReportMetadataValue(report: UsageReport, key: string): string | undefined {
721
+ #getUsageReportMetadataValue(report: UsageReport, key: string): string | undefined {
791
722
  const metadata = report.metadata;
792
723
  if (!metadata || typeof metadata !== "object") return undefined;
793
724
  const value = metadata[key];
794
725
  return typeof value === "string" ? value.trim() : undefined;
795
726
  }
796
727
 
797
- private getUsageReportScopeAccountId(report: UsageReport): string | undefined {
728
+ #getUsageReportScopeAccountId(report: UsageReport): string | undefined {
798
729
  const ids = new Set<string>();
799
730
  for (const limit of report.limits) {
800
731
  const accountId = limit.scope.accountId?.trim();
@@ -804,24 +735,24 @@ export class AuthStorage {
804
735
  return undefined;
805
736
  }
806
737
 
807
- private getUsageReportIdentifiers(report: UsageReport): string[] {
738
+ #getUsageReportIdentifiers(report: UsageReport): string[] {
808
739
  const identifiers: string[] = [];
809
- const email = this.getUsageReportMetadataValue(report, "email");
740
+ const email = this.#getUsageReportMetadataValue(report, "email");
810
741
  if (email) identifiers.push(`email:${email.toLowerCase()}`);
811
- const accountId = this.getUsageReportMetadataValue(report, "accountId");
742
+ const accountId = this.#getUsageReportMetadataValue(report, "accountId");
812
743
  if (accountId) identifiers.push(`account:${accountId}`);
813
- const account = this.getUsageReportMetadataValue(report, "account");
744
+ const account = this.#getUsageReportMetadataValue(report, "account");
814
745
  if (account) identifiers.push(`account:${account}`);
815
- const user = this.getUsageReportMetadataValue(report, "user");
746
+ const user = this.#getUsageReportMetadataValue(report, "user");
816
747
  if (user) identifiers.push(`account:${user}`);
817
- const username = this.getUsageReportMetadataValue(report, "username");
748
+ const username = this.#getUsageReportMetadataValue(report, "username");
818
749
  if (username) identifiers.push(`account:${username}`);
819
- const scopeAccountId = this.getUsageReportScopeAccountId(report);
750
+ const scopeAccountId = this.#getUsageReportScopeAccountId(report);
820
751
  if (scopeAccountId) identifiers.push(`account:${scopeAccountId}`);
821
752
  return identifiers.map(identifier => `${report.provider}:${identifier.toLowerCase()}`);
822
753
  }
823
754
 
824
- private mergeUsageReportGroup(reports: UsageReport[]): UsageReport {
755
+ #mergeUsageReportGroup(reports: UsageReport[]): UsageReport {
825
756
  if (reports.length === 1) return reports[0];
826
757
  const sorted = [...reports].sort((a, b) => {
827
758
  const limitDiff = b.limits.length - a.limits.length;
@@ -859,12 +790,12 @@ export class AuthStorage {
859
790
  };
860
791
  }
861
792
 
862
- private dedupeUsageReports(reports: UsageReport[]): UsageReport[] {
793
+ #dedupeUsageReports(reports: UsageReport[]): UsageReport[] {
863
794
  const groups: UsageReport[][] = [];
864
795
  const idToGroup = new Map<string, number>();
865
796
 
866
797
  for (const report of reports) {
867
- const identifiers = this.getUsageReportIdentifiers(report);
798
+ const identifiers = this.#getUsageReportIdentifiers(report);
868
799
  let groupIndex: number | undefined;
869
800
  for (const identifier of identifiers) {
870
801
  const existing = idToGroup.get(identifier);
@@ -883,9 +814,9 @@ export class AuthStorage {
883
814
  }
884
815
  }
885
816
 
886
- const deduped = groups.map(group => this.mergeUsageReportGroup(group));
817
+ const deduped = groups.map(group => this.#mergeUsageReportGroup(group));
887
818
  if (deduped.length !== reports.length) {
888
- this.usageLogger?.debug("Usage reports deduped", {
819
+ this.#usageLogger?.debug("Usage reports deduped", {
889
820
  before: reports.length,
890
821
  after: deduped.length,
891
822
  });
@@ -893,7 +824,7 @@ export class AuthStorage {
893
824
  return deduped;
894
825
  }
895
826
 
896
- private isUsageLimitExhausted(limit: UsageLimit): boolean {
827
+ #isUsageLimitExhausted(limit: UsageLimit): boolean {
897
828
  if (limit.status === "exhausted") return true;
898
829
  const amount = limit.amount;
899
830
  if (amount.usedFraction !== undefined && amount.usedFraction >= 1) return true;
@@ -905,15 +836,15 @@ export class AuthStorage {
905
836
  }
906
837
 
907
838
  /** Returns true if usage indicates rate limit has been reached. */
908
- private isUsageLimitReached(report: UsageReport): boolean {
909
- return report.limits.some(limit => this.isUsageLimitExhausted(limit));
839
+ #isUsageLimitReached(report: UsageReport): boolean {
840
+ return report.limits.some(limit => this.#isUsageLimitExhausted(limit));
910
841
  }
911
842
 
912
843
  /** Extracts the earliest reset timestamp from exhausted windows (in ms). */
913
- private getUsageResetAtMs(report: UsageReport, nowMs: number): number | undefined {
844
+ #getUsageResetAtMs(report: UsageReport, nowMs: number): number | undefined {
914
845
  const candidates: number[] = [];
915
846
  for (const limit of report.limits) {
916
- if (!this.isUsageLimitExhausted(limit)) continue;
847
+ if (!this.#isUsageLimitExhausted(limit)) continue;
917
848
  const window = limit.window;
918
849
  if (window?.resetsAt && window.resetsAt > nowMs) {
919
850
  candidates.push(window.resetsAt);
@@ -927,13 +858,13 @@ export class AuthStorage {
927
858
  return Math.min(...candidates);
928
859
  }
929
860
 
930
- private async getUsageReport(
861
+ async #getUsageReport(
931
862
  provider: Provider,
932
863
  credential: OAuthCredential,
933
864
  options?: { baseUrl?: string },
934
865
  ): Promise<UsageReport | null> {
935
- const resolver = this.usageProviderResolver;
936
- const cache = this.usageCache;
866
+ const resolver = this.#usageProviderResolver;
867
+ const cache = this.#usageCache;
937
868
  if (!resolver || !cache) return null;
938
869
 
939
870
  const providerImpl = resolver(provider);
@@ -941,7 +872,7 @@ export class AuthStorage {
941
872
 
942
873
  const params = {
943
874
  provider,
944
- credential: this.buildUsageCredential(credential),
875
+ credential: this.#buildUsageCredential(credential),
945
876
  baseUrl: options?.baseUrl,
946
877
  };
947
878
 
@@ -950,9 +881,9 @@ export class AuthStorage {
950
881
  try {
951
882
  return await providerImpl.fetchUsage(params, {
952
883
  cache,
953
- fetch: this.usageFetch,
954
- now: this.usageNow,
955
- logger: this.usageLogger,
884
+ fetch: this.#usageFetch,
885
+ now: this.#usageNow,
886
+ logger: this.#usageLogger,
956
887
  });
957
888
  } catch (error) {
958
889
  logger.debug("AuthStorage usage fetch failed", {
@@ -966,30 +897,33 @@ export class AuthStorage {
966
897
  async fetchUsageReports(options?: {
967
898
  baseUrlResolver?: (provider: Provider) => string | undefined;
968
899
  }): Promise<UsageReport[] | null> {
969
- const resolver = this.usageProviderResolver;
970
- const cache = this.usageCache;
900
+ const resolver = this.#usageProviderResolver;
901
+ const cache = this.#usageCache;
971
902
  if (!resolver || !cache) return null;
972
903
 
973
904
  const tasks: Array<Promise<UsageReport | null>> = [];
974
- const providers = new Set<string>([...this.data.keys(), ...DEFAULT_USAGE_PROVIDERS.map(provider => provider.id)]);
975
- this.usageLogger?.debug("Usage fetch requested", {
905
+ const providers = new Set<string>([
906
+ ...this.#data.keys(),
907
+ ...DEFAULT_USAGE_PROVIDERS.map(provider => provider.id),
908
+ ]);
909
+ this.#usageLogger?.debug("Usage fetch requested", {
976
910
  providers: Array.from(providers).sort(),
977
911
  });
978
912
  for (const provider of providers) {
979
913
  const providerImpl = resolver(provider as Provider);
980
914
  if (!providerImpl) continue;
981
915
  const baseUrl = options?.baseUrlResolver?.(provider as Provider);
982
- let entries = this.getStoredCredentials(provider);
916
+ let entries = this.#getStoredCredentials(provider);
983
917
  if (entries.length > 0) {
984
- const dedupedEntries = this.pruneDuplicateStoredCredentials(provider, entries);
918
+ const dedupedEntries = this.#pruneDuplicateStoredCredentials(provider, entries);
985
919
  if (dedupedEntries.length !== entries.length) {
986
- this.setStoredCredentials(provider, dedupedEntries);
920
+ this.#setStoredCredentials(provider, dedupedEntries);
987
921
  }
988
922
  entries = dedupedEntries;
989
923
  }
990
924
 
991
925
  if (entries.length === 0) {
992
- const runtimeKey = this.runtimeOverrides.get(provider);
926
+ const runtimeKey = this.#runtimeOverrides.get(provider);
993
927
  const envKey = getEnvApiKey(provider);
994
928
  const apiKey = runtimeKey ?? envKey;
995
929
  if (!apiKey) {
@@ -1003,7 +937,7 @@ export class AuthStorage {
1003
937
  if (providerImpl.supports && !providerImpl.supports(params)) {
1004
938
  continue;
1005
939
  }
1006
- this.usageLogger?.debug("Usage fetch queued", {
940
+ this.#usageLogger?.debug("Usage fetch queued", {
1007
941
  provider,
1008
942
  credentialType: "api_key",
1009
943
  baseUrl,
@@ -1012,9 +946,9 @@ export class AuthStorage {
1012
946
  providerImpl
1013
947
  .fetchUsage(params, {
1014
948
  cache,
1015
- fetch: this.usageFetch,
1016
- now: this.usageNow,
1017
- logger: this.usageLogger,
949
+ fetch: this.#usageFetch,
950
+ now: this.#usageNow,
951
+ logger: this.#usageLogger,
1018
952
  })
1019
953
  .catch(error => {
1020
954
  logger.debug("AuthStorage usage fetch failed", {
@@ -1032,7 +966,7 @@ export class AuthStorage {
1032
966
  const usageCredential: UsageCredential =
1033
967
  credential.type === "api_key"
1034
968
  ? { type: "api_key", apiKey: credential.key }
1035
- : this.buildUsageCredential(credential);
969
+ : this.#buildUsageCredential(credential);
1036
970
  const params = {
1037
971
  provider: provider as Provider,
1038
972
  credential: usageCredential,
@@ -1043,7 +977,7 @@ export class AuthStorage {
1043
977
  continue;
1044
978
  }
1045
979
 
1046
- this.usageLogger?.debug("Usage fetch queued", {
980
+ this.#usageLogger?.debug("Usage fetch queued", {
1047
981
  provider,
1048
982
  credentialType: usageCredential.type,
1049
983
  baseUrl,
@@ -1055,9 +989,9 @@ export class AuthStorage {
1055
989
  providerImpl
1056
990
  .fetchUsage(params, {
1057
991
  cache,
1058
- fetch: this.usageFetch,
1059
- now: this.usageNow,
1060
- logger: this.usageLogger,
992
+ fetch: this.#usageFetch,
993
+ now: this.#usageNow,
994
+ logger: this.#usageLogger,
1061
995
  })
1062
996
  .catch(error => {
1063
997
  logger.debug("AuthStorage usage fetch failed", {
@@ -1073,16 +1007,16 @@ export class AuthStorage {
1073
1007
  if (tasks.length === 0) return [];
1074
1008
  const results = await Promise.all(tasks);
1075
1009
  const reports = results.filter((report): report is UsageReport => report !== null);
1076
- const deduped = this.dedupeUsageReports(reports);
1077
- this.usageLogger?.debug("Usage fetch resolved", {
1010
+ const deduped = this.#dedupeUsageReports(reports);
1011
+ this.#usageLogger?.debug("Usage fetch resolved", {
1078
1012
  reports: deduped.map(report => {
1079
1013
  const accountLabel =
1080
- this.getUsageReportMetadataValue(report, "email") ??
1081
- this.getUsageReportMetadataValue(report, "accountId") ??
1082
- this.getUsageReportMetadataValue(report, "account") ??
1083
- this.getUsageReportMetadataValue(report, "user") ??
1084
- this.getUsageReportMetadataValue(report, "username") ??
1085
- this.getUsageReportScopeAccountId(report);
1014
+ this.#getUsageReportMetadataValue(report, "email") ??
1015
+ this.#getUsageReportMetadataValue(report, "accountId") ??
1016
+ this.#getUsageReportMetadataValue(report, "account") ??
1017
+ this.#getUsageReportMetadataValue(report, "user") ??
1018
+ this.#getUsageReportMetadataValue(report, "username") ??
1019
+ this.#getUsageReportScopeAccountId(report);
1086
1020
  return {
1087
1021
  provider: report.provider,
1088
1022
  limits: report.limits.length,
@@ -1103,19 +1037,19 @@ export class AuthStorage {
1103
1037
  sessionId: string | undefined,
1104
1038
  options?: { retryAfterMs?: number; baseUrl?: string },
1105
1039
  ): Promise<boolean> {
1106
- const sessionCredential = this.getSessionCredential(provider, sessionId);
1040
+ const sessionCredential = this.#getSessionCredential(provider, sessionId);
1107
1041
  if (!sessionCredential) return false;
1108
1042
 
1109
- const providerKey = this.getProviderTypeKey(provider, sessionCredential.type);
1110
- const now = this.usageNow();
1111
- let blockedUntil = now + (options?.retryAfterMs ?? AuthStorage.defaultBackoffMs);
1043
+ const providerKey = this.#getProviderTypeKey(provider, sessionCredential.type);
1044
+ const now = this.#usageNow();
1045
+ let blockedUntil = now + (options?.retryAfterMs ?? AuthStorage.#defaultBackoffMs);
1112
1046
 
1113
1047
  if (provider === "openai-codex" && sessionCredential.type === "oauth") {
1114
- const credential = this.getCredentialsForProvider(provider)[sessionCredential.index];
1048
+ const credential = this.#getCredentialsForProvider(provider)[sessionCredential.index];
1115
1049
  if (credential?.type === "oauth") {
1116
- const report = await this.getUsageReport(provider, credential, options);
1117
- if (report && this.isUsageLimitReached(report)) {
1118
- const resetAtMs = this.getUsageResetAtMs(report, this.usageNow());
1050
+ const report = await this.#getUsageReport(provider, credential, options);
1051
+ if (report && this.#isUsageLimitReached(report)) {
1052
+ const resetAtMs = this.#getUsageResetAtMs(report, this.#usageNow());
1119
1053
  if (resetAtMs && resetAtMs > blockedUntil) {
1120
1054
  blockedUntil = resetAtMs;
1121
1055
  }
@@ -1123,16 +1057,16 @@ export class AuthStorage {
1123
1057
  }
1124
1058
  }
1125
1059
 
1126
- this.markCredentialBlocked(providerKey, sessionCredential.index, blockedUntil);
1060
+ this.#markCredentialBlocked(providerKey, sessionCredential.index, blockedUntil);
1127
1061
 
1128
- const remainingCredentials = this.getCredentialsForProvider(provider)
1062
+ const remainingCredentials = this.#getCredentialsForProvider(provider)
1129
1063
  .map((credential, index) => ({ credential, index }))
1130
1064
  .filter(
1131
1065
  (entry): entry is { credential: AuthCredential; index: number } =>
1132
1066
  entry.credential.type === sessionCredential.type && entry.index !== sessionCredential.index,
1133
1067
  );
1134
1068
 
1135
- return remainingCredentials.some(candidate => !this.isCredentialBlocked(providerKey, candidate.index));
1069
+ return remainingCredentials.some(candidate => !this.#isCredentialBlocked(providerKey, candidate.index));
1136
1070
  }
1137
1071
 
1138
1072
  /**
@@ -1140,25 +1074,25 @@ export class AuthStorage {
1140
1074
  * Skips blocked credentials and checks usage limits for providers with usage data.
1141
1075
  * Falls back to earliest-unblocking credential if all are blocked.
1142
1076
  */
1143
- private async resolveOAuthApiKey(
1077
+ async #resolveOAuthApiKey(
1144
1078
  provider: string,
1145
1079
  sessionId?: string,
1146
1080
  options?: { baseUrl?: string },
1147
1081
  ): Promise<string | undefined> {
1148
- const credentials = this.getCredentialsForProvider(provider)
1082
+ const credentials = this.#getCredentialsForProvider(provider)
1149
1083
  .map((credential, index) => ({ credential, index }))
1150
1084
  .filter((entry): entry is { credential: OAuthCredential; index: number } => entry.credential.type === "oauth");
1151
1085
 
1152
1086
  if (credentials.length === 0) return undefined;
1153
1087
 
1154
- const providerKey = this.getProviderTypeKey(provider, "oauth");
1155
- const order = this.getCredentialOrder(providerKey, sessionId, credentials.length);
1088
+ const providerKey = this.#getProviderTypeKey(provider, "oauth");
1089
+ const order = this.#getCredentialOrder(providerKey, sessionId, credentials.length);
1156
1090
  const fallback = credentials[order[0]];
1157
1091
  const checkUsage = provider === "openai-codex" && credentials.length > 1;
1158
1092
 
1159
1093
  for (const idx of order) {
1160
1094
  const selection = credentials[idx];
1161
- const apiKey = await this.tryOAuthCredential(
1095
+ const apiKey = await this.#tryOAuthCredential(
1162
1096
  provider,
1163
1097
  selection,
1164
1098
  providerKey,
@@ -1170,15 +1104,15 @@ export class AuthStorage {
1170
1104
  if (apiKey) return apiKey;
1171
1105
  }
1172
1106
 
1173
- if (fallback && this.isCredentialBlocked(providerKey, fallback.index)) {
1174
- return this.tryOAuthCredential(provider, fallback, providerKey, sessionId, options, checkUsage, true);
1107
+ if (fallback && this.#isCredentialBlocked(providerKey, fallback.index)) {
1108
+ return this.#tryOAuthCredential(provider, fallback, providerKey, sessionId, options, checkUsage, true);
1175
1109
  }
1176
1110
 
1177
1111
  return undefined;
1178
1112
  }
1179
1113
 
1180
1114
  /** Attempts to use a single OAuth credential, checking usage and refreshing token. */
1181
- private async tryOAuthCredential(
1115
+ async #tryOAuthCredential(
1182
1116
  provider: string,
1183
1117
  selection: { credential: OAuthCredential; index: number },
1184
1118
  providerKey: string,
@@ -1187,7 +1121,7 @@ export class AuthStorage {
1187
1121
  checkUsage: boolean,
1188
1122
  allowBlocked: boolean,
1189
1123
  ): Promise<string | undefined> {
1190
- if (!allowBlocked && this.isCredentialBlocked(providerKey, selection.index)) {
1124
+ if (!allowBlocked && this.#isCredentialBlocked(providerKey, selection.index)) {
1191
1125
  return undefined;
1192
1126
  }
1193
1127
 
@@ -1195,14 +1129,14 @@ export class AuthStorage {
1195
1129
  let usageChecked = false;
1196
1130
 
1197
1131
  if (checkUsage) {
1198
- usage = await this.getUsageReport(provider, selection.credential, options);
1132
+ usage = await this.#getUsageReport(provider, selection.credential, options);
1199
1133
  usageChecked = true;
1200
- if (usage && this.isUsageLimitReached(usage)) {
1201
- const resetAtMs = this.getUsageResetAtMs(usage, this.usageNow());
1202
- this.markCredentialBlocked(
1134
+ if (usage && this.#isUsageLimitReached(usage)) {
1135
+ const resetAtMs = this.#getUsageResetAtMs(usage, this.#usageNow());
1136
+ this.#markCredentialBlocked(
1203
1137
  providerKey,
1204
1138
  selection.index,
1205
- resetAtMs ?? this.usageNow() + AuthStorage.defaultBackoffMs,
1139
+ resetAtMs ?? this.#usageNow() + AuthStorage.#defaultBackoffMs,
1206
1140
  );
1207
1141
  return undefined;
1208
1142
  }
@@ -1226,25 +1160,25 @@ export class AuthStorage {
1226
1160
  projectId: result.newCredentials.projectId ?? selection.credential.projectId,
1227
1161
  enterpriseUrl: result.newCredentials.enterpriseUrl ?? selection.credential.enterpriseUrl,
1228
1162
  };
1229
- this.replaceCredentialAt(provider, selection.index, updated);
1163
+ this.#replaceCredentialAt(provider, selection.index, updated);
1230
1164
 
1231
1165
  if (checkUsage) {
1232
1166
  const sameAccount = selection.credential.accountId === updated.accountId;
1233
1167
  if (!usageChecked || !sameAccount) {
1234
- usage = await this.getUsageReport(provider, updated, options);
1168
+ usage = await this.#getUsageReport(provider, updated, options);
1235
1169
  }
1236
- if (usage && this.isUsageLimitReached(usage)) {
1237
- const resetAtMs = this.getUsageResetAtMs(usage, this.usageNow());
1238
- this.markCredentialBlocked(
1170
+ if (usage && this.#isUsageLimitReached(usage)) {
1171
+ const resetAtMs = this.#getUsageResetAtMs(usage, this.#usageNow());
1172
+ this.#markCredentialBlocked(
1239
1173
  providerKey,
1240
1174
  selection.index,
1241
- resetAtMs ?? this.usageNow() + AuthStorage.defaultBackoffMs,
1175
+ resetAtMs ?? this.#usageNow() + AuthStorage.#defaultBackoffMs,
1242
1176
  );
1243
1177
  return undefined;
1244
1178
  }
1245
1179
  }
1246
1180
 
1247
- this.recordSessionCredential(provider, sessionId, "oauth", selection.index);
1181
+ this.#recordSessionCredential(provider, sessionId, "oauth", selection.index);
1248
1182
  return result.apiKey;
1249
1183
  } catch (error) {
1250
1184
  const errorMsg = String(error);
@@ -1263,13 +1197,13 @@ export class AuthStorage {
1263
1197
 
1264
1198
  if (isDefinitiveFailure) {
1265
1199
  // Permanently remove invalid credentials
1266
- this.removeCredentialAt(provider, selection.index);
1267
- if (this.getCredentialsForProvider(provider).some(credential => credential.type === "oauth")) {
1200
+ this.#removeCredentialAt(provider, selection.index);
1201
+ if (this.#getCredentialsForProvider(provider).some(credential => credential.type === "oauth")) {
1268
1202
  return this.getApiKey(provider, sessionId, options);
1269
1203
  }
1270
1204
  } else {
1271
1205
  // Block temporarily for transient failures (5 minutes)
1272
- this.markCredentialBlocked(providerKey, selection.index, this.usageNow() + 5 * 60 * 1000);
1206
+ this.#markCredentialBlocked(providerKey, selection.index, this.#usageNow() + 5 * 60 * 1000);
1273
1207
  }
1274
1208
  }
1275
1209
 
@@ -1287,18 +1221,18 @@ export class AuthStorage {
1287
1221
  */
1288
1222
  async getApiKey(provider: string, sessionId?: string, options?: { baseUrl?: string }): Promise<string | undefined> {
1289
1223
  // Runtime override takes highest priority
1290
- const runtimeKey = this.runtimeOverrides.get(provider);
1224
+ const runtimeKey = this.#runtimeOverrides.get(provider);
1291
1225
  if (runtimeKey) {
1292
1226
  return runtimeKey;
1293
1227
  }
1294
1228
 
1295
- const apiKeySelection = this.selectCredentialByType(provider, "api_key", sessionId);
1229
+ const apiKeySelection = this.#selectCredentialByType(provider, "api_key", sessionId);
1296
1230
  if (apiKeySelection) {
1297
- this.recordSessionCredential(provider, sessionId, "api_key", apiKeySelection.index);
1231
+ this.#recordSessionCredential(provider, sessionId, "api_key", apiKeySelection.index);
1298
1232
  return resolveConfigValue(apiKeySelection.credential.key);
1299
1233
  }
1300
1234
 
1301
- const oauthKey = await this.resolveOAuthApiKey(provider, sessionId, options);
1235
+ const oauthKey = await this.#resolveOAuthApiKey(provider, sessionId, options);
1302
1236
  if (oauthKey) {
1303
1237
  return oauthKey;
1304
1238
  }
@@ -1308,6 +1242,6 @@ export class AuthStorage {
1308
1242
  if (envKey) return envKey;
1309
1243
 
1310
1244
  // Fall back to custom resolver (e.g., models.json custom providers)
1311
- return this.fallbackResolver?.(provider) ?? undefined;
1245
+ return this.#fallbackResolver?.(provider) ?? undefined;
1312
1246
  }
1313
1247
  }