@elizaos/app-core 2.0.0-alpha.413 → 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/package.json +5 -5
- package/packages/agent/src/api/model-provider-helpers.js +10 -10
- package/packages/agent/src/api/provider-switch-config.js +2 -2
- 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/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.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/conversations/conversation-utils.js +4 -4
- package/packages/app-core/src/components/settings/ProviderSwitcher.js +1 -1
- package/packages/app-core/src/services/account-pool.d.ts +91 -0
- package/packages/app-core/src/services/account-pool.d.ts.map +1 -0
- package/packages/app-core/src/services/account-pool.js +376 -0
- package/packages/app-core/src/services/account-usage.d.ts +73 -0
- package/packages/app-core/src/services/account-usage.d.ts.map +1 -0
- package/packages/app-core/src/services/account-usage.js +179 -0
- package/packages/app-core/src/state/useOnboardingState.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/app-core",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.414",
|
|
4
4
|
"description": "Shared application core for elizaOS white-label agent apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -464,7 +464,7 @@
|
|
|
464
464
|
"@capacitor/preferences": "^8.0.1",
|
|
465
465
|
"@capacitor/push-notifications": "^8.0.0",
|
|
466
466
|
"@clack/prompts": "^1.0.0",
|
|
467
|
-
"@elizaos/agent": "^2.0.0-alpha.
|
|
467
|
+
"@elizaos/agent": "^2.0.0-alpha.414",
|
|
468
468
|
"@elizaos/app-companion": "^0.0.0",
|
|
469
469
|
"@elizaos/app-elizamaker": "^0.0.0",
|
|
470
470
|
"@elizaos/app-lifeops": "^0.0.0",
|
|
@@ -473,12 +473,12 @@
|
|
|
473
473
|
"@elizaos/app-task-coordinator": "^0.0.0",
|
|
474
474
|
"@elizaos/app-training": "^0.0.1",
|
|
475
475
|
"@elizaos/app-vincent": "^0.0.0",
|
|
476
|
-
"@elizaos/core": "^2.0.0-alpha.
|
|
476
|
+
"@elizaos/core": "^2.0.0-alpha.414",
|
|
477
477
|
"@elizaos/plugin-browser-bridge": "^0.1.0",
|
|
478
478
|
"@elizaos/plugin-sql": "^2.0.0-alpha.19",
|
|
479
479
|
"@elizaos/plugin-wechat": "^0.1.0",
|
|
480
|
-
"@elizaos/shared": "^2.0.0-alpha.
|
|
481
|
-
"@elizaos/ui": "^2.0.0-alpha.
|
|
480
|
+
"@elizaos/shared": "^2.0.0-alpha.414",
|
|
481
|
+
"@elizaos/ui": "^2.0.0-alpha.414",
|
|
482
482
|
"@node-rs/argon2": "^2.0.2",
|
|
483
483
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
484
484
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
@@ -34,28 +34,28 @@ export function getModelOptions() {
|
|
|
34
34
|
},
|
|
35
35
|
// OpenAI
|
|
36
36
|
{
|
|
37
|
-
id: "openai/gpt-5.
|
|
38
|
-
name: "GPT-5.
|
|
37
|
+
id: "openai/gpt-5.5-pro",
|
|
38
|
+
name: "GPT-5.5 Pro",
|
|
39
39
|
provider: "OpenAI",
|
|
40
|
-
description: "Highest-precision GPT-5.
|
|
40
|
+
description: "Highest-precision GPT-5.5 variant.",
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
|
-
id: "openai/gpt-5.
|
|
44
|
-
name: "GPT-5.
|
|
43
|
+
id: "openai/gpt-5.5",
|
|
44
|
+
name: "GPT-5.5",
|
|
45
45
|
provider: "OpenAI",
|
|
46
46
|
description: "Flagship OpenAI model for coding and reasoning.",
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
|
-
id: "openai/gpt-5.
|
|
50
|
-
name: "GPT-5.
|
|
49
|
+
id: "openai/gpt-5.5-mini",
|
|
50
|
+
name: "GPT-5.5 Mini",
|
|
51
51
|
provider: "OpenAI",
|
|
52
52
|
description: "High-volume OpenAI mini model.",
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
|
-
id: "openai/gpt-5.
|
|
56
|
-
name: "GPT-5.
|
|
55
|
+
id: "openai/gpt-5.5-nano",
|
|
56
|
+
name: "GPT-5.5 Nano",
|
|
57
57
|
provider: "OpenAI",
|
|
58
|
-
description: "Cheapest GPT-5.
|
|
58
|
+
description: "Cheapest GPT-5.5 tier for fast routing and gating.",
|
|
59
59
|
},
|
|
60
60
|
// Google
|
|
61
61
|
{
|
|
@@ -281,9 +281,9 @@ const PROVIDER_DEFAULT_MODELS = {
|
|
|
281
281
|
},
|
|
282
282
|
openai: {
|
|
283
283
|
smallKey: "OPENAI_SMALL_MODEL",
|
|
284
|
-
smallVal: "gpt-5.
|
|
284
|
+
smallVal: "gpt-5.5-mini",
|
|
285
285
|
largeKey: "OPENAI_LARGE_MODEL",
|
|
286
|
-
largeVal: "gpt-5.
|
|
286
|
+
largeVal: "gpt-5.5",
|
|
287
287
|
},
|
|
288
288
|
google: {
|
|
289
289
|
smallKey: "GOOGLE_SMALL_MODEL",
|
|
@@ -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"}
|
|
@@ -0,0 +1,349 @@
|
|
|
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 crypto from "node:crypto";
|
|
21
|
+
import { logger } from "@elizaos/core";
|
|
22
|
+
import { saveAccount, } from "./account-storage.js";
|
|
23
|
+
import { startCodexLogin } from "./openai-codex.js";
|
|
24
|
+
import { startAnthropicOAuthFlowRaw, } from "./vendor/pi-oauth/anthropic-login.js";
|
|
25
|
+
const FLOW_TIMEOUT_MS = 5 * 60 * 1000;
|
|
26
|
+
const FLOW_GC_MS = 10 * 60 * 1000;
|
|
27
|
+
const flows = new Map();
|
|
28
|
+
function newSessionId() {
|
|
29
|
+
return crypto.randomUUID();
|
|
30
|
+
}
|
|
31
|
+
function emit(entry) {
|
|
32
|
+
for (const listener of entry.listeners) {
|
|
33
|
+
listener(entry.state);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function scheduleGc(sessionId) {
|
|
37
|
+
const entry = flows.get(sessionId);
|
|
38
|
+
if (!entry)
|
|
39
|
+
return;
|
|
40
|
+
if (entry.gcTimer)
|
|
41
|
+
clearTimeout(entry.gcTimer);
|
|
42
|
+
entry.gcTimer = setTimeout(() => {
|
|
43
|
+
flows.delete(sessionId);
|
|
44
|
+
}, FLOW_GC_MS);
|
|
45
|
+
}
|
|
46
|
+
function resolveAccountId(opts) {
|
|
47
|
+
return opts.accountId?.trim() || crypto.randomUUID();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build an `AccountCredentialRecord` and persist it via `saveAccount`.
|
|
51
|
+
* Returns the canonical record (with `createdAt`/`updatedAt` filled).
|
|
52
|
+
*/
|
|
53
|
+
function persistAccount(args) {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const record = {
|
|
56
|
+
id: args.accountId,
|
|
57
|
+
providerId: args.providerId,
|
|
58
|
+
label: args.label,
|
|
59
|
+
source: "oauth",
|
|
60
|
+
credentials: {
|
|
61
|
+
access: args.access,
|
|
62
|
+
refresh: args.refresh,
|
|
63
|
+
expires: args.expires,
|
|
64
|
+
},
|
|
65
|
+
createdAt: now,
|
|
66
|
+
updatedAt: now,
|
|
67
|
+
...(args.organizationId ? { organizationId: args.organizationId } : {}),
|
|
68
|
+
...(args.email ? { email: args.email } : {}),
|
|
69
|
+
};
|
|
70
|
+
saveAccount(record);
|
|
71
|
+
return record;
|
|
72
|
+
}
|
|
73
|
+
// Anthropic.
|
|
74
|
+
/**
|
|
75
|
+
* Start a programmatic Anthropic OAuth flow. Anthropic's redirect URI
|
|
76
|
+
* is hardcoded to `console.anthropic.com/oauth/code/callback` — the
|
|
77
|
+
* UI must surface the auth URL, prompt the user to copy the
|
|
78
|
+
* `code#state` blob, and call `submitCode()` with it. Once the code is
|
|
79
|
+
* submitted, the token exchange runs and the account is persisted.
|
|
80
|
+
*/
|
|
81
|
+
export function startAnthropicOAuthFlow(opts) {
|
|
82
|
+
return startGenericFlow({
|
|
83
|
+
providerId: "anthropic-subscription",
|
|
84
|
+
opts,
|
|
85
|
+
needsCodeSubmission: true,
|
|
86
|
+
begin: async () => {
|
|
87
|
+
const raw = await startAnthropicOAuthFlowRaw();
|
|
88
|
+
const completion = (async () => {
|
|
89
|
+
const creds = await raw.completion;
|
|
90
|
+
return { creds };
|
|
91
|
+
})();
|
|
92
|
+
return {
|
|
93
|
+
authUrl: raw.authUrl,
|
|
94
|
+
completion,
|
|
95
|
+
submitCode: raw.submitCode,
|
|
96
|
+
cancel: raw.cancel,
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Codex (OpenAI ChatGPT subscription).
|
|
102
|
+
/**
|
|
103
|
+
* Start a programmatic Codex OAuth flow. Codex has a loopback listener
|
|
104
|
+
* on :1455, so the user just signs in in the browser and the listener
|
|
105
|
+
* picks up the redirect — `submitCode()` is a no-op. The accountId
|
|
106
|
+
* baked into the JWT is preserved on `LinkedAccountConfig.organizationId`
|
|
107
|
+
* (used by the Codex usage probe via the `ChatGPT-Account-Id` header).
|
|
108
|
+
*/
|
|
109
|
+
export function startCodexOAuthFlow(opts) {
|
|
110
|
+
return startGenericFlow({
|
|
111
|
+
providerId: "openai-codex",
|
|
112
|
+
opts,
|
|
113
|
+
needsCodeSubmission: false,
|
|
114
|
+
begin: async () => {
|
|
115
|
+
const flow = await startCodexLogin();
|
|
116
|
+
const completion = (async () => {
|
|
117
|
+
const creds = await flow.credentials;
|
|
118
|
+
return { creds, codexFlow: flow };
|
|
119
|
+
})();
|
|
120
|
+
return {
|
|
121
|
+
authUrl: flow.authUrl,
|
|
122
|
+
completion,
|
|
123
|
+
submitCode: (code) => flow.submitCode(code),
|
|
124
|
+
cancel: () => flow.close(),
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async function startGenericFlow(args) {
|
|
130
|
+
const { providerId, opts, needsCodeSubmission, begin } = args;
|
|
131
|
+
const sessionId = newSessionId();
|
|
132
|
+
const accountId = resolveAccountId(opts);
|
|
133
|
+
const vendor = await begin();
|
|
134
|
+
const startedAt = Date.now();
|
|
135
|
+
const initialState = {
|
|
136
|
+
sessionId,
|
|
137
|
+
providerId,
|
|
138
|
+
status: "pending",
|
|
139
|
+
authUrl: vendor.authUrl,
|
|
140
|
+
needsCodeSubmission,
|
|
141
|
+
startedAt,
|
|
142
|
+
};
|
|
143
|
+
let resolveCompletion;
|
|
144
|
+
let rejectCompletion;
|
|
145
|
+
const completion = new Promise((resolve, reject) => {
|
|
146
|
+
resolveCompletion = resolve;
|
|
147
|
+
rejectCompletion = reject;
|
|
148
|
+
});
|
|
149
|
+
const entry = {
|
|
150
|
+
state: initialState,
|
|
151
|
+
handle: {}, // filled below
|
|
152
|
+
listeners: new Set(),
|
|
153
|
+
};
|
|
154
|
+
flows.set(sessionId, entry);
|
|
155
|
+
const setTerminal = (next) => {
|
|
156
|
+
if (entry.state.status !== "pending")
|
|
157
|
+
return;
|
|
158
|
+
entry.state = {
|
|
159
|
+
...entry.state,
|
|
160
|
+
...next,
|
|
161
|
+
endedAt: Date.now(),
|
|
162
|
+
};
|
|
163
|
+
emit(entry);
|
|
164
|
+
scheduleGc(sessionId);
|
|
165
|
+
};
|
|
166
|
+
const timer = setTimeout(() => {
|
|
167
|
+
const err = new Error("OAuth flow timed out after 5 minutes");
|
|
168
|
+
try {
|
|
169
|
+
vendor.cancel("timeout");
|
|
170
|
+
}
|
|
171
|
+
catch (cancelErr) {
|
|
172
|
+
logger.debug(`[oauth-flow] cancel during timeout failed: ${String(cancelErr)}`);
|
|
173
|
+
}
|
|
174
|
+
setTerminal({ status: "timeout", error: err.message });
|
|
175
|
+
rejectCompletion(err);
|
|
176
|
+
}, FLOW_TIMEOUT_MS);
|
|
177
|
+
// Drive the vendor completion through to account-save and listener emit.
|
|
178
|
+
void (async () => {
|
|
179
|
+
try {
|
|
180
|
+
const { creds } = await vendor.completion;
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
let organizationId;
|
|
183
|
+
// Codex bakes the account_id into the JWT — pull it back out for
|
|
184
|
+
// the usage probe header.
|
|
185
|
+
if (providerId === "openai-codex") {
|
|
186
|
+
const codexAccountId = extractCodexAccountId(creds.access);
|
|
187
|
+
if (codexAccountId)
|
|
188
|
+
organizationId = codexAccountId;
|
|
189
|
+
}
|
|
190
|
+
const record = persistAccount({
|
|
191
|
+
providerId,
|
|
192
|
+
accountId,
|
|
193
|
+
label: opts.label,
|
|
194
|
+
access: creds.access,
|
|
195
|
+
refresh: creds.refresh,
|
|
196
|
+
expires: creds.expires,
|
|
197
|
+
...(organizationId ? { organizationId } : {}),
|
|
198
|
+
});
|
|
199
|
+
if (opts.onAccountSaved) {
|
|
200
|
+
await opts.onAccountSaved(record);
|
|
201
|
+
}
|
|
202
|
+
setTerminal({ status: "success", account: record });
|
|
203
|
+
resolveCompletion({ account: record });
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
208
|
+
setTerminal({ status: "error", error: message });
|
|
209
|
+
rejectCompletion(err instanceof Error ? err : new Error(message));
|
|
210
|
+
}
|
|
211
|
+
})();
|
|
212
|
+
const handle = {
|
|
213
|
+
sessionId,
|
|
214
|
+
authUrl: vendor.authUrl,
|
|
215
|
+
needsCodeSubmission,
|
|
216
|
+
completion,
|
|
217
|
+
submitCode: (code) => {
|
|
218
|
+
vendor.submitCode(code);
|
|
219
|
+
},
|
|
220
|
+
cancel: (reason = "Cancelled") => {
|
|
221
|
+
if (entry.state.status !== "pending")
|
|
222
|
+
return;
|
|
223
|
+
try {
|
|
224
|
+
vendor.cancel(reason);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
logger.debug(`[oauth-flow] vendor cancel failed: ${String(err)}`);
|
|
228
|
+
}
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
const error = new Error(reason);
|
|
231
|
+
setTerminal({ status: "cancelled", error: reason });
|
|
232
|
+
rejectCompletion(error);
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
entry.handle = handle;
|
|
236
|
+
emit(entry);
|
|
237
|
+
return handle;
|
|
238
|
+
}
|
|
239
|
+
// Registry surface used by the SSE / cancel routes.
|
|
240
|
+
export function getFlowState(sessionId) {
|
|
241
|
+
const entry = flows.get(sessionId);
|
|
242
|
+
return entry ? entry.state : null;
|
|
243
|
+
}
|
|
244
|
+
export function getFlowHandle(sessionId) {
|
|
245
|
+
const entry = flows.get(sessionId);
|
|
246
|
+
return entry ? entry.handle : null;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Subscribe to status updates for a flow. Replays the current state
|
|
250
|
+
* synchronously so SSE consumers always receive an immediate frame.
|
|
251
|
+
* Returns an unsubscribe function.
|
|
252
|
+
*/
|
|
253
|
+
export function subscribeFlow(sessionId, listener) {
|
|
254
|
+
const entry = flows.get(sessionId);
|
|
255
|
+
if (!entry)
|
|
256
|
+
return () => { };
|
|
257
|
+
entry.listeners.add(listener);
|
|
258
|
+
// Replay current state.
|
|
259
|
+
listener(entry.state);
|
|
260
|
+
return () => {
|
|
261
|
+
entry.listeners.delete(listener);
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
export function cancelFlow(sessionId, reason) {
|
|
265
|
+
const entry = flows.get(sessionId);
|
|
266
|
+
if (!entry)
|
|
267
|
+
return false;
|
|
268
|
+
if (entry.state.status !== "pending")
|
|
269
|
+
return false;
|
|
270
|
+
entry.handle.cancel(reason);
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
export function submitFlowCode(sessionId, code) {
|
|
274
|
+
const entry = flows.get(sessionId);
|
|
275
|
+
if (!entry)
|
|
276
|
+
return false;
|
|
277
|
+
if (entry.state.status !== "pending")
|
|
278
|
+
return false;
|
|
279
|
+
if (!entry.state.needsCodeSubmission)
|
|
280
|
+
return false;
|
|
281
|
+
entry.handle.submitCode(code);
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Remove every flow from the registry. Tests use this to reset
|
|
286
|
+
* between cases without resetting the whole module.
|
|
287
|
+
*/
|
|
288
|
+
export function _resetFlowRegistry() {
|
|
289
|
+
for (const entry of flows.values()) {
|
|
290
|
+
if (entry.gcTimer)
|
|
291
|
+
clearTimeout(entry.gcTimer);
|
|
292
|
+
}
|
|
293
|
+
flows.clear();
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Test-only helper to seed a synthetic flow without going through the
|
|
297
|
+
* vendor layer. Used by `accounts-routes.test.ts` to assert the SSE
|
|
298
|
+
* surface streams `success` / `error` payloads correctly.
|
|
299
|
+
*/
|
|
300
|
+
export function _registerSyntheticFlow(args) {
|
|
301
|
+
const sessionId = args.sessionId ?? newSessionId();
|
|
302
|
+
const state = {
|
|
303
|
+
sessionId,
|
|
304
|
+
providerId: args.providerId,
|
|
305
|
+
status: "pending",
|
|
306
|
+
authUrl: args.authUrl,
|
|
307
|
+
needsCodeSubmission: Boolean(args.needsCodeSubmission),
|
|
308
|
+
startedAt: Date.now(),
|
|
309
|
+
};
|
|
310
|
+
const entry = {
|
|
311
|
+
state,
|
|
312
|
+
handle: {
|
|
313
|
+
sessionId,
|
|
314
|
+
authUrl: args.authUrl,
|
|
315
|
+
needsCodeSubmission: Boolean(args.needsCodeSubmission),
|
|
316
|
+
completion: new Promise(() => undefined),
|
|
317
|
+
submitCode: () => undefined,
|
|
318
|
+
cancel: () => undefined,
|
|
319
|
+
},
|
|
320
|
+
listeners: new Set(),
|
|
321
|
+
};
|
|
322
|
+
flows.set(sessionId, entry);
|
|
323
|
+
const complete = (next) => {
|
|
324
|
+
entry.state = { ...next, endedAt: next.endedAt ?? Date.now() };
|
|
325
|
+
emit(entry);
|
|
326
|
+
scheduleGc(sessionId);
|
|
327
|
+
};
|
|
328
|
+
return { sessionId, complete };
|
|
329
|
+
}
|
|
330
|
+
/** OpenAI Codex JWT carries `chatgpt_account_id` under a vendor claim. */
|
|
331
|
+
function extractCodexAccountId(accessToken) {
|
|
332
|
+
try {
|
|
333
|
+
const parts = accessToken.split(".");
|
|
334
|
+
if (parts.length !== 3)
|
|
335
|
+
return null;
|
|
336
|
+
const payload = parts[1];
|
|
337
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64").toString("utf-8"));
|
|
338
|
+
const claim = decoded["https://api.openai.com/auth"];
|
|
339
|
+
if (!claim || typeof claim !== "object")
|
|
340
|
+
return null;
|
|
341
|
+
const accountId = claim.chatgpt_account_id;
|
|
342
|
+
return typeof accountId === "string" && accountId.length > 0
|
|
343
|
+
? accountId
|
|
344
|
+
: null;
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -1,15 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Anthropic OAuth (Claude Pro/Max) — authorization code + PKCE.
|
|
3
3
|
* Inlined OAuth flow (MIT) — vendored to avoid a runtime dependency.
|
|
4
|
+
*
|
|
5
|
+
* Anthropic's OAuth uses a fixed redirect URI on `console.anthropic.com`
|
|
6
|
+
* that displays the auth code on completion — there's no loopback
|
|
7
|
+
* listener. The flow surfaces an `authUrl` plus a `submitCode()` hook
|
|
8
|
+
* the UI / CLI calls once the user has copied the code.
|
|
4
9
|
*/
|
|
5
10
|
export interface AnthropicOAuthCredentials {
|
|
6
11
|
refresh: string;
|
|
7
12
|
access: string;
|
|
8
13
|
expires: number;
|
|
9
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Programmatic Anthropic OAuth flow.
|
|
17
|
+
*
|
|
18
|
+
* `authUrl` is ready immediately. The caller MUST eventually call
|
|
19
|
+
* either `submitCode(code)` (after the user has pasted the
|
|
20
|
+
* `code#state` blob from the redirect page) or `cancel()`. The
|
|
21
|
+
* `completion` promise rejects if the flow is cancelled.
|
|
22
|
+
*/
|
|
23
|
+
export interface AnthropicOAuthFlowHandle {
|
|
24
|
+
authUrl: string;
|
|
25
|
+
/** Pass `code#state` from the redirect page. */
|
|
26
|
+
submitCode: (code: string) => void;
|
|
27
|
+
/** Resolves with credentials once `submitCode` lands and the token exchange succeeds. */
|
|
28
|
+
completion: Promise<AnthropicOAuthCredentials>;
|
|
29
|
+
cancel: (reason?: string) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Start an Anthropic OAuth flow. Returns immediately with the auth
|
|
33
|
+
* URL and a `submitCode` hook. The token exchange happens inside
|
|
34
|
+
* `completion` once the caller submits the code.
|
|
35
|
+
*/
|
|
36
|
+
export declare function startAnthropicOAuthFlowRaw(): Promise<AnthropicOAuthFlowHandle>;
|
|
10
37
|
/**
|
|
11
38
|
* @param onAuthUrl - Receives the browser authorization URL
|
|
12
39
|
* @param onPromptCode - Resolves with pasted code (format: code#state)
|
|
40
|
+
*
|
|
41
|
+
* Thin compatibility wrapper around `startAnthropicOAuthFlowRaw` for
|
|
42
|
+
* the CLI entrypoint and any older callers. New code should use
|
|
43
|
+
* `startAnthropicOAuthFlowRaw` (or the higher-level
|
|
44
|
+
* `startAnthropicOAuthFlow` in `auth/oauth-flow.ts`) directly.
|
|
13
45
|
*/
|
|
14
46
|
export declare function loginAnthropic(onAuthUrl: (url: string) => void, onPromptCode: () => Promise<string>): Promise<AnthropicOAuthCredentials>;
|
|
15
47
|
export declare function refreshAnthropicToken(refreshToken: string): Promise<AnthropicOAuthCredentials>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic-login.d.ts","sourceRoot":"","sources":["../../../../../../../../agent/src/auth/vendor/pi-oauth/anthropic-login.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"anthropic-login.d.ts","sourceRoot":"","sources":["../../../../../../../../agent/src/auth/vendor/pi-oauth/anthropic-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,yFAAyF;IACzF,UAAU,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC/C,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,wBAAwB,CAAC,CA6DpF;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAClC,OAAO,CAAC,yBAAyB,CAAC,CAMpC;AAED,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,yBAAyB,CAAC,CAwBpC"}
|