@mono-agent/agent-runtime 0.20.7 → 0.20.8

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/MIGRATION.md CHANGED
@@ -74,8 +74,8 @@ the configuration schema.
74
74
  `@earendil-works/pi-ai` only for catalog, reasoning, or OAuth behavior should
75
75
  switch to `listPiBuiltinModels`, `getPiBuiltinModel`,
76
76
  `reasoningLevelsForPiModel`, `resolvePiOAuthApiKey`, and `loginPiOAuth` from
77
- `@mono-agent/agent-runtime/ai`. The runtime keeps Pi AI and Pi Agent Core
78
- exact-pinned at `0.83.0`; the façade returns cloned model and credential
77
+ `@mono-agent/agent-runtime/ai`. The runtime exact-pins Pi AI at `0.84.3` and Pi Agent Core at `0.83.0`;
78
+ the façade returns cloned model and credential
79
79
  snapshots rather than exposing upstream provider objects.
80
80
  - **Claude test seam:** downstream tests should pass
81
81
  `RuntimeRunOptions.claudeAgentQuery` instead of mocking
@@ -165,7 +165,7 @@ This baseline carries the whole 0.15.x contract forward and adds:
165
165
  to rediscover by trial and error what its parent could look up.
166
166
  - Provider failover detail reaches whoever is watching the run rather than being
167
167
  flattened at the boundary.
168
- - Pi SDK 0.83.0.
168
+ - Pi AI 0.84.3 with Pi Agent Core 0.83.0.
169
169
 
170
170
  ## 0.15.x
171
171
 
@@ -422,12 +422,18 @@ key-resolution contract is unchanged**: an `apiKeys` map entry wins, else the ho
422
422
  falls back to its own env vars, exactly as returning `undefined` from the old hook
423
423
  did). **No host action needed** — `resolvePiApiKey` behaves as before.
424
424
 
425
- Dependency bump: **`@earendil-works/pi-ai` and `@earendil-works/pi-agent-core` are
426
- now `0.83.0`** (the initial Pi 0.80 migration landed at `0.80.5`, from
427
- `^0.79.1`, and ran at `0.80.6` until the 0.83 upgrade). Compaction is driven
425
+ Dependency bump: **`@earendil-works/pi-ai` is now `0.84.3`; `@earendil-works/pi-agent-core` remains `0.83.0`**
426
+ (the initial Pi 0.80 migration landed at `0.80.5`, from `^0.79.1`, ran at
427
+ `0.80.6` until the 0.83 upgrade, and then at `0.83.0`). Agent Core 0.84.3 is
428
+ held back because its replacement durable harness does not yet implement the
429
+ prompt, subscription, compaction, or abort paths used here. Compaction is driven
428
430
  natively (section 3), and model-native `max` reasoning plus Pi's request-wide
429
431
  pricing tiers are preserved.
430
432
 
433
+ Packed npm consumers consequently retain Agent Core's nested Pi AI 0.83.0
434
+ compatibility copy beside the runtime-owned Pi AI 0.84.3 catalog/provider copy.
435
+ The release guard resolves and verifies both exact paths independently.
436
+
431
437
  The 0.83 upgrade carries two upstream removals, both absorbed inside the runtime
432
438
  so hosts need no action:
433
439
 
@@ -510,15 +516,16 @@ Worklab's runtime fork:
510
516
  2. **Remove direct provider ownership.** Delete Worklab production imports from
511
517
  `@earendil-works/pi-ai`, its separate Pi version constraint, and local copies
512
518
  of provider bridge code. Move tests off Pi's faux-provider helpers too; until
513
- that is complete, isolate the fixture or pin its development-only Pi
514
- dependency to exact `0.83.0` rather than a floating range. Do not restore the
519
+ that is complete, isolate the fixture or pin its development-only dependencies
520
+ to the exact Pi AI `0.84.3` and Pi Agent Core `0.83.0` compatibility pins
521
+ rather than floating ranges. Do not restore the
515
522
  removed `pi-sdk.js` subpath.
