@mastra/code-sdk 1.7.3-alpha.0 → 1.8.0-alpha.10
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.
- package/dist/agent-connections/tools.d.ts.map +1 -1
- package/dist/agent-connections/tools.js +26 -8
- package/dist/agent-connections/tools.js.map +1 -1
- package/dist/agents/background-completion-events.d.ts +16 -0
- package/dist/agents/background-completion-events.d.ts.map +1 -0
- package/dist/agents/background-completion-events.js +19 -0
- package/dist/agents/background-completion-events.js.map +1 -0
- package/dist/agents/background-completion.d.ts +9 -0
- package/dist/agents/background-completion.d.ts.map +1 -0
- package/dist/agents/background-completion.js +59 -0
- package/dist/agents/background-completion.js.map +1 -0
- package/dist/agents/mastracode-gateway.d.ts +2 -0
- package/dist/agents/mastracode-gateway.d.ts.map +1 -1
- package/dist/agents/mastracode-gateway.js +29 -10
- package/dist/agents/mastracode-gateway.js.map +1 -1
- package/dist/agents/memory.d.ts +1 -1
- package/dist/agents/memory.d.ts.map +1 -1
- package/dist/agents/memory.js +32 -25
- package/dist/agents/memory.js.map +1 -1
- package/dist/agents/model.d.ts +41 -1
- package/dist/agents/model.d.ts.map +1 -1
- package/dist/agents/model.js +141 -7
- package/dist/agents/model.js.map +1 -1
- package/dist/agents/tool-availability.d.ts +2 -0
- package/dist/agents/tool-availability.d.ts.map +1 -1
- package/dist/agents/tool-availability.js +33 -4
- package/dist/agents/tool-availability.js.map +1 -1
- package/dist/agents/tools.d.ts +1 -1
- package/dist/agents/tools.d.ts.map +1 -1
- package/dist/agents/tools.js +30 -2
- package/dist/agents/tools.js.map +1 -1
- package/dist/agents/workspace.d.ts +2 -1
- package/dist/agents/workspace.d.ts.map +1 -1
- package/dist/agents/workspace.js +3 -3
- package/dist/agents/workspace.js.map +1 -1
- package/dist/auth/account-rotation-processor.d.ts +207 -0
- package/dist/auth/account-rotation-processor.d.ts.map +1 -0
- package/dist/auth/account-rotation-processor.js +669 -0
- package/dist/auth/account-rotation-processor.js.map +1 -0
- package/dist/auth/account-routing-context.d.ts +13 -0
- package/dist/auth/account-routing-context.d.ts.map +1 -0
- package/dist/auth/account-routing-context.js +41 -0
- package/dist/auth/account-routing-context.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/providers/anthropic.d.ts.map +1 -1
- package/dist/auth/providers/anthropic.js +2 -8
- package/dist/auth/providers/anthropic.js.map +1 -1
- package/dist/auth/providers/github-copilot.d.ts.map +1 -1
- package/dist/auth/providers/github-copilot.js +34 -12
- package/dist/auth/providers/github-copilot.js.map +1 -1
- package/dist/auth/providers/kimi-coding.d.ts.map +1 -1
- package/dist/auth/providers/kimi-coding.js +2 -6
- package/dist/auth/providers/kimi-coding.js.map +1 -1
- package/dist/auth/providers/openai-codex.d.ts +3 -1
- package/dist/auth/providers/openai-codex.d.ts.map +1 -1
- package/dist/auth/providers/openai-codex.js +50 -24
- package/dist/auth/providers/openai-codex.js.map +1 -1
- package/dist/auth/providers/xai.d.ts +1 -1
- package/dist/auth/providers/xai.d.ts.map +1 -1
- package/dist/auth/providers/xai.js +36 -16
- package/dist/auth/providers/xai.js.map +1 -1
- package/dist/auth/storage.d.ts +130 -8
- package/dist/auth/storage.d.ts.map +1 -1
- package/dist/auth/storage.js +602 -39
- package/dist/auth/storage.js.map +1 -1
- package/dist/auth/types.d.ts +69 -2
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/goal-signal.d.ts +1 -1
- package/dist/goal-signal.d.ts.map +1 -1
- package/dist/goal-signal.js +1 -1
- package/dist/goal-signal.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +155 -38
- package/dist/index.js.map +1 -1
- package/dist/mcp/config.d.ts +17 -2
- package/dist/mcp/config.d.ts.map +1 -1
- package/dist/mcp/config.js +18 -3
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/manager.d.ts +2 -1
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +13 -8
- package/dist/mcp/manager.js.map +1 -1
- package/dist/mcp/types.d.ts +15 -3
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/onboarding/custom-packs.d.ts.map +1 -1
- package/dist/onboarding/custom-packs.js +3 -0
- package/dist/onboarding/custom-packs.js.map +1 -1
- package/dist/onboarding/index.js +1 -1
- package/dist/onboarding/packs.d.ts +44 -0
- package/dist/onboarding/packs.d.ts.map +1 -1
- package/dist/onboarding/packs.js +80 -2
- package/dist/onboarding/packs.js.map +1 -1
- package/dist/onboarding/settings.d.ts +33 -0
- package/dist/onboarding/settings.d.ts.map +1 -1
- package/dist/onboarding/settings.js +88 -8
- package/dist/onboarding/settings.js.map +1 -1
- package/dist/plugin.d.ts +12 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +2 -1
- package/dist/plugins/loader.js.map +1 -1
- package/dist/providers/claude-max.d.ts.map +1 -1
- package/dist/providers/claude-max.js +4 -5
- package/dist/providers/claude-max.js.map +1 -1
- package/dist/providers/github-copilot.d.ts +1 -1
- package/dist/providers/github-copilot.d.ts.map +1 -1
- package/dist/providers/github-copilot.js +89 -37
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/kimi-coding.d.ts.map +1 -1
- package/dist/providers/kimi-coding.js +16 -2
- package/dist/providers/kimi-coding.js.map +1 -1
- package/dist/providers/openai-codex.d.ts.map +1 -1
- package/dist/providers/openai-codex.js +42 -27
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/schema.d.ts +28 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +9 -0
- package/dist/schema.js.map +1 -1
- package/dist/tools/workflows/save-workflow.d.ts +1 -1
- package/dist/tools/workflows/save-workflow.d.ts.map +1 -1
- package/dist/workflows/register-primitives.js +2 -2
- package/dist/workflows/register-primitives.js.map +1 -1
- package/package.json +17 -17
|
@@ -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
|