@mastra/code-sdk 1.7.3-alpha.0 → 1.8.0-alpha.11

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 (138) hide show
  1. package/dist/agent-connections/ownership.d.ts +1 -0
  2. package/dist/agent-connections/ownership.d.ts.map +1 -1
  3. package/dist/agent-connections/ownership.js +42 -26
  4. package/dist/agent-connections/ownership.js.map +1 -1
  5. package/dist/agent-connections/registry.d.ts.map +1 -1
  6. package/dist/agent-connections/registry.js +5 -2
  7. package/dist/agent-connections/registry.js.map +1 -1
  8. package/dist/agent-connections/session-advertisement.d.ts +20 -0
  9. package/dist/agent-connections/session-advertisement.d.ts.map +1 -0
  10. package/dist/agent-connections/session-advertisement.js +79 -0
  11. package/dist/agent-connections/session-advertisement.js.map +1 -0
  12. package/dist/agent-connections/tools.d.ts.map +1 -1
  13. package/dist/agent-connections/tools.js +26 -8
  14. package/dist/agent-connections/tools.js.map +1 -1
  15. package/dist/agents/background-completion-events.d.ts +16 -0
  16. package/dist/agents/background-completion-events.d.ts.map +1 -0
  17. package/dist/agents/background-completion-events.js +19 -0
  18. package/dist/agents/background-completion-events.js.map +1 -0
  19. package/dist/agents/background-completion.d.ts +9 -0
  20. package/dist/agents/background-completion.d.ts.map +1 -0
  21. package/dist/agents/background-completion.js +59 -0
  22. package/dist/agents/background-completion.js.map +1 -0
  23. package/dist/agents/mastracode-gateway.d.ts +2 -0
  24. package/dist/agents/mastracode-gateway.d.ts.map +1 -1
  25. package/dist/agents/mastracode-gateway.js +29 -10
  26. package/dist/agents/mastracode-gateway.js.map +1 -1
  27. package/dist/agents/memory.d.ts +1 -1
  28. package/dist/agents/memory.d.ts.map +1 -1
  29. package/dist/agents/memory.js +32 -25
  30. package/dist/agents/memory.js.map +1 -1
  31. package/dist/agents/model.d.ts +41 -1
  32. package/dist/agents/model.d.ts.map +1 -1
  33. package/dist/agents/model.js +141 -7
  34. package/dist/agents/model.js.map +1 -1
  35. package/dist/agents/tool-availability.d.ts +2 -0
  36. package/dist/agents/tool-availability.d.ts.map +1 -1
  37. package/dist/agents/tool-availability.js +33 -4
  38. package/dist/agents/tool-availability.js.map +1 -1
  39. package/dist/agents/tools.d.ts +1 -1
  40. package/dist/agents/tools.d.ts.map +1 -1
  41. package/dist/agents/tools.js +30 -2
  42. package/dist/agents/tools.js.map +1 -1
  43. package/dist/agents/workspace.d.ts +2 -1
  44. package/dist/agents/workspace.d.ts.map +1 -1
  45. package/dist/agents/workspace.js +3 -3
  46. package/dist/agents/workspace.js.map +1 -1
  47. package/dist/auth/account-rotation-processor.d.ts +207 -0
  48. package/dist/auth/account-rotation-processor.d.ts.map +1 -0
  49. package/dist/auth/account-rotation-processor.js +669 -0
  50. package/dist/auth/account-rotation-processor.js.map +1 -0
  51. package/dist/auth/account-routing-context.d.ts +13 -0
  52. package/dist/auth/account-routing-context.d.ts.map +1 -0
  53. package/dist/auth/account-routing-context.js +41 -0
  54. package/dist/auth/account-routing-context.js.map +1 -0
  55. package/dist/auth/index.d.ts +1 -0
  56. package/dist/auth/index.d.ts.map +1 -1
  57. package/dist/auth/index.js +2 -1
  58. package/dist/auth/providers/anthropic.d.ts.map +1 -1
  59. package/dist/auth/providers/anthropic.js +2 -8
  60. package/dist/auth/providers/anthropic.js.map +1 -1
  61. package/dist/auth/providers/github-copilot.d.ts.map +1 -1
  62. package/dist/auth/providers/github-copilot.js +34 -12
  63. package/dist/auth/providers/github-copilot.js.map +1 -1
  64. package/dist/auth/providers/kimi-coding.d.ts.map +1 -1
  65. package/dist/auth/providers/kimi-coding.js +2 -6
  66. package/dist/auth/providers/kimi-coding.js.map +1 -1
  67. package/dist/auth/providers/openai-codex.d.ts +3 -1
  68. package/dist/auth/providers/openai-codex.d.ts.map +1 -1
  69. package/dist/auth/providers/openai-codex.js +50 -24
  70. package/dist/auth/providers/openai-codex.js.map +1 -1
  71. package/dist/auth/providers/xai.d.ts +1 -1
  72. package/dist/auth/providers/xai.d.ts.map +1 -1
  73. package/dist/auth/providers/xai.js +36 -16
  74. package/dist/auth/providers/xai.js.map +1 -1
  75. package/dist/auth/storage.d.ts +130 -8
  76. package/dist/auth/storage.d.ts.map +1 -1
  77. package/dist/auth/storage.js +602 -39
  78. package/dist/auth/storage.js.map +1 -1
  79. package/dist/auth/types.d.ts +69 -2
  80. package/dist/auth/types.d.ts.map +1 -1
  81. package/dist/goal-signal.d.ts +1 -1
  82. package/dist/goal-signal.d.ts.map +1 -1
  83. package/dist/goal-signal.js +1 -1
  84. package/dist/goal-signal.js.map +1 -1
  85. package/dist/index.d.ts +4 -0
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +127 -54
  88. package/dist/index.js.map +1 -1
  89. package/dist/mcp/config.d.ts +17 -2
  90. package/dist/mcp/config.d.ts.map +1 -1
  91. package/dist/mcp/config.js +18 -3
  92. package/dist/mcp/config.js.map +1 -1
  93. package/dist/mcp/manager.d.ts +2 -1
  94. package/dist/mcp/manager.d.ts.map +1 -1
  95. package/dist/mcp/manager.js +13 -8
  96. package/dist/mcp/manager.js.map +1 -1
  97. package/dist/mcp/types.d.ts +15 -3
  98. package/dist/mcp/types.d.ts.map +1 -1
  99. package/dist/onboarding/custom-packs.d.ts.map +1 -1
  100. package/dist/onboarding/custom-packs.js +3 -0
  101. package/dist/onboarding/custom-packs.js.map +1 -1
  102. package/dist/onboarding/index.js +1 -1
  103. package/dist/onboarding/packs.d.ts +44 -0
  104. package/dist/onboarding/packs.d.ts.map +1 -1
  105. package/dist/onboarding/packs.js +80 -2
  106. package/dist/onboarding/packs.js.map +1 -1
  107. package/dist/onboarding/settings.d.ts +33 -0
  108. package/dist/onboarding/settings.d.ts.map +1 -1
  109. package/dist/onboarding/settings.js +88 -8
  110. package/dist/onboarding/settings.js.map +1 -1
  111. package/dist/plugin.d.ts +12 -0
  112. package/dist/plugin.d.ts.map +1 -1
  113. package/dist/plugin.js.map +1 -1
  114. package/dist/plugins/loader.d.ts.map +1 -1
  115. package/dist/plugins/loader.js +2 -1
  116. package/dist/plugins/loader.js.map +1 -1
  117. package/dist/providers/claude-max.d.ts.map +1 -1
  118. package/dist/providers/claude-max.js +4 -5
  119. package/dist/providers/claude-max.js.map +1 -1
  120. package/dist/providers/github-copilot.d.ts +1 -1
  121. package/dist/providers/github-copilot.d.ts.map +1 -1
  122. package/dist/providers/github-copilot.js +89 -37
  123. package/dist/providers/github-copilot.js.map +1 -1
  124. package/dist/providers/kimi-coding.d.ts.map +1 -1
  125. package/dist/providers/kimi-coding.js +16 -2
  126. package/dist/providers/kimi-coding.js.map +1 -1
  127. package/dist/providers/openai-codex.d.ts.map +1 -1
  128. package/dist/providers/openai-codex.js +42 -27
  129. package/dist/providers/openai-codex.js.map +1 -1
  130. package/dist/schema.d.ts +28 -1
  131. package/dist/schema.d.ts.map +1 -1
  132. package/dist/schema.js +9 -0
  133. package/dist/schema.js.map +1 -1
  134. package/dist/tools/workflows/save-workflow.d.ts +1 -1
  135. package/dist/tools/workflows/save-workflow.d.ts.map +1 -1
  136. package/dist/workflows/register-primitives.js +2 -2
  137. package/dist/workflows/register-primitives.js.map +1 -1
  138. package/package.json +16 -16
