@fastagent-sh/fastagent 0.22.1 → 0.24.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 (128) hide show
  1. package/dist/agent.d.ts +10 -0
  2. package/dist/channels/agentcore-protocol.d.ts +2 -1
  3. package/dist/channels/agentcore-service.js +10 -18
  4. package/dist/channels/agentcore.js +6 -3
  5. package/dist/channels/control.js +23 -0
  6. package/dist/channels/kit/preview-kit.d.ts +6 -1
  7. package/dist/channels/kit/preview-kit.js +21 -4
  8. package/dist/channels/slack/config-api.d.ts +2 -2
  9. package/dist/channels/slack/config-api.js +2 -2
  10. package/dist/channels/slack/onboard.js +2 -2
  11. package/dist/channels/slack/preview.js +2 -1
  12. package/dist/channels/slack/register-webhook.js +2 -2
  13. package/dist/channels/slack/slack-api.js +4 -4
  14. package/dist/cli/add-feishu.js +4 -2
  15. package/dist/cli/auth-view.d.ts +15 -6
  16. package/dist/cli/auth-view.js +24 -8
  17. package/dist/cli/box-login.d.ts +33 -0
  18. package/dist/cli/box-login.js +162 -0
  19. package/dist/cli/commands/deploy/agentcore.js +48 -11
  20. package/dist/cli/commands/deploy/docker.js +24 -13
  21. package/dist/cli/commands/deploy/fly.js +30 -7
  22. package/dist/cli/commands/deploy/railway.js +21 -7
  23. package/dist/cli/commands/deploy/shared.d.ts +18 -17
  24. package/dist/cli/commands/deploy/shared.js +20 -21
  25. package/dist/cli/commands/destroy.js +2 -2
  26. package/dist/cli/commands/info.js +15 -12
  27. package/dist/cli/commands/invoke.js +2 -2
  28. package/dist/cli/commands/login.d.ts +7 -1
  29. package/dist/cli/commands/login.js +123 -10
  30. package/dist/cli/commands/logs.js +2 -2
  31. package/dist/cli/commands/models.d.ts +4 -1
  32. package/dist/cli/commands/models.js +37 -4
  33. package/dist/cli/commands/routine-run.js +2 -2
  34. package/dist/cli/commands/start.d.ts +0 -5
  35. package/dist/cli/commands/start.js +3 -13
  36. package/dist/cli/invoke-stream.js +14 -0
  37. package/dist/cli/kernel.d.ts +2 -0
  38. package/dist/cli/kernel.js +1 -1
  39. package/dist/cli/login-relay.d.ts +38 -0
  40. package/dist/cli/login-relay.js +121 -0
  41. package/dist/cli/program.js +46 -8
  42. package/dist/cli/shared.d.ts +6 -5
  43. package/dist/cli/shared.js +29 -36
  44. package/dist/deploy/agentcore/plan.d.ts +10 -11
  45. package/dist/deploy/agentcore/plan.js +8 -14
  46. package/dist/deploy/agentcore/run.d.ts +8 -1
  47. package/dist/deploy/agentcore/run.js +20 -14
  48. package/dist/deploy/agentcore/shell.d.ts +25 -0
  49. package/dist/deploy/agentcore/shell.js +202 -0
  50. package/dist/deploy/box-shell.d.ts +43 -0
  51. package/dist/deploy/box-shell.js +28 -0
  52. package/dist/deploy/build-context.js +16 -7
  53. package/dist/deploy/channel-ingress.d.ts +20 -1
  54. package/dist/deploy/channel-ingress.js +34 -7
  55. package/dist/deploy/container.d.ts +14 -0
  56. package/dist/deploy/container.js +45 -1
  57. package/dist/deploy/docker/plan.d.ts +7 -3
  58. package/dist/deploy/docker/plan.js +9 -14
  59. package/dist/deploy/docker/run.d.ts +14 -7
  60. package/dist/deploy/docker/run.js +58 -35
  61. package/dist/deploy/fly/plan.d.ts +6 -3
  62. package/dist/deploy/fly/plan.js +6 -8
  63. package/dist/deploy/fly/run.d.ts +8 -2
  64. package/dist/deploy/fly/run.js +15 -25
  65. package/dist/deploy/preflight.d.ts +9 -7
  66. package/dist/deploy/preflight.js +92 -20
  67. package/dist/deploy/railway/plan.d.ts +6 -3
  68. package/dist/deploy/railway/plan.js +7 -9
  69. package/dist/deploy/railway/run.d.ts +10 -6
  70. package/dist/deploy/railway/run.js +29 -13
  71. package/dist/deploy/runner.d.ts +16 -0
  72. package/dist/deploy/runner.js +31 -0
  73. package/dist/deploy/secrets.d.ts +4 -13
  74. package/dist/deploy/secrets.js +8 -27
  75. package/dist/dev-supervisor.js +7 -4
  76. package/dist/engines/pi/agent-models.d.ts +83 -0
  77. package/dist/engines/pi/agent-models.js +120 -0
  78. package/dist/engines/pi/agent-session-factory.d.ts +13 -13
  79. package/dist/engines/pi/agent-session-factory.js +132 -159
  80. package/dist/engines/pi/auth.d.ts +56 -3
  81. package/dist/engines/pi/auth.js +68 -109
  82. package/dist/engines/pi/config.d.ts +0 -25
  83. package/dist/engines/pi/config.js +3 -37
  84. package/dist/engines/pi/create.d.ts +34 -21
  85. package/dist/engines/pi/create.js +90 -57
  86. package/dist/engines/pi/entry-images.d.ts +14 -0
  87. package/dist/engines/pi/entry-images.js +23 -0
  88. package/dist/engines/pi/invoke-session.js +88 -20
  89. package/dist/engines/pi/locked-file.d.ts +9 -0
  90. package/dist/engines/pi/locked-file.js +102 -0
  91. package/dist/engines/pi/login.d.ts +27 -14
  92. package/dist/engines/pi/login.js +41 -18
  93. package/dist/engines/pi/machine.d.ts +23 -0
  94. package/dist/engines/pi/machine.js +24 -2
  95. package/dist/engines/pi/models.d.ts +120 -36
  96. package/dist/engines/pi/models.js +316 -66
  97. package/dist/engines/pi/open.d.ts +65 -20
  98. package/dist/engines/pi/open.js +88 -34
  99. package/dist/engines/pi/report.d.ts +3 -0
  100. package/dist/engines/pi/report.js +11 -0
  101. package/dist/engines/pi/service.d.ts +2 -2
  102. package/dist/engines/pi/service.js +1 -0
  103. package/dist/engines/pi/session-builder.d.ts +3 -2
  104. package/dist/engines/pi/session-builder.js +30 -76
  105. package/dist/engines/pi/session-control.d.ts +4 -4
  106. package/dist/engines/pi/session-control.js +86 -43
  107. package/dist/engines/pi/session-settings.d.ts +9 -0
  108. package/dist/engines/pi/session-settings.js +17 -0
  109. package/dist/engines/pi/session-store.d.ts +2 -2
  110. package/dist/engines/pi/tool-context.d.ts +3 -5
  111. package/dist/engines/pi/tool-context.js +4 -16
  112. package/dist/engines/pi/tool.d.ts +32 -21
  113. package/dist/engines/pi/tool.js +21 -25
  114. package/dist/engines/pi/turn-kit.d.ts +18 -9
  115. package/dist/engines/pi/turn-kit.js +115 -32
  116. package/dist/open-url.d.ts +15 -1
  117. package/dist/open-url.js +35 -3
  118. package/dist/paths.d.ts +4 -2
  119. package/dist/paths.js +6 -2
  120. package/dist/pi.d.ts +5 -3
  121. package/dist/pi.js +3 -2
  122. package/dist/service.d.ts +5 -8
  123. package/dist/session-remote.js +21 -0
  124. package/dist/session.d.ts +88 -15
  125. package/dist/session.js +5 -1
  126. package/package.json +6 -5
  127. package/dist/engines/pi/search-tools.d.ts +0 -4
  128. package/dist/engines/pi/search-tools.js +0 -109
