@elizaos/ui 2.0.0-alpha.411 → 2.0.0-alpha.414

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 (64) hide show
  1. package/apps/app-companion/src/components/companion/CompanionAppView.d.ts.map +1 -1
  2. package/apps/app-companion/src/components/companion/CompanionAppView.js +5 -5
  3. package/apps/app-companion/src/components/companion/CompanionView.d.ts.map +1 -1
  4. package/apps/app-companion/src/components/companion/CompanionView.js +5 -5
  5. package/package.json +1 -1
  6. package/packages/agent/src/api/model-provider-helpers.js +10 -10
  7. package/packages/agent/src/api/provider-switch-config.d.ts +3 -3
  8. package/packages/agent/src/api/provider-switch-config.d.ts.map +1 -1
  9. package/packages/agent/src/api/provider-switch-config.js +2 -2
  10. package/packages/agent/src/auth/account-storage.d.ts +48 -0
  11. package/packages/agent/src/auth/account-storage.d.ts.map +1 -0
  12. package/packages/agent/src/auth/account-storage.js +238 -0
  13. package/packages/agent/src/auth/credentials.d.ts +44 -25
  14. package/packages/agent/src/auth/credentials.d.ts.map +1 -1
  15. package/packages/agent/src/auth/credentials.js +175 -137
  16. package/packages/agent/src/auth/index.d.ts +2 -0
  17. package/packages/agent/src/auth/index.d.ts.map +1 -1
  18. package/packages/agent/src/auth/index.js +2 -0
  19. package/packages/agent/src/auth/oauth-flow.d.ts +106 -0
  20. package/packages/agent/src/auth/oauth-flow.d.ts.map +1 -0
  21. package/packages/agent/src/auth/oauth-flow.js +349 -0
  22. package/packages/agent/src/auth/refresh-mutex.d.ts +19 -0
  23. package/packages/agent/src/auth/refresh-mutex.d.ts.map +1 -0
  24. package/packages/agent/src/auth/refresh-mutex.js +33 -0
  25. package/packages/agent/src/auth/vendor/pi-oauth/anthropic-login.d.ts +32 -0
  26. package/packages/agent/src/auth/vendor/pi-oauth/anthropic-login.d.ts.map +1 -1
  27. package/packages/agent/src/auth/vendor/pi-oauth/anthropic-login.js +63 -28
  28. package/packages/agent/src/config/schema.js +1 -1
  29. package/packages/agent/src/config/types.eliza.d.ts +8 -3
  30. package/packages/agent/src/config/types.eliza.d.ts.map +1 -1
  31. package/packages/agent/src/config/types.messages.d.ts +1 -1
  32. package/packages/agent/src/config/types.tools.d.ts +1 -1
  33. package/packages/agent/src/runtime/eliza.js +3 -3
  34. package/packages/app-core/src/components/apps/launch-history.d.ts.map +1 -1
  35. package/packages/app-core/src/components/auth/LoginView.d.ts +1 -1
  36. package/packages/app-core/src/components/auth/LoginView.d.ts.map +1 -1
  37. package/packages/app-core/src/components/auth/LoginView.js +1 -1
  38. package/packages/app-core/src/components/conversations/conversation-utils.js +4 -4
  39. package/packages/app-core/src/components/pages/AppDetailsView.d.ts.map +1 -1
  40. package/packages/app-core/src/components/pages/AppDetailsView.js +10 -10
  41. package/packages/app-core/src/components/pages/AppsView.d.ts.map +1 -1
  42. package/packages/app-core/src/components/pages/AppsView.js +1 -0
  43. package/packages/app-core/src/components/pages/AutomationsView.d.ts.map +1 -1
  44. package/packages/app-core/src/components/pages/AutomationsView.js +0 -1
  45. package/packages/app-core/src/components/settings/ProviderSwitcher.d.ts.map +1 -1
  46. package/packages/app-core/src/components/settings/ProviderSwitcher.js +1 -1
  47. package/packages/app-core/src/components/settings/SubscriptionStatus.d.ts +2 -0
  48. package/packages/app-core/src/components/settings/SubscriptionStatus.d.ts.map +1 -1
  49. package/packages/app-core/src/onboarding-config.d.ts +2 -2
  50. package/packages/app-core/src/onboarding-config.d.ts.map +1 -1
  51. package/packages/app-core/src/services/auth-store.d.ts.map +1 -1
  52. package/packages/app-core/src/shell/AppWindowRenderer.d.ts.map +1 -1
  53. package/packages/app-core/src/shell/AppWindowRenderer.js +11 -17
  54. package/packages/app-core/src/shell/DetachedShellRoot.d.ts.map +1 -1
  55. package/packages/app-core/src/state/startup-phase-poll.js +1 -1
  56. package/packages/app-core/src/state/useOnboardingState.js +1 -1
  57. package/packages/shared/src/config/types.eliza.d.ts +8 -3
  58. package/packages/shared/src/config/types.eliza.d.ts.map +1 -1
  59. package/packages/shared/src/contracts/onboarding.d.ts +10 -2
  60. package/packages/shared/src/contracts/onboarding.d.ts.map +1 -1
  61. package/packages/shared/src/contracts/onboarding.js +2 -2
  62. package/packages/shared/src/contracts/service-routing.d.ts +87 -3
  63. package/packages/shared/src/contracts/service-routing.d.ts.map +1 -1
  64. package/packages/shared/src/contracts/service-routing.js +171 -3