516
523
  3. **Use the public Pi surfaces.** Run models through
517
524
  `generatePiNativeResponse` or the runtime registry. Use
518
525
  `listPiBuiltinModels`, `getPiBuiltinModel`,
519
526
  `reasoningLevelsForPiModel`, `resolvePiOAuthApiKey`, and `loginPiOAuth` for
520
527
  catalog and OAuth integration. Those façades keep Pi provider objects and the
521
- exact `0.83.0` compatibility pin inside the runtime. OAuth login adapters
528
+ exact Pi AI `0.84.3` and Pi Agent Core `0.83.0` compatibility pins inside the runtime. OAuth login adapters
522
529
  must supply `onAuth`, `onDeviceCode`, `onPrompt`, and `onSelect`; the façade
523
530
  rejects an incomplete callback contract before starting provider login.
524
531
  4. **Inject Claude tests.** Replace package-level mocks of
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mono-agent/agent-runtime",
3
- "version": "0.20.7",
3
+ "version": "0.20.8",
4
4
  "description": "Agent runtime supporting Claude SDK/CLI, Codex, OpenCode, Pi SDK, and ACP v1 bridges out of the box",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-only",
@@ -139,7 +139,7 @@
139
139
  "@anthropic-ai/claude-agent-sdk": "0.3.206",
140
140
  "@anthropic-ai/sdk": "^0.110.0",
141
141
  "@earendil-works/pi-agent-core": "0.83.0",
142
- "@earendil-works/pi-ai": "0.83.0",
142
+ "@earendil-works/pi-ai": "0.84.3",
143
143
  "@modelcontextprotocol/sdk": "^1.29.0",
144
144
  "@mozilla/readability": "0.6.0",
145
145
  "@opencode-ai/sdk": "^1.15.13",
