@oh-my-pi/pi-coding-agent 13.16.4 → 13.17.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 (47) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/package.json +7 -7
  3. package/src/cli/args.ts +7 -0
  4. package/src/cli/classify-install-target.ts +50 -0
  5. package/src/cli/plugin-cli.ts +245 -31
  6. package/src/commands/plugin.ts +3 -0
  7. package/src/config/model-registry.ts +37 -0
  8. package/src/config/model-resolver.ts +18 -3
  9. package/src/config/settings-schema.ts +24 -13
  10. package/src/cursor.ts +66 -1
  11. package/src/discovery/claude-plugins.ts +95 -5
  12. package/src/discovery/helpers.ts +168 -41
  13. package/src/discovery/plugin-dir-roots.ts +28 -0
  14. package/src/discovery/substitute-plugin-root.ts +29 -0
  15. package/src/extensibility/plugins/index.ts +1 -0
  16. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  17. package/src/extensibility/plugins/marketplace/fetcher.ts +354 -0
  18. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  19. package/src/extensibility/plugins/marketplace/manager.ts +528 -0
  20. package/src/extensibility/plugins/marketplace/registry.ts +181 -0
  21. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  22. package/src/extensibility/plugins/marketplace/types.ts +177 -0
  23. package/src/extensibility/skills.ts +3 -3
  24. package/src/internal-urls/index.ts +1 -0
  25. package/src/internal-urls/local-protocol.ts +2 -19
  26. package/src/internal-urls/parse.ts +72 -0
  27. package/src/internal-urls/router.ts +2 -18
  28. package/src/lsp/config.ts +9 -0
  29. package/src/main.ts +50 -1
  30. package/src/modes/components/plugin-selector.ts +86 -0
  31. package/src/modes/components/settings-defs.ts +9 -4
  32. package/src/modes/controllers/event-controller.ts +10 -0
  33. package/src/modes/controllers/mcp-command-controller.ts +14 -0
  34. package/src/modes/controllers/selector-controller.ts +104 -13
  35. package/src/modes/interactive-mode.ts +9 -0
  36. package/src/modes/types.ts +1 -0
  37. package/src/prompts/agents/reviewer.md +3 -4
  38. package/src/prompts/tools/bash.md +3 -3
  39. package/src/sdk.ts +0 -7
  40. package/src/session/agent-session.ts +292 -6
  41. package/src/slash-commands/builtin-registry.ts +273 -0
  42. package/src/tools/bash-skill-urls.ts +48 -5
  43. package/src/tools/bash.ts +2 -0
  44. package/src/tools/read.ts +15 -9
  45. package/src/web/search/code-search.ts +2 -179
  46. package/src/web/search/index.ts +2 -3
  47. package/src/web/search/types.ts +1 -5
@@ -55,7 +55,12 @@ import { abortableSleep, getAgentDbPath, isEnoent, logger } from "@oh-my-pi/pi-u
55
55
  import type { AsyncJob, AsyncJobManager } from "../async";
56
56
  import type { Rule } from "../capability/rule";
57
57
  import { MODEL_ROLE_IDS, type ModelRegistry } from "../config/model-registry";
58
- import { extractExplicitThinkingSelector, parseModelString, resolveModelRoleValue } from "../config/model-resolver";
58
+ import {
59
+ extractExplicitThinkingSelector,
60
+ formatModelString,
61
+ parseModelString,
62
+ resolveModelRoleValue,
63
+ } from "../config/model-resolver";
59
64
  import { expandPromptTemplate, type PromptTemplate, renderPromptTemplate } from "../config/prompt-templates";
60
65
  import type { Settings, SkillsSettings } from "../config/settings";
61
66
  import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
@@ -170,6 +175,8 @@ export type AgentSessionEvent =
170
175
  }
