@pasko70/pibo 1.16.5 → 2.0.0

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 (146) hide show
  1. package/dist/agent-runtime/auth.js +28 -0
  2. package/dist/agent-runtime/capabilities.js +221 -0
  3. package/dist/agent-runtime/context-build.js +313 -0
  4. package/dist/agent-runtime/contract.js +45 -0
  5. package/dist/agent-runtime/errors.js +57 -0
  6. package/dist/agent-runtime/events.js +1 -0
  7. package/dist/agent-runtime/history.js +1 -0
  8. package/dist/agent-runtime/profile-validation.js +109 -0
  9. package/dist/agent-runtime/registry.js +608 -0
  10. package/dist/agent-runtime/resource-files.js +87 -0
  11. package/dist/agent-runtime/resource-service.js +635 -0
  12. package/dist/agent-runtime/resources.js +1 -0
  13. package/dist/agent-runtime/routed-session.js +931 -0
  14. package/dist/agent-runtime/testing/contract.js +51 -0
  15. package/dist/agent-runtime/testing/fake-adapter.js +192 -0
  16. package/dist/agent-runtime/types.js +3 -0
  17. package/dist/agent-runtimes/codex-native/adapter.js +905 -0
  18. package/dist/agent-runtimes/codex-native/auth.js +476 -0
  19. package/dist/agent-runtimes/codex-native/client.js +734 -0
  20. package/dist/agent-runtimes/codex-native/config.js +152 -0
  21. package/dist/agent-runtimes/codex-native/history.js +283 -0
  22. package/dist/agent-runtimes/codex-native/mcp-stdio-launcher.js +89 -0
  23. package/dist/agent-runtimes/codex-native/models.js +581 -0
  24. package/dist/agent-runtimes/codex-native/process.js +459 -0
  25. package/dist/agent-runtimes/codex-native/protocol-types.js +1 -0
  26. package/dist/agent-runtimes/codex-native/protocol-version.js +7 -0
  27. package/dist/agent-runtimes/codex-native/redaction.js +31 -0
  28. package/dist/agent-runtimes/codex-native/requests.js +378 -0
  29. package/dist/agent-runtimes/codex-native/resource-delivery.js +445 -0
  30. package/dist/agent-runtimes/codex-native/thread.js +388 -0
  31. package/dist/agent-runtimes/codex-native/turn.js +849 -0
  32. package/dist/agent-runtimes/pi/adapter.js +653 -0
  33. package/dist/agent-runtimes/pi/auth.js +455 -0
  34. package/dist/agent-runtimes/pi/history.js +640 -0
  35. package/dist/agent-runtimes/pi/model-catalog.js +87 -0
  36. package/dist/agent-runtimes/pi/routed-session.js +1464 -0
  37. package/dist/agent-runtimes/pi/runtime.js +534 -0
  38. package/dist/agent-runtimes/pi/tool-compiler.js +69 -0
  39. package/dist/apps/chat/agent-profiles.js +40 -23
  40. package/dist/apps/chat/agent-store.js +111 -64
  41. package/dist/apps/chat/chat-api-routes.js +1 -1
  42. package/dist/apps/chat/chat-request-normalizers.js +47 -0
  43. package/dist/apps/chat/data/chat-data-mappers.js +25 -9
  44. package/dist/apps/chat/data/history-query-service.js +108 -0
  45. package/dist/apps/chat/data/session-query-service.js +24 -3
  46. package/dist/apps/chat/data/timeline-query-service.js +3 -3
  47. package/dist/apps/chat/model-catalog.js +1 -55
  48. package/dist/apps/chat/provider-auth-actions.js +171 -23
  49. package/dist/apps/chat/stream.js +12 -0
  50. package/dist/apps/chat/trace-v2.js +1 -0
  51. package/dist/apps/chat/trace.js +126 -415
  52. package/dist/apps/chat/web-app.js +582 -157
  53. package/dist/apps/chat-ui/assets/{dist-QXwINWYY.js → dist-9v2ZXT6V.js} +1 -1
  54. package/dist/apps/chat-ui/assets/{dist-CvZNkkOi.js → dist-B1LVAytl.js} +1 -1
  55. package/dist/apps/chat-ui/assets/{dist-_SP-MjLq.js → dist-C6LPDFXU.js} +1 -1
  56. package/dist/apps/chat-ui/assets/{dist-Bad4Yz4P.js → dist-DVtXxfHc.js} +1 -1
  57. package/dist/apps/chat-ui/assets/{dist-xy-zomwl.js → dist-DW4Qrfbd.js} +1 -1
  58. package/dist/apps/chat-ui/assets/{dist-DI4i80O2.js → dist-DmbsHdMC.js} +1 -1
  59. package/dist/apps/chat-ui/assets/{dist-D17yAUqp.js → dist-DyvwuSFO.js} +1 -1
  60. package/dist/apps/chat-ui/assets/{dist-Bc3Vbgjh.js → dist-I26VB0QO.js} +1 -1
  61. package/dist/apps/chat-ui/assets/{dist-BzqH7w58.js → dist-RtHbJRPV.js} +1 -1
  62. package/dist/apps/chat-ui/assets/{dist-CMDJ-WZm.js → dist-sZgJkWZb.js} +1 -1
  63. package/dist/apps/chat-ui/assets/index-BPbNvcZe.css +1 -0
  64. package/dist/apps/chat-ui/assets/index-CIOf2uL2.js +237 -0
  65. package/dist/apps/chat-ui/index.html +2 -2
  66. package/dist/apps/chat-vscode-web/assets/index-1ivRyjFs.js +41 -0
  67. package/dist/apps/chat-vscode-web/index.html +1 -1
  68. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  69. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.0.0.vsix +0 -0
  70. package/dist/auth/login-actions.js +5 -251
  71. package/dist/cli-session/localSessionSource.js +8 -1
  72. package/dist/core/context-build.js +124 -20
  73. package/dist/core/default-profile.js +8 -0
  74. package/dist/core/profiles.js +30 -3
  75. package/dist/core/routed-session.js +5 -1446
  76. package/dist/core/runtime.js +5 -561
  77. package/dist/core/session-router.js +637 -83
  78. package/dist/data/cli.js +20 -3
  79. package/dist/data/schema.js +104 -1
  80. package/dist/data/session-store.js +24 -1
  81. package/dist/debug/events.js +25 -10
  82. package/dist/debug/failures.js +23 -3
  83. package/dist/debug/index.js +24 -6
  84. package/dist/debug/messages.js +87 -7
  85. package/dist/debug/persisted-payloads.js +31 -0
  86. package/dist/debug/pty.js +7 -6
  87. package/dist/debug/runtime-binding.js +29 -0
  88. package/dist/debug/session.js +137 -0
  89. package/dist/debug/summary.js +12 -0
  90. package/dist/debug/telemetry.js +28 -0
  91. package/dist/debug/tools.js +12 -2
  92. package/dist/debug/trace.js +154 -76
  93. package/dist/gateway/server.js +87 -2
  94. package/dist/gateway/tool.js +5 -5
  95. package/dist/index.js +21 -2
  96. package/dist/loops/tools.js +13 -12
  97. package/dist/mcp/agent-context.js +13 -4
  98. package/dist/mcp/client.js +10 -6
  99. package/dist/mcp/config.js +5 -2
  100. package/dist/mcp/runtime-session.js +166 -0
  101. package/dist/plugins/builtin.js +191 -53
  102. package/dist/plugins/codex-native.js +29 -0
  103. package/dist/plugins/registry.js +66 -2
  104. package/dist/runs/tools.js +26 -25
  105. package/dist/session-ui/terminalRows.js +2 -2
  106. package/dist/sessions/pibo-data-store.js +125 -16
  107. package/dist/sessions/runtime-binding.js +107 -0
  108. package/dist/sessions/sqlite-store.js +239 -18
  109. package/dist/sessions/store.js +86 -7
  110. package/dist/shared/trace-engine.js +18 -9
  111. package/dist/shared/trace-event-projection.js +29 -11
  112. package/dist/shared/{trace-transcript.js → trace-history.js} +193 -195
  113. package/dist/shared/trace-order.js +9 -4
  114. package/dist/shared/trace-page-merge.js +1 -1
  115. package/dist/signals/projector.js +20 -2
  116. package/dist/signals/registry.js +11 -0
  117. package/dist/subagents/tool.js +8 -6
  118. package/dist/tools/codex-browser.js +20 -19
  119. package/dist/tools/codex-compat.js +10 -9
  120. package/dist/tools/codex-image-generation.js +8 -6
  121. package/dist/tools/contract.js +45 -0
  122. package/dist/tools/credential-registry.js +182 -0
  123. package/dist/tools/mcp-bridge.js +511 -0
  124. package/dist/tools/payload-writer.js +17 -0
  125. package/dist/tools/runtime/tool.js +10 -9
  126. package/dist/tools/schema.js +9 -0
  127. package/dist/tools/session-service.js +193 -0
  128. package/dist/tools/session-tool-set.js +85 -0
  129. package/dist/web-annotations/tools.js +17 -16
  130. package/docs/README.md +11 -0
  131. package/package.json +3 -2
  132. package/skills/builtin/pibo-agent-runtime-adapter/SKILL.md +183 -0
  133. package/skills/builtin/pibo-agent-runtime-adapter/evals/README.md +10 -0
  134. package/skills/builtin/pibo-agent-runtime-adapter/evals/evals.json +45 -0
  135. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/orion-full-harness.md +60 -0
  136. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/relay-partial-harness.md +52 -0
  137. package/skills/builtin/pibo-agent-runtime-adapter/references/capabilities-and-designer.md +194 -0
  138. package/skills/builtin/pibo-agent-runtime-adapter/references/history-debug-and-security.md +213 -0
  139. package/skills/builtin/pibo-agent-runtime-adapter/references/interfaces-and-registration.md +214 -0
  140. package/skills/builtin/pibo-agent-runtime-adapter/references/lifecycle-bindings-and-events.md +193 -0
  141. package/skills/builtin/pibo-agent-runtime-adapter/references/portable-delivery-and-native-behavior.md +226 -0
  142. package/skills/builtin/pibo-agent-runtime-adapter/references/testing-migration-and-validation.md +256 -0
  143. package/dist/apps/chat-ui/assets/index-C45B0rw7.js +0 -237
  144. package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +0 -1
  145. package/dist/apps/chat-vscode-web/assets/index-D-tSsXLG.js +0 -41
  146. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.16.5.vsix +0 -0
