@fastagent-sh/fastagent 0.22.0 → 0.23.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.
- package/dist/channels/agentcore-protocol.d.ts +2 -1
- package/dist/channels/agentcore-service.js +10 -18
- package/dist/channels/agentcore.js +6 -3
- package/dist/channels/control.js +23 -0
- package/dist/channels/slack/config-api.d.ts +2 -2
- package/dist/channels/slack/config-api.js +2 -2
- package/dist/channels/slack/onboard.js +2 -2
- package/dist/channels/slack/register-webhook.js +2 -2
- package/dist/channels/slack/slack-api.js +4 -4
- package/dist/cli/add-feishu.js +4 -2
- package/dist/cli/auth-view.d.ts +15 -6
- package/dist/cli/auth-view.js +24 -8
- package/dist/cli/box-login.d.ts +33 -0
- package/dist/cli/box-login.js +162 -0
- package/dist/cli/commands/deploy/agentcore.js +48 -11
- package/dist/cli/commands/deploy/docker.js +24 -13
- package/dist/cli/commands/deploy/fly.js +30 -7
- package/dist/cli/commands/deploy/railway.js +21 -7
- package/dist/cli/commands/deploy/shared.d.ts +18 -17
- package/dist/cli/commands/deploy/shared.js +20 -21
- package/dist/cli/commands/destroy.js +2 -2
- package/dist/cli/commands/info.js +9 -6
- package/dist/cli/commands/invoke.js +2 -2
- package/dist/cli/commands/login.d.ts +7 -1
- package/dist/cli/commands/login.js +123 -10
- package/dist/cli/commands/logs.js +2 -2
- package/dist/cli/commands/models.d.ts +4 -1
- package/dist/cli/commands/models.js +37 -4
- package/dist/cli/commands/routine-run.js +2 -2
- package/dist/cli/commands/start.d.ts +0 -5
- package/dist/cli/commands/start.js +3 -13
- package/dist/cli/kernel.d.ts +2 -0
- package/dist/cli/kernel.js +1 -1
- package/dist/cli/login-relay.d.ts +38 -0
- package/dist/cli/login-relay.js +121 -0
- package/dist/cli/program.js +46 -8
- package/dist/cli/shared.d.ts +4 -4
- package/dist/cli/shared.js +24 -30
- package/dist/deploy/agentcore/plan.d.ts +10 -11
- package/dist/deploy/agentcore/plan.js +8 -14
- package/dist/deploy/agentcore/run.d.ts +8 -1
- package/dist/deploy/agentcore/run.js +20 -14
- package/dist/deploy/agentcore/shell.d.ts +25 -0
- package/dist/deploy/agentcore/shell.js +202 -0
- package/dist/deploy/box-shell.d.ts +43 -0
- package/dist/deploy/box-shell.js +28 -0
- package/dist/deploy/build-context.js +16 -7
- package/dist/deploy/channel-ingress.d.ts +20 -1
- package/dist/deploy/channel-ingress.js +34 -7
- package/dist/deploy/container.d.ts +14 -0
- package/dist/deploy/container.js +45 -1
- package/dist/deploy/docker/plan.d.ts +7 -3
- package/dist/deploy/docker/plan.js +9 -14
- package/dist/deploy/docker/run.d.ts +14 -7
- package/dist/deploy/docker/run.js +58 -35
- package/dist/deploy/fly/plan.d.ts +6 -3
- package/dist/deploy/fly/plan.js +6 -8
- package/dist/deploy/fly/run.d.ts +8 -2
- package/dist/deploy/fly/run.js +15 -25
- package/dist/deploy/preflight.d.ts +9 -7
- package/dist/deploy/preflight.js +74 -20
- package/dist/deploy/railway/plan.d.ts +6 -3
- package/dist/deploy/railway/plan.js +7 -9
- package/dist/deploy/railway/run.d.ts +10 -6
- package/dist/deploy/railway/run.js +29 -13
- package/dist/deploy/runner.d.ts +16 -0
- package/dist/deploy/runner.js +31 -0
- package/dist/deploy/secrets.d.ts +4 -13
- package/dist/deploy/secrets.js +8 -27
- package/dist/dev-supervisor.js +5 -2
- package/dist/engines/pi/agent-models.d.ts +70 -0
- package/dist/engines/pi/agent-models.js +93 -0
- package/dist/engines/pi/auth.d.ts +56 -3
- package/dist/engines/pi/auth.js +63 -16
- package/dist/engines/pi/config.d.ts +0 -25
- package/dist/engines/pi/config.js +3 -37
- package/dist/engines/pi/create.d.ts +22 -16
- package/dist/engines/pi/create.js +28 -28
- package/dist/engines/pi/entry-images.d.ts +14 -0
- package/dist/engines/pi/entry-images.js +23 -0
- package/dist/engines/pi/invoke-session.js +26 -0
- package/dist/engines/pi/login.d.ts +20 -13
- package/dist/engines/pi/login.js +19 -12
- package/dist/engines/pi/models.d.ts +115 -36
- package/dist/engines/pi/models.js +218 -48
- package/dist/engines/pi/open.d.ts +42 -11
- package/dist/engines/pi/open.js +53 -21
- package/dist/engines/pi/service.d.ts +2 -2
- package/dist/engines/pi/service.js +1 -0
- package/dist/engines/pi/session-builder.d.ts +3 -2
- package/dist/engines/pi/session-builder.js +28 -58
- package/dist/engines/pi/session-control.js +14 -3
- package/dist/engines/pi/turn-kit.js +11 -5
- package/dist/open-url.d.ts +15 -1
- package/dist/open-url.js +35 -3
- package/dist/paths.d.ts +4 -2
- package/dist/paths.js +6 -2
- package/dist/pi.d.ts +5 -3
- package/dist/pi.js +3 -2
- package/dist/service.d.ts +5 -8
- package/dist/session-remote.js +21 -0
- package/dist/session.d.ts +39 -6
- package/package.json +2 -1
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Building pi's model registry: the built-ins, an agent's `models.json` and model catalog layered over the machine's,
|
|
3
|
+
* the catalog refresh, and the reads of those files a report or a deploy check needs. Which credential store an agent
|
|
4
|
+
* runs it over is agent-models.ts's; registry and auth are one collection, so the model's provider auth is in scope.
|
|
5
5
|
*/
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { readFile } from "node:fs/promises";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
9
|
import { join } from "node:path";
|
|
11
|
-
import { InMemoryCredentialStore, defaultProviderAuthContext, } from "@earendil-works/pi-ai";
|
|
12
|
-
import { builtinModels, builtinProviders } from "@earendil-works/pi-ai/providers/all";
|
|
10
|
+
import { InMemoryCredentialStore, InMemoryModelsStore, defaultProviderAuthContext, } from "@earendil-works/pi-ai";
|
|
11
|
+
import { builtinModels, builtinProviders, getBuiltinModelDataGeneratedAt } from "@earendil-works/pi-ai/providers/all";
|
|
13
12
|
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import { fastagentCredentialStore } from "./auth.js";
|
|
15
|
-
import {
|
|
16
|
-
import { AGENT_MODELS_FILE, GLOBAL_HOME_DIR, resolveOverridePath, resolveStateRoot } from "../../paths.js";
|
|
13
|
+
import { GLOBAL_AUTH_PATH, fastagentCredentialStore } from "./auth.js";
|
|
14
|
+
import { AGENT_MODEL_CATALOG_FILE, AGENT_MODELS_FILE, globalHome, resolveOverridePath } from "../../paths.js";
|
|
17
15
|
import { writeFileAtomic } from "../../atomic-write.js";
|
|
18
|
-
/** A `Models` with every built-in pi provider, wired to fastagent's auth. */
|
|
19
|
-
export function createPiModels(options = {}) {
|
|
20
|
-
return piModelsOver(fastagentCredentialStore(options.authPath, { warn: options.warn }), options.providers);
|
|
21
|
-
}
|
|
22
16
|
/** The built-ins plus `providers` (a same id replaces a built-in), over any credential store. */
|
|
23
17
|
export function piModelsOver(credentials, providers = []) {
|
|
24
18
|
const models = builtinModels({ credentials, authContext: defaultProviderAuthContext() });
|
|
@@ -33,8 +27,8 @@ export const DEFAULT_THINKING_LEVEL = "medium";
|
|
|
33
27
|
* (a local Ollama, a company gateway), which every agent here inherits the way it inherits the machine's skills.
|
|
34
28
|
* `FASTAGENT_MODELS_PATH` moves it. It never ships: a deployed agent has only its definition's file.
|
|
35
29
|
*/
|
|
36
|
-
|
|
37
|
-
return resolveOverridePath(env.FASTAGENT_MODELS_PATH) ?? join(
|
|
30
|
+
function machineModelsPath(env = process.env) {
|
|
31
|
+
return resolveOverridePath(env.FASTAGENT_MODELS_PATH) ?? join(globalHome(), AGENT_MODELS_FILE);
|
|
38
32
|
}
|
|
39
33
|
/** A custom-endpoint file's providers by id, or undefined when there is no file. Strict JSON: see {@link modelLayers}. */
|
|
40
34
|
async function readProviders(path) {
|
|
@@ -95,7 +89,7 @@ async function modelsFileFor(agentDir) {
|
|
|
95
89
|
return { path: definition };
|
|
96
90
|
const content = JSON.stringify({ providers: { ...layers.machine, ...layers.own } }, null, 2);
|
|
97
91
|
const hash = createHash("sha256").update(content).digest("hex").slice(0, 32);
|
|
98
|
-
const path = join(
|
|
92
|
+
const path = join(globalHome(), ".cache", "models", `${hash}.json`);
|
|
99
93
|
// 0600: it may carry literal keys. Several processes may create the same snapshot at once.
|
|
100
94
|
if (!existsSync(path))
|
|
101
95
|
writeFileAtomic(path, content, 0o600, true);
|
|
@@ -120,25 +114,190 @@ export async function machineModels(agentDir) {
|
|
|
120
114
|
export function isBuiltinProvider(providerId) {
|
|
121
115
|
return builtinProviders().some((provider) => provider.id === providerId);
|
|
122
116
|
}
|
|
123
|
-
/**
|
|
124
|
-
|
|
117
|
+
/**
|
|
118
|
+
* The MACHINE's model catalog: the models pi.dev lists that are newer than the catalog bundled with pi, fetched by
|
|
119
|
+
* `fastagent models --refresh -g`. Every agent here reads it under its own; like `~/.fastagent/models.json`, it
|
|
120
|
+
* never ships.
|
|
121
|
+
*/
|
|
122
|
+
export function globalCatalogPath() {
|
|
123
|
+
return join(globalHome(), AGENT_MODEL_CATALOG_FILE);
|
|
124
|
+
}
|
|
125
|
+
/** A catalog file as pi writes it (entries by provider), or `{}` when there is none. */
|
|
126
|
+
async function readCatalog(path) {
|
|
127
|
+
if (!existsSync(path))
|
|
128
|
+
return {};
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
throw new Error(`model catalog ${path} is not valid JSON (${error.message}): refresh or delete it`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The catalog files layered into one read-only store, later files winning a model id. An entry pi would ignore (no
|
|
138
|
+
* newer than the catalog bundled with it) is dropped before the merge, so a stale layer cannot ride a newer one's
|
|
139
|
+
* date past pi's rule.
|
|
140
|
+
*
|
|
141
|
+
* Read here rather than handed to pi as a file: pi takes one store, and its file store creates the file (and a lock)
|
|
142
|
+
* on read, which would drop an empty catalog into every agent dir that was never refreshed.
|
|
143
|
+
*/
|
|
144
|
+
async function layeredCatalog(paths) {
|
|
145
|
+
const bundledAt = getBuiltinModelDataGeneratedAt();
|
|
146
|
+
const merged = new Map();
|
|
147
|
+
for (const path of paths) {
|
|
148
|
+
for (const [provider, entry] of Object.entries(await readCatalog(path))) {
|
|
149
|
+
if (bundledAt !== undefined && (entry.lastModified === undefined || entry.lastModified <= bundledAt))
|
|
150
|
+
continue;
|
|
151
|
+
const held = merged.get(provider);
|
|
152
|
+
const ids = new Set(entry.models.map((model) => model.id));
|
|
153
|
+
merged.set(provider, held
|
|
154
|
+
? {
|
|
155
|
+
models: [...held.models.filter((model) => !ids.has(model.id)), ...entry.models],
|
|
156
|
+
lastModified: Math.max(held.lastModified ?? 0, entry.lastModified ?? 0),
|
|
157
|
+
}
|
|
158
|
+
: entry);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const store = new InMemoryModelsStore();
|
|
162
|
+
for (const [provider, entry] of merged)
|
|
163
|
+
await store.write(provider, entry);
|
|
164
|
+
return store;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Whether the machine's model catalog supplies this model, by the same rule every registry reads it with
|
|
168
|
+
* ({@link layeredCatalog}): an entry pi would ignore supplies nothing. The one layer a deployed agent does not have.
|
|
169
|
+
*/
|
|
170
|
+
export async function inGlobalCatalog(provider, id) {
|
|
171
|
+
const entry = await (await layeredCatalog([globalCatalogPath()])).read(provider);
|
|
172
|
+
return entry?.models.some((model) => model.id === id) ?? false;
|
|
173
|
+
}
|
|
174
|
+
/** The models.json a runtime for these options loads, and which model catalog it reads. */
|
|
175
|
+
export async function modelRuntimeFiles(options) {
|
|
125
176
|
const { agentDir } = options;
|
|
177
|
+
const machine = options.machineLayer !== false;
|
|
126
178
|
const models = !agentDir
|
|
127
179
|
? undefined
|
|
128
|
-
:
|
|
129
|
-
?
|
|
130
|
-
:
|
|
180
|
+
: machine
|
|
181
|
+
? await modelsFileFor(agentDir)
|
|
182
|
+
: { path: join(agentDir, AGENT_MODELS_FILE) };
|
|
183
|
+
const catalogs = !agentDir
|
|
184
|
+
? []
|
|
185
|
+
: [...(machine ? [globalCatalogPath()] : []), join(agentDir, AGENT_MODEL_CATALOG_FILE)];
|
|
186
|
+
return {
|
|
187
|
+
models,
|
|
188
|
+
create: {
|
|
189
|
+
modelsPath: models?.path ?? null,
|
|
190
|
+
// Always a store of our own whenever modelsPath is set: pi's default is a file at
|
|
191
|
+
// `<dirname(modelsPath)>/models-store.json`. Without a directory, pi keeps an empty one in memory.
|
|
192
|
+
...(options.catalogFile
|
|
193
|
+
? { modelsStorePath: options.catalogFile }
|
|
194
|
+
: agentDir
|
|
195
|
+
? { modelsStore: await layeredCatalog(catalogs) }
|
|
196
|
+
: {}),
|
|
197
|
+
// Never fetched while a runtime is built: serving stays offline and reproducible. A refresh is asked for.
|
|
198
|
+
allowModelNetwork: false,
|
|
199
|
+
...(options.catalogBaseUrl ? { catalogBaseUrl: options.catalogBaseUrl } : {}),
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* This machine's registry, without an agent: pi's built-ins, the machine's models.json and its model catalog. What
|
|
205
|
+
* `fastagent models` lists outside an agent; {@link catalogFile} is the refresh's (it writes the file).
|
|
206
|
+
*/
|
|
207
|
+
export async function machineModelRuntime(options = {}) {
|
|
131
208
|
const runtime = await ModelRuntime.create({
|
|
132
|
-
credentials: options.credentials ??
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
...(agentDir
|
|
138
|
-
? { modelsStorePath: join(options.stateRoot ?? resolveStateRoot(agentDir), "models-store.json") }
|
|
139
|
-
: {}),
|
|
209
|
+
credentials: options.credentials ?? new InMemoryCredentialStore(),
|
|
210
|
+
modelsPath: machineModelsPath(),
|
|
211
|
+
...(options.catalogFile
|
|
212
|
+
? { modelsStorePath: options.catalogFile }
|
|
213
|
+
: { modelsStore: await layeredCatalog([globalCatalogPath()]) }),
|
|
140
214
|
allowModelNetwork: false,
|
|
215
|
+
...(options.catalogBaseUrl ? { catalogBaseUrl: options.catalogBaseUrl } : {}),
|
|
216
|
+
});
|
|
217
|
+
const error = runtime.getError();
|
|
218
|
+
if (error)
|
|
219
|
+
throw new Error(error);
|
|
220
|
+
return runtime;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Refresh the machine's catalog ({@link globalCatalogPath}) with the machine's credentials: the global credentials
|
|
224
|
+
* file and the environment. See {@link refreshCatalog} for what it asks and when it rejects.
|
|
225
|
+
*/
|
|
226
|
+
export async function refreshGlobalModelCatalog(options = {}) {
|
|
227
|
+
const credentials = fastagentCredentialStore(GLOBAL_AUTH_PATH);
|
|
228
|
+
await refreshCatalog((catalogFile) => machineModelRuntime({
|
|
229
|
+
credentials,
|
|
230
|
+
...(catalogFile ? { catalogFile: globalCatalogPath() } : {}),
|
|
231
|
+
...(options.catalogBaseUrl ? { catalogBaseUrl: options.catalogBaseUrl } : {}),
|
|
232
|
+
}), options.signal ? { signal: options.signal } : {});
|
|
233
|
+
}
|
|
234
|
+
/** How long a catalog refresh may take, as `pi update --models` allows. */
|
|
235
|
+
const CATALOG_REFRESH_TIMEOUT_MS = 15_000;
|
|
236
|
+
/**
|
|
237
|
+
* Fetch the model catalog of every provider the runtime can authenticate into its catalog file: the refresh
|
|
238
|
+
* `pi update --models` runs. `runtime(true)` is built over that file, `runtime(false)` over the read-only layers; the
|
|
239
|
+
* file-backed one is built only once the refresh is known to run, because pi's file store creates the file on read and
|
|
240
|
+
* a refused refresh must leave nothing behind. pi asks pi.dev only for a provider with a usable
|
|
241
|
+
* credential, and may refresh an expired OAuth token of the runtime's store to get one. Rejects, naming each provider
|
|
242
|
+
* that failed, when any part fails, when it outlasts 15 seconds, when `PI_OFFLINE` is set, and when no provider has a
|
|
243
|
+
* usable credential (the refresh would ask for nothing).
|
|
244
|
+
*/
|
|
245
|
+
export async function refreshCatalog(runtime, options = {}) {
|
|
246
|
+
// pi skips its own background refresh under PI_OFFLINE, but an explicit `allowNetwork: true` overrides that.
|
|
247
|
+
if (process.env.PI_OFFLINE !== undefined)
|
|
248
|
+
throw new Error("PI_OFFLINE is set, so the model catalog is not refreshed");
|
|
249
|
+
// pi skips a provider it cannot authenticate without recording anything, so with no usable credential at all the
|
|
250
|
+
// refresh would "succeed" having asked for nothing.
|
|
251
|
+
const probe = await runtime(false);
|
|
252
|
+
const refreshable = probe.getProviders().filter((provider) => provider.refreshModels !== undefined);
|
|
253
|
+
const usable = await Promise.all(refreshable.map(async (provider) => (await probe.checkAuth(provider.id)) !== undefined));
|
|
254
|
+
if (!usable.includes(true)) {
|
|
255
|
+
throw new Error("no provider has a usable credential here, so there is no model catalog to fetch — log in " +
|
|
256
|
+
"(`fastagent login`) or set a provider's API key first");
|
|
257
|
+
}
|
|
258
|
+
const timeout = AbortSignal.timeout(CATALOG_REFRESH_TIMEOUT_MS);
|
|
259
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
260
|
+
const result = await (await runtime(true)).refresh({ allowNetwork: true, force: true, signal });
|
|
261
|
+
if (result.aborted) {
|
|
262
|
+
throw new Error(options.signal?.aborted
|
|
263
|
+
? "model catalog refresh cancelled"
|
|
264
|
+
: `model catalog refresh did not finish within ${CATALOG_REFRESH_TIMEOUT_MS / 1000}s`);
|
|
265
|
+
}
|
|
266
|
+
if (result.errors.size > 0) {
|
|
267
|
+
const details = [...result.errors].map(([provider, error]) => `${provider}: ${error.message}`).join("; ");
|
|
268
|
+
throw new Error(`could not refresh the model catalog: ${details}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* What satisfies a provider from `env` alone (its API-key variable, or an ambient source such as an AWS profile), by
|
|
273
|
+
* pi's own check, or undefined. Passed explicitly rather than read from `process.env`: only the environment a SHELL
|
|
274
|
+
* hands down is shared by every agent on the machine, and a process that has loaded an agent's `.secrets/.env` holds
|
|
275
|
+
* that agent's variables too. What the shell shares outranks the global credentials file (agent-models.ts),
|
|
276
|
+
* so a global login for such a provider is not used.
|
|
277
|
+
*
|
|
278
|
+
* `fileExists` answers for the files a source points at (an AWS profile, Google ADC): this machine's by default, which
|
|
279
|
+
* is right wherever the answer is about THIS machine. A deployment passes one that finds nothing, because no file of
|
|
280
|
+
* the builder's travels with it.
|
|
281
|
+
*/
|
|
282
|
+
export async function environmentAuthSource(providerId, env, fileExists = defaultProviderAuthContext().fileExists) {
|
|
283
|
+
const models = builtinModels({
|
|
284
|
+
credentials: new InMemoryCredentialStore(),
|
|
285
|
+
authContext: {
|
|
286
|
+
// pi's own reading of a variable (a blank one is unset), over the given environment.
|
|
287
|
+
env: async (name) => (env[name]?.trim() ? env[name] : undefined),
|
|
288
|
+
fileExists,
|
|
289
|
+
},
|
|
141
290
|
});
|
|
291
|
+
return (await models.checkAuth(providerId))?.source;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* A registry over the given credential store. What an agent runs on is built through `agentModels`
|
|
295
|
+
* (agent-models.ts); this is for a registry over some OTHER store: none at all (`models`, a deploy check of what
|
|
296
|
+
* ships), a trial key under test (login), or the catalog file a refresh writes.
|
|
297
|
+
*/
|
|
298
|
+
export async function createPiModelRuntime(options) {
|
|
299
|
+
const { models, create } = await modelRuntimeFiles(options);
|
|
300
|
+
const runtime = await ModelRuntime.create({ credentials: options.credentials, ...create });
|
|
142
301
|
// A malformed models.json does NOT throw upstream — `create` resolves with the built-ins and parks the reason in
|
|
143
302
|
// getError().
|
|
144
303
|
const error = runtime.getError();
|
|
@@ -152,17 +311,6 @@ export async function createPiModelRuntime(options = {}) {
|
|
|
152
311
|
runtime.registerNativeProvider(provider);
|
|
153
312
|
return runtime;
|
|
154
313
|
}
|
|
155
|
-
/** How a model's credential will REACH a deployed agent. */
|
|
156
|
-
export function modelCredentialCarry(runtime, spec) {
|
|
157
|
-
const status = runtime.getProviderAuthStatus(providerOf(spec));
|
|
158
|
-
if (!status.configured)
|
|
159
|
-
return { inDefinition: false };
|
|
160
|
-
// An env-var name is only useful downstream if it IS one.
|
|
161
|
-
if (status.source === "environment" && status.label && /^[A-Z][A-Z0-9_]*$/.test(status.label)) {
|
|
162
|
-
return { envVar: status.label, inDefinition: false };
|
|
163
|
-
}
|
|
164
|
-
return { inDefinition: status.source !== "stored" };
|
|
165
|
-
}
|
|
166
314
|
/**
|
|
167
315
|
* EVERY provider whose `models.json` entry writes its key as a LITERAL rather than a `"$NAME"` reference or a
|
|
168
316
|
* `"!cmd"`. The file ships inside the image, so a literal there is a credential in a readable layer.
|
|
@@ -178,6 +326,27 @@ export function modelCredentialCarry(runtime, spec) {
|
|
|
178
326
|
* gates what IT causes; what the author wrote into their own committed file, it reports.
|
|
179
327
|
*/
|
|
180
328
|
export async function literalKeyProviders(agentDir) {
|
|
329
|
+
return Object.entries(await definitionApiKeys(agentDir))
|
|
330
|
+
.filter(([, apiKey]) => isLiteralKey(apiKey))
|
|
331
|
+
.map(([id]) => id);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* How the agent's OWN models.json supplies `providerId`'s key, read from the file for the reason
|
|
335
|
+
* {@link literalKeyProviders} gives: `reference` is the variable a `"$NAME"` reads (the value file must hold it),
|
|
336
|
+
* `inFile` a key that travels with the definition itself (a literal, or a `"!command"` run on the box), and undefined
|
|
337
|
+
* no apiKey there at all.
|
|
338
|
+
*/
|
|
339
|
+
export async function definitionKeyOf(agentDir, providerId) {
|
|
340
|
+
const apiKey = (await definitionApiKeys(agentDir))[providerId];
|
|
341
|
+
if (typeof apiKey !== "string" || apiKey === "")
|
|
342
|
+
return undefined;
|
|
343
|
+
if (apiKey.startsWith("!") || isLiteralKey(apiKey))
|
|
344
|
+
return { inFile: true };
|
|
345
|
+
// Not a literal, so isLiteralKey's own pattern matched: there is a name to read.
|
|
346
|
+
return { reference: apiKey.replaceAll("$$", "").match(/\$\{?([A-Za-z_][A-Za-z0-9_]*)/)?.[1] };
|
|
347
|
+
}
|
|
348
|
+
/** Each provider's `apiKey` as the agent's own models.json writes it. */
|
|
349
|
+
async function definitionApiKeys(agentDir) {
|
|
181
350
|
const file = join(agentDir, AGENT_MODELS_FILE);
|
|
182
351
|
let raw;
|
|
183
352
|
try {
|
|
@@ -186,15 +355,13 @@ export async function literalKeyProviders(agentDir) {
|
|
|
186
355
|
catch (error) {
|
|
187
356
|
// No custom endpoints is the normal case; anything else (unreadable, a directory) is the caller's problem.
|
|
188
357
|
if (error.code === "ENOENT")
|
|
189
|
-
return
|
|
358
|
+
return {};
|
|
190
359
|
throw error;
|
|
191
360
|
}
|
|
192
361
|
// Malformed JSON already threw out of createPiModelRuntime before this runs, so a parse failure here would be a
|
|
193
362
|
// genuine surprise and must not be swallowed.
|
|
194
363
|
const providers = JSON.parse(raw).providers ?? {};
|
|
195
|
-
return Object.entries(providers)
|
|
196
|
-
.filter(([, provider]) => isLiteralKey(provider?.apiKey))
|
|
197
|
-
.map(([id]) => id);
|
|
364
|
+
return Object.fromEntries(Object.entries(providers).map(([id, provider]) => [id, provider?.apiKey]));
|
|
198
365
|
}
|
|
199
366
|
/**
|
|
200
367
|
* `!cmd` runs on the box; `$NAME` / `${NAME}` reads the environment; anything else is the key itself.
|
|
@@ -216,7 +383,7 @@ export function interactiveLoginKind(p) {
|
|
|
216
383
|
export function interactiveAuth(provider, method) {
|
|
217
384
|
return method === "oauth" ? provider.auth.oauth : provider.auth.apiKey?.login ? provider.auth.apiKey : undefined;
|
|
218
385
|
}
|
|
219
|
-
/** The providers a sign-in can target: pi's built-ins plus `extra`, composed exactly as {@link
|
|
386
|
+
/** The providers a sign-in can target: pi's built-ins plus `extra`, composed exactly as {@link piModelsOver} does. */
|
|
220
387
|
export function loginProviders(extra) {
|
|
221
388
|
return piModelsOver(new InMemoryCredentialStore(), extra).getProviders();
|
|
222
389
|
}
|
|
@@ -238,7 +405,11 @@ export async function providerAuthStatuses(models) {
|
|
|
238
405
|
}
|
|
239
406
|
return statuses;
|
|
240
407
|
}
|
|
241
|
-
/**
|
|
408
|
+
/**
|
|
409
|
+
* Which source currently satisfies auth for `spec`, or undefined when none does (or the spec is not in `models`).
|
|
410
|
+
* Rejects when resolving fails — a corrupt entry, a refresh the provider refused — which is a different answer from
|
|
411
|
+
* "nothing configured" and must reach whoever reports it.
|
|
412
|
+
*/
|
|
242
413
|
export async function probeAuthSource(models, spec) {
|
|
243
414
|
const slash = spec.indexOf("/");
|
|
244
415
|
if (slash < 1)
|
|
@@ -246,8 +417,7 @@ export async function probeAuthSource(models, spec) {
|
|
|
246
417
|
const model = models.getModel(spec.slice(0, slash), spec.slice(slash + 1));
|
|
247
418
|
if (!model)
|
|
248
419
|
return undefined;
|
|
249
|
-
|
|
250
|
-
return auth?.source;
|
|
420
|
+
return (await models.getAuth(model))?.source;
|
|
251
421
|
}
|
|
252
422
|
/** Quick-fail probe for a just-stored API key. */
|
|
253
423
|
export async function probeApiKey(models, model, signal) {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Agent } from "../../agent.ts";
|
|
2
|
-
import { type
|
|
2
|
+
import { type FastagentConfig } from "./config.ts";
|
|
3
3
|
import type { AgentCommand, SessionControl } from "../../session.ts";
|
|
4
|
+
import { type PiAssembly } from "./create.ts";
|
|
4
5
|
import type { SessionObserver } from "./turn-kit.ts";
|
|
5
6
|
import { type LoadedDefinition } from "./definition.ts";
|
|
6
7
|
import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import type {
|
|
8
|
+
import type { CredentialStore } from "@earendil-works/pi-ai";
|
|
9
|
+
import type { CredentialSourceOptions, FastagentAuthOptions } from "./auth.ts";
|
|
10
|
+
import { type AgentModels } from "./agent-models.ts";
|
|
8
11
|
import { type PiSessionRecordStore } from "./session-store.ts";
|
|
9
12
|
import type { ToolCollision, MountedTool } from "./tool.ts";
|
|
10
13
|
import type { DeclaredSecret } from "../../declared-secrets.ts";
|
|
@@ -36,6 +39,8 @@ export interface CreatePiAgentFromDirOptions {
|
|
|
36
39
|
sessionsDir?: string;
|
|
37
40
|
/** Credentials file override. */
|
|
38
41
|
authPath?: string;
|
|
42
|
+
/** The caller's own credential store, in place of any file ({@link CredentialSourceOptions}). */
|
|
43
|
+
credentialStore?: CredentialStore;
|
|
39
44
|
/**
|
|
40
45
|
* This is a long-running SERVE (`dev`/`start`), where the scheduler poller runs — so a self-scheduled wake-up is
|
|
41
46
|
* actually honored.
|
|
@@ -65,8 +70,8 @@ export interface AgentAssembly {
|
|
|
65
70
|
workspace: string;
|
|
66
71
|
/** Absolute state root (FASTAGENT_STATE_DIR > <agentDir>/.state). */
|
|
67
72
|
stateRoot: string;
|
|
68
|
-
/** The
|
|
69
|
-
|
|
73
|
+
/** The credential store and model registry every turn and every report reads ({@link agentModels}). */
|
|
74
|
+
models: AgentModels;
|
|
70
75
|
/** The full mounted tool surface (all coding tools + config.tools + discovered tools/, search_tools applied). */
|
|
71
76
|
tools: MountedTool[];
|
|
72
77
|
toolNames: string[];
|
|
@@ -77,8 +82,19 @@ export interface AgentAssembly {
|
|
|
77
82
|
}
|
|
78
83
|
export declare function resolveAgentAssembly(dir: string, options?: {
|
|
79
84
|
model?: string;
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
} & CredentialSourceOptions): Promise<AgentAssembly>;
|
|
86
|
+
/**
|
|
87
|
+
* L2 over a resolved front half: the ONE mapping from what the directory resolved to what the assembly runs on, for
|
|
88
|
+
* both session shapes (the served agent and `chat`'s resident one). Two copies of it drifted once (#566, chat ran at
|
|
89
|
+
* the wrong reasoning effort). `models` is the front's own, so the assembly runs on what the report describes.
|
|
90
|
+
*/
|
|
91
|
+
export declare function assembleFront(front: AgentAssembly, extra?: {
|
|
92
|
+
tools?: MountedTool[];
|
|
93
|
+
sessions?: PiSessionRecordStore;
|
|
94
|
+
}): Promise<{
|
|
95
|
+
assembly: PiAssembly;
|
|
96
|
+
definition: LoadedDefinition;
|
|
97
|
+
}>;
|
|
82
98
|
/**
|
|
83
99
|
* The model specs `createPiAgentFromDir(dir, { authPath })` could run now: the agent's registry (pi's built-ins plus
|
|
84
100
|
* its `models.json`) filtered to the providers whose credentials are configured, sorted. The directory needs no model
|
|
@@ -88,9 +104,24 @@ export declare function resolveAgentAssembly(dir: string, options?: {
|
|
|
88
104
|
* `warn` reaches the credential store. An unreadable or corrupt credentials file otherwise reads as "nothing
|
|
89
105
|
* configured", so a client that must not show that as an empty list passes a sink that throws.
|
|
90
106
|
*/
|
|
91
|
-
export declare function availableModelsFromDir(dir: string, options?: FastagentAuthOptions &
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
export declare function availableModelsFromDir(dir: string, options?: FastagentAuthOptions & CredentialSourceOptions): Promise<string[]>;
|
|
108
|
+
/**
|
|
109
|
+
* Refresh the agent's model catalog (`<agent dir>/models-store.json`) with the credentials
|
|
110
|
+
* `createPiAgentFromDir(dir, { authPath })` would use, so a model released after the installed pi appears in
|
|
111
|
+
* {@link availableModelsFromDir} and runs. Only providers those credentials authenticate are fetched (pi asks pi.dev
|
|
112
|
+
* for no other). Nothing refreshes it on its own. The file is part of the definition: commit it, and it ships with a
|
|
113
|
+
* deploy, so the deployed agent knows the same models without a network call.
|
|
114
|
+
*
|
|
115
|
+
* Rejects, naming each provider that failed, when the refresh fails, outlasts 15 seconds or `PI_OFFLINE` is set, and
|
|
116
|
+
* when the credentials authenticate no provider at all.
|
|
117
|
+
*/
|
|
118
|
+
export declare function refreshModelCatalog(dir: string, options?: FastagentAuthOptions & CredentialSourceOptions & {
|
|
119
|
+
signal?: AbortSignal;
|
|
120
|
+
}): Promise<void>;
|
|
121
|
+
/** {@link refreshModelCatalog} against another catalog server. Not public: a test seam. */
|
|
122
|
+
export declare function refreshModelCatalogOver(dir: string, options: FastagentAuthOptions & CredentialSourceOptions & {
|
|
123
|
+
signal?: AbortSignal;
|
|
124
|
+
}, catalogBaseUrl?: string): Promise<void>;
|
|
94
125
|
/**
|
|
95
126
|
* "Point at a directory → agent": resolve the placement (`dir` may be either end — the workspace or the agent dir
|
|
96
127
|
* itself), load the config, resolve model and tools, then L2.
|
|
@@ -110,8 +141,8 @@ export declare function createPiAgentFromDir(dir: string, options?: CreatePiAgen
|
|
|
110
141
|
stateRoot: string;
|
|
111
142
|
/** Absolute session store directory in use (for the startup report). */
|
|
112
143
|
sessionsDir: string;
|
|
113
|
-
/** The
|
|
114
|
-
|
|
144
|
+
/** The credential store and registry the agent runs on — what the startup report describes. */
|
|
145
|
+
models: AgentModels;
|
|
115
146
|
sessions: PiSessionRecordStore;
|
|
116
147
|
/** The observation plane over this agent's sessions; present on every serve (a channel's stop command reaches the
|
|
117
148
|
* live run through it). Its boundary is wired only when {@link publishControl}. */
|
package/dist/engines/pi/open.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* drive.
|
|
4
4
|
*/
|
|
5
5
|
import { mkdir } from "node:fs/promises";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { loadConfig, resolveModelSpec } from "./config.js";
|
|
8
|
+
import { AGENT_MODEL_CATALOG_FILE, resolveSessionsDir, resolveStateRoot, resolvePlacement } from "../../paths.js";
|
|
9
|
+
import { agentOf, assemblePiFromDefinition, resolveAgentTools } from "./create.js";
|
|
9
10
|
import { createPiSessionControl } from "./session-control.js";
|
|
10
11
|
import { withWakeTool } from "./wake-tool.js";
|
|
11
12
|
import { refuseBrokenDeclarations } from "../../loader.js";
|
|
@@ -13,6 +14,8 @@ import { loadAgentSkills } from "./definition.js";
|
|
|
13
14
|
import { servedExtensionCommands } from "./agent-session-factory.js";
|
|
14
15
|
import { reportFindingsIfChanged } from "./report.js";
|
|
15
16
|
import { readMachine, withMachine } from "./machine.js";
|
|
17
|
+
import { createPiModelRuntime, refreshCatalog } from "./models.js";
|
|
18
|
+
import { agentModels } from "./agent-models.js";
|
|
16
19
|
import { piSessionRecordStore } from "./session-store.js";
|
|
17
20
|
import { gateSecrets } from "../../secrets-gate.js";
|
|
18
21
|
/**
|
|
@@ -83,8 +86,6 @@ export async function resolveAgentAssembly(dir, options = {}) {
|
|
|
83
86
|
// The state root: sessions/channel state/schedule state derive from it (FASTAGENT_STATE_DIR moves it in one knob —
|
|
84
87
|
// a container points it at its volume).
|
|
85
88
|
const stateRoot = resolveStateRoot(agentDir);
|
|
86
|
-
// The credentials file: project-level by default (under `<agentDir>/.secrets`).
|
|
87
|
-
const auth = resolveAuthLayers(agentDir, options.authPath);
|
|
88
89
|
return {
|
|
89
90
|
config,
|
|
90
91
|
configPath,
|
|
@@ -92,7 +93,8 @@ export async function resolveAgentAssembly(dir, options = {}) {
|
|
|
92
93
|
agentDir,
|
|
93
94
|
workspace,
|
|
94
95
|
stateRoot,
|
|
95
|
-
|
|
96
|
+
// Project-level by default (under `<agentDir>/.secrets`), with the global file behind it per provider.
|
|
97
|
+
models: agentModels(agentDir, options),
|
|
96
98
|
tools,
|
|
97
99
|
toolNames,
|
|
98
100
|
deferredToolNames,
|
|
@@ -100,6 +102,21 @@ export async function resolveAgentAssembly(dir, options = {}) {
|
|
|
100
102
|
toolSecrets,
|
|
101
103
|
};
|
|
102
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* L2 over a resolved front half: the ONE mapping from what the directory resolved to what the assembly runs on, for
|
|
107
|
+
* both session shapes (the served agent and `chat`'s resident one). Two copies of it drifted once (#566, chat ran at
|
|
108
|
+
* the wrong reasoning effort). `models` is the front's own, so the assembly runs on what the report describes.
|
|
109
|
+
*/
|
|
110
|
+
export function assembleFront(front, extra = {}) {
|
|
111
|
+
return assemblePiFromDefinition(front.agentDir, {
|
|
112
|
+
model: front.modelSpec,
|
|
113
|
+
thinkingLevel: front.config.thinkingLevel,
|
|
114
|
+
cwd: front.workspace,
|
|
115
|
+
tools: extra.tools ?? front.tools,
|
|
116
|
+
models: front.models,
|
|
117
|
+
...(extra.sessions ? { sessions: extra.sessions } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
103
120
|
/**
|
|
104
121
|
* The model specs `createPiAgentFromDir(dir, { authPath })` could run now: the agent's registry (pi's built-ins plus
|
|
105
122
|
* its `models.json`) filtered to the providers whose credentials are configured, sorted. The directory needs no model
|
|
@@ -111,18 +128,40 @@ export async function resolveAgentAssembly(dir, options = {}) {
|
|
|
111
128
|
*/
|
|
112
129
|
export async function availableModelsFromDir(dir, options = {}) {
|
|
113
130
|
const { agentDir } = resolvePlacement(dir);
|
|
114
|
-
const models = await
|
|
115
|
-
auth: resolveAuthLayers(agentDir, options.authPath),
|
|
116
|
-
...(options.warn ? { warn: options.warn } : {}),
|
|
117
|
-
});
|
|
131
|
+
const models = await agentModels(agentDir, options).runtime();
|
|
118
132
|
return (await models.getAvailable()).map((model) => `${model.provider}/${model.id}`).sort();
|
|
119
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Refresh the agent's model catalog (`<agent dir>/models-store.json`) with the credentials
|
|
136
|
+
* `createPiAgentFromDir(dir, { authPath })` would use, so a model released after the installed pi appears in
|
|
137
|
+
* {@link availableModelsFromDir} and runs. Only providers those credentials authenticate are fetched (pi asks pi.dev
|
|
138
|
+
* for no other). Nothing refreshes it on its own. The file is part of the definition: commit it, and it ships with a
|
|
139
|
+
* deploy, so the deployed agent knows the same models without a network call.
|
|
140
|
+
*
|
|
141
|
+
* Rejects, naming each provider that failed, when the refresh fails, outlasts 15 seconds or `PI_OFFLINE` is set, and
|
|
142
|
+
* when the credentials authenticate no provider at all.
|
|
143
|
+
*/
|
|
144
|
+
export function refreshModelCatalog(dir, options = {}) {
|
|
145
|
+
return refreshModelCatalogOver(dir, options);
|
|
146
|
+
}
|
|
147
|
+
/** {@link refreshModelCatalog} against another catalog server. Not public: a test seam. */
|
|
148
|
+
export async function refreshModelCatalogOver(dir, options, catalogBaseUrl) {
|
|
149
|
+
const { agentDir } = resolvePlacement(dir);
|
|
150
|
+
const { credentials } = agentModels(agentDir, options);
|
|
151
|
+
await refreshCatalog((catalogFile) => createPiModelRuntime({
|
|
152
|
+
agentDir,
|
|
153
|
+
credentials,
|
|
154
|
+
...(catalogFile ? { catalogFile: join(agentDir, AGENT_MODEL_CATALOG_FILE) } : {}),
|
|
155
|
+
...(catalogBaseUrl ? { catalogBaseUrl } : {}),
|
|
156
|
+
}), options.signal ? { signal: options.signal } : {});
|
|
157
|
+
}
|
|
120
158
|
/**
|
|
121
159
|
* "Point at a directory → agent": resolve the placement (`dir` may be either end — the workspace or the agent dir
|
|
122
160
|
* itself), load the config, resolve model and tools, then L2.
|
|
123
161
|
*/
|
|
124
162
|
export async function createPiAgentFromDir(dir, options = {}) {
|
|
125
|
-
const
|
|
163
|
+
const front = await resolveAgentAssembly(dir, options);
|
|
164
|
+
const { config, configPath, modelSpec, agentDir, workspace, stateRoot, models, tools, toolNames, deferredToolNames, toolCollisions, } = front;
|
|
126
165
|
// Every serve mounts the built-in `wake` tool: the agent's own follow-up work is a default capability, and a serve
|
|
127
166
|
// is where the poller that honors it runs. A one-shot `invoke` has no poller, so nothing there would fire it.
|
|
128
167
|
const mountedTools = withWakeTool(tools, stateRoot, !!options.serving);
|
|
@@ -131,15 +170,8 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
131
170
|
const sessionsDir = options.sessionsDir ?? resolveSessionsDir(agentDir);
|
|
132
171
|
await mkdir(sessionsDir, { recursive: true });
|
|
133
172
|
const sessions = piSessionRecordStore({ dir: sessionsDir, cwd: workspace });
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
thinkingLevel: config.thinkingLevel,
|
|
137
|
-
cwd: workspace,
|
|
138
|
-
tools: mountedTools,
|
|
139
|
-
auth,
|
|
140
|
-
// Skills are definition-only (the agent is its directory), so dev mirrors deployment exactly.
|
|
141
|
-
sessions,
|
|
142
|
-
});
|
|
173
|
+
// Skills are definition-only (the agent is its directory), so dev mirrors deployment exactly.
|
|
174
|
+
const { assembly, definition } = await assembleFront(front, { tools: mountedTools, sessions });
|
|
143
175
|
// The hub is wired HERE because the store is created here (an external `createPiSessionControl` cannot exist before
|
|
144
176
|
// the store does).
|
|
145
177
|
const caller = options.observer;
|
|
@@ -199,7 +231,7 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
199
231
|
modelSpec,
|
|
200
232
|
stateRoot,
|
|
201
233
|
sessionsDir,
|
|
202
|
-
|
|
234
|
+
models,
|
|
203
235
|
toolNames,
|
|
204
236
|
deferredToolNames,
|
|
205
237
|
toolCollisions,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** `createAgentService` — the product as one call, with pi supplying the engine. */
|
|
2
2
|
import { type AgentService, type MountAgentServiceOptions } from "../../service.ts";
|
|
3
|
-
|
|
3
|
+
import type { CredentialSourceOptions } from "./auth.ts";
|
|
4
|
+
export interface CreateAgentServiceOptions extends MountAgentServiceOptions, CredentialSourceOptions {
|
|
4
5
|
model?: string;
|
|
5
|
-
authPath?: string;
|
|
6
6
|
sessionsDir?: string;
|
|
7
7
|
}
|
|
8
8
|
/** Open an agent directory as a live service: one handler, mounted wherever you serve. */
|
|
@@ -6,6 +6,7 @@ export async function createAgentService(dir, options = {}) {
|
|
|
6
6
|
const opened = await createPiAgentFromDir(dir, {
|
|
7
7
|
...(options.model !== undefined ? { model: options.model } : {}),
|
|
8
8
|
...(options.authPath !== undefined ? { authPath: options.authPath } : {}),
|
|
9
|
+
...(options.credentialStore !== undefined ? { credentialStore: options.credentialStore } : {}),
|
|
9
10
|
...(options.sessionsDir !== undefined ? { sessionsDir: options.sessionsDir } : {}),
|
|
10
11
|
serving: true, // a mounted service is long-running: the scheduler poller runs
|
|
11
12
|
});
|
|
@@ -6,7 +6,8 @@ export interface BuildSessionRuntimeOptions {
|
|
|
6
6
|
authPath?: string;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Build pi's interactive runtime
|
|
10
|
-
*
|
|
9
|
+
* Build pi's interactive runtime over the SAME assembly `dev`/`start` serve ({@link assembleFront}): model
|
|
10
|
+
* registry, prompt, tools, skills, reasoning effort and credentials all come from it, so what differs is only the
|
|
11
|
+
* session's shape (one resident runtime rather than one session per invoke).
|
|
11
12
|
*/
|
|
12
13
|
export declare function buildAgentSessionRuntime(dir: string, options?: BuildSessionRuntimeOptions, sessionManager?: SessionManager): Promise<AgentSessionRuntime>;
|