@@ -1,120 +1,147 @@
1
1
  /**
2
2
  * Credential storage and token refresh for subscription providers.
3
3
  *
4
- * Stores OAuth credentials in ~/.eliza/auth/ as JSON files.
4
+ * Credentials live under `~/.eliza/auth/{providerId}/{accountId}.json`
5
+ * (see `account-storage.ts` for the on-disk format and atomic-write
6
+ * details). The `loadCredentials` / `saveCredentials` /
7
+ * `deleteCredentials` / `hasValidCredentials` / `getAccessToken`
8
+ * helpers all default to `accountId="default"` so callers that pre-date
9
+ * multi-account support keep working without changes.
5
10
  */
6
11
  import { execSync } from "node:child_process";
7
12
  import fs from "node:fs";
8
13
  import os from "node:os";
9
14
  import path from "node:path";
10
15
  import { logger } from "@elizaos/core";
16
+ import { deleteAccount, listAccounts, loadAccount, migrateLegacySingleAccount, saveAccount, } from "./account-storage.js";
11
17
  import { refreshAnthropicToken } from "./anthropic.js";
12
18
  import { refreshCodexToken } from "./openai-codex.js";
19
+ import { accountRefreshMutex } from "./refresh-mutex.js";
13
20
  import { SUBSCRIPTION_PROVIDER_MAP, } from "./types.js";
14
- const AUTH_DIR = path.join(process.env.ELIZA_HOME || path.join(os.homedir(), ".eliza"), "auth");
21
+ const DEFAULT_ACCOUNT_ID = "default";
15
22
  /** Buffer before expiry to trigger refresh (5 minutes) */
16
23
  const REFRESH_BUFFER_MS = 5 * 60 * 1000;
17
24
  const invalidClaudeCodeRefreshTokens = new Set();