@@ -0,0 +1,669 @@
1
+ import { getOAuthProviders } from "./storage.js";
2
+ import { ProviderAuthRequiredError } from "./provider-auth-error.js";
3
+ import { resolveModePackFallbackChain } from "../onboarding/packs.js";
4
+ import { findModePackForModel, loadSettings, resolveModePackModels } from "../onboarding/settings.js";
5
+ import { resolveCredentialStore } from "../agents/credential-resolver.js";
6
+ import { getRequestAccountSelection, markRequestAccountRoutingExhausted, setRequestAccountSelection } from "./account-routing-context.js";
7
+ import { listResolvableModePacks, resolveModel, resolveRequestThinkingLevel } from "../agents/model.js";
8
+ import { TripWire } from "@mastra/core/agent";
9
+ //#region src/auth/account-rotation-processor.ts
10
+ /**
11
+ * Error-driven rotation across a provider's OAuth account registry.
12
+ *
13
+ * When the active account cannot serve a request (rate limit, quota
14
+ * exhaustion, dead token, persistent outage), the next account in insertion
15
+ * order is selected for the request and the request retried. OAuth fetch
16
+ * wrappers read that request-scoped selection on every HTTP attempt, so
17
+ * concurrent runs can't swap each other's credentials. Every switch is
18
+ * persisted as a non-transient `data-mastracode-account-switch` part so it
19
+ * shows in the transcript and survives history reload.
20
+ *
21
+ * Two classes, two lanes — the split is load-bearing. The processor runner
22
+ * walks `[...inputProcessors, ...outputProcessors, ...errorProcessors]` when
23
+ * running `processAPIError`, so a processor registered in the input lane that
24
+ * also implemented `processAPIError` would run BEFORE
25
+ * `StreamErrorRetryProcessor` and hop on 5xx errors before transient retries
26
+ * had a chance. `AccountRotationProcessor` therefore implements
27
+ * `processAPIError` only and is registered in `errorProcessors` after
28
+ * `StreamErrorRetryProcessor`; `AccountStartNoticeProcessor` implements
29
+ * `processInput` only and is registered in `inputProcessors`.
30
+ */
31
+ const ACCOUNT_SWITCH_PART_TYPE = "data-mastracode-account-switch";
32
+ /** Rotatable providers = the OAuth provider registry; API-key-only providers never rotate. */
33
+ const KNOWN_PROVIDER_IDS = new Set(getOAuthProviders().map((provider) => provider.id));
34
+ /** Hostname suffixes → provider ids, for `APICallError.url`. */
35
+ const PROVIDER_HOST_PATTERNS = [
36
+ [/(^|\.)api\.anthropic\.com$/i, "anthropic"],
37
+ [/(^|\.)api\.kimi\.com$/i, "kimi-for-coding"],
38
+ [/(^|\.)api\.x\.ai$/i, "xai"],
39
+ [/(^|\.)chatgpt\.com$/i, "openai-codex"],
40
+ [/(^|\.)backend-api\.codex\.ai$/i, "openai-codex"],
41
+ [/(^|\.)githubcopilot\.com$/i, "github-copilot"],
42
+ [/(^|\.)api\.github\.com$/i, "github-copilot"]
43
+ ];
44
+ /**
45
+ * Provider usage/quota-limit wording (locked Q7 bucket 1). Statuses 429/402
46
+ * are matched before this; some providers deliver quota errors as plain 400s,
47
+ * which only the message reveals. Deliberately restricted to unambiguous
48
+ * quota phrases — a generic "exceeded your … limit" can appear on non-quota
49
+ * client errors (request size, context length) that must never rotate.
50
+ */
51
+ const USAGE_LIMIT_MESSAGE_PATTERN = /usage (?:limit|cap)|insufficient[_ ]quota|quota (?:exceeded|exhausted|reached)|exceeded (?:your )?current quota|insufficient (?:balance|credits?)|credits? exhausted|rate limit (?:exceeded|reached)|weekly limit|monthly limit/i;
52
+ const NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
53
+ "ECONNRESET",
54
+ "EPIPE",
55
+ "ECONNREFUSED",
56
+ "ENOTFOUND",
57
+ "ETIMEDOUT",
58
+ "EAI_AGAIN",
59
+ "UND_ERR_CONNECT_TIMEOUT",
60
+ "UND_ERR_SOCKET"
61
+ ]);
62
+ const NETWORK_MESSAGE_PATTERN = /fetch failed|network error|socket hang up|getaddrinfo|econnrefused|enotfound|etimedout|connection (?:refused|reset|closed|timed out)|other side closed|write epipe/i;
63
+ const MAX_CAUSE_DEPTH = 4;
64
+ /** Persisted data-part type for a fallback-pack hop. */
65
+ const PACK_FALLBACK_PART_TYPE = "data-mastracode-pack-fallback";
66
+ /**
67
+ * Session-state key the processor sets when a pack hop happens. The TUI
68
+ * listens for it on the typed `state_changed` controller event (data parts
69
+ * never ride controller message events) and applies thread stickiness.
70
+ */
71
+ const PACK_FALLBACK_STATE_KEY = "mastracodePendingPackFallback";
72
+ function isErrorWithCode(error) {
73
+ return typeof error === "object" && error !== null && "code" in error;
74
+ }
75
+ function readErrorStatus(error) {
76
+ let current = error;
77
+ for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {
78
+ if (typeof current !== "object") break;
79
+ const { statusCode, status } = current;
80
+ if (typeof statusCode === "number") return statusCode;
81
+ if (typeof status === "number") return status;
82
+ current = current.cause;
83
+ }
84
+ }
85
+ function isProviderAuthRequiredError(error) {
86
+ let current = error;
87
+ for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {
88
+ if (current instanceof ProviderAuthRequiredError || current instanceof Error && current.name === "ProviderAuthRequiredError") return true;
89
+ if (typeof current !== "object") break;
90
+ current = current.cause;
91
+ }
92
+ return false;
93
+ }
94
+ /** Concatenate the error's message with its cause chain (bounded). */
95
+ function collectErrorText(error) {
96
+ const parts = [];
97
+ let current = error;
98
+ for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) if (current instanceof Error) {
99
+ parts.push(current.message);
100
+ current = current.cause;
101
+ } else if (typeof current === "object" && "message" in current) {
102
+ const message = current.message;
103
+ if (typeof message === "string") parts.push(message);
104
+ current = current.cause;
105
+ } else break;
106
+ return parts.join(" | ");
107
+ }
108
+ function isNetworkErrorLike(error) {
109
+ let current = error;
110
+ for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {
111
+ if (isErrorWithCode(current) && typeof current.code === "string" && NETWORK_ERROR_CODES.has(current.code.toUpperCase())) return true;
112
+ if (current instanceof Error) {
113
+ if (NETWORK_MESSAGE_PATTERN.test(current.message)) return true;
114
+ current = current.cause;
115
+ } else if (typeof current === "object") current = current.cause;
116
+ else break;
117
+ }
118
+ return false;
119
+ }
120
+ /**
121
+ * The locked Q7 taxonomy. 429/402/usage-limit rotate immediately (429 is not
122
+ * covered by any transient matcher, so it arrives here on first occurrence);
123
+ * 401/403 and `ProviderAuthRequiredError` rotate after one forced refresh;
124
+ * 5xx/network hop only when the transient retry budget is already spent (they
125
+ * reach this processor because `StreamErrorRetryProcessor` declined); 400 and
126
+ * everything unknown never touch the registry.
127
+ */
128
+ function classifyRotationError(error) {
129
+ if (isProviderAuthRequiredError(error)) return {
130
+ kind: "rotate",
131
+ reason: "auth-failed"
132
+ };
133
+ const status = readErrorStatus(error);
134
+ if (status === 429) return {
135
+ kind: "rotate",
136
+ reason: "rate-limit"
137
+ };
138
+ if (status === 402) return {
139
+ kind: "rotate",
140
+ reason: "quota-exhausted"
141
+ };
142
+ if (status === 401 || status === 403) return {
143
+ kind: "rotate",
144
+ reason: "auth-failed"
145
+ };
146
+ if (status !== void 0 && status >= 500 && status < 600) return { kind: "hop" };
147
+ if (status === void 0 || status === 400) {
148
+ if (USAGE_LIMIT_MESSAGE_PATTERN.test(collectErrorText(error))) return {
149
+ kind: "rotate",
150
+ reason: "quota-exhausted"
151
+ };
152
+ }
153
+ if (isNetworkErrorLike(error)) return { kind: "hop" };
154
+ return { kind: "never" };
155
+ }
156
+ /** Provider id off a router model id: `mastracode/<provider>/<model>` or `<provider>/<model>`. */
157
+ function providerFromModelId(modelId) {
158
+ const providerId = modelId.replace(/^mastracode\//, "").split("/")[0];
159
+ if (providerId === "openai") return "openai-codex";
160
+ return providerId && KNOWN_PROVIDER_IDS.has(providerId) ? providerId : void 0;
161
+ }
162
+ function providerFromHost(hostname) {
163
+ for (const [pattern, providerId] of PROVIDER_HOST_PATTERNS) if (pattern.test(hostname)) return providerId;
164
+ }
165
+ /**
166
+ * Identify the provider an error came from: the request URL's hostname
167
+ * (`APICallError.url`, cause chain included), falling back to the router
168
+ * model id. Unknown → undefined (caller treats as never-rotate).
169
+ */
170
+ function providerFromError(error) {
171
+ const causeChain = [];
172
+ let current = error;
173
+ for (let depth = 0; current && depth < MAX_CAUSE_DEPTH; depth++) {
174
+ if (typeof current !== "object") break;
175
+ causeChain.push(current);
176
+ current = current.cause;
177
+ }
178
+ for (const item of causeChain) {
179
+ const urls = [
180
+ item.url,
181
+ item.requestURL,
182
+ item.requestUrl
183
+ ];
184
+ for (const url of urls) {
185
+ if (typeof url !== "string") continue;
186
+ try {
187
+ const providerId = providerFromHost(new URL(url).hostname);
188
+ if (providerId) return providerId;
189
+ } catch {}
190
+ }
191
+ }
192
+ for (const item of causeChain) {
193
+ const modelId = item.modelId;
194
+ if (typeof modelId !== "string") continue;
195
+ const providerId = providerFromModelId(modelId);
196
+ if (providerId) return providerId;
197
+ }
198
+ }
199
+ function getTriedInstances(state) {
200
+ const existing = state.triedInstances;
201
+ if (existing instanceof Set) return existing;
202
+ const created = /* @__PURE__ */ new Set();
203
+ state.triedInstances = created;
204
+ return created;
205
+ }
206
+ function getForcedRefreshInstances(state) {
207
+ const existing = state.forcedAuthRefresh;
208
+ if (existing instanceof Set) return existing;
209
+ const created = /* @__PURE__ */ new Set();
210
+ state.forcedAuthRefresh = created;
211
+ return created;
212
+ }
213
+ /** Friendly names for the part/notice copy (display only — never auth data). */
214
+ const PROVIDER_DISPLAY_NAMES = {
215
+ anthropic: "Anthropic",
216
+ "kimi-for-coding": "Kimi",
217
+ "openai-codex": "Codex",
218
+ "github-copilot": "GitHub Copilot",
219
+ xai: "xAI"
220
+ };
221
+ const REASON_TEXT = {
222
+ "rate-limit": "rate limit",
223
+ "quota-exhausted": "quota exhausted",
224
+ "auth-failed": "auth failed",
225
+ "pool-exhausted": "pool exhausted",
226
+ "persistent-outage": "persistent outage",
227
+ "preferred-routing": "subscription routing"
228
+ };
229
+ /** Validates untrusted `reason` values (e.g. persisted message parts). */
230
+ function isAccountSwitchReason(value) {
231
+ return value === "starting-on-account" || typeof value === "string" && Object.prototype.hasOwnProperty.call(REASON_TEXT, value);
232
+ }
233
+ /**
234
+ * One-line transcript copy for a switch part. Shared by the live `info`
235
+ * controller event (the TUI shows controller `info` events as transcript
236
+ * lines, and no message event carries data parts mid-run) and by the TUI's
237
+ * history rendering of the persisted part — one formatter, identical text.
238
+ */
239
+ function accountSwitchNoticeText(data) {
240
+ const provider = PROVIDER_DISPLAY_NAMES[data.provider] ?? data.provider;
241
+ if (data.reason === "starting-on-account" && data.to) return `Starting on ${provider} account: ${data.to.label}`;
242
+ const reason = data.reason === "starting-on-account" ? data.reason : REASON_TEXT[data.reason];
243
+ if (data.to === null) {
244
+ if (data.exclusive) return `Pinned ${provider} account unavailable (${reason})`;
245
+ return `All ${provider} accounts unavailable (${reason})`;
246
+ }
247
+ return `Switched ${provider} account: ${data.from?.label ?? "unknown"} → ${data.to.label} (${reason})`;
248
+ }
249
+ /** Transcript line for a pack-fallback hop; shared by the live info event and the TUI's history render. */
250
+ function packFallbackNoticeText(data) {
251
+ const reason = REASON_TEXT[data.reason] ?? data.reason;
252
+ return `Switched model pack: ${data.from.label} → ${data.to.label} (${reason})`;
253
+ }
254
+ /**
255
+ * Whether a persisted pack-fallback reason is one this build understands.
256
+ * `REASON_TEXT` also covers the account-switch reasons, so the notice text
257
+ * alone would happily render a foreign value such as `rate-limit` as a
258
+ * pack-fallback reason; history parsed from another build's part must be
259
+ * rejected instead.
260
+ */
261
+ function isPackFallbackReason(value) {
262
+ return value === "pool-exhausted" || value === "persistent-outage";
263
+ }
264
+ async function emitAccountSwitchPart(args, data) {
265
+ await args.writer?.custom({
266
+ type: ACCOUNT_SWITCH_PART_TYPE,
267
+ data
268
+ });
269
+ (args.requestContext?.get("controller"))?.emitEvent?.({
270
+ type: "info",
271
+ message: accountSwitchNoticeText(data)
272
+ });
273
+ }
274
+ /**
275
+ * Provider of the model the controller session is currently running — the
276
+ * fallback identification path for errors that carry neither a request URL
277
+ * nor a model id (e.g. `ProviderAuthRequiredError` thrown by a fetch wrapper
278
+ * before any HTTP request exists).
279
+ */
280
+ function providerFromSession(args) {
281
+ const modelId = (args.requestContext?.get("controller"))?.session?.modelId;
282
+ return typeof modelId === "string" ? providerFromModelId(modelId) : void 0;
283
+ }
284
+ function getRoutingController(args) {
285
+ return args.requestContext?.get("controller");
286
+ }
287
+ /**
288
+ * A12: the account a pack/model route targets, or `undefined` when the route
289
+ * is `Automatic` (no entry — insertion order, full pool rotation).
290
+ *
291
+ * A targeted route is *exclusive*: only that account may serve the route, so
292
+ * rotation must never spill the request onto a sibling subscription's quota.
293
+ * A heavy model on one subscription can burn that subscription's quota in
294
+ * hours; spilling it into a second subscription defeats the point of having
295
+ * one.
296
+ */
297
+ function getRouteTargetAccountId(settingsPath, route) {
298
+ if (!route) return void 0;
299
+ return loadSettings(settingsPath).models.packAccountPreferences?.[route.packId]?.[route.modelId];
300
+ }
301
+ function getRequestActiveAccount(args, store, providerId) {
302
+ const selectedId = getRequestAccountSelection(args.requestContext, providerId);
303
+ return (selectedId ? store.listAccounts?.(providerId).find((account) => account.id === selectedId) : void 0) ?? store.getActiveAccount?.(providerId) ?? store.listAccounts?.(providerId).find((account) => account.active);
304
+ }
305
+ function resolveAccountRoute(args, settingsPath, explicit) {
306
+ const controller = getRoutingController(args);
307
+ const state = controller?.getState?.();
308
+ const pending = state?.mastracodePendingPackFallback;
309
+ const pendingMatchesThread = pending && typeof pending.toPackId === "string" && typeof pending.toModelId === "string" && (pending.threadId === void 0 || pending.threadId === controller?.threadId);
310
+ const resolvedModelId = explicit?.modelId ?? (pendingMatchesThread ? pending.toModelId : void 0) ?? (typeof controller?.session?.modelId === "string" ? controller.session.modelId : void 0);
311
+ if (typeof resolvedModelId !== "string" || resolvedModelId.length === 0) return null;
312
+ const modelId = resolvedModelId;
313
+ const settings = loadSettings(settingsPath);
314
+ const packs = listResolvableModePacks(settings);
315
+ const modeId = typeof controller?.session?.modeId === "string" && controller.session.modeId.length > 0 ? controller.session.modeId : "build";
316
+ const explicitPackId = explicit?.packId ?? (pendingMatchesThread ? pending.toPackId : void 0) ?? (typeof state?.activeModelPackId === "string" ? state.activeModelPackId : settings.models.activeModelPackId);
317
+ const pack = explicitPackId ? packs.find((candidate) => candidate.id === explicitPackId) : findModePackForModel(settings, packs, modelId, modeId, void 0);
318
+ if (!pack || resolveModePackModels(settings, pack)[modeId] !== modelId) return null;
319
+ const providerId = providerFromModelId(modelId);
320
+ return providerId ? {
321
+ packId: pack.id,
322
+ modelId,
323
+ providerId
324
+ } : null;
325
+ }
326
+ /**
327
+ * A14: `Automatic` rotation starts *at* the persisted active account and walks
328
+ * insertion order from there, wrapping once. The active account is the cursor
329
+ * rotation last left at, so a turn that starts at index 0 regardless would
330
+ * re-activate and re-try an account the previous turn already moved past —
331
+ * which is the whole job the removed sticky-exclusion set was doing badly.
332
+ * With no persisted active account the order is plain insertion order.
333
+ */
334
+ function orderAccountsFromActive(accounts, activeId) {
335
+ const index = activeId === void 0 ? -1 : accounts.findIndex((account) => account.id === activeId);
336
+ if (index <= 0) return accounts;
337
+ return [...accounts.slice(index), ...accounts.slice(0, index)];
338
+ }
339
+ async function applyPreferredAccountRoute(args, store, settingsPath, route) {
340
+ const preferredId = getRouteTargetAccountId(settingsPath, route);
341
+ const accounts = store.listAccounts?.(route.providerId) ?? [];
342
+ if (accounts.length === 0) return false;
343
+ const tried = getTriedInstances(args.state);
344
+ const unavailable = new Set(accounts.filter((account) => tried.has(account.id)).map((account) => account.id));
345
+ const active = getRequestActiveAccount(args, store, route.providerId);
346
+ const selected = (preferredId ? accounts.filter((account) => account.id === preferredId) : orderAccountsFromActive(accounts, active?.id)).find((account) => !unavailable.has(account.id));
347
+ for (const accountId of unavailable) tried.add(accountId);
348
+ if (!selected) {
349
+ markRequestAccountRoutingExhausted(args.requestContext, route.providerId);
350
+ return false;
351
+ }
352
+ if (active?.id === selected.id) {
353
+ setRequestAccountSelection(args.requestContext, route.providerId, selected.id);
354
+ return false;
355
+ }
356
+ const activated = store.activateAccount?.(route.providerId, selected.id) ?? selected;
357
+ setRequestAccountSelection(args.requestContext, route.providerId, activated.id);
358
+ await emitAccountSwitchPart(args, {
359
+ provider: route.providerId,
360
+ from: active ? {
361
+ id: active.id,
362
+ label: active.label
363
+ } : null,
364
+ to: {
365
+ id: activated.id,
366
+ label: activated.label
367
+ },
368
+ reason: "preferred-routing",
369
+ at: (/* @__PURE__ */ new Date()).toISOString()
370
+ });
371
+ return true;
372
+ }
373
+ var AccountRotationProcessor = class {
374
+ options;
375
+ id = "mastracode-account-rotation";
376
+ constructor(options) {
377
+ this.options = options;
378
+ }
379
+ async processAPIError(args) {
380
+ const { error, state } = args;
381
+ if (args.retryCount >= this.options.maxProcessorRetries) {
382
+ await this.gateChainHop(args, error);
383
+ return { retry: false };
384
+ }
385
+ const classification = classifyRotationError(error);
386
+ if (classification.kind === "never") {
387
+ await this.gateChainHop(args, error);
388
+ return { retry: false };
389
+ }
390
+ const cascade = await this.getPackCascade(args);
391
+ const currentPack = cascade?.packs[cascade.position];
392
+ const cascadeModelId = currentPack ? cascade.models[currentPack.packId]?.[cascade.modeId] : void 0;
393
+ const providerId = providerFromError(error) ?? (typeof cascadeModelId === "string" ? providerFromModelId(cascadeModelId) : void 0) ?? (cascade ? void 0 : providerFromSession(args));
394
+ if (!providerId) {
395
+ await this.emitPackFallbackPart(args, classification.kind === "hop" ? "persistent-outage" : "pool-exhausted");
396
+ return { retry: false };
397
+ }
398
+ const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;
399
+ const accounts = store.listAccounts?.(providerId) ?? [];
400
+ const active = getRequestActiveAccount(args, store, providerId);
401
+ const route = currentPack && typeof cascadeModelId === "string" ? resolveAccountRoute(args, this.options.settingsPath, {
402
+ packId: currentPack.packId,
403
+ modelId: cascadeModelId
404
+ }) : resolveAccountRoute(args, this.options.settingsPath);
405
+ if (classification.kind === "rotate" && classification.reason === "auth-failed" && active) {
406
+ const forced = getForcedRefreshInstances(state);
407
+ const refreshKey = `${providerId}:${active?.id ?? "active"}`;
408
+ if (!forced.has(refreshKey) && typeof store.forceRefreshActiveAccount === "function") {
409
+ forced.add(refreshKey);
410
+ if (await store.forceRefreshActiveAccount(providerId, active?.id) !== void 0) return { retry: true };
411
+ }
412
+ }
413
+ const targetAccountId = route?.providerId === providerId ? getRouteTargetAccountId(this.options.settingsPath, route) : void 0;
414
+ if (classification.kind === "hop") return this.declarePoolUnavailable(args, providerId, "persistent-outage", targetAccountId !== void 0);
415
+ if (targetAccountId !== void 0) return this.declarePoolUnavailable(args, providerId, "pool-exhausted", true);
416
+ if (accounts.length < 2) return this.declarePoolUnavailable(args, providerId, "pool-exhausted");
417
+ const tried = getTriedInstances(state);
418
+ if (active) tried.add(active.id);
419
+ if (accounts.filter((account) => tried.has(account.id)).length >= accounts.length) return this.declarePoolUnavailable(args, providerId, "pool-exhausted");
420
+ const nextCandidate = accounts.find((account) => !tried.has(account.id));
421
+ const next = nextCandidate ? store.activateAccount?.(providerId, nextCandidate.id) : void 0;
422
+ if (!next) return this.declarePoolUnavailable(args, providerId, "pool-exhausted");
423
+ setRequestAccountSelection(args.requestContext, providerId, next.id);
424
+ tried.add(next.id);
425
+ args.rotateResponseMessageId?.();
426
+ await emitAccountSwitchPart(args, {
427
+ provider: providerId,
428
+ from: active ? {
429
+ id: active.id,
430
+ label: active.label
431
+ } : null,
432
+ to: {
433
+ id: next.id,
434
+ label: next.label
435
+ },
436
+ reason: classification.reason,
437
+ at: (/* @__PURE__ */ new Date()).toISOString()
438
+ });
439
+ return { retry: true };
440
+ }
441
+ /**
442
+ * The request's pack cascade, computed once from the session's pack and
443
+ * cached in processor state. Truncated at the first pack that lacks the
444
+ * session mode's model — mirroring getDynamicModel's truncation — so the
445
+ * cascade never promises a hop core's fallback array cannot make. The
446
+ * position advances per hop, so a second hop in the same turn reports B→C
447
+ * even though the session modelId still points at pack A (stickiness lands
448
+ * TUI-side only after the part renders). Returns null when the session has
449
+ * no active pack or the pack has no fallback chain.
450
+ */
451
+ async getPackCascade(args) {
452
+ if (args.state.packCascade !== void 0) return args.state.packCascade ?? null;
453
+ const controller = args.requestContext?.get("controller");
454
+ const modelId = controller?.session?.modelId;
455
+ if (typeof modelId !== "string" || modelId.length === 0) {
456
+ args.state.packCascade = null;
457
+ return null;
458
+ }
459
+ const modeId = typeof controller?.session?.modeId === "string" && controller.session.modeId.length > 0 ? controller.session.modeId : "build";
460
+ const settings = loadSettings(this.options.settingsPath);
461
+ const packs = listResolvableModePacks(settings);
462
+ const controllerState = controller?.getState?.();
463
+ const statePackId = controllerState?.activeModelPackId ?? settings.models.activeModelPackId;
464
+ const activePack = findModePackForModel(settings, packs, modelId, modeId, typeof statePackId === "string" ? statePackId : void 0);
465
+ const chain = activePack ? resolveModePackFallbackChain(settings.models.packFallbacks ?? {}, activePack.id, settings.customModelPacks) : [];
466
+ if (chain.length < 2) {
467
+ args.state.packCascade = null;
468
+ return null;
469
+ }
470
+ const resolvedPacks = [];
471
+ const models = {};
472
+ for (const packId of chain) {
473
+ const pack = packs.find((candidate) => candidate.id === packId);
474
+ const packModels = pack ? resolveModePackModels(settings, pack) : {};
475
+ if (resolvedPacks.length > 0) {
476
+ const entryModelId = packModels[modeId];
477
+ if (!entryModelId) break;
478
+ try {
479
+ resolveModel(entryModelId, {
480
+ thinkingLevel: resolveRequestThinkingLevel({
481
+ state: { thinkingLevel: controllerState?.thinkingLevel },
482
+ session: { modeId }
483
+ }, this.options.settingsPath),
484
+ remapForCodexOAuth: true,
485
+ requestContext: args.requestContext
486
+ });
487
+ } catch {
488
+ break;
489
+ }
490
+ }
491
+ resolvedPacks.push({
492
+ packId,
493
+ label: pack?.name ?? packId
494
+ });
495
+ models[packId] = packModels;
496
+ }
497
+ if (resolvedPacks.length < 2) {
498
+ args.state.packCascade = null;
499
+ return null;
500
+ }
501
+ const cascade = {
502
+ packs: resolvedPacks,
503
+ models,
504
+ modeId,
505
+ position: 0
506
+ };
507
+ args.state.packCascade = cascade;
508
+ return cascade;
509
+ }
510
+ /**
511
+ * Q14 gate: 400/unknown/unattributable errors never hop packs. Core's
512
+ * fallback array advances on ANY thrown error except TripWire
513
+ * (llm-execution-step.ts), so when the session's pack has a chain and the
514
+ * current entry is non-last, a bare `retry: false` would silently hop on an
515
+ * error class the user excluded from hop triggers. TripWire is the only
516
+ * no-advance escape: the runner rethrows it and the fallback loop declines
517
+ * to advance. No chain (or already on the last entry) → return and let the
518
+ * plain `retry: false` surface the error exactly as before.
519
+ */
520
+ async gateChainHop(args, error) {
521
+ const cascade = await this.getPackCascade(args);
522
+ if (!cascade) return;
523
+ if (cascade.position >= cascade.packs.length - 1) return;
524
+ throw new TripWire(error instanceof Error ? error.message : String(error), {}, this.id);
525
+ }
526
+ /**
527
+ * Emit the pack-fallback part when a configured chain has a next pack and
528
+ * queue the thread stickiness trigger. Core's fallback array does the hop
529
+ * itself on `retry: false`; this only announces it. The cascade is
530
+ * truncated exactly like getDynamicModel's chain (missing mode model or
531
+ * unresolvable model), so an announced hop is always one core can make;
532
+ * the toModelId guard stays as defense against a stale cache.
533
+ */
534
+ async emitPackFallbackPart(args, reason) {
535
+ const cascade = await this.getPackCascade(args);
536
+ if (!cascade) return;
537
+ const from = cascade.packs[cascade.position];
538
+ const to = cascade.packs[cascade.position + 1];
539
+ if (!from || !to) return;
540
+ const toModelId = cascade.models[to.packId]?.[cascade.modeId];
541
+ if (!toModelId) return;
542
+ const controller = args.requestContext?.get("controller");
543
+ const at = (/* @__PURE__ */ new Date()).toISOString();
544
+ const threadId = typeof controller?.threadId === "string" ? controller.threadId : void 0;
545
+ const pending = {
546
+ fromPackId: from.packId,
547
+ toPackId: to.packId,
548
+ toModelId,
549
+ ...threadId ? { threadId } : {},
550
+ reason,
551
+ at
552
+ };
553
+ await controller?.setThreadSetting?.({
554
+ key: PACK_FALLBACK_STATE_KEY,
555
+ value: pending
556
+ });
557
+ const data = {
558
+ from,
559
+ to,
560
+ reason,
561
+ at
562
+ };
563
+ try {
564
+ await args.writer?.custom({
565
+ type: PACK_FALLBACK_PART_TYPE,
566
+ data
567
+ });
568
+ } catch (error) {
569
+ await controller?.setThreadSetting?.({
570
+ key: PACK_FALLBACK_STATE_KEY,
571
+ value: void 0
572
+ });
573
+ throw error;
574
+ }
575
+ await controller?.setState?.({ [PACK_FALLBACK_STATE_KEY]: pending });
576
+ args.state.startNoticeEmitted = false;
577
+ const targetRoute = resolveAccountRoute(args, this.options.settingsPath, {
578
+ packId: to.packId,
579
+ modelId: toModelId
580
+ });
581
+ if (targetRoute) await applyPreferredAccountRoute(args, resolveCredentialStore(args.requestContext) ?? this.options.credentialStore, this.options.settingsPath, targetRoute).catch(() => void 0);
582
+ controller?.emitEvent?.({
583
+ type: "info",
584
+ message: packFallbackNoticeText(data)
585
+ });
586
+ cascade.position++;
587
+ }
588
+ /**
589
+ * Pool done for this request: every instance recorded in the tried-set and
590
+ * a `to: null` part emitted. Returns `retry: false` so core's fallback
591
+ * array can hop to the next pack (announced by `emitPackFallbackPart`).
592
+ * Silent on the account part when the provider has no registry at all —
593
+ * there are no accounts to declare unavailable, but the hop still applies.
594
+ */
595
+ async declarePoolUnavailable(args, providerId, reason, exclusive = false) {
596
+ const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;
597
+ const accounts = store.listAccounts?.(providerId) ?? [];
598
+ if (accounts.length === 0) {
599
+ await this.emitPackFallbackPart(args, reason);
600
+ return { retry: false };
601
+ }
602
+ const tried = getTriedInstances(args.state);
603
+ for (const account of accounts) tried.add(account.id);
604
+ const active = getRequestActiveAccount(args, store, providerId);
605
+ await emitAccountSwitchPart(args, {
606
+ provider: providerId,
607
+ from: active ? {
608
+ id: active.id,
609
+ label: active.label
610
+ } : null,
611
+ to: null,
612
+ reason,
613
+ at: (/* @__PURE__ */ new Date()).toISOString(),
614
+ ...exclusive ? { exclusive: true } : {}
615
+ });
616
+ await this.emitPackFallbackPart(args, reason);
617
+ return { retry: false };
618
+ }
619
+ };
620
+ /**
621
+ * Emits the "starting on a non-default account" notice (Q19) once per
622
+ * request when the active registry account is not the first entry.
623
+ *
624
+ * TYPE CONSTRAINT: this class must NEVER implement `processAPIError`. It
625
+ * lives in `inputProcessors`; the runner walks input processors first when
626
+ * running `processAPIError`, so adding the method here would classify and
627
+ * rotate errors BEFORE `StreamErrorRetryProcessor`'s transient retries run.
628
+ */
629
+ var AccountStartNoticeProcessor = class {
630
+ options;
631
+ id = "mastracode-account-start-notice";
632
+ constructor(options) {
633
+ this.options = options;
634
+ }
635
+ async processInput(args) {
636
+ if (args.state.startNoticeEmitted) return args.messageList;
637
+ const store = resolveCredentialStore(args.requestContext) ?? this.options.credentialStore;
638
+ const route = resolveAccountRoute(args, this.options.settingsPath);
639
+ if (route) {
640
+ if (await applyPreferredAccountRoute(args, store, this.options.settingsPath, route)) {
641
+ args.state.startNoticeEmitted = true;
642
+ return args.messageList;
643
+ }
644
+ }
645
+ const providerId = route?.providerId ?? providerFromSession(args);
646
+ if (!providerId) return args.messageList;
647
+ const accounts = store.listAccounts?.(providerId) ?? [];
648
+ if (accounts.length < 2) return args.messageList;
649
+ const active = store.getActiveAccount?.(providerId);
650
+ const first = accounts[0];
651
+ if (!active || !first || active.id === first.id) return args.messageList;
652
+ args.state.startNoticeEmitted = true;
653
+ await emitAccountSwitchPart(args, {
654
+ provider: providerId,
655
+ from: null,
656
+ to: {
657
+ id: active.id,
658
+ label: active.label
659
+ },
660
+ reason: "starting-on-account",
661
+ at: (/* @__PURE__ */ new Date()).toISOString()
662
+ });
663
+ return args.messageList;
664
+ }
665
+ };
666
+ //#endregion
667
+ export { ACCOUNT_SWITCH_PART_TYPE, AccountRotationProcessor, AccountStartNoticeProcessor, PACK_FALLBACK_PART_TYPE, PACK_FALLBACK_STATE_KEY, PROVIDER_DISPLAY_NAMES, accountSwitchNoticeText, classifyRotationError, isAccountSwitchReason, isPackFallbackReason, packFallbackNoticeText, providerFromError, providerFromModelId };
668
+
669
+ //# sourceMappingURL=account-rotation-processor.js.map