@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
@@ -30,7 +30,7 @@ const DEPLOY_OWNED = new Set([
30
30
  ]);
31
31
  /** Is `name` one the deployment sets itself (see {@link DEPLOY_OWNED}), including the chunked carriers? */
32
32
  function isDeployOwned(name) {
33
- return DEPLOY_OWNED.has(name) || /^FASTAGENT_(AUTH_SEED|ENV)(_\d+)?$/.test(name);
33
+ return DEPLOY_OWNED.has(name) || /^FASTAGENT_ENV(_\d+)?$/.test(name);
34
34
  }
35
35
  /** The value-file variables that travel: everything with a value that the deployment does not set itself. */
36
36
  function carriedValues(values) {
@@ -59,36 +59,25 @@ export function deploymentSecrets(modelAuth, declared, values, valueFile) {
59
59
  return secrets;
60
60
  }
61
61
  /**
62
- * Assemble the VALUES a `--run` deploy sets on the host: every variable the value file carries, plus the local
63
- * credential. Never minted here.
62
+ * Assemble the VALUES a `--run` deploy sets on the host: every variable the value file carries. Never minted here.
64
63
  *
65
64
  * `values` is the selected value file, read as data — **the environment running `deploy` is not a source**. A
66
65
  * deployment must be reproducible from what it carries, and a variable that happens to be exported on the builder is
67
66
  * written down nowhere. CI supplies values by writing the file before running the command.
67
+ *
68
+ * No credentials file travels: a model that does not authenticate through a variable (or its own definition) logs in
69
+ * on the deployment itself (`fastagent login --deployment`), so the box is the only holder of its grant.
68
70
  */
69
71
  export function assembleSecrets(input) {
70
72
  const secrets = carriedValues(input.values);
71
73
  const missingSecrets = [];
72
- let needsModelCredential = false;
73
- if (isEnvKey(input.modelAuth)) {
74
- if (!secrets[input.modelAuth])
75
- missingSecrets.push(input.modelAuth); // `.env` remediation fits
76
- }
77
- else if (input.authFile) {
78
- secrets.FASTAGENT_AUTH_SEED = input.authFile.toString("base64");
79
- }
80
- else if (input.modelKeyInDefinition) {
81
- // The definition authenticates itself (models.json literal key, or a command run on the host), so it travels in
82
- // the image with everything else.
83
- }
84
- else {
85
- needsModelCredential = true; // no env key, no auth.json — `fastagent login` remediation
86
- }
74
+ if (isEnvKey(input.modelAuth) && !secrets[input.modelAuth])
75
+ missingSecrets.push(input.modelAuth);
87
76
  for (const { name } of dedupeSecrets(input.declared ?? [])) {
88
77
  if (!secrets[name] && !missingSecrets.includes(name))
89
78
  missingSecrets.push(name);
90
79
  }
91
- return { secrets, missingSecrets, needsModelCredential };
80
+ return { secrets, missingSecrets };
92
81
  }
93
82
  /**
94
83
  * The ONE refusal for declared names the deployed environment does not supply a value for — every host reaches it
@@ -101,10 +90,6 @@ export function missingValuesGate(missing, valueFile) {
101
90
  return (`no value for: ${missing.join(", ")} — the deployed environment is declared by ${valueFile}, and this deploy ` +
102
91
  `reads only that file (exporting the variable here does not reach the deployment). Add them there and re-run`);
103
92
  }
104
- /** The bytes to seed to the auth file, or undefined to leave it alone. */
105
- export function authSeedBytes(seed, fileExists) {
106
- return !seed || fileExists ? undefined : Buffer.from(seed, "base64");
107
- }
108
93
  /**
109
94
  * Collect a (possibly CHUNKED) carrier from the environment: `<name>` plus numbered continuations (`_2`, `_3`, …)
110
95
  * concatenated in order. A host whose env values have a length cap splits a long value this way.
@@ -122,10 +107,6 @@ function collectChunked(env, name) {
122
107
  }
123
108
  return value;
124
109
  }
125
- /** `FASTAGENT_AUTH_SEED` (+ `_2`, …): the base64 auth.json a deploy carries. */
126
- export function collectAuthSeed(env) {
127
- return collectChunked(env, "FASTAGENT_AUTH_SEED");
128
- }
129
110
  /**
130
111
  * `FASTAGENT_ENV` (+ `_2`, …): the carried variables as ONE base64 JSON object, for a host whose deployment artifact
131
112
  * must not depend on which names the value file holds (AgentCore's template is committed and gated on drift). The
@@ -5,7 +5,7 @@
5
5
  import { spawn } from "node:child_process";
6
6
  import { relative, sep } from "node:path";
7
7
  import { watch as watchTree } from "chokidar";
8
- import { AGENT_CONFIG_FILE, AGENT_MODELS_FILE, resolveStateRoot, isUnderDir } from "./paths.js";
8
+ import { AGENT_CONFIG_FILE, AGENT_MODEL_CATALOG_FILE, AGENT_MODELS_FILE, resolveStateRoot, isUnderDir, } from "./paths.js";
9
9
  import { dotEnvPath } from "./env.js";
10
10
  import { log } from "./log.js";
11
11
  import { openExternalUrl } from "./open-url.js";
@@ -14,7 +14,7 @@ import { announceWebhooks, startCloudflareTunnel } from "./tunnel.js";
14
14
  /** What the dev watcher restarts on (agent-dir-relative): the process-bound code inputs only. */
15
15
  /** The agent-dir directories loaded ONCE per worker: a restart is their only re-read. */
16
16
  const CODE_INPUT_DIRS = ["tools", "channels", "routines", "extensions"];
17
- const WATCHED_HINT = `${CODE_INPUT_DIRS.map((dir) => `${dir}/`).join(", ")}, package.json, fastagent.config.ts, models.json, .secrets/.env`;
17
+ const WATCHED_HINT = `${CODE_INPUT_DIRS.map((dir) => `${dir}/`).join(", ")}, package.json, fastagent.config.ts, models.json, models-store.json, .secrets/.env`;
18
18
  /** chokidar `ignored` matcher for the narrow watch scope (true = ignore), rooted at the AGENT DIR. */
19
19
  export function devWatchIgnored(root, envFile) {
20
20
  // The `.env` is allow-listed by its RESOLVED path, not by the `.secrets` name.
@@ -29,10 +29,13 @@ export function devWatchIgnored(root, envFile) {
29
29
  return false;
30
30
  if (rel === "package.json")
31
31
  return false;
32
- // models.json is read ONCE per worker (the model hub is built during assembly), so an edit needs a restart like
33
- // any other code input.
32
+ // models.json is read ONCE per worker (agentModels' snapshot, which every session's runtime is built from), so an
33
+ // edit needs a restart like any other code input.
34
34
  if (rel === AGENT_MODELS_FILE)
35
35
  return false;
36
+ // So is the model catalog: a `models --refresh` during `dev` restarts the worker onto the new models.
37
+ if (rel === AGENT_MODEL_CATALOG_FILE)
38
+ return false;
36
39
  const segments = rel.split(sep);
37
40
  if (CODE_INPUT_DIRS.includes(segments[0]))
38
41
  return false;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * An agent's MODEL ENVIRONMENT as one value: the credential store its turns authenticate through, the registry they
3
+ * resolve models against, and what authenticates a provider there. Every reader builds it through {@link agentModels}
4
+ * — the opener and L2, the startup report and a deployed box's `login --if-missing`, `info`, the first-run picker,
5
+ * login, the model list — so none of them can read other credential layers or another registry than the runtime
6
+ * does. Each used to compose these parts itself, and two of those copies drifted from the rule (#636, #660).
7
+ */
8
+ import type { ExecutionEnv } from "@earendil-works/pi-agent-core";
9
+ import type { Credential, CredentialStore, Models, Provider } from "@earendil-works/pi-ai";
10
+ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
11
+ import { type AuthLayers, type CredentialSourceOptions, type FastagentAuthOptions } from "./auth.ts";
12
+ /** What authenticates one provider for an agent ({@link AgentModels.authStatus}). */
13
+ interface AuthStatus {
14
+ /** What satisfies it now (`OAuth`, `stored credential`, an env variable's name), after a due refresh. */
15
+ source?: string;
16
+ /** Why resolving it failed (a corrupt entry, a refresh the provider refused), when it did. */
17
+ error?: string;
18
+ /** The file that holds the stored credential, or the one a login would write; absent over a caller's store. */
19
+ path?: string;
20
+ /** The kind of credential the store holds for it, usable or not. */
21
+ stored?: Credential["type"];
22
+ /**
23
+ * An env variable that also authenticates it but goes unused, because pi lets a stored credential own its provider
24
+ * — a key added to a deployment's value file after the box was logged in, typically.
25
+ */
26
+ shadowed?: string;
27
+ }
28
+ export interface AgentModels {
29
+ /** The credentials files behind {@link credentials}; absent when the caller supplied its own store. */
30
+ auth?: AuthLayers;
31
+ credentials: CredentialStore;
32
+ /**
33
+ * The registry: pi's built-ins, plus (with a directory) the agent's `models.json` and model catalog over the
34
+ * machine's, plus `providers` and extension model declarations. An unbound catalog, built on first use and shared
35
+ * after; session bindings use createRuntime and their own extension instances.
36
+ */
37
+ runtime(): Promise<ModelRuntime>;
38
+ /** A session-local registry, before its extensions are loaded. Credentials remain shared. */
39
+ createRuntime(): Promise<ModelRuntime>;
40
+ /**
41
+ * What authenticates `provider` here, resolved exactly as a turn resolves it: the ONE answer the startup report and a
42
+ * deployed box's `login --if-missing` both give. `modelId` picks the model to probe with; the provider's first one
43
+ * otherwise (auth is provider-scoped).
44
+ */
45
+ authStatus(provider: string, modelId?: string): Promise<AuthStatus>;
46
+ /**
47
+ * The definition's extension entry points, discovered once: what this catalog registers models from and what every
48
+ * session of the assembly built over it loads. Empty without a directory.
49
+ */
50
+ extensionPaths(): Promise<readonly string[]>;
51
+ }
52
+ export interface AgentModelsOptions {
53
+ /** Workspace for extension model registration; defaults to agentDir for standalone definitions. */
54
+ cwd?: string;
55
+ /** Where `extensions/` is listed from; Node's filesystem by default. */
56
+ env?: ExecutionEnv;
57
+ /** Extra providers registered on top of the built-ins (a same id replaces one). */
58
+ providers?: readonly Provider[];
59
+ /**
60
+ * Layer the machine's models.json and model catalog under the agent's (default). Off for the registry a DEPLOYED
61
+ * agent has, since neither ships.
62
+ */
63
+ machineLayer?: boolean;
64
+ }
65
+ /**
66
+ * The model environment of the agent in `agentDir` (or, without one, of a caller with no directory: pi's built-ins
67
+ * over the named or global credentials file). A supplied `credentialStore` is used as given; otherwise the files:
68
+ * the directory's own layers ({@link resolveAuthLayers}), or the named file or the global one.
69
+ */
70
+ export declare function agentModels(agentDir: string | undefined, source?: {
71
+ authPath?: string;
72
+ credentialStore?: undefined;
73
+ } & FastagentAuthOptions, options?: AgentModelsOptions): AgentModels & {
74
+ auth: AuthLayers;
75
+ };
76
+ export declare function agentModels(agentDir: string | undefined, source?: CredentialSourceOptions & FastagentAuthOptions, options?: AgentModelsOptions): AgentModels;
77
+ export interface CreatePiModelsOptions extends FastagentAuthOptions, CredentialSourceOptions {
78
+ /** Extra providers registered on top of the built-ins (same id overrides a built-in). */
79
+ providers?: Provider[];
80
+ }
81
+ /** A `Models` with every built-in pi provider, wired to fastagent's auth. */
82
+ export declare function createPiModels(options?: CreatePiModelsOptions): Models;
83
+ export {};
@@ -0,0 +1,120 @@
1
+ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
2
+ import { definitionServices } from "./agent-session-factory.js";
3
+ import { loadExtensionPaths } from "./definition.js";
4
+ import { GLOBAL_AUTH_PATH, assertOneCredentialSource, fastagentCredentialStore, resolveAuthLayers, } from "./auth.js";
5
+ import { createPiModelRuntime, environmentAuthSource, modelRuntimeFiles, piModelsOver, probeAuthSource, } from "./models.js";
6
+ export function agentModels(agentDir, source = {}, options = {}) {
7
+ assertOneCredentialSource(source);
8
+ const { providers, machineLayer } = options;
9
+ const files = source.credentialStore
10
+ ? undefined
11
+ : agentDir
12
+ ? resolveAuthLayers(agentDir, source.authPath)
13
+ : { path: source.authPath ?? GLOBAL_AUTH_PATH };
14
+ // The model files, read once for every runtime this agent builds: a session's and the control plane's.
15
+ let modelFiles;
16
+ const readModelFiles = () => (modelFiles ??= modelRuntimeFiles({
17
+ ...(agentDir ? { agentDir } : {}),
18
+ ...(machineLayer !== undefined ? { machineLayer } : {}),
19
+ }));
20
+ const store = files
21
+ ? agentCredentialStore(files, readModelFiles, { providers, warn: source.warn })
22
+ : undefined;
23
+ const credentials = source.credentialStore ?? store;
24
+ const createRuntime = async () => createPiModelRuntime({
25
+ credentials,
26
+ files: await readModelFiles(),
27
+ ...(agentDir ? { agentDir } : {}),
28
+ ...(providers ? { providers } : {}),
29
+ ...(machineLayer !== undefined ? { machineLayer } : {}),
30
+ });
31
+ const cwd = options.cwd ?? agentDir;
32
+ let discovered;
33
+ const extensionPaths = () => (discovered ??= agentDir
34
+ ? loadExtensionPaths(agentDir, { ...(cwd ? { cwd } : {}), ...(options.env ? { env: options.env } : {}) })
35
+ : Promise.resolve([]));
36
+ let registry;
37
+ const runtime = () => {
38
+ registry ??= createRuntime().then(async (models) => {
39
+ if (agentDir) {
40
+ await definitionServices({
41
+ cwd: cwd ?? agentDir,
42
+ modelRuntime: models,
43
+ definition: { skills: [] },
44
+ extensionPaths: await extensionPaths(),
45
+ });
46
+ }
47
+ return models;
48
+ });
49
+ return registry;
50
+ };
51
+ return {
52
+ ...(files ? { auth: files } : {}),
53
+ credentials,
54
+ runtime,
55
+ createRuntime,
56
+ extensionPaths,
57
+ async authStatus(provider, modelId) {
58
+ const models = await runtime();
59
+ const id = modelId ?? models.getProvider(provider)?.getModels()[0]?.id;
60
+ let resolved = {};
61
+ if (id !== undefined) {
62
+ try {
63
+ const found = await probeAuthSource(models, `${provider}/${id}`);
64
+ if (found !== undefined)
65
+ resolved = { source: found };
66
+ }
67
+ catch (error) {
68
+ // The first line: pi folds the request's stack into the message of a failed refresh.
69
+ resolved = { error: String(error.message).split("\n")[0] };
70
+ }
71
+ }
72
+ // `read` never refreshes: this is the kind on file, whatever became of it.
73
+ const stored = (await credentials.read(provider))?.type;
74
+ const fromEnvironment = stored === undefined ? undefined : await environmentAuthSource(provider, process.env);
75
+ const shadowed = resolved.source !== undefined && fromEnvironment !== resolved.source ? fromEnvironment : undefined;
76
+ return {
77
+ ...(store ? { path: await store.layerOf(provider) } : {}),
78
+ ...resolved,
79
+ ...(stored !== undefined ? { stored } : {}),
80
+ ...(shadowed !== undefined ? { shadowed } : {}),
81
+ };
82
+ },
83
+ };
84
+ }
85
+ /** A `Models` with every built-in pi provider, wired to fastagent's auth. */
86
+ export function createPiModels(options = {}) {
87
+ return piModelsOver(agentModels(undefined, options).credentials, options.providers);
88
+ }
89
+ /**
90
+ * The credential store an agent reads through: its own file, then the user-global one for a provider the PROJECT
91
+ * authenticates no other way (its own auth file, a models.json key, or the environment). "No other way" is answered
92
+ * by pi's own resolution over a registry that has no global layer, built the first time a provider is looked up
93
+ * there. A deployment never has the global file, so a global login that outranked, say, an `ANTHROPIC_API_KEY` in
94
+ * `.secrets/.env` would run one credential here and a different one deployed.
95
+ */
96
+ function agentCredentialStore(auth, modelFiles, options) {
97
+ const { warn, providers = [] } = options;
98
+ const fallback = auth.fallback;
99
+ if (fallback === undefined)
100
+ return fastagentCredentialStore(auth.path, { warn });
101
+ let project;
102
+ const projectRuntime = () => {
103
+ project ??= (async () => {
104
+ const runtime = await ModelRuntime.create({
105
+ credentials: fastagentCredentialStore(auth.path, { warn }),
106
+ ...(await (await modelFiles()).create()),
107
+ refreshOnCreate: false,
108
+ });
109
+ for (const provider of providers)
110
+ runtime.registerNativeProvider(provider);
111
+ return runtime;
112
+ })();
113
+ return project;
114
+ };
115
+ return fastagentCredentialStore(auth.path, {
116
+ warn,
117
+ fallbackPath: fallback,
118
+ projectAuthenticates: async (providerId) => (await (await projectRuntime()).checkAuth(providerId)) !== undefined,
119
+ });
120
+ }
@@ -3,7 +3,7 @@ import type { Skill, ThinkingLevel } from "@earendil-works/pi-agent-core";
3
3
  import { type AgentSession, type AgentSessionServices, type CompactionResult, type CreateAgentSessionServicesOptions, type ModelRuntime, type ResolvedCommand, SessionManager, createAgentSessionFromServices } from "@earendil-works/pi-coding-agent";
4
4
  import type { PiAgentSessionFactory } from "./invoke-session.ts";
5
5
  import type { PiSessionRecordStore } from "./session-store.ts";
6
- import { type MountedTool } from "./tool.ts";
6
+ import type { MountedTool } from "./tool.ts";
7
7
  import { type AnyModel } from "./models.ts";
8
8
  interface PiSessionDefinition {
9
9
  systemPrompt?: string;
@@ -12,11 +12,12 @@ interface PiSessionDefinition {
12
12
  export interface PiAgentSessionFactoryOptions {
13
13
  /** Where conversations live. */
14
14
  sessions: PiSessionRecordStore;
15
- /** The model to run and the hub that authenticates it, resolved on FIRST USE and kept. */
15
+ /** A fresh model runtime per binding: extension routers and provider registrations belong to this session. */
16
16
  engine: () => Promise<{
17
17
  modelRuntime: ModelRuntime;
18
- model: AnyModel;
19
18
  }>;
19
+ /** Resolved after extensions register their models. */
20
+ modelSpec: string;
20
21
  thinkingLevel?: ThinkingLevel;
21
22
  tools?: MountedTool[];
22
23
  /** Read once per binding; prompt and skills come from the same definition read. */
@@ -42,19 +43,11 @@ export interface BindPiSessionOptions {
42
43
  excludedToolNames?: readonly string[];
43
44
  /** The CALLER's session id — what a tool asking which conversation it is in hears. */
44
45
  sessionId?: string;
45
- /** Record each discovered activation on the session, so the next bind restores it. */
46
- recordActivations: boolean;
47
46
  }
48
47
  /**
49
- * Bind ONE pi session to a record: the definition's tools as pi definitions over one turn context, pi's own tool
50
- * copies kept off, and deferral applied.
48
+ * Bind ONE pi session to a record: authored tools replace same-name built-ins; Pi owns exposure and discovery.
51
49
  */
52
50
  export declare function bindPiSession(options: BindPiSessionOptions): ReturnType<typeof createAgentSessionFromServices>;
53
- /**
54
- * Announce extensions pi failed to load. pi collects them into `LoadExtensionsResult.errors` and carries on with the
55
- * rest.
56
- */
57
- export declare function reportExtensionErrors(services: AgentSessionServices): void;
58
51
  /** What pi is allowed to discover, minus the parts each assembly fills in itself. */
59
52
  type DefinitionLoaderOptions = NonNullable<CreateAgentSessionServicesOptions["resourceLoaderOptions"]>;
60
53
  /** The resource posture a fastagent definition asks pi for — ONE definition of it, for both assemblies. */
@@ -81,9 +74,16 @@ export declare function startCompaction(session: AgentSession, instructions: str
81
74
  admission: Promise<CompactAdmission>;
82
75
  done: Promise<CompactionResult>;
83
76
  };
77
+ /** A fresh loader and Pi's native model registration, over a session-local runtime. */
78
+ export declare function definitionServices(options: {
79
+ cwd: string;
80
+ modelRuntime: ModelRuntime;
81
+ definition: PiSessionDefinition;
82
+ extensionPaths: readonly string[];
83
+ }): Promise<AgentSessionServices>;
84
84
  /**
85
85
  * The `/name` commands a served session dispatches, named the way pi resolves them (a name two extensions share gets
86
- * a `:N` suffix). Loaded through the same {@link servingServices} a turn binds, so the menu and the dispatch cannot
86
+ * a `:N` suffix). Loaded through the same {@link definitionServices} a turn binds, so the menu and the dispatch cannot
87
87
  * disagree. Loading runs the extensions' factories; no session opens, so `session_start` does not fire, and the
88
88
  * instances are never bound (any action they call throws).
89
89
  */