18
- function ensureAuthDir() {
19
- if (!fs.existsSync(AUTH_DIR)) {
20
- fs.mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });
21
- }
22
- }
23
- function credentialPath(provider) {
24
- return path.join(AUTH_DIR, `${provider}.json`);
25
+ // Run the legacy → per-account migration eagerly at module load. This
26
+ // is cheap when there's nothing to migrate (one `existsSync` per
27
+ // provider) and ensures every code path sees the per-account layout.
28
+ migrateLegacySingleAccount();
29
+ function recordToStored(record) {
30
+ return {
31
+ provider: record.providerId,
32
+ credentials: record.credentials,
33
+ createdAt: record.createdAt,
34
+ updatedAt: record.updatedAt,
35
+ };
25
36
  }
26
37
  /**
27
- * Save credentials for a provider.
38
+ * Save credentials for a provider account.
39
+ *
40
+ * The `accountId` defaults to `"default"`. New accounts are persisted
41
+ * with `source: "oauth"` and `label: "Default"` (or the existing
42
+ * record's label when overwriting).
28
43
  */
29
- export function saveCredentials(provider, credentials) {
30
- ensureAuthDir();
31
- const stored = {
32
- provider,
44
+ export function saveCredentials(provider, credentials, accountId = DEFAULT_ACCOUNT_ID) {
45
+ const existing = loadAccount(provider, accountId);
46
+ const now = Date.now();
47
+ const record = {
48
+ id: accountId,
49
+ providerId: provider,
50
+ label: existing?.label ??
51
+ (accountId === DEFAULT_ACCOUNT_ID ? "Default" : accountId),
52
+ source: existing?.source ?? "oauth",
33
53
  credentials,
34
- createdAt: Date.now(),
35
- updatedAt: Date.now(),
54
+ createdAt: existing?.createdAt ?? now,
55
+ updatedAt: now,
56
+ ...(existing?.lastUsedAt !== undefined
57
+ ? { lastUsedAt: existing.lastUsedAt }
58
+ : {}),
59
+ ...(existing?.organizationId !== undefined
60
+ ? { organizationId: existing.organizationId }
61
+ : {}),
62
+ ...(existing?.userId !== undefined ? { userId: existing.userId } : {}),
63
+ ...(existing?.email !== undefined ? { email: existing.email } : {}),
36
64
  };
37
- fs.writeFileSync(credentialPath(provider), JSON.stringify(stored, null, 2), {
38
- encoding: "utf-8",
39
- mode: 0o600,
40
- });
41
- logger.info(`[auth] Saved ${provider} credentials`);
65
+ saveAccount(record);
42
66
  }
43
67
  /**
44
- * Load stored credentials for a provider.
68
+ * Load stored credentials for a provider account.
69
+ * Returns `null` when no account is configured for the given id.
45
70
  */
46
- export function loadCredentials(provider) {
47
- const filePath = credentialPath(provider);
48
- try {
49
- const data = fs.readFileSync(filePath, "utf-8");
50
- return JSON.parse(data);
51
- }
52
- catch (err) {
53
- if (err.code === "ENOENT") {
54
- return null;
55
- }
56
- throw err;
57
- }
71
+ export function loadCredentials(provider, accountId = DEFAULT_ACCOUNT_ID) {
72
+ const record = loadAccount(provider, accountId);
73
+ if (!record)
74
+ return null;
75
+ return recordToStored(record);
58
76
  }
59
77
  /**
60
- * Delete stored credentials for a provider.
78
+ * Delete stored credentials for a provider account.
61
79
  */
62
- export function deleteCredentials(provider) {
63
- const filePath = credentialPath(provider);
64
- try {
65
- fs.unlinkSync(filePath);
66
- logger.info(`[auth] Deleted ${provider} credentials`);
67
- }
68
- catch (err) {
69
- if (err.code !== "ENOENT") {
70
- throw err;
71
- }
72
- }
80
+ export function deleteCredentials(provider, accountId = DEFAULT_ACCOUNT_ID) {
81
+ deleteAccount(provider, accountId);
73
82
  }
74
83
  /**
75
84
  * Check if credentials exist and are not expired.
76
85
  */
77
- export function hasValidCredentials(provider) {
78
- const stored = loadCredentials(provider);
86
+ export function hasValidCredentials(provider, accountId = DEFAULT_ACCOUNT_ID) {
87
+ const stored = loadCredentials(provider, accountId);
79
88
  if (!stored)
80
89
  return false;
81
90
  return stored.credentials.expires > Date.now();
82
91
  }
92
+ /**
93
+ * List all accounts configured for a provider.
94
+ */
95
+ export function listProviderAccounts(provider) {
96
+ return listAccounts(provider);
97
+ }
83
98
  /**
84
99
  * Get a valid access token, refreshing if needed.
85
- * Returns null if no credentials stored or refresh fails.
100
+ *
101
+ * Refreshes are serialized per `{provider}:{accountId}` via
102
+ * `accountRefreshMutex` so concurrent callers don't race on the
103
+ * refresh-token grant or the credential file write.
104
+ *
105
+ * Returns `null` when no credentials are stored or the refresh fails.
86
106
  */
87
- export async function getAccessToken(provider) {
88
- const stored = loadCredentials(provider);
89
- if (!stored)
107
+ export async function getAccessToken(provider, accountId = DEFAULT_ACCOUNT_ID) {
108
+ const initial = loadCredentials(provider, accountId);
109
+ if (!initial)
90
110
  return null;
91
- const { credentials } = stored;
92
- // Token still valid
93
- if (credentials.expires > Date.now() + REFRESH_BUFFER_MS) {
94
- return credentials.access;
111
+ if (initial.credentials.expires > Date.now() + REFRESH_BUFFER_MS) {
112
+ return initial.credentials.access;
95
113
  }
96
- // Need to refresh
97
- logger.info(`[auth] Refreshing ${provider} token...`);
98
- try {
99
- let refreshed;
100
- if (provider === "anthropic-subscription") {
101
- refreshed = await refreshAnthropicToken(credentials.refresh);
114
+ return accountRefreshMutex.acquire(`${provider}:${accountId}`, async () => {
115
+ // Re-read after acquiring the lock — a concurrent caller may have
116
+ // already refreshed the token, in which case we want the new one.
117
+ const stored = loadCredentials(provider, accountId);
118
+ if (!stored)
119
+ return null;
120
+ const { credentials } = stored;
121
+ if (credentials.expires > Date.now() + REFRESH_BUFFER_MS) {
122
+ return credentials.access;
102
123
  }
103
- else if (provider === "openai-codex") {
104
- refreshed = await refreshCodexToken(credentials.refresh);
124
+ logger.info(`[auth] Refreshing ${provider} token for account "${accountId}"...`);
125
+ let refreshed;
126
+ try {
127
+ if (provider === "anthropic-subscription") {
128
+ refreshed = await refreshAnthropicToken(credentials.refresh);
129
+ }
130
+ else if (provider === "openai-codex") {
131
+ refreshed = await refreshCodexToken(credentials.refresh);
132
+ }
133
+ else {
134
+ logger.error(`[auth] Unknown provider: ${provider}`);
135
+ return null;
136
+ }
105
137
  }
106
- else {
107
- logger.error(`[auth] Unknown provider: ${provider}`);
138
+ catch (err) {
139
+ logger.error(`[auth] Failed to refresh ${provider} token for "${accountId}": ${err}`);
108
140
  return null;
109
141
  }
110
- // Save refreshed credentials
111
- saveCredentials(provider, refreshed);
142
+ saveCredentials(provider, refreshed, accountId);
112
143
  return refreshed.access;
113
- }
114
- catch (err) {
115
- logger.error(`[auth] Failed to refresh ${provider} token: ${err}`);
116
- return null;
117
- }
144
+ });
118
145
  }
119
146
  function readConfiguredAnthropicSetupToken() {
120
147
  const namespace = process.env.ELIZA_NAMESPACE?.trim() || "milady";
@@ -147,20 +174,28 @@ export function getSubscriptionStatus() {
147
174
  "anthropic-subscription",
148
175
  "openai-codex",
149
176
  ];
150
- return providers.map((provider) => {
151
- const stored = loadCredentials(provider);
152
- // Read the Claude Code OAuth blob exactly once per provider row.
153
- // On macOS this helper shells out to `security` to query the
154
- // keychain — calling it twice per poll used to double the cost
155
- // of every `GET /api/subscription/status` request.
177
+ const rows = [];
178
+ for (const provider of providers) {
179
+ const accounts = listProviderAccounts(provider);
180
+ for (const account of accounts) {
181
+ rows.push({
182
+ provider,
183
+ accountId: account.id,
184
+ label: account.label,
185
+ configured: true,
186
+ valid: account.credentials.expires > Date.now(),
187
+ expiresAt: account.credentials.expires,
188
+ source: "app",
189
+ });
190
+ }
191
+ // Read the Claude Code OAuth blob exactly once per provider —
192
+ // `readClaudeCodeOAuthBlob()` shells out to `security` on macOS
193
+ // and calling it twice doubled the cost of every status poll.
156
194
  const claudeBlob = provider === "anthropic-subscription" ? readClaudeCodeOAuthBlob() : null;
157
- let importedClaudeAuth = null;
158
- let claudeSource = null;
159
195
  if (provider === "anthropic-subscription") {
196
+ let importedClaudeAuth = null;
197
+ let claudeSource = null;
160
198
  if (claudeBlob?.accessToken) {
161
- // Blob exists with a parsed accessToken — the user has Claude
162
- // Code installed and authenticated. Expiry is validated
163
- // below via the `valid` field.
164
199
  importedClaudeAuth = claudeBlob.accessToken;
165
200
  claudeSource = "claude-code-cli";
166
201
  }
@@ -169,44 +204,37 @@ export function getSubscriptionStatus() {
169
204
  if (importedClaudeAuth)
170
205
  claudeSource = "setup-token";
171
206
  }
207
+ if (importedClaudeAuth) {
208
+ const blobExpiresAt = claudeBlob?.expiresAt ?? null;
209
+ const blobValid = claudeBlob
210
+ ? blobExpiresAt === null || blobExpiresAt > Date.now()
211
+ : true;
212
+ const accountId = claudeSource === "claude-code-cli" ? "claude-code-cli" : "setup-token";
213
+ const label = claudeSource === "claude-code-cli" ? "Claude Code CLI" : "Setup Token";
214
+ rows.push({
215
+ provider,
216
+ accountId,
217
+ label,
218
+ configured: true,
219
+ valid: blobValid,
220
+ expiresAt: blobExpiresAt,
221
+ source: claudeSource,
222
+ });
223
+ }
172
224
  }
173
- const importedCodexAuth = provider === "openai-codex" && hasCodexCliSubscriptionAuth();
174
- // For the Claude blob path, derive expiry from the blob itself
175
- // so the UI can surface an accurate "valid" state even before a
176
- // refresh runs. Older Claude Code credential files omit
177
- // `expiresAt` entirely — treat a null expiry on an otherwise
178
- // parseable blob as "valid" (the presence of an accessToken is
179
- // itself evidence the user is authenticated; the runtime will
180
- // refresh via the refresh token on first use if needed).
181
- const blobExpiresAt = claudeBlob?.expiresAt ?? null;
182
- const blobValid = claudeBlob
183
- ? blobExpiresAt === null || blobExpiresAt > Date.now()
184
- : false;
185
- // App-owned credentials take priority over system/CLI sources —
186
- // they represent an explicit in-app OAuth, and they're the only
187
- // source the DELETE /api/subscription/{provider} route can clear.
188
- let source = null;
189
- if (stored !== null) {
190
- source = "app";
191
- }
192
- else if (provider === "anthropic-subscription" && claudeSource) {
193
- source = claudeSource;
194
- }
195
- else if (importedCodexAuth) {
196
- source = "codex-cli";
225
+ if (provider === "openai-codex" && hasCodexCliSubscriptionAuth()) {
226
+ rows.push({
227
+ provider,
228
+ accountId: "codex-cli",
229
+ label: "Codex CLI",
230
+ configured: true,
231
+ valid: true,
232
+ expiresAt: null,
233
+ source: "codex-cli",
234
+ });
197
235
  }
198
- return {
199
- provider,
200
- configured: stored !== null || Boolean(importedClaudeAuth || importedCodexAuth),
201
- valid: stored
202
- ? stored.credentials.expires > Date.now()
203
- : provider === "anthropic-subscription" && importedClaudeAuth
204
- ? blobValid
205
- : Boolean(importedCodexAuth),
206
- expiresAt: stored?.credentials.expires ?? blobExpiresAt,
207
- source,
208
- };
209
- });
236
+ }
237
+ return rows;
210
238
  }
211
239
  function isClaudeCodeInvalidGrantError(err) {
212
240
  const message = err instanceof Error ? err.message : String(err);
@@ -300,9 +328,6 @@ async function importClaudeCodeOAuthToken() {
300
328
  if (invalidClaudeCodeRefreshTokens.has(refreshTokenCacheKey)) {
301
329
  return null;
302
330
  }
303
- // Try to refresh. Claude Code's persisted access token is often stale even
304
- // when the user is actively using Claude Code, because Claude Code keeps the
305
- // live token in memory and only persists the original OAuth grant.
306
331
  try {
307
332
  const refreshed = await refreshAnthropicToken(blob.refreshToken);
308
333
  logger.info(`[auth] Refreshed Claude Code OAuth token from ${blob.source}`);
@@ -332,10 +357,9 @@ async function importClaudeCodeOAuthToken() {
332
357
  * Codex / ChatGPT subscription tokens *are* applied to the environment
333
358
  * because OpenAI permits direct API usage with those tokens.
334
359
  *
335
- * When a `config` is provided and the active subscription provider has
336
- * credentials, `model.primary` is auto-set so the user doesn't need to
337
- * configure it manually — but only for providers whose tokens are applied
338
- * to the runtime (currently Codex only).
360
+ * For multi-account installs the FIRST configured Codex account is the one
361
+ * applied to `process.env.OPENAI_API_KEY`. Real account selection lands in
362
+ * WS2 (AccountPool).
339
363
  */
340
364
  export async function applySubscriptionCredentials(config) {
341
365
  const subscriptionCredentialsDisabled = process.env.ELIZA_DISABLE_SUBSCRIPTION_CREDENTIALS?.trim().toLowerCase();
@@ -355,19 +379,33 @@ export async function applySubscriptionCredentials(config) {
355
379
  // (which ARE Claude Code). If the user has only a subscription and no
356
380
  // API key, the runtime simply won't have an Anthropic provider — they
357
381
  // need an API key or Eliza Cloud for the main agent.
358
- let anthropicToken = await getAccessToken("anthropic-subscription");
359
- if (!anthropicToken) {
360
- anthropicToken = await importClaudeCodeOAuthToken();
361
- }
362
- if (anthropicToken) {
363
- logger.info("[auth] Anthropic subscription detected — available for coding agents (Claude Code CLI). " +
382
+ const anthropicAccounts = listProviderAccounts("anthropic-subscription");
383
+ if (anthropicAccounts.length > 0) {
384
+ const labels = anthropicAccounts
385
+ .map((a) => `"${a.label}" (${a.id})`)
386
+ .join(", ");
387
+ logger.info(`[auth] Anthropic subscription accounts configured: ${labels} — available for coding agents (Claude Code CLI). ` +
364
388
  "Not applied to runtime env. Add an API key or connect Eliza Cloud for the main agent.");
365
389
  }
390
+ else {
391
+ const claudeImported = await importClaudeCodeOAuthToken();
392
+ if (claudeImported) {
393
+ logger.info("[auth] Anthropic subscription detected via Claude Code CLI — available for coding agents. " +
394
+ "Not applied to runtime env. Add an API key or connect Eliza Cloud for the main agent.");
395
+ }
396
+ }
366
397
  // ── OpenAI Codex subscription → set OPENAI_API_KEY ────────────────────
367
- const codexToken = await getAccessToken("openai-codex");
368
- if (codexToken) {
369
- process.env.OPENAI_API_KEY = codexToken;
370
- logger.info("[auth] Applied OpenAI Codex subscription credentials to environment");
398
+ //
399
+ // For now, the FIRST configured Codex account wins. WS2's AccountPool
400
+ // will replace this with priority/health-aware selection.
401
+ const codexAccounts = listProviderAccounts("openai-codex");
402
+ if (codexAccounts.length > 0) {
403
+ const primary = codexAccounts[0];
404
+ const codexToken = await getAccessToken("openai-codex", primary.id);
405
+ if (codexToken) {
406
+ process.env.OPENAI_API_KEY = codexToken;
407
+ logger.info(`[auth] Applied OpenAI Codex subscription credentials to environment from account "${primary.label}" (${primary.id})`);
408
+ }
371
409
  }
372
410
  // Auto-set model.primary from subscription provider (Codex only —
373
411
  // anthropic subscription tokens don't power the runtime directly).
@@ -1,6 +1,8 @@
1
+ export * from "./account-storage.js";
1
2
  export * from "./anthropic.js";
2
3
  export * from "./claude-code-stealth.js";
3
4
  export * from "./credentials.js";
5
+ export * from "./oauth-flow.js";
4
6
  export * from "./openai-codex.js";
5
7
  export * from "./types.js";
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../agent/src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../agent/src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
@@ -1,5 +1,7 @@
1
+ export * from "./account-storage.js";
1
2
  export * from "./anthropic.js";
2
3
  export * from "./claude-code-stealth.js";
3
4
  export * from "./credentials.js";
5
+ export * from "./oauth-flow.js";
4
6
  export * from "./openai-codex.js";
5
7
  export * from "./types.js";
@@ -0,0 +1,106 @@
1
+ /**
2
+ * OAuth flow orchestration registry.
3
+ *
4
+ * Wraps the provider-specific programmatic OAuth helpers
5
+ * (`startAnthropicOAuthFlowRaw` from
6
+ * `vendor/pi-oauth/anthropic-login.ts` and the loopback-listener flow
7
+ * in `openai-codex.ts`) with:
8
+ *
9
+ * - a 5-minute timeout per flow,
10
+ * - automatic `saveAccount(...)` after token exchange,
11
+ * - an in-memory registry keyed by `sessionId` so the HTTP API can
12
+ * stream progress over SSE and the CLI can `await` completion,
13
+ * - garbage collection 10 minutes after a flow reaches a terminal
14
+ * state.
15
+ *
16
+ * Both the CLI and the new accounts-routes HTTP endpoints drive flows
17
+ * through this module — there is no direct caller of the vendor-level
18
+ * OAuth helpers anymore.
19
+ */
20
+ import { type AccountCredentialRecord } from "./account-storage.js";
21
+ import type { SubscriptionProvider } from "./types.js";
22
+ /** Server-tracked status of an in-flight OAuth flow. */
23
+ export type FlowStatus = "pending" | "success" | "error" | "cancelled" | "timeout";
24
+ export interface FlowState {
25
+ sessionId: string;
26
+ providerId: SubscriptionProvider;
27
+ status: FlowStatus;
28
+ /** Set on `pending` (so the UI can re-open the browser) and on `success`. */
29
+ authUrl?: string;
30
+ /** Anthropic only — the user must paste `code#state`; Codex uses the loopback callback. */
31
+ needsCodeSubmission: boolean;
32
+ account?: AccountCredentialRecord;
33
+ error?: string;
34
+ startedAt: number;
35
+ endedAt?: number;
36
+ }
37
+ export interface OAuthFlowHandle {
38
+ sessionId: string;
39
+ authUrl: string;
40
+ /** Codex flows resolve via the loopback listener; Anthropic requires `submitCode`. */
41
+ needsCodeSubmission: boolean;
42
+ /** Resolves with the saved AccountCredentialRecord; rejects on cancel/timeout/error. */
43
+ completion: Promise<{
44
+ account: AccountCredentialRecord;
45
+ }>;
46
+ /** Anthropic only — submit `code#state` from the redirect page. No-op for Codex. */
47
+ submitCode: (code: string) => void;
48
+ cancel: (reason?: string) => void;
49
+ }
50
+ interface StartOptions {
51
+ label: string;
52
+ accountId?: string;
53
+ /**
54
+ * Called after the account is saved on disk. Used by the HTTP
55
+ * route layer to also write a `LinkedAccountConfig` row into
56
+ * `milady.json`. Failures here propagate as flow `error`.
57
+ */
58
+ onAccountSaved?: (account: AccountCredentialRecord) => void | Promise<void>;
59
+ }
60
+ /**
61
+ * Start a programmatic Anthropic OAuth flow. Anthropic's redirect URI
62
+ * is hardcoded to `console.anthropic.com/oauth/code/callback` — the
63
+ * UI must surface the auth URL, prompt the user to copy the
64
+ * `code#state` blob, and call `submitCode()` with it. Once the code is
65
+ * submitted, the token exchange runs and the account is persisted.
66
+ */
67
+ export declare function startAnthropicOAuthFlow(opts: StartOptions): Promise<OAuthFlowHandle>;
68
+ /**
69
+ * Start a programmatic Codex OAuth flow. Codex has a loopback listener
70
+ * on :1455, so the user just signs in in the browser and the listener
71
+ * picks up the redirect — `submitCode()` is a no-op. The accountId
72
+ * baked into the JWT is preserved on `LinkedAccountConfig.organizationId`
73
+ * (used by the Codex usage probe via the `ChatGPT-Account-Id` header).
74
+ */
75
+ export declare function startCodexOAuthFlow(opts: StartOptions): Promise<OAuthFlowHandle>;
76
+ export declare function getFlowState(sessionId: string): FlowState | null;
77
+ export declare function getFlowHandle(sessionId: string): OAuthFlowHandle | null;
78
+ /**
79
+ * Subscribe to status updates for a flow. Replays the current state
80
+ * synchronously so SSE consumers always receive an immediate frame.
81
+ * Returns an unsubscribe function.
82
+ */
83
+ export declare function subscribeFlow(sessionId: string, listener: (state: FlowState) => void): () => void;
84
+ export declare function cancelFlow(sessionId: string, reason?: string): boolean;
85
+ export declare function submitFlowCode(sessionId: string, code: string): boolean;
86
+ /**
87
+ * Remove every flow from the registry. Tests use this to reset
88
+ * between cases without resetting the whole module.
89
+ */
90
+ export declare function _resetFlowRegistry(): void;
91
+ /**
92
+ * Test-only helper to seed a synthetic flow without going through the
93
+ * vendor layer. Used by `accounts-routes.test.ts` to assert the SSE
94
+ * surface streams `success` / `error` payloads correctly.
95
+ */
96
+ export declare function _registerSyntheticFlow(args: {
97
+ sessionId?: string;
98
+ providerId: SubscriptionProvider;
99
+ authUrl: string;
100
+ needsCodeSubmission?: boolean;
101
+ }): {
102
+ sessionId: string;
103
+ complete: (state: FlowState) => void;
104
+ };
105
+ export {};
106
+ //# sourceMappingURL=oauth-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-flow.d.ts","sourceRoot":"","sources":["../../../../../../agent/src/auth/oauth-flow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EACL,KAAK,uBAAuB,EAE7B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAMvD,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,SAAS,GACT,OAAO,GACP,WAAW,GACX,SAAS,CAAC;AAEd,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,oBAAoB,CAAC;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2FAA2F;IAC3F,mBAAmB,EAAE,OAAO,CAAC;IAC7B,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,mBAAmB,EAAE,OAAO,CAAC;IAC7B,wFAAwF;IACxF,UAAU,EAAE,OAAO,CAAC;QAAE,OAAO,EAAE,uBAAuB,CAAA;KAAE,CAAC,CAAC;IAC1D,oFAAoF;IACpF,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAuED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7E;AAID;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,eAAe,CAAC,CAqB1B;AAID;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,eAAe,CAAC,CAmB1B;AA2ID,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGhE;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAGvE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GACnC,MAAM,IAAI,CASZ;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAMtE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOvE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,oBAAoB,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAA;CAAE,CA+B9D"}