@@ -0,0 +1,455 @@
1
+ import { AuthStorage } from "@earendil-works/pi-coding-agent";
2
+ import { randomBytes, randomUUID } from "node:crypto";
3
+ import { AgentRuntimeAuthError } from "../../agent-runtime/errors.js";
4
+ const OPENAI_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
5
+ const OPENAI_AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
6
+ const OPENAI_ISSUER_URL = "https://auth.openai.com";
7
+ const OPENAI_DEVICE_USER_CODE_URL = `${OPENAI_ISSUER_URL}/api/accounts/deviceauth/usercode`;
8
+ const OPENAI_DEVICE_TOKEN_URL = `${OPENAI_ISSUER_URL}/api/accounts/deviceauth/token`;
9
+ const OPENAI_DEVICE_VERIFICATION_URL = `${OPENAI_ISSUER_URL}/codex/device`;
10
+ const OPENAI_TOKEN_URL = "https://auth.openai.com/oauth/token";
11
+ const OPENAI_REDIRECT_URI = "http://localhost:1455/auth/callback";
12
+ const OPENAI_DEVICE_REDIRECT_URI = `${OPENAI_ISSUER_URL}/deviceauth/callback`;
13
+ const OPENAI_SCOPE = "openid profile email offline_access";
14
+ const OPENAI_JWT_CLAIM_PATH = "https://api.openai.com/auth";
15
+ const OPENAI_DEVICE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000;
16
+ function base64urlEncode(bytes) {
17
+ let binary = "";
18
+ for (const byte of bytes) {
19
+ binary += String.fromCharCode(byte);
20
+ }
21
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
22
+ }
23
+ async function generatePKCE() {
24
+ const verifierBytes = new Uint8Array(32);
25
+ crypto.getRandomValues(verifierBytes);
26
+ const verifier = base64urlEncode(verifierBytes);
27
+ const encoder = new TextEncoder();
28
+ const data = encoder.encode(verifier);
29
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
30
+ const challenge = base64urlEncode(new Uint8Array(hashBuffer));
31
+ return { verifier, challenge };
32
+ }
33
+ const pendingLogins = new Map();
34
+ function createAuthStorage() {
35
+ return AuthStorage.create();
36
+ }
37
+ function decodeJwt(token) {
38
+ try {
39
+ const parts = token.split(".");
40
+ if (parts.length !== 3)
41
+ return null;
42
+ const payload = parts[1] ?? "";
43
+ const decoded = atob(payload);
44
+ return JSON.parse(decoded);
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ }
50
+ function getOpenAiAccountId(accessToken) {
51
+ const payload = decodeJwt(accessToken);
52
+ const auth = payload?.[OPENAI_JWT_CLAIM_PATH];
53
+ const accountId = auth?.chatgpt_account_id;
54
+ return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
55
+ }
56
+ function requirePendingLogin(state, provider) {
57
+ const pending = pendingLogins.get(state);
58
+ if (!pending) {
59
+ throw new Error("Invalid or expired login state. Start a new login flow with /login-start.");
60
+ }
61
+ if (pending.provider !== provider) {
62
+ throw new Error(`Login state mismatch: expected provider "${pending.provider}", got "${provider}".`);
63
+ }
64
+ // Expire after 10 minutes
65
+ if (Date.now() - pending.createdAt > 10 * 60 * 1000) {
66
+ pendingLogins.delete(state);
67
+ throw new Error("Login state expired. Start a new login flow with /login-start.");
68
+ }
69
+ return pending;
70
+ }
71
+ async function readResponseText(response) {
72
+ return await response.text().catch(() => "");
73
+ }
74
+ function parseDeviceIntervalMs(value) {
75
+ const parsed = typeof value === "number" ? value : Number.parseInt(value ?? "", 10);
76
+ return Number.isFinite(parsed) && parsed > 0 ? parsed * 1000 : 5000;
77
+ }
78
+ async function startOpenAiDeviceLogin(provider) {
79
+ const response = await fetch(OPENAI_DEVICE_USER_CODE_URL, {
80
+ method: "POST",
81
+ headers: { "Content-Type": "application/json" },
82
+ body: JSON.stringify({ client_id: OPENAI_CLIENT_ID }),
83
+ });
84
+ if (!response.ok) {
85
+ const text = await readResponseText(response);
86
+ const suffix = text ? ` ${text}` : "";
87
+ if (response.status === 404) {
88
+ throw new Error(`OpenAI device code login is not enabled for this account or Codex server.${suffix}`);
89
+ }
90
+ throw new Error(`OpenAI device code request failed: ${response.status}${suffix}`);
91
+ }
92
+ const json = (await response.json());
93
+ const deviceAuthId = json.device_auth_id;
94
+ const userCode = json.user_code ?? json.usercode;
95
+ if (!deviceAuthId || !userCode) {
96
+ throw new Error("Invalid device code response from OpenAI");
97
+ }
98
+ const state = randomBytes(16).toString("hex");
99
+ pendingLogins.set(state, {
100
+ type: "device",
101
+ deviceAuthId,
102
+ userCode,
103
+ intervalMs: parseDeviceIntervalMs(json.interval),
104
+ state,
105
+ provider,
106
+ createdAt: Date.now(),
107
+ });
108
+ return {
109
+ type: "device_code",
110
+ url: OPENAI_DEVICE_VERIFICATION_URL,
111
+ verificationUrl: OPENAI_DEVICE_VERIFICATION_URL,
112
+ userCode,
113
+ state,
114
+ provider,
115
+ instructions: "Open the URL in any browser, enter the one-time code, finish sign-in, then return here and complete login.",
116
+ };
117
+ }
118
+ async function pollOpenAiDeviceAuthorization(pending) {
119
+ const startedAt = Date.now();
120
+ while (Date.now() - startedAt < OPENAI_DEVICE_LOGIN_TIMEOUT_MS) {
121
+ const response = await fetch(OPENAI_DEVICE_TOKEN_URL, {
122
+ method: "POST",
123
+ headers: { "Content-Type": "application/json" },
124
+ body: JSON.stringify({
125
+ device_auth_id: pending.deviceAuthId,
126
+ user_code: pending.userCode,
127
+ }),
128
+ });
129
+ if (response.ok) {
130
+ const json = (await response.json());
131
+ if (!json.authorization_code || !json.code_verifier) {
132
+ throw new Error("Invalid device authorization response from OpenAI");
133
+ }
134
+ return json;
135
+ }
136
+ if (response.status !== 403 && response.status !== 404) {
137
+ const text = await readResponseText(response);
138
+ throw new Error(`OpenAI device authorization failed: ${response.status}${text ? ` ${text}` : ""}`);
139
+ }
140
+ await new Promise((resolve) => setTimeout(resolve, pending.intervalMs));
141
+ }
142
+ throw new Error("OpenAI device authorization timed out after 15 minutes.");
143
+ }
144
+ async function exchangeOpenAiAuthorizationCode(code, verifier, redirectUri) {
145
+ const response = await fetch(OPENAI_TOKEN_URL, {
146
+ method: "POST",
147
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
148
+ body: new URLSearchParams({
149
+ grant_type: "authorization_code",
150
+ client_id: OPENAI_CLIENT_ID,
151
+ code,
152
+ code_verifier: verifier,
153
+ redirect_uri: redirectUri,
154
+ }),
155
+ });
156
+ if (!response.ok) {
157
+ const text = await readResponseText(response);
158
+ throw new Error(`Token exchange failed: ${response.status} ${text}`);
159
+ }
160
+ const json = (await response.json());
161
+ if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
162
+ throw new Error("Invalid token response from OpenAI");
163
+ }
164
+ return { accessToken: json.access_token, refreshToken: json.refresh_token, expiresIn: json.expires_in };
165
+ }
166
+ export async function startLogin(provider) {
167
+ if (provider === "openai-codex") {
168
+ return await startOpenAiDeviceLogin(provider);
169
+ }
170
+ if (provider === "openai-codex-browser") {
171
+ const { verifier, challenge } = await generatePKCE();
172
+ const state = randomBytes(16).toString("hex");
173
+ const url = new URL(OPENAI_AUTHORIZE_URL);
174
+ url.searchParams.set("response_type", "code");
175
+ url.searchParams.set("client_id", OPENAI_CLIENT_ID);
176
+ url.searchParams.set("redirect_uri", OPENAI_REDIRECT_URI);
177
+ url.searchParams.set("scope", OPENAI_SCOPE);
178
+ url.searchParams.set("code_challenge", challenge);
179
+ url.searchParams.set("code_challenge_method", "S256");
180
+ url.searchParams.set("state", state);
181
+ url.searchParams.set("id_token_add_organizations", "true");
182
+ url.searchParams.set("codex_cli_simplified_flow", "true");
183
+ url.searchParams.set("originator", "pibo");
184
+ pendingLogins.set(state, { type: "browser", verifier, state, provider, createdAt: Date.now() });
185
+ return {
186
+ type: "browser_oauth",
187
+ url: url.toString(),
188
+ state,
189
+ provider,
190
+ instructions: "Open the URL in your browser, complete login, then paste the authorization code (or full redirect URL) via /login-complete.",
191
+ };
192
+ }
193
+ throw new Error(`OAuth login start not supported for provider "${provider}". Supported: openai-codex.`);
194
+ }
195
+ export async function completeLogin(provider, code, state) {
196
+ if (provider === "openai-codex") {
197
+ const pending = requirePendingLogin(state, provider);
198
+ if (pending.type !== "device") {
199
+ throw new Error("Login flow mismatch. Start a new OpenAI device login.");
200
+ }
201
+ const deviceResult = await pollOpenAiDeviceAuthorization(pending);
202
+ const tokenResult = await exchangeOpenAiAuthorizationCode(deviceResult.authorization_code, deviceResult.code_verifier, OPENAI_DEVICE_REDIRECT_URI);
203
+ const accountId = getOpenAiAccountId(tokenResult.accessToken);
204
+ const authStorage = createAuthStorage();
205
+ authStorage.set(provider, {
206
+ type: "oauth",
207
+ access: tokenResult.accessToken,
208
+ refresh: tokenResult.refreshToken,
209
+ expires: Date.now() + tokenResult.expiresIn * 1000,
210
+ accountId: accountId ?? undefined,
211
+ });
212
+ pendingLogins.delete(state);
213
+ return { success: true, provider, accountId };
214
+ }
215
+ if (provider === "openai-codex-browser") {
216
+ if (!code)
217
+ throw new Error("login.complete requires params.code");
218
+ const pending = requirePendingLogin(state, provider);
219
+ pendingLogins.delete(state);
220
+ if (pending.type !== "browser") {
221
+ throw new Error("Login flow mismatch. Start a new OpenAI browser login.");
222
+ }
223
+ const tokenResult = await exchangeOpenAiAuthorizationCode(code, pending.verifier, OPENAI_REDIRECT_URI);
224
+ const accountId = getOpenAiAccountId(tokenResult.accessToken);
225
+ const authStorage = createAuthStorage();
226
+ authStorage.set("openai-codex", {
227
+ type: "oauth",
228
+ access: tokenResult.accessToken,
229
+ refresh: tokenResult.refreshToken,
230
+ expires: Date.now() + tokenResult.expiresIn * 1000,
231
+ accountId: accountId ?? undefined,
232
+ });
233
+ return { success: true, provider: "openai-codex", accountId };
234
+ }
235
+ throw new Error(`OAuth login complete not supported for provider "${provider}". Supported: openai-codex.`);
236
+ }
237
+ export function setApiKey(provider, apiKey) {
238
+ const authStorage = createAuthStorage();
239
+ authStorage.set(provider, { type: "api_key", key: apiKey });
240
+ return { success: true, provider };
241
+ }
242
+ export function getLoginStatus(provider) {
243
+ const authStorage = createAuthStorage();
244
+ if (provider) {
245
+ return [{ id: provider, provider, ...authStorage.getAuthStatus(provider) }];
246
+ }
247
+ return authStorage.list().map((p) => ({ id: p, provider: p, ...authStorage.getAuthStatus(p) }));
248
+ }
249
+ export function cancelLogin(provider, state) {
250
+ requirePendingLogin(state, provider);
251
+ pendingLogins.delete(state);
252
+ return { success: true, provider };
253
+ }
254
+ export function removeLogin(provider) {
255
+ const authStorage = createAuthStorage();
256
+ authStorage.logout(provider);
257
+ return { success: true, provider };
258
+ }
259
+ const PI_AUTH_FLOW_TTL_MS = 10 * 60 * 1_000;
260
+ const PI_API_KEY_PROVIDERS = new Set([
261
+ "openai",
262
+ "anthropic",
263
+ "kimi-coding",
264
+ "google",
265
+ "groq",
266
+ "ollama",
267
+ "minimax",
268
+ "minimax-cn",
269
+ "glm",
270
+ ]);
271
+ const PI_DEVICE_METHOD = { id: "device_code", completion: "explicit" };
272
+ const PI_BROWSER_METHOD = { id: "browser_oauth", completion: "explicit" };
273
+ const PI_API_KEY_METHOD = { id: "api_key", completion: "immediate" };
274
+ export function piAuthMethodsForProvider(providerId) {
275
+ if (providerId === "openai-codex")
276
+ return [PI_DEVICE_METHOD, PI_BROWSER_METHOD];
277
+ if (PI_API_KEY_PROVIDERS.has(providerId))
278
+ return [PI_API_KEY_METHOD];
279
+ return [];
280
+ }
281
+ export class PiAgentRuntimeAuthController {
282
+ loadCatalog;
283
+ invalidateCatalog;
284
+ now;
285
+ createFlowId;
286
+ pending = new Map();
287
+ constructor(loadCatalog, invalidateCatalog, now = Date.now, createFlowId = randomUUID) {
288
+ this.loadCatalog = loadCatalog;
289
+ this.invalidateCatalog = invalidateCatalog;
290
+ this.now = now;
291
+ this.createFlowId = createFlowId;
292
+ }
293
+ async getStatus() {
294
+ try {
295
+ this.cleanupExpired();
296
+ const catalog = await this.loadCatalog();
297
+ const providers = new Map(catalog.providers.map((provider) => [provider.id, {
298
+ id: provider.id,
299
+ displayName: provider.label,
300
+ }]));
301
+ for (const configured of getLoginStatus()) {
302
+ if (!providers.has(configured.id))
303
+ providers.set(configured.id, { id: configured.id, displayName: configured.id });
304
+ }
305
+ return [...providers.values()].map((provider) => {
306
+ const configured = getLoginStatus(provider.id)[0]?.configured ?? false;
307
+ const pending = [...this.pending.values()].find((entry) => entry.providerId === provider.id);
308
+ return {
309
+ id: provider.id,
310
+ displayName: provider.displayName,
311
+ state: pending ? "pending" : configured ? "connected" : "disconnected",
312
+ configured,
313
+ methods: [...piAuthMethodsForProvider(provider.id)],
314
+ ...(pending ? { pending: { ...pending.flow } } : {}),
315
+ };
316
+ });
317
+ }
318
+ catch (error) {
319
+ if (error instanceof AgentRuntimeAuthError)
320
+ throw error;
321
+ throw new AgentRuntimeAuthError("pi_auth_failed", "Pi provider authentication status is unavailable.", true);
322
+ }
323
+ }
324
+ async start(input) {
325
+ this.cleanupExpired();
326
+ const supported = piAuthMethodsForProvider(input.providerId).some((method) => method.id === input.method);
327
+ if (!supported)
328
+ throw new Error(`Pi does not support ${input.method} authentication for provider "${input.providerId}".`);
329
+ if (input.method === "api_key") {
330
+ try {
331
+ setApiKey(input.providerId, input.apiKey);
332
+ }
333
+ catch {
334
+ throw new AgentRuntimeAuthError("pi_auth_failed", "Pi API-key authentication failed safely.", true);
335
+ }
336
+ this.invalidateCatalog();
337
+ return { providerId: input.providerId, state: "connected", configured: true, details: { accountType: "api_key" } };
338
+ }
339
+ const nativeProviderId = input.method === "browser_oauth" ? "openai-codex-browser" : input.providerId;
340
+ let started;
341
+ try {
342
+ started = await startLogin(nativeProviderId);
343
+ }
344
+ catch {
345
+ throw new AgentRuntimeAuthError("pi_auth_failed", "Pi provider login could not be started safely.", true);
346
+ }
347
+ const flowId = this.createFlowId();
348
+ const startedAt = this.now();
349
+ const flow = {
350
+ flowId,
351
+ method: input.method,
352
+ completion: "explicit",
353
+ startedAt: new Date(startedAt).toISOString(),
354
+ expiresAt: new Date(startedAt + PI_AUTH_FLOW_TTL_MS).toISOString(),
355
+ verificationUrl: started.url,
356
+ ...(started.type === "device_code" ? { userCode: started.userCode } : {}),
357
+ instructions: started.instructions,
358
+ };
359
+ this.pending.set(flowId, {
360
+ providerId: input.providerId,
361
+ nativeProviderId,
362
+ nativeState: started.state,
363
+ flow,
364
+ });
365
+ return { providerId: input.providerId, state: "pending", configured: false, flow: { ...flow } };
366
+ }
367
+ async complete(input) {
368
+ this.cleanupExpired();
369
+ const pending = this.requirePending(input.providerId, input.flowId);
370
+ try {
371
+ await completeLogin(pending.nativeProviderId, input.code, pending.nativeState);
372
+ this.pending.delete(input.flowId);
373
+ this.invalidateCatalog();
374
+ return { providerId: input.providerId, state: "connected", configured: true, details: { accountType: "oauth" } };
375
+ }
376
+ catch {
377
+ if (pending.flow.method === "browser_oauth")
378
+ this.pending.delete(input.flowId);
379
+ throw new AgentRuntimeAuthError("pi_auth_failed", "Pi provider login could not be completed safely.", true);
380
+ }
381
+ }
382
+ async cancel(input) {
383
+ try {
384
+ const pending = this.requirePending(input.providerId, input.flowId);
385
+ cancelLogin(pending.nativeProviderId, pending.nativeState);
386
+ this.pending.delete(input.flowId);
387
+ const configured = getLoginStatus(input.providerId)[0]?.configured ?? false;
388
+ return {
389
+ providerId: input.providerId,
390
+ state: configured ? "connected" : "disconnected",
391
+ configured,
392
+ message: "Login was canceled.",
393
+ };
394
+ }
395
+ catch (error) {
396
+ if (error instanceof AgentRuntimeAuthError)
397
+ throw error;
398
+ throw new AgentRuntimeAuthError("pi_auth_failed", "Pi provider login could not be canceled safely.", true);
399
+ }
400
+ }
401
+ async logout(input) {
402
+ for (const [flowId, pending] of this.pending) {
403
+ if (pending.providerId !== input.providerId)
404
+ continue;
405
+ try {
406
+ cancelLogin(pending.nativeProviderId, pending.nativeState);
407
+ }
408
+ catch {
409
+ // The legacy pending flow may already have expired.
410
+ }
411
+ this.pending.delete(flowId);
412
+ }
413
+ try {
414
+ removeLogin(input.providerId);
415
+ }
416
+ catch {
417
+ throw new AgentRuntimeAuthError("pi_auth_failed", "Pi provider logout failed safely.", true);
418
+ }
419
+ this.invalidateCatalog();
420
+ return { providerId: input.providerId, state: "disconnected", configured: false };
421
+ }
422
+ async dispose() {
423
+ for (const pending of this.pending.values()) {
424
+ try {
425
+ cancelLogin(pending.nativeProviderId, pending.nativeState);
426
+ }
427
+ catch {
428
+ // Best-effort cleanup of legacy in-memory state.
429
+ }
430
+ }
431
+ this.pending.clear();
432
+ }
433
+ requirePending(providerId, flowId) {
434
+ const pending = this.pending.get(flowId);
435
+ if (!pending)
436
+ throw new Error("Invalid or expired login flow. Start a new login.");
437
+ if (pending.providerId !== providerId)
438
+ throw new Error("Login flow does not match the selected provider.");
439
+ return pending;
440
+ }
441
+ cleanupExpired() {
442
+ const now = this.now();
443
+ for (const [flowId, pending] of this.pending) {
444
+ if (Date.parse(pending.flow.expiresAt ?? pending.flow.startedAt) > now)
445
+ continue;
446
+ try {
447
+ cancelLogin(pending.nativeProviderId, pending.nativeState);
448
+ }
449
+ catch {
450
+ // Expiry is terminal even when the legacy state already disappeared.
451
+ }
452
+ this.pending.delete(flowId);
453
+ }
454
+ }
455
+ }