@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.
- package/apps/app-companion/src/components/companion/CompanionAppView.d.ts.map +1 -1
- package/apps/app-companion/src/components/companion/CompanionAppView.js +5 -5
- package/apps/app-companion/src/components/companion/CompanionView.d.ts.map +1 -1
- package/apps/app-companion/src/components/companion/CompanionView.js +5 -5
- package/package.json +1 -1
- package/packages/agent/src/api/model-provider-helpers.js +10 -10
- package/packages/agent/src/api/provider-switch-config.d.ts +3 -3
- package/packages/agent/src/api/provider-switch-config.d.ts.map +1 -1
- package/packages/agent/src/api/provider-switch-config.js +2 -2
- package/packages/agent/src/auth/account-storage.d.ts +48 -0
- package/packages/agent/src/auth/account-storage.d.ts.map +1 -0
- package/packages/agent/src/auth/account-storage.js +238 -0
- package/packages/agent/src/auth/credentials.d.ts +44 -25
- package/packages/agent/src/auth/credentials.d.ts.map +1 -1
- package/packages/agent/src/auth/credentials.js +175 -137
- package/packages/agent/src/auth/index.d.ts +2 -0
- package/packages/agent/src/auth/index.d.ts.map +1 -1
- package/packages/agent/src/auth/index.js +2 -0
- package/packages/agent/src/auth/oauth-flow.d.ts +106 -0
- package/packages/agent/src/auth/oauth-flow.d.ts.map +1 -0
- package/packages/agent/src/auth/oauth-flow.js +349 -0
- package/packages/agent/src/auth/refresh-mutex.d.ts +19 -0
- package/packages/agent/src/auth/refresh-mutex.d.ts.map +1 -0
- package/packages/agent/src/auth/refresh-mutex.js +33 -0
- package/packages/agent/src/auth/vendor/pi-oauth/anthropic-login.d.ts +32 -0
- package/packages/agent/src/auth/vendor/pi-oauth/anthropic-login.d.ts.map +1 -1
- package/packages/agent/src/auth/vendor/pi-oauth/anthropic-login.js +63 -28
- package/packages/agent/src/config/schema.js +1 -1
- package/packages/agent/src/config/types.eliza.d.ts +8 -3
- package/packages/agent/src/config/types.eliza.d.ts.map +1 -1
- package/packages/agent/src/config/types.messages.d.ts +1 -1
- package/packages/agent/src/config/types.tools.d.ts +1 -1
- package/packages/agent/src/runtime/eliza.js +3 -3
- package/packages/app-core/src/components/apps/launch-history.d.ts.map +1 -1
- package/packages/app-core/src/components/auth/LoginView.d.ts +1 -1
- package/packages/app-core/src/components/auth/LoginView.d.ts.map +1 -1
- package/packages/app-core/src/components/auth/LoginView.js +1 -1
- package/packages/app-core/src/components/conversations/conversation-utils.js +4 -4
- package/packages/app-core/src/components/pages/AppDetailsView.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/AppDetailsView.js +10 -10
- package/packages/app-core/src/components/pages/AppsView.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/AppsView.js +1 -0
- package/packages/app-core/src/components/pages/AutomationsView.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/AutomationsView.js +0 -1
- package/packages/app-core/src/components/settings/ProviderSwitcher.d.ts.map +1 -1
- package/packages/app-core/src/components/settings/ProviderSwitcher.js +1 -1
- package/packages/app-core/src/components/settings/SubscriptionStatus.d.ts +2 -0
- package/packages/app-core/src/components/settings/SubscriptionStatus.d.ts.map +1 -1
- package/packages/app-core/src/onboarding-config.d.ts +2 -2
- package/packages/app-core/src/onboarding-config.d.ts.map +1 -1
- package/packages/app-core/src/services/auth-store.d.ts.map +1 -1
- package/packages/app-core/src/shell/AppWindowRenderer.d.ts.map +1 -1
- package/packages/app-core/src/shell/AppWindowRenderer.js +11 -17
- package/packages/app-core/src/shell/DetachedShellRoot.d.ts.map +1 -1
- package/packages/app-core/src/state/startup-phase-poll.js +1 -1
- package/packages/app-core/src/state/useOnboardingState.js +1 -1
- package/packages/shared/src/config/types.eliza.d.ts +8 -3
- package/packages/shared/src/config/types.eliza.d.ts.map +1 -1
- package/packages/shared/src/contracts/onboarding.d.ts +10 -2
- package/packages/shared/src/contracts/onboarding.d.ts.map +1 -1
- package/packages/shared/src/contracts/onboarding.js +2 -2
- package/packages/shared/src/contracts/service-routing.d.ts +87 -3
- package/packages/shared/src/contracts/service-routing.d.ts.map +1 -1
- 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
|
-
*
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
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:
|
|
35
|
-
updatedAt:
|
|
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
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
89
|
-
if (!
|
|
107
|
+
export async function getAccessToken(provider, accountId = DEFAULT_ACCOUNT_ID) {
|
|
108
|
+
const initial = loadCredentials(provider, accountId);
|
|
109
|
+
if (!initial)
|
|
90
110
|
return null;
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
logger.error(`[auth]
|
|
138
|
+
catch (err) {
|
|
139
|
+
logger.error(`[auth] Failed to refresh ${provider} token for "${accountId}": ${err}`);
|
|
108
140
|
return null;
|
|
109
141
|
}
|
|
110
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
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
|
-
|
|
359
|
-
if (
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
logger.info(
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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"}
|
|
@@ -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"}
|