@@ -4,7 +4,7 @@
4
4
  * (one writer, one lock/corruption semantics); {@link loginOptions} is the list a picker offers. `fastagent login` is
5
5
  * one front end ({@link loginFlow}: terminal menus, then {@link login}); a GUI client is another.
6
6
  */
7
- import { type AuthInteraction, type Provider } from "@earendil-works/pi-ai";
7
+ import { type AuthInteraction, type CredentialStore, type Provider } from "@earendil-works/pi-ai";
8
8
  export type LoginMethod = "oauth" | "api_key";
9
9
  /** The user backed out of a prompt/menu, or the flow was aborted — a decision, not a failure. */
10
10
  export declare class LoginCancelled extends Error {
@@ -18,26 +18,38 @@ export interface LoginOption {
18
18
  /** An OAuth login backed by a provider subscription. */
19
19
  subscription: boolean;
20
20
  /**
21
- * What the file holds for this provider now. It keeps ONE credential per provider, so signing in with the other
21
+ * What the store holds for this provider now. It keeps ONE credential per provider, so signing in with the other
22
22
  * method replaces it — worth a warning in a client.
23
23
  */
24
24
  stored?: LoginMethod;
25
25
  }
26
26
  /**
27
- * Every interactive sign-in of pi's built-in providers, with what `authPath` already holds for each. A provider whose
28
- * key can only come from its env var offers none and is left out.
27
+ * Every interactive sign-in of pi's built-in providers, with what `credentials` (a credentials file, or the caller's
28
+ * own store) already holds for each. A provider whose key can only come from its env var offers none and is left out.
29
29
  */
30
- export declare function loginOptions(authPath: string): Promise<LoginOption[]>;
30
+ export declare function loginOptions(credentials: string | CredentialStore): Promise<LoginOption[]>;
31
31
  /** {@link loginOptions} over pi's built-ins plus `providers` (a same id replaces one). Not public: a test seam. */
32
- export declare function loginOptionsOver(authPath: string, providers?: readonly Provider[]): Promise<LoginOption[]>;
33
- export interface LoginRequest {
32
+ export declare function loginOptionsOver(credentials: string | CredentialStore, providers?: readonly Provider[]): Promise<LoginOption[]>;
33
+ export type LoginRequest = {
34
+ /**
35
+ * Stable installation identity (Sign in with ChatGPT requires one). With `authPath` it defaults to Pi settings
36
+ * beside that file (`settings.json`, written before the flow runs); over a caller's `credentialStore` there is no
37
+ * file to keep it beside, so a flow that needs one fails unless this supplies it.
38
+ */
39
+ getDeviceId?: () => string;
34
40
  provider: string;
35
41
  method: LoginMethod;
36
- /** The credentials file the sign-in is written to. */
37
- authPath: string;
38
42
  /** pi-ai's own interaction: prompts (`text`, `secret`, `select`, `manual_code`) and events (`auth_url`, …). */
39
43
  interaction: AuthInteraction;
40
- }
44
+ } & ({
45
+ /** The credentials file the sign-in is written to. */
46
+ authPath: string;
47
+ credentialStore?: never;
48
+ } | {
49
+ /** The caller's own store the sign-in is written to, in place of any file. */
50
+ credentialStore: CredentialStore;
51
+ authPath?: never;
52
+ });
41
53
  export interface LoginResult {
42
54
  provider: string;
43
55
  method: LoginMethod;
@@ -45,10 +57,11 @@ export interface LoginResult {
45
57
  verified: "ok" | "unknown" | "n/a";
46
58
  }
47
59
  /**
48
- * Sign in to one of pi's built-in providers and persist the credential to `authPath`.
60
+ * Sign in to one of pi's built-in providers and persist the credential to `authPath`, or to the caller's
61
+ * `credentialStore`.
49
62
  *
50
- * The file is checked FIRST (a no-op write runs the refuse-corrupt and writability checks), so a flow never runs
51
- * toward a credential that could not be saved. An entered API key is verified with one minimal request to pi's
63
+ * The store is checked FIRST (a no-op write runs the file store's refuse-corrupt and writability checks, or whatever
64
+ * the caller's store does on a write), so a flow never runs toward a credential that could not be saved. An entered API key is verified with one minimal request to pi's
52
65
  * default model for the provider (its first model when pi names none), at pi's built-in endpoint, BEFORE it is
53
66
  * written. Credentials are provider-scoped, so any of the provider's models answers the question the check asks (does
54
67
  * the provider refuse the key, HTTP 401); a key the provider rejects is never stored, and the provider's key flow runs
@@ -56,7 +69,7 @@ export interface LoginResult {
56
69
  * A provider an agent's models.json points elsewhere is not this call's: store that key with `fastagentCredentialStore`.
57
70
  *
58
71
  * Aborting `interaction.signal` at any point, the verification included, rejects with {@link LoginCancelled} and
59
- * writes nothing. Every prompt carries a signal that aborts on it, on the prompt's own signal (a callback server
72
+ * writes no credential (an OAuth device ID, the installation's identity, is kept once created). Every prompt carries a signal that aborts on it, on the prompt's own signal (a callback server
60
73
  * beating a `manual_code` prompt), and when the flow settles with a prompt still pending.
61
74
  */
62
75
  export declare function login(request: LoginRequest): Promise<LoginResult>;
@@ -5,23 +5,28 @@
5
5
  * one front end ({@link loginFlow}: terminal menus, then {@link login}); a GUI client is another.
6
6
  */
7
7
  import { InMemoryCredentialStore, } from "@earendil-works/pi-ai";
8
+ import { dirname } from "node:path";
9
+ import { SettingsManager } from "@earendil-works/pi-coding-agent";
8
10
  import { fastagentCredentialStore } from "./auth.js";
9
11
  import { createPiModelRuntime, interactiveAuth, loginProviders, piModelsOver, probeApiKey } from "./models.js";
12
+ import { agentModels } from "./agent-models.js";
10
13
  /** The user backed out of a prompt/menu, or the flow was aborted — a decision, not a failure. */
11
14
  export class LoginCancelled extends Error {
12
15
  }
13
16
  /**
14
- * Every interactive sign-in of pi's built-in providers, with what `authPath` already holds for each. A provider whose
15
- * key can only come from its env var offers none and is left out.
17
+ * Every interactive sign-in of pi's built-in providers, with what `credentials` (a credentials file, or the caller's
18
+ * own store) already holds for each. A provider whose key can only come from its env var offers none and is left out.
16
19
  */
17
- export function loginOptions(authPath) {
18
- return loginOptionsOver(authPath);
20
+ export function loginOptions(credentials) {
21
+ return loginOptionsOver(credentials);
19
22
  }
23
+ /** A credentials file path names fastagent's store over it; a store is used as given. */
24
+ const storeOf = (credentials) => typeof credentials === "string" ? fastagentCredentialStore(credentials) : credentials;
20
25
  /** {@link loginOptions} over pi's built-ins plus `providers` (a same id replaces one). Not public: a test seam. */
21
- export async function loginOptionsOver(authPath, providers) {
22
- // ONE read of the file: `list` is metadata only, and reading per provider would repeat a corrupt file's warning for
26
+ export async function loginOptionsOver(credentials, providers) {
27
+ // ONE read of the store: `list` is metadata only, and reading per provider would repeat a corrupt file's warning for
23
28
  // every provider.
24
- const held = new Map((await fastagentCredentialStore(authPath).list()).map((info) => [info.providerId, info.type]));
29
+ const held = new Map((await storeOf(credentials).list()).map((info) => [info.providerId, info.type]));
25
30
  const offered = [];
26
31
  for (const provider of loginProviders(providers)) {
27
32
  const methods = ["oauth", "api_key"].filter((method) => interactiveAuth(provider, method));
@@ -46,10 +51,11 @@ function anySignal(...signals) {
46
51
  return present.length === 0 ? undefined : present.length === 1 ? present[0] : AbortSignal.any(present);
47
52
  }
48
53
  /**
49
- * Sign in to one of pi's built-in providers and persist the credential to `authPath`.
54
+ * Sign in to one of pi's built-in providers and persist the credential to `authPath`, or to the caller's
55
+ * `credentialStore`.
50
56
  *
51
- * The file is checked FIRST (a no-op write runs the refuse-corrupt and writability checks), so a flow never runs
52
- * toward a credential that could not be saved. An entered API key is verified with one minimal request to pi's
57
+ * The store is checked FIRST (a no-op write runs the file store's refuse-corrupt and writability checks, or whatever
58
+ * the caller's store does on a write), so a flow never runs toward a credential that could not be saved. An entered API key is verified with one minimal request to pi's
53
59
  * default model for the provider (its first model when pi names none), at pi's built-in endpoint, BEFORE it is
54
60
  * written. Credentials are provider-scoped, so any of the provider's models answers the question the check asks (does
55
61
  * the provider refuse the key, HTTP 401); a key the provider rejects is never stored, and the provider's key flow runs
@@ -57,7 +63,7 @@ function anySignal(...signals) {
57
63
  * A provider an agent's models.json points elsewhere is not this call's: store that key with `fastagentCredentialStore`.
58
64
  *
59
65
  * Aborting `interaction.signal` at any point, the verification included, rejects with {@link LoginCancelled} and
60
- * writes nothing. Every prompt carries a signal that aborts on it, on the prompt's own signal (a callback server
66
+ * writes no credential (an OAuth device ID, the installation's identity, is kept once created). Every prompt carries a signal that aborts on it, on the prompt's own signal (a callback server
61
67
  * beating a `manual_code` prompt), and when the flow settles with a prompt still pending.
62
68
  */
63
69
  export function login(request) {
@@ -66,13 +72,17 @@ export function login(request) {
66
72
  /** {@link login}, plus {@link LoginInternals}. */
67
73
  export async function loginOver(request, internals = {}) {
68
74
  const { providers, verifyWith, agentDir } = internals;
69
- const { method, authPath, interaction } = request;
75
+ const { method, interaction } = request;
76
+ if (request.credentialStore === undefined && typeof request.authPath !== "string") {
77
+ throw new Error("login needs authPath or credentialStore: where the sign-in is written");
78
+ }
70
79
  const provider = loginProviders(providers).find((p) => p.id === request.provider);
71
80
  if (!provider)
72
81
  throw new Error(`unknown provider "${request.provider}"`);
73
82
  const auth = interactiveAuth(provider, method);
74
83
  if (!auth?.login)
75
84
  throw new Error(`provider "${provider.id}" has no interactive ${method} login`);
85
+ const flow = auth.login.bind(auth);
76
86
  // The key under test lives here, not in the file, until it passes.
77
87
  const trial = new InMemoryCredentialStore();
78
88
  // Only a key needs the registry, and building an agent's fails on a broken models.json: an OAuth sign-in must not.
@@ -86,12 +96,25 @@ export async function loginOver(request, internals = {}) {
86
96
  ...(providers ? { providers: [...providers] } : {}),
87
97
  })
88
98
  : piModelsOver(trial, providers);
89
- const store = fastagentCredentialStore(authPath);
99
+ const store = agentModels(undefined, request).credentials;
90
100
  await store.modify(provider.id, async () => undefined);
91
101
  const signal = interaction.signal ?? new AbortController().signal;
92
102
  const notify = (event) => interaction.notify(event);
103
+ let getDeviceId = request.getDeviceId;
104
+ if (method === "oauth" && !getDeviceId && request.authPath !== undefined) {
105
+ // Only the settings file beside the credentials: no project's `.pi/settings.json` has a say in this write.
106
+ const settings = SettingsManager.create(dirname(request.authPath), dirname(request.authPath), {
107
+ projectTrusted: false,
108
+ });
109
+ const deviceId = settings.getOrCreateDeviceId();
110
+ await settings.flush();
111
+ const errors = settings.drainErrors();
112
+ if (errors.length)
113
+ throw new Error(`could not persist login device ID: ${errors.map(({ error }) => error.message).join("; ")}`);
114
+ getDeviceId = () => deviceId;
115
+ }
93
116
  for (;;) {
94
- const credential = await runFlow(auth.login.bind(auth), interaction, signal);
117
+ const credential = await runFlow((io) => flow(io, { getDeviceId }), interaction, signal);
95
118
  let verified = "n/a";
96
119
  if (models) {
97
120
  await trial.modify(provider.id, async () => credential);
@@ -143,7 +166,7 @@ export const PI_DEFAULT_MODELS = {
143
166
  anthropic: "claude-opus-4-8",
144
167
  openai: "gpt-5.5",
145
168
  "azure-openai-responses": "gpt-5.4",
146
- "openai-codex": "gpt-5.5",
169
+ "openai-codex": "gpt-6.1-sol",
147
170
  radius: "balanced",
148
171
  nvidia: "nvidia/nemotron-3-super-120b-a12b",
149
172
  deepseek: "deepseek-v4-pro",
@@ -163,11 +186,11 @@ export const PI_DEFAULT_MODELS = {
163
186
  moonshotai: "kimi-k2.6",
164
187
  "moonshotai-cn": "kimi-k2.6",
165
188
  huggingface: "moonshotai/Kimi-K2.6",
166
- fireworks: "accounts/fireworks/models/kimi-k2p6",
167
- together: "moonshotai/Kimi-K2.6",
189
+ fireworks: "accounts/fireworks/models/kimi-k3",
190
+ together: "moonshotai/Kimi-K3",
168
191
  baseten: "zai-org/GLM-5.2",
169
192
  opencode: "kimi-k2.6",
170
- "opencode-go": "kimi-k2.6",
193
+ "opencode-go": "kimi-k3",
171
194
  "kimi-coding": "kimi-for-coding",
172
195
  meta: "muse-spark-1.3",
173
196
  "cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6",
@@ -13,10 +13,33 @@ import { DefaultResourceLoader, SettingsManager } from "@earendil-works/pi-codin
13
13
  /** pi's own shapes, taken from its loader rather than re-declared. */
14
14
  export type MachineSkill = ReturnType<DefaultResourceLoader["getSkills"]>["skills"][number];
15
15
  type MachinePrompt = ReturnType<DefaultResourceLoader["getPrompts"]>["prompts"][number];
16
+ /** The Pi built-in extensions a definition loads, each unless the machine's settings disable it. */
17
+ export declare const BUILTIN_EXTENSIONS: readonly ["codemode", "tool-search"];
18
+ /**
19
+ * The way in for an authored tool the model is not given up front, by its exposure: the built-in extension that
20
+ * provides it and the tool that extension registers. A `codemode` tool is called from codemode scripts, a `deferred`
21
+ * one is loaded by tool_search — the rule Pi's MCP extension applies to its own tools (`ensureDiscoveryActive`).
22
+ * Without that extension the tool has no way in at all.
23
+ */
24
+ export declare const DISCOVERY: {
25
+ readonly codemode: {
26
+ readonly extension: "codemode";
27
+ readonly tool: "codemode";
28
+ };
29
+ readonly deferred: {
30
+ readonly extension: "tool-search";
31
+ readonly tool: "tool_search";
32
+ };
33
+ };
16
34
  /** What this box lends an agent. */
17
35
  export interface Machine {
18
36
  skills: MachineSkill[];
19
37
  prompts: MachinePrompt[];
38
+ /**
39
+ * The {@link BUILTIN_EXTENSIONS} left enabled, by Pi's own rule: `"extensions": ["-builtin:codemode"]` in the user
40
+ * settings disables one, and the workspace's `.pi/settings.json` overrides that either way.
41
+ */
42
+ builtinExtensions: string[];
20
43
  /** pi's engine settings (retry, compaction, cache warming, …) as read at boot — a fresh manager per caller. */
21
44
  settingsManager(): SettingsManager;
22
45
  }
@@ -11,6 +11,20 @@
11
11
  */
12
12
  import { DefaultPackageManager, DefaultResourceLoader, SettingsManager, getAgentDir, } from "@earendil-works/pi-coding-agent";
13
13
  import { log } from "../../log.js";
14
+ /** The Pi built-in extensions a definition loads, each unless the machine's settings disable it. */
15
+ // Not `mcp`: its server connections live as long as a session, and a served session lives one turn, so every turn
16
+ // would start every configured server.
17
+ export const BUILTIN_EXTENSIONS = ["codemode", "tool-search"];
18
+ /**
19
+ * The way in for an authored tool the model is not given up front, by its exposure: the built-in extension that
20
+ * provides it and the tool that extension registers. A `codemode` tool is called from codemode scripts, a `deferred`
21
+ * one is loaded by tool_search — the rule Pi's MCP extension applies to its own tools (`ensureDiscoveryActive`).
22
+ * Without that extension the tool has no way in at all.
23
+ */
24
+ export const DISCOVERY = {
25
+ codemode: { extension: "codemode", tool: "codemode" },
26
+ deferred: { extension: "tool-search", tool: "tool_search" },
27
+ };
14
28
  /** Keyed by the two places it reads: the workspace (project scope) and pi's own directory (user scope). */
15
29
  const reads = new Map();
16
30
  /** This process's one read of the machine, shared by the run plane and `commands()` so the two cannot disagree. */
@@ -37,7 +51,12 @@ async function read(workspace, agentDir) {
37
51
  // (`npm install`, `git clone`), throwing out of `reload()` when that fails — measured. So fastagent resolves them
38
52
  // here, telling pi to skip what is absent, and hands the loader a packageless copy of the settings so its own
39
53
  // resolve has nothing to do.
40
- const manager = new DefaultPackageManager({ cwd: workspace, agentDir, settingsManager: files });
54
+ const manager = new DefaultPackageManager({
55
+ cwd: workspace,
56
+ agentDir,
57
+ settingsManager: files,
58
+ builtinExtensions: [...BUILTIN_EXTENSIONS],
59
+ });
41
60
  const packages = await manager.resolve(async () => "skip");
42
61
  // A package skipped is said, once: its skills would otherwise just not be there. Asked of the configured list, not
43
62
  // of `onMissing` — pi never calls that under `PI_OFFLINE`, so the warning would vanish exactly when offline.
@@ -72,7 +91,10 @@ async function read(workspace, agentDir) {
72
91
  log.warn(`[fastagent] ${kind} ${diagnostic.type}: ${diagnostic.message}${diagnostic.path ? ` (${diagnostic.path})` : ""}`);
73
92
  }
74
93
  }
75
- return { skills, prompts, settingsManager: () => scopedSettings(settings) };
94
+ const builtinExtensions = packages.extensions
95
+ .filter((resource) => resource.enabled && resource.metadata.source === "builtin")
96
+ .map((resource) => resource.path.slice("builtin:".length));
97
+ return { skills, prompts, builtinExtensions, settingsManager: () => scopedSettings(settings) };
76
98
  }
77
99
  /** A package's enabled resources; the loader discovers the top-level ones itself. */
78
100
  function fromPackages(resources) {
@@ -1,17 +1,6 @@
1
1
  import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
- import { type Api, type CredentialStore, type Model, type Models, type Provider } from "@earendil-works/pi-ai";
2
+ import { type Api, type CredentialStore, type Model, type Models, type ModelsStore, type Provider } from "@earendil-works/pi-ai";
3
3
  import { ModelRuntime } from "@earendil-works/pi-coding-agent";
4
- import { type FastagentAuthOptions } from "./auth.ts";
5
- import { type AuthLayers } from "./config.ts";
6
- /** The DEFINITION-LOCAL custom-endpoint file, in pi's own models.json schema (see pi's docs/models.md). */
7
- export interface CreatePiModelsOptions extends FastagentAuthOptions {
8
- /** Credentials file (default `GLOBAL_AUTH_PATH`). A named file is an instruction: no second layer. */
9
- authPath?: string;
10
- /** Extra providers registered on top of the built-ins (same id overrides a built-in). */
11
- providers?: Provider[];
12
- }
13
- /** A `Models` with every built-in pi provider, wired to fastagent's auth. */
14
- export declare function createPiModels(options?: CreatePiModelsOptions): Models;
15
4
  /** The built-ins plus `providers` (a same id replaces a built-in), over any credential store. */
16
5
  export declare function piModelsOver(credentials: CredentialStore, providers?: readonly Provider[]): Models;
17
6
  /**
@@ -21,12 +10,17 @@ export declare function piModelsOver(credentials: CredentialStore, providers?: r
21
10
  export type AnyModel = Model<any>;
22
11
  /** The serving default for reasoning effort, pinned to what pi's TUI defaults to (its own DEFAULT_THINKING_LEVEL). */
23
12
  export declare const DEFAULT_THINKING_LEVEL: ThinkingLevel;
24
- /**
25
- * The MACHINE's custom-endpoint file, in the same schema as an agent's own: endpoints a person set up for this machine
26
- * (a local Ollama, a company gateway), which every agent here inherits the way it inherits the machine's skills.
27
- * `FASTAGENT_MODELS_PATH` moves it. It never ships: a deployed agent has only its definition's file.
28
- */
29
- export declare function machineModelsPath(env?: NodeJS.ProcessEnv): string;
13
+ /** The models.json pi loads for an agent, and where its content came from (for a load error). */
14
+ export interface ModelsFile {
15
+ /** A snapshot pi loads; null when the agent has no models.json. */
16
+ path: string | null;
17
+ /** The agent's own file, when the snapshot is a copy of it alone. */
18
+ snapshotOf?: string;
19
+ merged?: {
20
+ machine: string;
21
+ definition: string;
22
+ };
23
+ }
30
24
  /**
31
25
  * Where an agent's custom endpoints come from, for a report: the machine file, the providers the agent inherits from
32
26
  * it, and the ones its own file overrides. Undefined when the machine has no file.
@@ -38,29 +32,104 @@ export declare function machineModels(agentDir: string): Promise<{
38
32
  } | undefined>;
39
33
  /** Whether pi ships this provider id itself, so an agent resolves it without any models.json entry. */
40
34
  export declare function isBuiltinProvider(providerId: string): boolean;
41
- /** The `ModelRuntime`-shaped sibling of {@link createPiModels}. */
42
- export declare function createPiModelRuntime(options?: FastagentAuthOptions & {
43
- /** The credentials files to read (default: the global file alone). */
44
- auth?: AuthLayers;
45
- /** The agent dir, whose {@link AGENT_MODELS_FILE} declares custom endpoints. */
35
+ export interface PiModelRuntimeOptions {
36
+ /** The store every credential is read from and refreshed into. */
37
+ credentials: CredentialStore;
38
+ /** The agent dir, whose {@link AGENT_MODELS_FILE} and {@link AGENT_MODEL_CATALOG_FILE} the registry reads. */
46
39
  agentDir?: string;
47
40
  /**
48
- * Layer the machine's models.json under the agent's (default). Off for the registry a DEPLOYED agent has, which
49
- * `deploy` must judge by: the machine's file does not ship.
41
+ * Layer the machine under the agent (default): its models.json and its model catalog ({@link globalCatalogPath}).
42
+ * Off for the registry a DEPLOYED agent has, which `deploy` must judge by: neither ships, so that registry is the
43
+ * agent's own two files over the catalog bundled with pi.
50
44
  */
51
45
  machineLayer?: boolean;
52
- /** Where the dynamic model-catalog cache goes; defaults to the agent's resolved state root. */
53
- stateRoot?: string;
54
46
  /** Extra providers for the ids the built-ins do not cover. */
55
- providers?: Provider[];
56
- /** A credential store to use instead of the files (login verifies an entered key from memory). */
47
+ providers?: readonly Provider[];
48
+ /**
49
+ * Read and write THIS catalog file instead of the layered, read-only snapshot: what a refresh runs on, since the
50
+ * refresh writes it.
51
+ */
52
+ catalogFile?: string;
53
+ /** Where a catalog refresh asks instead of pi.dev: a test seam. */
54
+ catalogBaseUrl?: string;
55
+ }
56
+ /**
57
+ * The MACHINE's model catalog: the models pi.dev lists that are newer than the catalog bundled with pi, fetched by
58
+ * `fastagent models --refresh -g`. Every agent here reads it under its own; like `~/.fastagent/models.json`, it
59
+ * never ships.
60
+ */
61
+ export declare function globalCatalogPath(): string;
62
+ /**
63
+ * The catalog file a refresh writes, as pi's store. It replaces pi's own file store, which rewrites the file in place
64
+ * under its lock: a reader that takes no lock (every catalog read here, and anything else reading the file) could open
65
+ * it cut short mid-write and blame a healthy file. Same lock as pi's, so concurrent refreshes still queue; reading
66
+ * creates nothing, so a refresh that is refused writes no file.
67
+ */
68
+ export declare function catalogFileStore(path: string): ModelsStore;
69
+ /**
70
+ * Whether the machine's model catalog supplies this model, by the same rule every registry reads it with
71
+ * ({@link layeredCatalog}): an entry pi would ignore supplies nothing. The one layer a deployed agent does not have.
72
+ */
73
+ export declare function inGlobalCatalog(provider: string, id: string): Promise<boolean>;
74
+ /**
75
+ * The model files a runtime reads, READ ONCE: `agentModels` keeps one of these per agent, so every session's runtime
76
+ * and the control plane's catalog see the same content however the files change while serving.
77
+ */
78
+ export interface ModelFiles {
79
+ models?: ModelsFile;
80
+ /** `ModelRuntime.create` options over this read; a fresh catalog store each call. */
81
+ create(): Promise<{
82
+ modelsPath: string | null;
83
+ modelsStore?: ModelsStore;
84
+ allowModelNetwork: false;
85
+ catalogBaseUrl?: string;
86
+ }>;
87
+ }
88
+ /** The models.json a runtime for these options loads, and which model catalog it reads. */
89
+ export declare function modelRuntimeFiles(options: Omit<PiModelRuntimeOptions, "credentials">): Promise<ModelFiles>;
90
+ /**
91
+ * This machine's registry, without an agent: pi's built-ins, the machine's models.json and its model catalog. What
92
+ * `fastagent models` lists outside an agent; {@link catalogFile} is the refresh's (it writes the file).
93
+ */
94
+ export declare function machineModelRuntime(options?: {
57
95
  credentials?: CredentialStore;
96
+ catalogFile?: string;
97
+ catalogBaseUrl?: string;
98
+ }): Promise<ModelRuntime>;
99
+ /**
100
+ * Fetch the model catalog of every provider the runtime can authenticate into `catalogFile` ({@link
101
+ * catalogFileStore}): the refresh `pi update --models` runs. `build` makes the runtime over that file, and runs only
102
+ * once the refresh is known to go ahead. A refusal writes nothing: the store creates the file only when pi writes an
103
+ * entry. pi asks pi.dev only for a provider with a usable credential, and may refresh an expired OAuth token of the
104
+ * runtime's store to get one. Rejects, naming each provider that failed, when any part fails, when it outlasts 15
105
+ * seconds, when `PI_OFFLINE` is set, when the catalog file is corrupt, and when no provider has a usable credential (the
106
+ * refresh would ask for nothing).
107
+ */
108
+ export declare function refreshCatalog(catalogFile: string, build: (catalogFile: string) => Promise<ModelRuntime>, options?: {
109
+ signal?: AbortSignal;
110
+ }): Promise<void>;
111
+ /**
112
+ * What satisfies a provider from `env` alone (its API-key variable, or an ambient source such as an AWS profile), by
113
+ * pi's own check, or undefined. Passed explicitly rather than read from `process.env`: only the environment a SHELL
114
+ * hands down is shared by every agent on the machine, and a process that has loaded an agent's `.secrets/.env` holds
115
+ * that agent's variables too. What the shell shares outranks the global credentials file (agent-models.ts),
116
+ * so a global login for such a provider is not used.
117
+ *
118
+ * `fileExists` answers for the files a source points at (an AWS profile, Google ADC): this machine's by default, which
119
+ * is right wherever the answer is about THIS machine. A deployment passes one that finds nothing, because no file of
120
+ * the builder's travels with it.
121
+ */
122
+ export declare function environmentAuthSource(providerId: string, env: NodeJS.ProcessEnv, fileExists?: (path: string) => Promise<boolean>): Promise<string | undefined>;
123
+ /** Pi registration starts background refreshes; wait for all of them before publishing this fresh runtime. */
124
+ export declare function withModelRegistration<T>(runtime: ModelRuntime, register: () => Promise<T>): Promise<T>;
125
+ /**
126
+ * A registry over the given credential store. What an agent runs on is built through `agentModels`
127
+ * (agent-models.ts); this is for a registry over some OTHER store: none at all (`models`, a deploy check of what
128
+ * ships), a trial key under test (login), or the catalog file a refresh writes.
129
+ */
130
+ export declare function createPiModelRuntime(options: PiModelRuntimeOptions & {
131
+ files?: ModelFiles;
58
132
  }): Promise<ModelRuntime>;
59
- /** How a model's credential will REACH a deployed agent. */
60
- export declare function modelCredentialCarry(runtime: ModelRuntime, spec: string): {
61
- envVar?: string;
62
- inDefinition: boolean;
63
- };
64
133
  /**
65
134
  * EVERY provider whose `models.json` entry writes its key as a LITERAL rather than a `"$NAME"` reference or a
66
135
  * `"!cmd"`. The file ships inside the image, so a literal there is a credential in a readable layer.
@@ -76,6 +145,17 @@ export declare function modelCredentialCarry(runtime: ModelRuntime, spec: string
76
145
  * gates what IT causes; what the author wrote into their own committed file, it reports.
77
146
  */
78
147
  export declare function literalKeyProviders(agentDir: string): Promise<string[]>;
148
+ /**
149
+ * How the agent's OWN models.json supplies `providerId`'s key, read from the file for the reason
150
+ * {@link literalKeyProviders} gives: `reference` is the variable a `"$NAME"` reads (the value file must hold it),
151
+ * `inFile` a key that travels with the definition itself (a literal, or a `"!command"` run on the box), and undefined
152
+ * no apiKey there at all.
153
+ */
154
+ export declare function definitionKeyOf(agentDir: string, providerId: string): Promise<{
155
+ reference: string;
156
+ } | {
157
+ inFile: true;
158
+ } | undefined>;
79
159
  /**
80
160
  * What a provider can offer as an interactive login: an OAuth flow, an API-key ENTRY prompt, or nothing ("none" — the
81
161
  * key must come from the provider's env var).
@@ -84,7 +164,7 @@ export type InteractiveLoginKind = "oauth" | "api_key" | "none";
84
164
  export declare function interactiveLoginKind(p: Provider): InteractiveLoginKind;
85
165
  /** THE rule for "can this method be signed in to interactively": the auth to run for it, or undefined. */
86
166
  export declare function interactiveAuth(provider: Provider, method: Exclude<InteractiveLoginKind, "none">): import("@earendil-works/pi-ai").ApiKeyAuth | import("@earendil-works/pi-ai").OAuthAuth | undefined;
87
- /** The providers a sign-in can target: pi's built-ins plus `extra`, composed exactly as {@link createPiModels} does. */
167
+ /** The providers a sign-in can target: pi's built-ins plus `extra`, composed exactly as {@link piModelsOver} does. */
88
168
  export declare function loginProviders(extra?: readonly Provider[]): readonly Provider[];
89
169
  /** Per-provider auth status for the first-run model picker. */
90
170
  export type ProviderAuthStatus = {
@@ -100,7 +180,11 @@ export type ProviderAuthStatus = {
100
180
  };
101
181
  /** Probe every provider's auth once (auth is provider-scoped, so any of its models works as the probe). */
102
182
  export declare function providerAuthStatuses(models: Models): Promise<Map<string, ProviderAuthStatus>>;
103
- /** Which source currently satisfies auth for `spec` — a startup diagnostic. */
183
+ /**
184
+ * Which source currently satisfies auth for `spec`, or undefined when none does (or the spec is not in `models`).
185
+ * Rejects when resolving fails — a corrupt entry, a refresh the provider refused — which is a different answer from
186
+ * "nothing configured" and must reach whoever reports it.
187
+ */
104
188
  export declare function probeAuthSource(models: Models, spec: string): Promise<string | undefined>;
105
189
  /**
106
190
  * Verdict of {@link probeApiKey}: `rejected` is DEFINITIVE (the provider answered HTTP 401 — the key is wrong);