@@ -82,7 +82,7 @@ export function normalizeBackgroundBashTimeoutMs(value) {
82
82
  /**
83
83
  * Compatibility wrapper retained for direct callers and tests.
84
84
  *
85
- * @param {{command: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
85
+ * @param {{command: string, description?: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
86
86
  * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: import("./shared/process-jobs.js").ProcessJobsController}} [options]
87
87
  */
88
88
  export async function bashToolImpl(params, options = {}) {
@@ -92,12 +92,13 @@ export async function bashToolImpl(params, options = {}) {
92
92
  /**
93
93
  * Structured Bash execution used by the Pi bridge.
94
94
  *
95
- * @param {{command: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
95
+ * @param {{command: string, description?: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
96
96
  * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: import("./shared/process-jobs.js").ProcessJobsController}} [options]
97
97
  */
98
98
  export async function bashToolRun(
99
99
  {
100
100
  command,
101
+ description,
101
102
  timeout,
102
103
  timeout_ms,
103
104
  max_output_chars,
@@ -166,6 +167,7 @@ export async function bashToolRun(
166
167
  tool: "Bash",
167
168
  prepared,
168
169
  summary: `Bash command (${command.length} characters; content redacted)`,
170
+ description,
169
171
  timeoutMs: requestedTimeoutMs,
170
172
  maxOutputChars: max_output_chars === undefined ? undefined : maxChars,
171
173
  startedAt,
@@ -25,7 +25,7 @@ const MAX_EXEC_ARGS = 256;
25
25
  /** @typedef {import("./shared/process-jobs.js").ProcessJobsController} ProcessJobsController */
26
26
 
27
27
  /**
28
- * @param {{executable: string, args?: string[], workdir?: string, timeout_ms?: number, max_output_chars?: number, background?: boolean}} params
28
+ * @param {{executable: string, args?: string[], workdir?: string, description?: string, timeout_ms?: number, max_output_chars?: number, background?: boolean}} params
29
29
  * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: ProcessJobsController}} [options]
30
30
  */
31
31
  export async function execToolImpl(params, options = {}) {
@@ -35,7 +35,7 @@ export async function execToolImpl(params, options = {}) {
35
35
  /**
36
36
  * Execute an argv vector directly, without shell parsing.
37
37
  *
38
- * @param {{executable: string, args?: string[], workdir?: string, timeout_ms?: number, max_output_chars?: number, background?: boolean}} params
38
+ * @param {{executable: string, args?: string[], workdir?: string, description?: string, timeout_ms?: number, max_output_chars?: number, background?: boolean}} params
39
39
  * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: ProcessJobsController}} [options]
40
40
  */
41
41
  export async function execToolRun(
@@ -43,6 +43,7 @@ export async function execToolRun(
43
43
  executable,
44
44
  args = [],
45
45
  workdir,
46
+ description,
46
47
  timeout_ms,
47
48
  max_output_chars,
48
49
  background,
@@ -101,6 +102,7 @@ export async function execToolRun(
101
102
  tool: "Exec",
102
103
  prepared,
103
104
  summary: `Exec command (${args.length} argument${args.length === 1 ? "" : "s"}; values redacted)`,
105
+ description,
104
106
  // Re-derived from the raw param: `timeoutMs` carries the foreground
105
107
  // ceiling, and a background job is bounded by processJobs instead.
106
108
  timeoutMs: timeout_ms === undefined ? undefined : normalizeBackgroundTimeoutMs(timeout_ms),
@@ -515,6 +515,10 @@ export function getPiBuiltinTools(allowedTools, {
515
515
  : ` This host runs a background job for up to ${formatDurationForModel(backgroundLimitMs)}; \`timeout_ms\` may lower that but never raise it, and the start receipt reports \`max_runtime_ms\`, the budget actually granted — check it, because a job is killed at that limit.`
516
516
  }`,
517
517
  };
518
+ const processDescriptionSchema = {
519
+ type: "string",
520
+ description: "Short present-participle phrase describing what the command is doing, shown in tool activity and background-job lifecycle messages (for example, \"Running the full repository test suite\"). Always provide this when background=true. Describe the purpose, not command syntax; never include arguments, paths, credentials, or secrets.",
521
+ };
518
522
  // Per-tool closure config (cwd/event sink/limits/policy) plus the per-instance
519
523
  // ToolContext `ctx` that the tool impls and shared helpers read from.
520
524
  const toolContext = {
@@ -571,7 +575,7 @@ export function getPiBuiltinTools(allowedTools, {
571
575
  Bash: createBuiltinTool("Bash", "Bash", "Execute a shell command for pipelines, redirection, conditionals, or other shell syntax. Prefer Exec for one executable with an argv array. This is macOS: do not assume GNU-only commands or flags.", objectSchema({
572
576
  command: { type: "string" },
573
577
  workdir: { type: "string" },
574
- description: { type: "string" },
578
+ description: processDescriptionSchema,
575
579
  timeout_ms: processTimeoutSchema,
576
580
  timeout: legacyBashTimeoutSchema,
577
581
  max_output_chars: bashLimitSchema,
@@ -581,6 +585,7 @@ export function getPiBuiltinTools(allowedTools, {
581
585
  executable: { type: "string", minLength: 1 },
582
586
  args: { type: "array", items: { type: "string" }, maxItems: 256 },
583
587
  workdir: { type: "string" },
588
+ description: processDescriptionSchema,
584
589
  timeout_ms: processTimeoutSchema,
585
590
  max_output_chars: bashLimitSchema,
586
591
  ...(processJobsController ? { background: backgroundSchema } : {}),
@@ -13,6 +13,7 @@ import { startPreparedProcess } from "./process-runner.js";
13
13
  * tool: "Exec"|"Bash",
14
14
  * prepared: import("../../sandbox-seam.js").PreparedSandboxCommand,
15
15
  * summary: string,
16
+ * description?: string,
16
17
  * timeoutMs?: number,
17
18
  * maxOutputChars?: number,
18
19
  * launch: (options?: {timeoutMs?: number, signal?: AbortSignal, maxBufferBytes?: number, onStdout?: (chunk: Buffer) => void, onStderr?: (chunk: Buffer) => void}) => ReturnType<typeof startPreparedProcess>,
@@ -28,6 +29,7 @@ import { startPreparedProcess } from "./process-runner.js";
28
29
  * tool: "Exec"|"Bash",
29
30
  * prepared: import("../../sandbox-seam.js").PreparedSandboxCommand,
30
31
  * summary: string,
32
+ * description?: string,
31
33
  * timeoutMs?: number,
32
34
  * maxOutputChars?: number,
33
35
  * startedAt: number,
@@ -39,6 +41,7 @@ export async function handOffProcessJob({
39
41
  tool,
40
42
  prepared,
41
43
  summary,
44
+ description,
42
45
  timeoutMs,
43
46
  maxOutputChars,
44
47
  startedAt,
@@ -52,6 +55,7 @@ export async function handOffProcessJob({
52
55
  tool,
53
56
  prepared: ownedPrepared,
54
57
  summary,
58
+ ...(description === undefined ? {} : { description }),
55
59
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
56
60
  ...(maxOutputChars === undefined ? {} : { maxOutputChars }),
57
61
  launch(options = {}) {
@@ -24,7 +24,7 @@ import { builtinProviders } from "@earendil-works/pi-ai/providers/all";
24
24
  /**
25
25
  * @typedef {import("@earendil-works/pi-ai").OAuthAuth} OAuthAuth
26
26
  * @typedef {import("@earendil-works/pi-ai").OAuthCredential} OAuthCredential
27
- * @typedef {import("@earendil-works/pi-ai").AuthInteraction} AuthInteraction
27
+ * @typedef {import("@earendil-works/pi-ai").ProviderAuthInteraction} ProviderAuthInteraction
28
28
  * @typedef {import("@earendil-works/pi-ai").AuthPrompt} AuthPrompt
29
29
  * @typedef {import("@earendil-works/pi-ai").AuthEvent} AuthEvent
30
30
  * @typedef {import("@earendil-works/pi-ai/oauth").OAuthLoginCallbacks} OAuthLoginCallbacks
@@ -112,7 +112,10 @@ export async function resolveOAuthApiKey(providerId, credentials) {
112
112
 
113
113
  if (Date.now() >= credential.expires) {
114
114
  try {
115
- credential = await oauth.refresh(credential);
115
+ // Pi 0.84 requires provider refreshes to receive an AbortSignal. This
116
+ // legacy resolver has no cancellation input, so give the refresh its own
117
+ // non-aborted signal while keeping the public contract unchanged.
118
+ credential = await oauth.refresh(credential, new AbortController().signal);
116
119
  } catch {
117
120
  throw new Error(`Failed to refresh OAuth token for ${providerId}`);
118
121
  }
@@ -126,15 +129,17 @@ export async function resolveOAuthApiKey(providerId, credentials) {
126
129
  }
127
130
 
128
131
  /**
129
- * Bridge the legacy six-callback OAuth surface onto 0.83.0's single
130
- * `prompt`/`notify` pair.
132
+ * Bridge the legacy six-callback OAuth surface onto Pi's single
133
+ * `prompt`/`notify` pair. Pi 0.84 requires every provider login interaction to
134
+ * carry an AbortSignal, so callbacks without one receive a private non-aborted
135
+ * signal.
131
136
  *
132
137
  * `manual_code` must stay wired to `onManualCodeInput`: Anthropic races its
133
138
  * localhost callback against a pasted redirect URL, and that path is the reason
134
139
  * `agent-app`'s `runPiOAuthLogin` exists at all.
135
140
  *
136
141
  * @param {OAuthLoginCallbacks} callbacks
137
- * @returns {AuthInteraction}
142
+ * @returns {ProviderAuthInteraction}
138
143
  */
139
144
  export function toAuthInteraction(callbacks) {
140
145
  if (callbacks === null || typeof callbacks !== "object") {
@@ -142,7 +147,7 @@ export function toAuthInteraction(callbacks) {
142
147
  }
143
148
 
144
149
  return {
145
- ...(callbacks.signal === undefined ? {} : { signal: callbacks.signal }),
150
+ signal: callbacks.signal ?? new AbortController().signal,
146
151
 
147
152
  /** @param {AuthPrompt} prompt */
148
153
  async prompt(prompt) {
@@ -24,11 +24,12 @@ export function normalizeBackgroundBashTimeoutMs(value: any): number;
24
24
  /**
25
25
  * Compatibility wrapper retained for direct callers and tests.
26
26
  *
27
- * @param {{command: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
27
+ * @param {{command: string, description?: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
28
28
  * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: import("./shared/process-jobs.js").ProcessJobsController}} [options]
29
29
  */
30
30
  export function bashToolImpl(params: {
31
31
  command: string;
32
+ description?: string;
32
33
  timeout?: number;
33
34
  timeout_ms?: number;
34
35
  max_output_chars?: number;
@@ -44,11 +45,12 @@ export function bashToolImpl(params: {
44
45
  /**
45
46
  * Structured Bash execution used by the Pi bridge.
46
47
  *
47
- * @param {{command: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
48
+ * @param {{command: string, description?: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string, background?: boolean}} params
48
49
  * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: import("./shared/process-jobs.js").ProcessJobsController}} [options]
49
50
  */
50
- export function bashToolRun({ command, timeout, timeout_ms, max_output_chars, workdir, background, }: {
51
+ export function bashToolRun({ command, description, timeout, timeout_ms, max_output_chars, workdir, background, }: {
51
52
  command: string;
53
+ description?: string;
52
54
  timeout?: number;
53
55
  timeout_ms?: number;
54
56
  max_output_chars?: number;
@@ -1,12 +1,13 @@
1
1
  /** @typedef {import("./shared/process-jobs.js").ProcessJobsController} ProcessJobsController */
2
2
  /**
3
- * @param {{executable: string, args?: string[], workdir?: string, timeout_ms?: number, max_output_chars?: number, background?: boolean}} params
3
+ * @param {{executable: string, args?: string[], workdir?: string, description?: string, timeout_ms?: number, max_output_chars?: number, background?: boolean}} params
4
4
  * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: ProcessJobsController}} [options]
5
5
  */
6
6
  export function execToolImpl(params: {
7
7
  executable: string;
8
8
  args?: string[];
9
9
  workdir?: string;
10
+ description?: string;
10
11
  timeout_ms?: number;
11
12
  max_output_chars?: number;
12
13
  background?: boolean;
@@ -20,13 +21,14 @@ export function execToolImpl(params: {
20
21
  /**
21
22
  * Execute an argv vector directly, without shell parsing.
22
23
  *
23
- * @param {{executable: string, args?: string[], workdir?: string, timeout_ms?: number, max_output_chars?: number, background?: boolean}} params
24
+ * @param {{executable: string, args?: string[], workdir?: string, description?: string, timeout_ms?: number, max_output_chars?: number, background?: boolean}} params
24
25
  * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, processJobsController?: ProcessJobsController}} [options]
25
26
  */
26
- export function execToolRun({ executable, args, workdir, timeout_ms, max_output_chars, background, }: {
27
+ export function execToolRun({ executable, args, workdir, description, timeout_ms, max_output_chars, background, }: {
27
28
  executable: string;
28
29
  args?: string[];
29
30
  workdir?: string;
31
+ description?: string;
30
32
  timeout_ms?: number;
31
33
  max_output_chars?: number;
32
34
  background?: boolean;
@@ -7,6 +7,7 @@
7
7
  * tool: "Exec"|"Bash",
8
8
  * prepared: import("../../sandbox-seam.js").PreparedSandboxCommand,
9
9
  * summary: string,
10
+ * description?: string,
10
11
  * timeoutMs?: number,
11
12
  * maxOutputChars?: number,
12
13
  * launch: (options?: {timeoutMs?: number, signal?: AbortSignal, maxBufferBytes?: number, onStdout?: (chunk: Buffer) => void, onStderr?: (chunk: Buffer) => void}) => ReturnType<typeof startPreparedProcess>,
@@ -21,17 +22,19 @@
21
22
  * tool: "Exec"|"Bash",
22
23
  * prepared: import("../../sandbox-seam.js").PreparedSandboxCommand,
23
24
  * summary: string,
25
+ * description?: string,
24
26
  * timeoutMs?: number,
25
27
  * maxOutputChars?: number,
26
28
  * startedAt: number,
27
29
  * failed: (text: string, code: string, startedAt: number) => any,
28
30
  * }} input
29
31
  */
30
- export function handOffProcessJob({ controller, tool, prepared, summary, timeoutMs, maxOutputChars, startedAt, failed, }: {
32
+ export function handOffProcessJob({ controller, tool, prepared, summary, description, timeoutMs, maxOutputChars, startedAt, failed, }: {
31
33
  controller: ProcessJobsController;
32
34
  tool: "Exec" | "Bash";
33
35
  prepared: import("../../sandbox-seam.js").PreparedSandboxCommand;
34
36
  summary: string;
37
+ description?: string;
35
38
  timeoutMs?: number;
36
39
  maxOutputChars?: number;
37
40
  startedAt: number;
@@ -46,6 +49,7 @@ export type ProcessJobsController = {
46
49
  tool: "Exec" | "Bash";
47
50
  prepared: import("../../sandbox-seam.js").PreparedSandboxCommand;
48
51
  summary: string;
52
+ description?: string;
49
53
  timeoutMs?: number;
50
54
  maxOutputChars?: number;
51
55
  launch: (options?: {
@@ -38,20 +38,22 @@ export function resolveOAuthApiKey(providerId: string, credentials: Record<strin
38
38
  apiKey: string | undefined;
39
39
  } | null>;
40
40
  /**
41
- * Bridge the legacy six-callback OAuth surface onto 0.83.0's single
42
- * `prompt`/`notify` pair.
41
+ * Bridge the legacy six-callback OAuth surface onto Pi's single
42
+ * `prompt`/`notify` pair. Pi 0.84 requires every provider login interaction to
43
+ * carry an AbortSignal, so callbacks without one receive a private non-aborted
44
+ * signal.
43
45
  *
44
46
  * `manual_code` must stay wired to `onManualCodeInput`: Anthropic races its
45
47
  * localhost callback against a pasted redirect URL, and that path is the reason
46
48
  * `agent-app`'s `runPiOAuthLogin` exists at all.
47
49
  *
48
50
  * @param {OAuthLoginCallbacks} callbacks
49
- * @returns {AuthInteraction}
51
+ * @returns {ProviderAuthInteraction}
50
52
  */
51
- export function toAuthInteraction(callbacks: OAuthLoginCallbacks): AuthInteraction;
53
+ export function toAuthInteraction(callbacks: OAuthLoginCallbacks): ProviderAuthInteraction;
52
54
  export type OAuthAuth = import("@earendil-works/pi-ai").OAuthAuth;
53
55
  export type OAuthCredential = import("@earendil-works/pi-ai").OAuthCredential;
54
- export type AuthInteraction = import("@earendil-works/pi-ai").AuthInteraction;
56
+ export type ProviderAuthInteraction = import("@earendil-works/pi-ai").ProviderAuthInteraction;
55
57
  export type AuthPrompt = import("@earendil-works/pi-ai").AuthPrompt;
56
58
  export type AuthEvent = import("@earendil-works/pi-ai").AuthEvent;
57
59
  export type OAuthLoginCallbacks = import("@earendil-works/pi-ai/oauth").OAuthLoginCallbacks;