@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.
- package/dist/agent.d.ts +10 -0
- 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/kit/preview-kit.d.ts +6 -1
- package/dist/channels/kit/preview-kit.js +21 -4
- 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/preview.js +2 -1
- 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 +15 -12
- 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/invoke-stream.js +14 -0
- 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 +6 -5
- package/dist/cli/shared.js +29 -36
- 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 +92 -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 +7 -4
- package/dist/engines/pi/agent-models.d.ts +83 -0
- package/dist/engines/pi/agent-models.js +120 -0
- package/dist/engines/pi/agent-session-factory.d.ts +13 -13
- package/dist/engines/pi/agent-session-factory.js +132 -159
- package/dist/engines/pi/auth.d.ts +56 -3
- package/dist/engines/pi/auth.js +68 -109
- package/dist/engines/pi/config.d.ts +0 -25
- package/dist/engines/pi/config.js +3 -37
- package/dist/engines/pi/create.d.ts +34 -21
- package/dist/engines/pi/create.js +90 -57
- 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 +88 -20
- package/dist/engines/pi/locked-file.d.ts +9 -0
- package/dist/engines/pi/locked-file.js +102 -0
- package/dist/engines/pi/login.d.ts +27 -14
- package/dist/engines/pi/login.js +41 -18
- package/dist/engines/pi/machine.d.ts +23 -0
- package/dist/engines/pi/machine.js +24 -2
- package/dist/engines/pi/models.d.ts +120 -36
- package/dist/engines/pi/models.js +316 -66
- package/dist/engines/pi/open.d.ts +65 -20
- package/dist/engines/pi/open.js +88 -34
- package/dist/engines/pi/report.d.ts +3 -0
- package/dist/engines/pi/report.js +11 -0
- 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 +30 -76
- package/dist/engines/pi/session-control.d.ts +4 -4
- package/dist/engines/pi/session-control.js +86 -43
- package/dist/engines/pi/session-settings.d.ts +9 -0
- package/dist/engines/pi/session-settings.js +17 -0
- package/dist/engines/pi/session-store.d.ts +2 -2
- package/dist/engines/pi/tool-context.d.ts +3 -5
- package/dist/engines/pi/tool-context.js +4 -16
- package/dist/engines/pi/tool.d.ts +32 -21
- package/dist/engines/pi/tool.js +21 -25
- package/dist/engines/pi/turn-kit.d.ts +18 -9
- package/dist/engines/pi/turn-kit.js +115 -32
- 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 +88 -15
- package/dist/session.js +5 -1
- package/package.json +6 -5
- package/dist/engines/pi/search-tools.d.ts +0 -4
- package/dist/engines/pi/search-tools.js +0 -109
package/dist/engines/pi/auth.js
CHANGED
|
@@ -1,112 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth for the pi engine: a read-WRITE {@link CredentialStore} over a fastagent credentials file, consumed by the
|
|
3
|
-
* `Models` collection (models.ts). The write path refuses to overwrite a
|
|
4
|
-
* the other providers' credentials.
|
|
3
|
+
* `Models` collection (models.ts), and which of those files an agent reads. The write path refuses to overwrite a
|
|
4
|
+
* corrupt file, so a torn read never clobbers the other providers' credentials.
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync,
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { GLOBAL_HOME_DIR, SECRETS_DIRNAME, SECRET_FILE_MODE } from "../../paths.js";
|
|
10
|
-
import { writeFileAtomic } from "../../atomic-write.js";
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { globalHome, SECRETS_DIRNAME, resolveOverridePath, resolveSecretsDir } from "../../paths.js";
|
|
11
9
|
import { log } from "../../log.js";
|
|
12
|
-
import
|
|
10
|
+
import { withLockedFile } from "./locked-file.js";
|
|
13
11
|
/**
|
|
14
12
|
* The GLOBAL fastagent credentials file (distinct from pi's `~/.pi`), under the user-global machinery home
|
|
15
13
|
* `~/.fastagent/`.
|
|
16
14
|
*/
|
|
17
|
-
export const GLOBAL_AUTH_PATH = join(
|
|
15
|
+
export const GLOBAL_AUTH_PATH = join(globalHome(), SECRETS_DIRNAME, "auth.json");
|
|
16
|
+
/** The one refusal for a caller that names both sources: silently preferring one would hide the other's mistake. */
|
|
17
|
+
export function assertOneCredentialSource(options) {
|
|
18
|
+
if (options.authPath !== undefined && options.credentialStore !== undefined) {
|
|
19
|
+
throw new Error("pass authPath or credentialStore, not both: a supplied store replaces the credentials file");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
18
22
|
/** A valid stored credential, or undefined — a foreign/old entry reads as not-configured, not a crash. */
|
|
19
23
|
function pick(creds, providerId) {
|
|
20
24
|
const cred = creds[providerId];
|
|
21
25
|
return cred && (cred.type === "oauth" || cred.type === "api_key") ? cred : undefined;
|
|
22
26
|
}
|
|
23
|
-
const AUTH_FILE_WRITE_OPTIONS = { encoding: "utf8", mode: SECRET_FILE_MODE };
|
|
24
|
-
/**
|
|
25
|
-
* The file a write must land IN, which is not always the path it is addressed BY. pi writes the auth file in place,
|
|
26
|
-
* so it follows a symlink to the target; an atomic rename would replace the link with a regular file instead, and
|
|
27
|
-
* from then on the two tools would be editing different files — the same split the shared lock exists to prevent,
|
|
28
|
-
* arriving one write later. A dangling link (a dotfile manager that has not checked the target out yet) still names
|
|
29
|
-
* where the credentials belong, so it is followed too rather than overwritten.
|
|
30
|
-
*/
|
|
31
|
-
function writeTarget(authPath) {
|
|
32
|
-
if (existsSync(authPath))
|
|
33
|
-
return realpathSync(authPath);
|
|
34
|
-
const link = lstatSync(authPath, { throwIfNoEntry: false });
|
|
35
|
-
// ponytail: one hop. A chain of dangling links would have its second link replaced; resolve iteratively if that
|
|
36
|
-
// ever shows up in a real layout.
|
|
37
|
-
return link?.isSymbolicLink() ? resolve(dirname(authPath), readlinkSync(authPath)) : authPath;
|
|
38
|
-
}
|
|
39
|
-
/** Serialized cross-process read-modify-write of the credentials file. */
|
|
40
|
-
async function withLockedAuthFile(authPath, fn) {
|
|
41
|
-
mkdirSync(dirname(authPath), { recursive: true });
|
|
42
|
-
if (!existsSync(authPath)) {
|
|
43
|
-
try {
|
|
44
|
-
// `wx` makes this an exclusive CREATE, which is the one case `writeFileSync` honours `mode` in — so no chmod
|
|
45
|
-
// after it (unlike writeFileAtomic, whose temp may already exist from a crashed writer).
|
|
46
|
-
writeFileSync(authPath, "{}", { ...AUTH_FILE_WRITE_OPTIONS, flag: "wx" });
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
// EEXIST: the path IS taken, by something `existsSync` does not see through — either another process created
|
|
50
|
-
// the file just now (its credentials must not be clobbered) or the path is a dangling symlink. Both are left
|
|
51
|
-
// alone: the write below resolves where they belong.
|
|
52
|
-
if (error.code !== "EEXIST")
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
let compromised;
|
|
57
|
-
const throwIfCompromised = () => {
|
|
58
|
-
if (compromised)
|
|
59
|
-
throw compromised;
|
|
60
|
-
};
|
|
61
|
-
const release = await lockfile.lock(authPath, {
|
|
62
|
-
// MUST match pi-coding-agent's `core/auth-storage.js`, which locks with `realpath: false`. When the auth file
|
|
63
|
-
// itself is a symlink (dotfile managers do this), the two settings name DIFFERENT lock files and both locks can
|
|
64
|
-
// be held at once — so `pi` and `fastagent` would refresh the same provider concurrently, and a rotated refresh
|
|
65
|
-
// token logs one of them out. Resolving the link would be the stronger rule, but only if BOTH sides did it, and
|
|
66
|
-
// we do not own the other side.
|
|
67
|
-
realpath: false,
|
|
68
|
-
retries: { retries: 10, factor: 2, minTimeout: 100, maxTimeout: 10_000, randomize: true },
|
|
69
|
-
stale: 30_000,
|
|
70
|
-
onCompromised: (error) => {
|
|
71
|
-
compromised = error;
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
let result;
|
|
75
|
-
try {
|
|
76
|
-
throwIfCompromised();
|
|
77
|
-
const current = existsSync(authPath) ? readFileSync(authPath, "utf8") : undefined;
|
|
78
|
-
const out = await fn(current);
|
|
79
|
-
throwIfCompromised();
|
|
80
|
-
// Rename, not an in-place rewrite: it is what lets `read` stay unlocked, and it is the only spelling that applies
|
|
81
|
-
// the mode before the content is reachable.
|
|
82
|
-
if (out.next !== undefined) {
|
|
83
|
-
writeFileAtomic(writeTarget(authPath), out.next, SECRET_FILE_MODE);
|
|
84
|
-
}
|
|
85
|
-
throwIfCompromised();
|
|
86
|
-
result = out.result;
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
// The primary failure stays the signal; unlock noise must not mask it.
|
|
90
|
-
try {
|
|
91
|
-
await release();
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// Secondary: a compromised or stale-reclaimed lock often cannot release cleanly.
|
|
95
|
-
}
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
// Success path: a failed release is a real cleanup failure (the leftover auth.json.lock stalls the next writer for
|
|
99
|
-
// the staleness window with zero diagnostics), so it surfaces instead of resolving a silently degraded operation.
|
|
100
|
-
try {
|
|
101
|
-
await release();
|
|
102
|
-
}
|
|
103
|
-
catch (releaseError) {
|
|
104
|
-
if (compromised === undefined)
|
|
105
|
-
throw releaseError;
|
|
106
|
-
}
|
|
107
|
-
throwIfCompromised();
|
|
108
|
-
return result;
|
|
109
|
-
}
|
|
110
27
|
/** Decode the credentials JSON, shared by the read and write paths. */
|
|
111
28
|
function decodeCreds(raw) {
|
|
112
29
|
let parsed;
|
|
@@ -153,16 +70,25 @@ function parseForWrite(raw, where) {
|
|
|
153
70
|
}
|
|
154
71
|
/**
|
|
155
72
|
* A read-write `CredentialStore` over the given credentials file (default {@link GLOBAL_AUTH_PATH}; the directory
|
|
156
|
-
* opener passes the project-level `<root>/.secrets/auth.json`), optionally falling back to a second file.
|
|
73
|
+
* opener passes the project-level `<root>/.secrets/auth.json`), optionally falling back to a second file. A path is
|
|
74
|
+
* read like `FASTAGENT_AUTH_PATH` (`~` is the home directory, a relative one resolves against the cwd), so every
|
|
75
|
+
* caller that names a file names the same one.
|
|
157
76
|
*
|
|
158
77
|
* The fallback is PER PROVIDER, not per file: logging one provider into a project must not hide the others a person
|
|
159
78
|
* already has globally. And the layer a credential was READ from is the layer its refresh is written back to —
|
|
160
79
|
* anything else would conjure a second holder of the same OAuth grant, which is the failure this whole area exists
|
|
161
80
|
* to avoid. A provider present in neither layer is new, and new credentials belong to the primary.
|
|
81
|
+
*
|
|
82
|
+
* The fallback also yields to `projectAuthenticates`: a provider the project authenticates some other way (an env key,
|
|
83
|
+
* its own models.json) never reaches it — for reading, listing or writing back.
|
|
162
84
|
*/
|
|
163
|
-
export function fastagentCredentialStore(
|
|
85
|
+
export function fastagentCredentialStore(path, options = {}) {
|
|
164
86
|
const warn = options.warn ?? ((message) => log.warn(message));
|
|
165
|
-
const
|
|
87
|
+
const authPath = resolveOverridePath(path) ?? GLOBAL_AUTH_PATH;
|
|
88
|
+
const fallbackPath = resolveOverridePath(options.fallbackPath);
|
|
89
|
+
const fallback = fallbackPath !== undefined && fallbackPath !== authPath ? fallbackPath : undefined;
|
|
90
|
+
/** The fallback, when it may serve this provider at all. */
|
|
91
|
+
const fallbackFor = async (providerId) => fallback === undefined || (await options.projectAuthenticates?.(providerId)) ? undefined : fallback;
|
|
166
92
|
/**
|
|
167
93
|
* The file that owns this provider: where it already is, else the primary.
|
|
168
94
|
*
|
|
@@ -171,18 +97,21 @@ export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options =
|
|
|
171
97
|
* "one grant, one copy" rule can be lost. Re-checking under the lock means locking both files in a fixed order;
|
|
172
98
|
* worth it only if concurrent logins stop being a rounding error.
|
|
173
99
|
*/
|
|
174
|
-
const owner = (providerId) => {
|
|
175
|
-
|
|
100
|
+
const owner = async (providerId) => {
|
|
101
|
+
const second = await fallbackFor(providerId);
|
|
102
|
+
if (second === undefined)
|
|
176
103
|
return authPath;
|
|
177
104
|
const primary = readCreds(authPath, warn);
|
|
178
105
|
if (primary && pick(primary, providerId))
|
|
179
106
|
return authPath;
|
|
180
|
-
const secondary = readCreds(
|
|
181
|
-
return secondary && pick(secondary, providerId) ?
|
|
107
|
+
const secondary = readCreds(second, warn);
|
|
108
|
+
return secondary && pick(secondary, providerId) ? second : authPath;
|
|
182
109
|
};
|
|
183
110
|
return {
|
|
111
|
+
layerOf: owner,
|
|
184
112
|
async read(providerId) {
|
|
185
|
-
|
|
113
|
+
const second = await fallbackFor(providerId);
|
|
114
|
+
for (const path of second === undefined ? [authPath] : [authPath, second]) {
|
|
186
115
|
const creds = readCreds(path, warn);
|
|
187
116
|
const found = creds && pick(creds, providerId);
|
|
188
117
|
if (found)
|
|
@@ -196,15 +125,17 @@ export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options =
|
|
|
196
125
|
const infos = new Map();
|
|
197
126
|
for (const path of fallback === undefined ? [authPath] : [fallback, authPath]) {
|
|
198
127
|
for (const [providerId, cred] of Object.entries(readCreds(path, warn) ?? {})) {
|
|
128
|
+
if (path === fallback && (await fallbackFor(providerId)) === undefined)
|
|
129
|
+
continue;
|
|
199
130
|
if (cred && (cred.type === "oauth" || cred.type === "api_key"))
|
|
200
131
|
infos.set(providerId, { providerId, type: cred.type });
|
|
201
132
|
}
|
|
202
133
|
}
|
|
203
134
|
return [...infos.values()];
|
|
204
135
|
},
|
|
205
|
-
modify(providerId, fn) {
|
|
206
|
-
const path = owner(providerId);
|
|
207
|
-
return
|
|
136
|
+
async modify(providerId, fn) {
|
|
137
|
+
const path = await owner(providerId);
|
|
138
|
+
return withLockedFile(path, async (current) => {
|
|
208
139
|
const creds = parseForWrite(current, path); // corrupt → throw → no clobber
|
|
209
140
|
const next = await fn(pick(creds, providerId));
|
|
210
141
|
if (next === undefined)
|
|
@@ -214,12 +145,12 @@ export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options =
|
|
|
214
145
|
});
|
|
215
146
|
},
|
|
216
147
|
async delete(providerId) {
|
|
217
|
-
const path = owner(providerId);
|
|
148
|
+
const path = await owner(providerId);
|
|
218
149
|
// No-op when nothing is stored: do NOT take the lock (which would create the file) on a machine that never
|
|
219
150
|
// stored this provider.
|
|
220
151
|
if (!existsSync(path))
|
|
221
152
|
return;
|
|
222
|
-
await
|
|
153
|
+
await withLockedFile(path, async (current) => {
|
|
223
154
|
const creds = parseForWrite(current, path);
|
|
224
155
|
if (!(providerId in creds))
|
|
225
156
|
return { result: undefined }; // absent: no write
|
|
@@ -229,3 +160,31 @@ export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options =
|
|
|
229
160
|
},
|
|
230
161
|
};
|
|
231
162
|
}
|
|
163
|
+
// ── Which files an agent reads ───────────────────────────────────────────────
|
|
164
|
+
/**
|
|
165
|
+
* The auth-file override: the SDK's `authPath` option > `FASTAGENT_AUTH_PATH` env > undefined (then
|
|
166
|
+
* `<secrets dir>/auth.json`). The CLI has no flag for it: a second spelling of one environment variable buys nothing,
|
|
167
|
+
* and the variable is what a deployed container reads anyway.
|
|
168
|
+
*/
|
|
169
|
+
function resolveAuthPathOverride(flag, env) {
|
|
170
|
+
return resolveOverridePath(flag ?? env.FASTAGENT_AUTH_PATH);
|
|
171
|
+
}
|
|
172
|
+
/** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
|
|
173
|
+
export function resolveAuthPath(dir, flag, env = process.env) {
|
|
174
|
+
return resolveAuthPathOverride(flag, env) ?? join(resolveSecretsDir(dir, env), "auth.json");
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The {@link AuthLayers} an agent directory reads: `authPath` option > `FASTAGENT_AUTH_PATH` > its own file, and
|
|
178
|
+
* behind it the user-global store, because a login is a PERSON on a machine and not a project — one `login -g` then
|
|
179
|
+
* serves every agent here.
|
|
180
|
+
*
|
|
181
|
+
* No fallback when a path was named: "use this file" is an instruction, not a preference. BOTH knobs
|
|
182
|
+
* {@link resolveAuthPath} reads count as that instruction, `FASTAGENT_SECRETS_DIR` included — it is what the
|
|
183
|
+
* Fly/Railway/AgentCore artifacts set, and a deployed container must read its mounted credentials and nothing else
|
|
184
|
+
* (the artifact is the truth).
|
|
185
|
+
*/
|
|
186
|
+
export function resolveAuthLayers(agentDir, authPath, env = process.env) {
|
|
187
|
+
const path = resolveAuthPath(agentDir, authPath, env);
|
|
188
|
+
const named = resolveAuthPathOverride(authPath, env) ?? resolveOverridePath(env.FASTAGENT_SECRETS_DIR);
|
|
189
|
+
return named === undefined ? { path, fallback: GLOBAL_AUTH_PATH } : { path };
|
|
190
|
+
}
|
|
@@ -63,28 +63,3 @@ export declare function rewriteConfigModel(src: string, spec: string): string |
|
|
|
63
63
|
* would refuse to start an agent whose config names a model.
|
|
64
64
|
*/
|
|
65
65
|
export declare function resolveModelSpec(flag: string | undefined, config: FastagentConfig, env?: NodeJS.ProcessEnv): string | undefined;
|
|
66
|
-
/** The default credentials file under a resolved secrets dir ({@link resolveSecretsDir}). */
|
|
67
|
-
export declare function defaultAuthPath(secretsDir: string): string;
|
|
68
|
-
/** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
|
|
69
|
-
export declare function resolveAuthPath(dir: string, flag?: string, env?: NodeJS.ProcessEnv): string;
|
|
70
|
-
/**
|
|
71
|
-
* Which credentials files an agent reads: `path` first, then, per provider, `fallback` — present only when nothing
|
|
72
|
-
* named the path ({@link resolveAuthFallback}). One value because it is one decision: the opener, the model list, the
|
|
73
|
-
* startup report and the first-run picker all read through it.
|
|
74
|
-
*/
|
|
75
|
-
export interface AuthLayers {
|
|
76
|
-
path: string;
|
|
77
|
-
fallback?: string;
|
|
78
|
-
}
|
|
79
|
-
/** The {@link AuthLayers} an agent directory reads: `authPath` option > `FASTAGENT_AUTH_PATH` > its own file. */
|
|
80
|
-
export declare function resolveAuthLayers(agentDir: string, authPath?: string, env?: NodeJS.ProcessEnv): AuthLayers;
|
|
81
|
-
/**
|
|
82
|
-
* Where a credential the project does not have is read from instead: the user-global store, because a login is a
|
|
83
|
-
* PERSON on a machine and not a project — one `login -g` then serves every agent here. Undefined when an explicit
|
|
84
|
-
* path was named: "use this file" is an instruction, not a preference, so it gets no second layer.
|
|
85
|
-
*
|
|
86
|
-
* BOTH knobs {@link resolveAuthPath} reads count as that instruction, `FASTAGENT_SECRETS_DIR` included — it is what
|
|
87
|
-
* the Fly/Railway/AgentCore artifacts set, and a deployed container must read its mounted credentials and nothing
|
|
88
|
-
* else (the artifact is the truth).
|
|
89
|
-
*/
|
|
90
|
-
export declare function resolveAuthFallback(flag?: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
@@ -14,11 +14,10 @@ import { existsSync, statSync } from "node:fs";
|
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { pathToFileURL } from "node:url";
|
|
16
16
|
import { THINKING_LEVELS } from "./session-settings.js";
|
|
17
|
-
import { GLOBAL_AUTH_PATH } from "./auth.js";
|
|
18
17
|
import { readSecretDeclaration } from "../../declared-secrets.js";
|
|
19
18
|
import { assertCorsOrigins } from "../../channels/serve.js";
|
|
20
19
|
import { moduleLoadHint } from "../../loader.js";
|
|
21
|
-
import { AGENT_CONFIG_FILE
|
|
20
|
+
import { AGENT_CONFIG_FILE } from "../../paths.js";
|
|
22
21
|
/** Identity function for typing and IDE completion (vite/next-style). */
|
|
23
22
|
export function defineConfig(config) {
|
|
24
23
|
return config;
|
|
@@ -152,7 +151,8 @@ export function resolveModel(models, spec) {
|
|
|
152
151
|
const modelId = spec.slice(slash + 1);
|
|
153
152
|
const model = models.getModel(provider, modelId);
|
|
154
153
|
if (!model) {
|
|
155
|
-
throw new Error(`unknown model "${spec}" (provider "${provider}" / id "${modelId}" not in registry); run \`fastagent models\` to list
|
|
154
|
+
throw new Error(`unknown model "${spec}" (provider "${provider}" / id "${modelId}" not in registry); run \`fastagent models\` to list ` +
|
|
155
|
+
"available specs, or `fastagent models --refresh` in the agent to fetch models newer than the installed pi");
|
|
156
156
|
}
|
|
157
157
|
return model;
|
|
158
158
|
}
|
|
@@ -189,37 +189,3 @@ export function rewriteConfigModel(src, spec) {
|
|
|
189
189
|
export function resolveModelSpec(flag, config, env = process.env) {
|
|
190
190
|
return flag ?? (env.FASTAGENT_MODEL || config.model);
|
|
191
191
|
}
|
|
192
|
-
/**
|
|
193
|
-
* The auth-file override: the SDK's `authPath` option > `FASTAGENT_AUTH_PATH` env > undefined (the opener then falls
|
|
194
|
-
* back to {@link defaultAuthPath} under the {@link resolveSecretsDir} dir). The CLI has no flag for it: a second
|
|
195
|
-
* spelling of one environment variable buys nothing, and the variable is what a deployed container reads anyway.
|
|
196
|
-
*/
|
|
197
|
-
function resolveAuthPathOverride(flag, env = process.env) {
|
|
198
|
-
return resolveOverridePath(flag ?? env.FASTAGENT_AUTH_PATH);
|
|
199
|
-
}
|
|
200
|
-
/** The default credentials file under a resolved secrets dir ({@link resolveSecretsDir}). */
|
|
201
|
-
export function defaultAuthPath(secretsDir) {
|
|
202
|
-
return join(secretsDir, "auth.json");
|
|
203
|
-
}
|
|
204
|
-
/** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
|
|
205
|
-
export function resolveAuthPath(dir, flag, env = process.env) {
|
|
206
|
-
return resolveAuthPathOverride(flag, env) ?? defaultAuthPath(resolveSecretsDir(dir, env));
|
|
207
|
-
}
|
|
208
|
-
/** The {@link AuthLayers} an agent directory reads: `authPath` option > `FASTAGENT_AUTH_PATH` > its own file. */
|
|
209
|
-
export function resolveAuthLayers(agentDir, authPath, env = process.env) {
|
|
210
|
-
const fallback = resolveAuthFallback(authPath, env);
|
|
211
|
-
return { path: resolveAuthPath(agentDir, authPath, env), ...(fallback !== undefined ? { fallback } : {}) };
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Where a credential the project does not have is read from instead: the user-global store, because a login is a
|
|
215
|
-
* PERSON on a machine and not a project — one `login -g` then serves every agent here. Undefined when an explicit
|
|
216
|
-
* path was named: "use this file" is an instruction, not a preference, so it gets no second layer.
|
|
217
|
-
*
|
|
218
|
-
* BOTH knobs {@link resolveAuthPath} reads count as that instruction, `FASTAGENT_SECRETS_DIR` included — it is what
|
|
219
|
-
* the Fly/Railway/AgentCore artifacts set, and a deployed container must read its mounted credentials and nothing
|
|
220
|
-
* else (the artifact is the truth).
|
|
221
|
-
*/
|
|
222
|
-
export function resolveAuthFallback(flag, env = process.env) {
|
|
223
|
-
const explicit = resolveAuthPathOverride(flag, env) ?? resolveOverridePath(env.FASTAGENT_SECRETS_DIR);
|
|
224
|
-
return explicit === undefined ? GLOBAL_AUTH_PATH : undefined;
|
|
225
|
-
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import type { ExecutionEnv, Skill, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import { type ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import type { Provider } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { CredentialStore, Provider } from "@earendil-works/pi-ai";
|
|
4
4
|
import type { Agent } from "../../agent.ts";
|
|
5
|
-
import { type
|
|
6
|
-
import type { FastagentAuthOptions } from "./auth.ts";
|
|
5
|
+
import { type FastagentConfig } from "./config.ts";
|
|
7
6
|
import { type LoadedDefinition } from "./definition.ts";
|
|
8
7
|
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
9
8
|
import { type ToolCollision, type MountedTool } from "./tool.ts";
|
|
10
9
|
import { type DeclaredSecret } from "../../declared-secrets.ts";
|
|
11
10
|
import { type PiAgentSessionFactory } from "./invoke-session.ts";
|
|
11
|
+
import { type PiAgentSessionFactoryOptions } from "./agent-session-factory.ts";
|
|
12
12
|
import { type AnyModel } from "./models.ts";
|
|
13
|
+
import { type AgentModels } from "./agent-models.ts";
|
|
13
14
|
import { type PiSessionRecordStore } from "./session-store.ts";
|
|
14
15
|
import { type Lease, type SessionObserver } from "./turn-kit.ts";
|
|
15
16
|
/**
|
|
@@ -18,6 +19,13 @@ import { type Lease, type SessionObserver } from "./turn-kit.ts";
|
|
|
18
19
|
*/
|
|
19
20
|
export declare const CODING_TOOL_NAMES: readonly ["read", "grep", "find", "ls", "bash", "edit", "write"];
|
|
20
21
|
export declare function piAllCodingTools(cwd: string): MountedTool[];
|
|
22
|
+
/** How the model reaches a mounted tool it is not given up front. */
|
|
23
|
+
export type ToolReach = "tool_search" | "codemode" | "hidden" | "inactive" | "unreachable";
|
|
24
|
+
/** A mounted authored tool outside the default-active surface, and its way in. */
|
|
25
|
+
export interface IndirectTool {
|
|
26
|
+
name: string;
|
|
27
|
+
reach: ToolReach;
|
|
28
|
+
}
|
|
21
29
|
/**
|
|
22
30
|
* The full directory-agent tool set: all pi coding tools + `config.tools` + discovered `tools/` (deduped, existing
|
|
23
31
|
* win), plus the authored names and collisions to report.
|
|
@@ -25,11 +33,8 @@ export declare function piAllCodingTools(cwd: string): MountedTool[];
|
|
|
25
33
|
export declare function resolveAgentTools(config: FastagentConfig, agentDir: string, cwd: string): Promise<{
|
|
26
34
|
tools: MountedTool[];
|
|
27
35
|
toolNames: string[];
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
* `search_tools` loader.
|
|
31
|
-
*/
|
|
32
|
-
deferredToolNames: string[];
|
|
36
|
+
/** Mounted authored tools the model is not given up front, with how each is reached. */
|
|
37
|
+
indirectTools: IndirectTool[];
|
|
33
38
|
toolCollisions: ToolCollision[];
|
|
34
39
|
toolFailures: ModuleLoadFailure[];
|
|
35
40
|
/** Env vars the MOUNTED tools declared they need, BY TOOL NAME — a discovered tool shadowed by a
|
|
@@ -44,6 +49,8 @@ export declare function resolveAgentTools(config: FastagentConfig, agentDir: str
|
|
|
44
49
|
export declare function piBasePrompt(options?: {
|
|
45
50
|
tools?: MountedTool[];
|
|
46
51
|
persona?: string;
|
|
52
|
+
/** The {@link BUILTIN_EXTENSIONS} this machine leaves enabled (`Machine.builtinExtensions`); all by default. */
|
|
53
|
+
builtinExtensions?: readonly string[];
|
|
47
54
|
}): string;
|
|
48
55
|
export interface AssembleSystemPromptOptions {
|
|
49
56
|
/**
|
|
@@ -65,6 +72,10 @@ export declare function assembleSystemPrompt(options: AssembleSystemPromptOption
|
|
|
65
72
|
export interface PiAssembly {
|
|
66
73
|
lease: Lease;
|
|
67
74
|
sessionFactory: PiAgentSessionFactory;
|
|
75
|
+
/** Extension-aware catalog for startup reporting and control-plane validation; never bound to a session. */
|
|
76
|
+
modelRuntime: () => Promise<ModelRuntime>;
|
|
77
|
+
/** A fresh runtime for every session, before loading its extensions. */
|
|
78
|
+
createModelRuntime: () => Promise<ModelRuntime>;
|
|
68
79
|
/** The registry and configured model, resolved on first use (a credential read is async). */
|
|
69
80
|
engine: () => Promise<{
|
|
70
81
|
modelRuntime: ModelRuntime;
|
|
@@ -72,6 +83,12 @@ export interface PiAssembly {
|
|
|
72
83
|
}>;
|
|
73
84
|
/** The configured reasoning effort — the other half of the pair a session without overrides runs on. */
|
|
74
85
|
thinkingLevel: ThinkingLevel;
|
|
86
|
+
/** The tools every session mounts. */
|
|
87
|
+
tools: MountedTool[];
|
|
88
|
+
/** Pi built-ins the mounted tools leave out, denied in every session so discovery cannot bring them back. */
|
|
89
|
+
excludedToolNames: readonly string[];
|
|
90
|
+
/** The prompt and skills a session runs on, read when a session is bound. */
|
|
91
|
+
readDefinition: PiAgentSessionFactoryOptions["readDefinition"];
|
|
75
92
|
/** The extension entry points every session loads, discovered once with the assembly. */
|
|
76
93
|
extensionPaths: readonly string[];
|
|
77
94
|
}
|
|
@@ -98,6 +115,8 @@ export interface CreatePiAgentOptions {
|
|
|
98
115
|
providers?: Provider[];
|
|
99
116
|
/** Credentials file for stored OAuth/API-key auth (default `GLOBAL_AUTH_PATH`). */
|
|
100
117
|
authPath?: string;
|
|
118
|
+
/** The caller's own credential store, in place of any file ({@link CredentialSourceOptions}). */
|
|
119
|
+
credentialStore?: CredentialStore;
|
|
101
120
|
sessions?: PiSessionRecordStore;
|
|
102
121
|
/** Supplies the working directory at L1 (default: process.cwd()), which loads no definition. */
|
|
103
122
|
env?: ExecutionEnv;
|
|
@@ -129,6 +148,8 @@ export interface CreatePiAgentFromDefinitionOptions {
|
|
|
129
148
|
* gets no second layer.
|
|
130
149
|
*/
|
|
131
150
|
authPath?: string;
|
|
151
|
+
/** The caller's own credential store, in place of any file ({@link CredentialSourceOptions}). */
|
|
152
|
+
credentialStore?: CredentialStore;
|
|
132
153
|
sessions?: PiSessionRecordStore;
|
|
133
154
|
/** Filesystem/process environment; see {@link CreatePiAgentOptions.env}. */
|
|
134
155
|
env?: ExecutionEnv;
|
|
@@ -137,20 +158,12 @@ export interface CreatePiAgentFromDefinitionOptions {
|
|
|
137
158
|
observer?: SessionObserver;
|
|
138
159
|
}
|
|
139
160
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* The directory rung assembles from it and `availableModelsFromDir` lists from it, so a layer added here reaches both.
|
|
161
|
+
* L2, as the value: load the directory (base + AGENTS.md + skills + env) and assemble. `models` is the directory's
|
|
162
|
+
* model environment when the caller already built it (the opener, whose report must describe what runs); otherwise it
|
|
163
|
+
* is built here from the credential options.
|
|
144
164
|
*/
|
|
145
|
-
export declare function
|
|
146
|
-
|
|
147
|
-
providers?: Provider[];
|
|
148
|
-
}): Promise<ModelRuntime>;
|
|
149
|
-
/** L2, as the value: load the directory (base + AGENTS.md + skills + env) and assemble. */
|
|
150
|
-
export declare function assemblePiFromDefinition(dir: string,
|
|
151
|
-
/** `auth` is the opener's already-resolved layers, which take the place of `authPath`. */
|
|
152
|
-
options: Omit<CreatePiAgentFromDefinitionOptions, "observer"> & {
|
|
153
|
-
auth?: AuthLayers;
|
|
165
|
+
export declare function assemblePiFromDefinition(dir: string, options: Omit<CreatePiAgentFromDefinitionOptions, "observer"> & {
|
|
166
|
+
models?: AgentModels;
|
|
154
167
|
}): Promise<{
|
|
155
168
|
assembly: PiAssembly;
|
|
156
169
|
definition: LoadedDefinition;
|