171
176
  | { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
172
177
  | { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
178
+ | { type: "retry_fallback_applied"; from: string; to: string; role: string }
179
+ | { type: "retry_fallback_succeeded"; model: string; role: string }
173
180
  | { type: "ttsr_triggered"; rules: Rule[] }
174
181
  | { type: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number }
175
182
  | { type: "todo_auto_clear" };
@@ -315,6 +322,46 @@ interface HandoffOptions {
315
322
 
316
323
  const AUTO_HANDOFF_THRESHOLD_FOCUS = renderPromptTemplate(autoHandoffThresholdFocusPrompt);
317
324
 
325
+ type RetryFallbackChains = Record<string, string[]>;
326
+
327
+ type RetryFallbackRevertPolicy = "never" | "cooldown-expiry";
328
+
329
+ interface RetryFallbackSelector {
330
+ raw: string;
331
+ provider: string;
332
+ id: string;
333
+ thinkingLevel: ThinkingLevel | undefined;
334
+ }
335
+
336
+ interface ActiveRetryFallbackState {
337
+ role: string;
338
+ originalSelector: string;
339
+ originalThinkingLevel: ThinkingLevel | undefined;
340
+ lastAppliedFallbackThinkingLevel: ThinkingLevel | undefined;
341
+ }
342
+
343
+ function parseRetryFallbackSelector(selector: string): RetryFallbackSelector | undefined {
344
+ const trimmed = selector.trim();
345
+ if (!trimmed) return undefined;
346
+ const parsed = parseModelString(trimmed);
347
+ if (!parsed) return undefined;
348
+ return {
349
+ raw: trimmed,
350
+ provider: parsed.provider,
351
+ id: parsed.id,
352
+ thinkingLevel: parsed.thinkingLevel,
353
+ };
354
+ }
355
+
356
+ function formatRetryFallbackSelector(model: Model, thinkingLevel: ThinkingLevel | undefined): string {
357
+ const selector = formatModelString(model);
358
+ return thinkingLevel ? `${selector}:${thinkingLevel}` : selector;
359
+ }
360
+
361
+ function formatRetryFallbackBaseSelector(selector: RetryFallbackSelector): string {
362
+ return `${selector.provider}/${selector.id}`;
363
+ }
364
+
318
365
  const noOpUIContext: ExtensionUIContext = {
319
366
  select: async (_title, _options, _dialogOptions) => undefined,
320
367
  confirm: async (_title, _message, _dialogOptions) => false,
@@ -352,6 +399,7 @@ export class AgentSession {
352
399
  readonly sessionManager: SessionManager;
353
400
  readonly settings: Settings;
354
401
  readonly searchDb: SearchDb | undefined;
402
+ readonly configWarnings: string[] = [];
355
403
 
356
404
  #asyncJobManager: AsyncJobManager | undefined = undefined;
357
405
  #scopedModels: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
@@ -391,7 +439,7 @@ export class AgentSession {
391
439
  #retryAttempt = 0;
392
440
  #retryPromise: Promise<void> | undefined = undefined;
393
441
  #retryResolve: (() => void) | undefined = undefined;
394
-
442
+ #activeRetryFallback: ActiveRetryFallbackState | undefined = undefined;
395
443
  // Todo completion reminder state
396
444
  #todoReminderCount = 0;
397
445
  #todoPhases: TodoPhase[] = [];
@@ -478,6 +526,7 @@ export class AgentSession {
478
526
  this.#customCommands = config.customCommands ?? [];
479
527
  this.#skillsSettings = config.skillsSettings;
480
528
  this.#modelRegistry = config.modelRegistry;
529
+ this.#validateRetryFallbackChains();
481
530
  this.#toolRegistry = config.toolRegistry ?? new Map();
482
531
  this.#transformContext = config.transformContext ?? (messages => messages);
483
532
  this.#onPayload = config.onPayload;
@@ -787,6 +836,13 @@ export class AgentSession {
787
836
  assistantMsg.stopReason !== "aborted" &&
788
837
  this.#retryAttempt > 0
789
838
  ) {
839
+ if (this.#activeRetryFallback && this.model) {
840
+ await this.#emitSessionEvent({
841
+ type: "retry_fallback_succeeded",
842
+ model: formatRetryFallbackSelector(this.model, this.thinkingLevel),
843
+ role: this.#activeRetryFallback.role,
844
+ });
845
+ }
790
846
  await this.#emitSessionEvent({
791
847
  type: "auto_retry_end",
792
848
  success: true,
@@ -985,6 +1041,7 @@ export class AgentSession {
985
1041
  return;
986
1042
  }
987
1043
  try {
1044
+ await this.#maybeRestoreRetryFallbackPrimary();
988
1045
  await this.agent.continue();
989
1046
  } catch {
990
1047
  options?.onError?.();
@@ -2288,6 +2345,8 @@ export class AgentSession {
2288
2345
  // Reset todo reminder count on new user prompt
2289
2346
  this.#todoReminderCount = 0;
2290
2347
 
2348
+ await this.#maybeRestoreRetryFallbackPrimary();
2349
+
2291
2350
  // Validate model
2292
2351
  if (!this.model) {
2293
2352
  throw new Error(
@@ -3108,6 +3167,7 @@ export class AgentSession {
3108
3167
  throw new Error(`No API key for ${model.provider}/${model.id}`);
3109
3168
  }
3110
3169
 
3170
+ this.#clearActiveRetryFallback();
3111
3171
  this.#setModelWithProviderSessionReset(model);
3112
3172
  this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, role);
3113
3173
  this.settings.setModelRole(role, this.#formatRoleModelValue(role, model));
@@ -3128,6 +3188,7 @@ export class AgentSession {
3128
3188
  throw new Error(`No API key for ${model.provider}/${model.id}`);
3129
3189
  }
3130
3190
 
3191
+ this.#clearActiveRetryFallback();
3131
3192
  this.#setModelWithProviderSessionReset(model);
3132
3193
  this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, "temporary");
3133
3194
  this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
@@ -3253,6 +3314,7 @@ export class AgentSession {
3253
3314
  const next = scopedModels[nextIndex];
3254
3315
 
3255
3316
  // Apply model
3317
+ this.#clearActiveRetryFallback();
3256
3318
  this.#setModelWithProviderSessionReset(next.model);
3257
3319
  this.sessionManager.appendModelChange(`${next.model.provider}/${next.model.id}`);
3258
3320
  this.settings.setModelRole("default", this.#formatRoleModelValue("default", next.model));
@@ -3281,11 +3343,11 @@ export class AgentSession {
3281
3343
  throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
3282
3344
  }
3283
3345
 
3346
+ this.#clearActiveRetryFallback();
3284
3347
  this.#setModelWithProviderSessionReset(nextModel);
3285
3348
  this.sessionManager.appendModelChange(`${nextModel.provider}/${nextModel.id}`);
3286
3349
  this.settings.setModelRole("default", this.#formatRoleModelValue("default", nextModel));
3287
3350
  this.settings.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`);
3288
-
3289
3351
  // Re-apply the current thinking level for the newly selected model
3290
3352
  this.setThinkingLevel(this.thinkingLevel);
3291
3353
 
@@ -4768,6 +4830,217 @@ export class AgentSession {
4768
4830
  );
4769
4831
  }
4770
4832
 
4833
+ #getRetryFallbackChains(): RetryFallbackChains {
4834
+ const configuredChains = this.settings.get("retry.fallbackChains");
4835
+ if (!configuredChains || typeof configuredChains !== "object") return {};
4836
+ return configuredChains as RetryFallbackChains;
4837
+ }
4838
+
4839
+ #validateRetryFallbackChains(): void {
4840
+ const configuredChains = this.settings.get("retry.fallbackChains");
4841
+ if (configuredChains === undefined) return;
4842
+ if (!configuredChains || typeof configuredChains !== "object" || Array.isArray(configuredChains)) {
4843
+ const msg = "retry.fallbackChains must be a mapping of role names to selector arrays.";
4844
+ logger.warn(msg);
4845
+ this.configWarnings.push(msg);
4846
+ return;
4847
+ }
4848
+
4849
+ for (const [role, chain] of Object.entries(configuredChains)) {
4850
+ if (!Array.isArray(chain)) {
4851
+ const msg = `Fallback chain for role '${role}' must be an array of selector strings.`;
4852
+ logger.warn(msg);
4853
+ this.configWarnings.push(msg);
4854
+ continue;
4855
+ }
4856
+ for (const selectorStr of chain) {
4857
+ if (typeof selectorStr !== "string") {
4858
+ const msg = `Fallback chain for role '${role}' contains a non-string selector.`;
4859
+ logger.warn(msg);
4860
+ this.configWarnings.push(msg);
4861
+ continue;
4862
+ }
4863
+ const parsed = parseRetryFallbackSelector(selectorStr);
4864
+ if (!parsed) {
4865
+ const msg = `Invalid fallback selector format in role '${role}': ${selectorStr}`;
4866
+ logger.warn(msg);
4867
+ this.configWarnings.push(msg);
4868
+ continue;
4869
+ }
4870
+ const exists = this.#modelRegistry.find(parsed.provider, parsed.id);
4871
+ if (!exists) {
4872
+ const msg = `Fallback chain for role '${role}' references unknown model: ${selectorStr}`;
4873
+ logger.warn(msg);
4874
+ this.configWarnings.push(msg);
4875
+ }
4876
+ }
4877
+ }
4878
+ }
4879
+
4880
+ #getRetryFallbackRevertPolicy(): RetryFallbackRevertPolicy {
4881
+ return this.settings.get("retry.fallbackRevertPolicy") === "never" ? "never" : "cooldown-expiry";
4882
+ }
4883
+
4884
+ #getRetryFallbackPrimarySelector(role: string): RetryFallbackSelector | undefined {
4885
+ const configuredSelector = this.settings.getModelRole(role);
4886
+ return configuredSelector ? parseRetryFallbackSelector(configuredSelector) : undefined;
4887
+ }
4888
+
4889
+ #clearActiveRetryFallback(): void {
4890
+ this.#activeRetryFallback = undefined;
4891
+ }
4892
+
4893
+ #isRetryFallbackSelectorSuppressed(selector: RetryFallbackSelector): boolean {
4894
+ return this.#modelRegistry.isSelectorSuppressed(selector.raw);
4895
+ }
4896
+
4897
+ #noteRetryFallbackCooldown(currentSelector: string, retryAfterMs: number | undefined, errorMessage: string): void {
4898
+ let cooldownMs = retryAfterMs;
4899
+ if (!cooldownMs || cooldownMs <= 0) {
4900
+ const reason = parseRateLimitReason(errorMessage);
4901
+ cooldownMs = reason === "UNKNOWN" ? 5 * 60 * 1000 : calculateRateLimitBackoffMs(reason);
4902
+ }
4903
+ this.#modelRegistry.suppressSelector(currentSelector, Date.now() + cooldownMs);
4904
+ }
4905
+
4906
+ #resolveRetryFallbackRole(currentSelector: string): string | undefined {
4907
+ const parsedCurrent = parseRetryFallbackSelector(currentSelector);
4908
+ if (!parsedCurrent) return undefined;
4909
+ const currentBaseSelector = formatRetryFallbackBaseSelector(parsedCurrent);
4910
+ for (const role of Object.keys(this.#getRetryFallbackChains())) {
4911
+ const primarySelector = this.#getRetryFallbackPrimarySelector(role);
4912
+ if (!primarySelector) continue;
4913
+ if (primarySelector.raw === currentSelector) return role;
4914
+ if (formatRetryFallbackBaseSelector(primarySelector) === currentBaseSelector) return role;
4915
+ }
4916
+ return undefined;
4917
+ }
4918
+
4919
+ #getRetryFallbackEffectiveChain(role: string): RetryFallbackSelector[] {
4920
+ const primarySelector = this.#getRetryFallbackPrimarySelector(role);
4921
+ if (!primarySelector) return [];
4922
+ const chain = [primarySelector];
4923
+ const seen = new Set<string>([primarySelector.raw]);
4924
+ for (const selector of this.#getRetryFallbackChains()[role] ?? []) {
4925
+ const parsed = parseRetryFallbackSelector(selector);
4926
+ if (!parsed || seen.has(parsed.raw)) continue;
4927
+ seen.add(parsed.raw);
4928
+ chain.push(parsed);
4929
+ }
4930
+ return chain;
4931
+ }
4932
+
4933
+ #findRetryFallbackCandidates(role: string, currentSelector: string): RetryFallbackSelector[] {
4934
+ const chain = this.#getRetryFallbackEffectiveChain(role);
4935
+ if (chain.length <= 1) return [];
4936
+ const parsedCurrent = parseRetryFallbackSelector(currentSelector);
4937
+ const currentBaseSelector = parsedCurrent ? formatRetryFallbackBaseSelector(parsedCurrent) : undefined;
4938
+ const exactIndex = chain.findIndex(selector => selector.raw === currentSelector);
4939
+ if (exactIndex >= 0) return chain.slice(exactIndex + 1);
4940
+ const baseIndex = currentBaseSelector
4941
+ ? chain.findIndex(selector => formatRetryFallbackBaseSelector(selector) === currentBaseSelector)
4942
+ : -1;
4943
+ if (baseIndex >= 0) return chain.slice(baseIndex + 1);
4944
+ return chain.slice(1);
4945
+ }
4946
+
4947
+ async #applyRetryFallbackCandidate(
4948
+ role: string,
4949
+ selector: RetryFallbackSelector,
4950
+ currentSelector: string,
4951
+ ): Promise<void> {
4952
+ const candidate = this.#modelRegistry.find(selector.provider, selector.id);
4953
+ if (!candidate) {
4954
+ throw new Error(`Retry fallback model not found: ${selector.raw}`);
4955
+ }
4956
+ const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
4957
+ if (!apiKey) {
4958
+ throw new Error(`No API key for retry fallback ${selector.raw}`);
4959
+ }
4960
+
4961
+ const currentThinkingLevel = this.thinkingLevel;
4962
+ const nextThinkingLevel = selector.thinkingLevel ?? currentThinkingLevel;
4963
+
4964
+ this.#setModelWithProviderSessionReset(candidate);
4965
+ this.sessionManager.appendModelChange(`${candidate.provider}/${candidate.id}`, "temporary");
4966
+ this.settings.getStorage()?.recordModelUsage(`${candidate.provider}/${candidate.id}`);
4967
+ this.setThinkingLevel(nextThinkingLevel);
4968
+ if (!this.#activeRetryFallback) {
4969
+ this.#activeRetryFallback = {
4970
+ role,
4971
+ originalSelector: currentSelector,
4972
+ originalThinkingLevel: currentThinkingLevel,
4973
+ lastAppliedFallbackThinkingLevel: nextThinkingLevel,
4974
+ };
4975
+ } else {
4976
+ this.#activeRetryFallback.lastAppliedFallbackThinkingLevel = nextThinkingLevel;
4977
+ }
4978
+ await this.#emitSessionEvent({
4979
+ type: "retry_fallback_applied",
4980
+ from: currentSelector,
4981
+ to: selector.raw,
4982
+ role,
4983
+ });
4984
+ }
4985
+
4986
+ async #tryRetryModelFallback(currentSelector: string): Promise<boolean> {
4987
+ const role = this.#activeRetryFallback?.role ?? this.#resolveRetryFallbackRole(currentSelector);
4988
+ if (!role) return false;
4989
+
4990
+ for (const selector of this.#findRetryFallbackCandidates(role, currentSelector)) {
4991
+ if (this.#isRetryFallbackSelectorSuppressed(selector)) continue;
4992
+ const candidate = this.#modelRegistry.find(selector.provider, selector.id);
4993
+ if (!candidate) continue;
4994
+ const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
4995
+ if (!apiKey) continue;
4996
+ await this.#applyRetryFallbackCandidate(role, selector, currentSelector);
4997
+ return true;
4998
+ }
4999
+
5000
+ return false;
5001
+ }
5002
+
5003
+ async #maybeRestoreRetryFallbackPrimary(): Promise<void> {
5004
+ if (!this.#activeRetryFallback) return;
5005
+ if (this.#getRetryFallbackRevertPolicy() !== "cooldown-expiry") return;
5006
+
5007
+ const {
5008
+ originalSelector: originalSelectorRaw,
5009
+ originalThinkingLevel,
5010
+ lastAppliedFallbackThinkingLevel,
5011
+ } = this.#activeRetryFallback;
5012
+ const originalSelector = parseRetryFallbackSelector(originalSelectorRaw);
5013
+ if (!originalSelector) {
5014
+ this.#clearActiveRetryFallback();
5015
+ return;
5016
+ }
5017
+
5018
+ const currentModel = this.model;
5019
+ if (!currentModel) return;
5020
+ const currentSelector = formatRetryFallbackSelector(currentModel, this.thinkingLevel);
5021
+ if (currentSelector === originalSelector.raw) {
5022
+ if (!this.#isRetryFallbackSelectorSuppressed(originalSelector)) {
5023
+ this.#clearActiveRetryFallback();
5024
+ }
5025
+ return;
5026
+ }
5027
+ if (this.#isRetryFallbackSelectorSuppressed(originalSelector)) return;
5028
+
5029
+ const primaryModel = this.#modelRegistry.find(originalSelector.provider, originalSelector.id);
5030
+ if (!primaryModel) return;
5031
+ const apiKey = await this.#modelRegistry.getApiKey(primaryModel, this.sessionId);
5032
+ if (!apiKey) return;
5033
+
5034
+ const currentThinkingLevel = this.thinkingLevel;
5035
+ const thinkingToApply =
5036
+ currentThinkingLevel === lastAppliedFallbackThinkingLevel ? originalThinkingLevel : currentThinkingLevel;
5037
+ this.#setModelWithProviderSessionReset(primaryModel);
5038
+ this.sessionManager.appendModelChange(`${primaryModel.provider}/${primaryModel.id}`, "temporary");
5039
+ this.settings.getStorage()?.recordModelUsage(`${primaryModel.provider}/${primaryModel.id}`);
5040
+ this.setThinkingLevel(thinkingToApply);
5041
+ this.#clearActiveRetryFallback();
5042
+ }
5043
+
4771
5044
  #parseRetryAfterMsFromError(errorMessage: string): number | undefined {
4772
5045
  const now = Date.now();
4773
5046
  const retryAfterMsMatch = /retry-after-ms\s*[:=]\s*(\d+)/i.exec(errorMessage);
@@ -4847,12 +5120,13 @@ export class AgentSession {
4847
5120
  }
4848
5121
 
4849
5122
  const errorMessage = message.errorMessage || "Unknown error";
5123
+ const parsedRetryAfterMs = this.#parseRetryAfterMsFromError(errorMessage);
4850
5124
  let delayMs = retrySettings.baseDelayMs * 2 ** (this.#retryAttempt - 1);
5125
+ let switchedCredential = false;
5126
+ let switchedModel = false;
4851
5127
 
4852
5128
  if (this.model && isUsageLimitError(errorMessage)) {
4853
- const retryAfterMs =
4854
- this.#parseRetryAfterMsFromError(errorMessage) ??
4855
- calculateRateLimitBackoffMs(parseRateLimitReason(errorMessage));
5129
+ const retryAfterMs = parsedRetryAfterMs ?? calculateRateLimitBackoffMs(parseRateLimitReason(errorMessage));
4856
5130
  const switched = await this.#modelRegistry.authStorage.markUsageLimitReached(
4857
5131
  this.model.provider,
4858
5132
  this.sessionId,
@@ -4862,6 +5136,7 @@ export class AgentSession {
4862
5136
  },
4863
5137
  );
4864
5138
  if (switched) {
5139
+ switchedCredential = true;
4865
5140
  delayMs = 0;
4866
5141
  } else if (retryAfterMs > delayMs) {
4867
5142
  // No more accounts to switch to — wait out the backoff
@@ -4869,6 +5144,17 @@ export class AgentSession {
4869
5144
  }
4870
5145
  }
4871
5146
 
5147
+ const currentSelector = this.model ? formatRetryFallbackSelector(this.model, this.thinkingLevel) : undefined;
5148
+ if (!switchedCredential && currentSelector) {
5149
+ this.#noteRetryFallbackCooldown(currentSelector, parsedRetryAfterMs, errorMessage);
5150
+ switchedModel = await this.#tryRetryModelFallback(currentSelector);
5151
+ if (switchedModel) {
5152
+ delayMs = 0;
5153
+ } else if (parsedRetryAfterMs && parsedRetryAfterMs > delayMs) {
5154
+ delayMs = parsedRetryAfterMs;
5155
+ }
5156
+ }
5157
+
4872
5158
  await this.#emitSessionEvent({
4873
5159
  type: "auto_retry_start",
4874
5160
  attempt: this.#retryAttempt,