@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
package/dist/deploy/preflight.js
CHANGED
|
@@ -6,13 +6,15 @@ import { readFile } from "node:fs/promises";
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { basename, join, relative } from "node:path";
|
|
8
8
|
import { isModelSpec, isReleaseAgentName } from "./workspace.js";
|
|
9
|
-
import { providerOf
|
|
10
|
-
import {
|
|
9
|
+
import { providerOf } from "../engines/pi/config.js";
|
|
10
|
+
import { resolveAuthPath } from "../engines/pi/auth.js";
|
|
11
|
+
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
12
|
+
import { AGENT_MODEL_CATALOG_FILE, AGENT_MODELS_FILE, exists } from "../paths.js";
|
|
11
13
|
import { inspectChannels } from "../channels/discover.js";
|
|
12
14
|
import { loadRoutines } from "../schedule/discover.js";
|
|
13
15
|
import { resolveAgentTools } from "../engines/pi/create.js";
|
|
14
16
|
import { allSecrets } from "../declared-secrets.js";
|
|
15
|
-
import { createPiModelRuntime, literalKeyProviders, isBuiltinProvider, machineModels,
|
|
17
|
+
import { createPiModelRuntime, definitionKeyOf, environmentAuthSource, literalKeyProviders, isBuiltinProvider, machineModels, globalCatalogPath, inGlobalCatalog, interactiveLoginKind, loginProviders, } from "../engines/pi/models.js";
|
|
16
18
|
import { CHANNEL_KINDS } from "../scaffold/add-channel.js";
|
|
17
19
|
import { detectRuntime, readPackageJson } from "../runtime.js";
|
|
18
20
|
import { fastagentVersion } from "../version.js";
|
|
@@ -147,21 +149,30 @@ async function gatherFacts(input, report) {
|
|
|
147
149
|
: ""));
|
|
148
150
|
}
|
|
149
151
|
await checkMachineModels(agentDir, modelSpec, report);
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
152
|
+
// The registry the DEPLOYED agent has (its own models.json and model catalog, nothing of the machine's), built first
|
|
153
|
+
// so a malformed file stops the deploy with pi's own reason before anything below reads the file raw. No credential
|
|
154
|
+
// is read for that.
|
|
155
|
+
const deployed = await createPiModelRuntime({
|
|
156
|
+
agentDir,
|
|
157
|
+
credentials: new InMemoryCredentialStore(),
|
|
158
|
+
machineLayer: false,
|
|
159
|
+
});
|
|
160
|
+
if (modelSpec)
|
|
161
|
+
await checkGlobalCatalogModel(modelSpec, deployed, report);
|
|
153
162
|
const authPath = resolveAuthPath(agentDir);
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
const route = modelSpec ? await credentialRoute(agentDir, modelSpec, values) : {};
|
|
164
|
+
const modelAuth = route.envVar;
|
|
165
|
+
const boxLogin = route.boxLogin;
|
|
166
|
+
if (boxLogin !== undefined) {
|
|
167
|
+
if (!hasLogin(boxLogin)) {
|
|
168
|
+
report.issue(`no credential for ${modelSpec} reaches the deployment, and "${boxLogin}" has no login to run there — set ` +
|
|
169
|
+
`its API key in ${valueFile}`);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
report.note(`${modelSpec}: no credential ships with this deploy (this machine's logins and shell stay here) — once the ` +
|
|
173
|
+
`box is up, \`--run\` asks it: it keeps what it already authenticates ${boxLogin} with, else logs in ` +
|
|
174
|
+
`(\`fastagent login ${boxLogin} --deployment\`). Or set ${boxLogin}'s API key in ${valueFile}`);
|
|
175
|
+
}
|
|
165
176
|
}
|
|
166
177
|
// Reported, not refused. Whether a string is a credential is the AUTHOR's knowledge: pi's docs prescribe
|
|
167
178
|
// `"apiKey": "ollama"` for a keyless local server, and no static rule separates that from a leaked key. The
|
|
@@ -254,14 +265,43 @@ async function gatherFacts(input, report) {
|
|
|
254
265
|
values,
|
|
255
266
|
valueFile,
|
|
256
267
|
modelAuth,
|
|
257
|
-
|
|
258
|
-
authPath,
|
|
268
|
+
boxLogin,
|
|
259
269
|
container,
|
|
260
270
|
port,
|
|
261
271
|
declaredSecrets,
|
|
262
272
|
secrets: deploymentSecrets(modelAuth, declaredSecrets, values, valueFile),
|
|
263
273
|
};
|
|
264
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* HOW THE MODEL'S CREDENTIAL REACHES THE BOX, from what the deploy ships and nothing else. The box is the one authority
|
|
277
|
+
* on what it authenticates with (pi ranks a stored login above the environment, and only the box knows what it has
|
|
278
|
+
* stored, which platform variables it was given, or what role it runs as), so this never asks what authenticates the
|
|
279
|
+
* model on THIS machine: its stored logins and the shell running deploy do not travel. In order:
|
|
280
|
+
*
|
|
281
|
+
* 1. the definition's own models.json: a `"$NAME"` reference is a variable the value file must hold (the values gate
|
|
282
|
+
* asks for it by name), and a literal or `"!command"` travels in the image;
|
|
283
|
+
* 2. a credential pi reads for the provider from the value file alone: its key variable (which the values gate then
|
|
284
|
+
* requires, as it does every declared name), or a keyless one such as `AWS_ACCESS_KEY_ID` — never one that needs a
|
|
285
|
+
* file, which would be this machine's;
|
|
286
|
+
* 3. otherwise the box answers — `boxLogin`: after readiness, `--run` asks it, and it logs in only if it cannot
|
|
287
|
+
* already authenticate the provider.
|
|
288
|
+
*/
|
|
289
|
+
async function credentialRoute(agentDir, spec, values) {
|
|
290
|
+
const provider = providerOf(spec);
|
|
291
|
+
const declared = await definitionKeyOf(agentDir, provider);
|
|
292
|
+
if (declared)
|
|
293
|
+
return "reference" in declared ? { envVar: declared.reference } : {};
|
|
294
|
+
// No file of this machine's travels, so a source that needs one (Google ADC, an AWS profile) is the box's to find.
|
|
295
|
+
const fromValues = await environmentAuthSource(provider, Object.fromEntries(values), async () => false);
|
|
296
|
+
if (fromValues !== undefined)
|
|
297
|
+
return isEnvKey(fromValues) ? { envVar: fromValues } : {};
|
|
298
|
+
return { boxLogin: provider };
|
|
299
|
+
}
|
|
300
|
+
/** Does `provider` offer an interactive login, i.e. can `fastagent login --deployment` authenticate it on the box? */
|
|
301
|
+
function hasLogin(providerId) {
|
|
302
|
+
const provider = loginProviders().find((p) => p.id === providerId);
|
|
303
|
+
return provider !== undefined && interactiveLoginKind(provider) !== "none";
|
|
304
|
+
}
|
|
265
305
|
/**
|
|
266
306
|
* The machine's models.json is this box's environment, not the artifact: whatever the model takes from it is
|
|
267
307
|
* absent wherever the agent is deployed. Said in so many words here; the credential probe already reads the deployed
|
|
@@ -285,6 +325,20 @@ async function checkMachineModels(agentDir, modelSpec, report) {
|
|
|
285
325
|
report.issue(issue);
|
|
286
326
|
}
|
|
287
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* The machine's model catalog is this box's environment, like its models.json: a model only it knows (newer than the
|
|
330
|
+
* catalog bundled with pi) is unknown wherever the agent is deployed. The agent's own catalog ships, so the remedy is
|
|
331
|
+
* one refresh in the agent.
|
|
332
|
+
*/
|
|
333
|
+
async function checkGlobalCatalogModel(modelSpec, deployed, report) {
|
|
334
|
+
const provider = providerOf(modelSpec);
|
|
335
|
+
const id = modelSpec.slice(provider.length + 1);
|
|
336
|
+
if (deployed.getModel(provider, id) || !(await inGlobalCatalog(provider, id)))
|
|
337
|
+
return;
|
|
338
|
+
report.issue(`model "${modelSpec}" is known only from ${globalCatalogPath()}, the machine's model catalog, which does not ` +
|
|
339
|
+
"ship — the deployed agent would fail with an unknown model. Run `fastagent models --refresh` in the agent " +
|
|
340
|
+
`to record it in its own ${AGENT_MODEL_CATALOG_FILE}, which ships.`);
|
|
341
|
+
}
|
|
288
342
|
/**
|
|
289
343
|
* What a KEPT hand-written Dockerfile drops. `deploy.apt` is the obvious one; the resolved model is the one that
|
|
290
344
|
* looks safe and is not: the manifest is always written, but only the generated Dockerfile sets
|
|
@@ -341,7 +395,7 @@ valueFile) {
|
|
|
341
395
|
const source = fromEnv ? valueFile : "fastagent.config";
|
|
342
396
|
// BOTH sources are checked, at the one point that reads them: a `provider`-less spec resolves to nothing on the
|
|
343
397
|
// box, so letting `config.model` through would ship exactly the crash-loop this resolution exists to prevent —
|
|
344
|
-
//
|
|
398
|
+
// the auth probe reports "unconfigured" for it here and the failure only appears after deployment.
|
|
345
399
|
const spec = fromEnv || config.model;
|
|
346
400
|
if (spec && !isModelSpec(spec))
|
|
347
401
|
return { source, invalid: spec };
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/** `fastagent deploy railway` — the Railway deploy PLAN, computed from the resolved definition. */
|
|
2
2
|
import type { DeclaredChannel } from "../../channels/discover.ts";
|
|
3
3
|
import { type Artifact, type ContainerInput } from "../container.ts";
|
|
4
|
-
import {
|
|
4
|
+
import type { DeploymentSecret } from "../secrets.ts";
|
|
5
5
|
export interface RailwayPlanInput extends ContainerInput {
|
|
6
6
|
/** The service name to create (`railway add --service`). */
|
|
7
7
|
serviceName: string;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The provider the deployment logs in to itself (`fastagent login --deployment`): its credential is not a variable
|
|
10
|
+
* the plan carries.
|
|
11
|
+
*/
|
|
12
|
+
boxLogin?: string;
|
|
10
13
|
/**
|
|
11
14
|
* Every declared channel and its ingress — the source of the secret list, the webhook steps, and whether App
|
|
12
15
|
* Sleeping must stay off for an outbound connection.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { webhookRunbook } from "../channel-ingress.js";
|
|
2
2
|
import { containerArtifacts } from "../container.js";
|
|
3
|
+
import { deploymentLoginCommand } from "../box-shell.js";
|
|
3
4
|
import { CRON_CAN_BE_EXTERNAL, WAKEUPS_WHEN_ASLEEP, residencyFor } from "../residency.js";
|
|
4
|
-
import { isEnvKey } from "../secrets.js";
|
|
5
5
|
/** State root = the volume mount path, kept in lockstep. */
|
|
6
6
|
const MOUNT = "/data";
|
|
7
7
|
/** The `RAILWAY_DOCKERFILE_PATH` value for an agent under `prefix`. */
|
|
@@ -33,7 +33,7 @@ function railwayJson(prefix) {
|
|
|
33
33
|
}
|
|
34
34
|
/** Compute the Railway deploy plan from the resolved definition. */
|
|
35
35
|
export function planRailwayDeploy(input) {
|
|
36
|
-
const { serviceName,
|
|
36
|
+
const { serviceName, channels } = input;
|
|
37
37
|
// railway.json is namespaced under the agent dir too (the workspace may carry its own railway.toml/json for the
|
|
38
38
|
// product).
|
|
39
39
|
const configPath = `${input.agentPrefix}railway.json`;
|
|
@@ -60,7 +60,7 @@ export function planRailwayDeploy(input) {
|
|
|
60
60
|
`# the later commands resolve it without --service (--run passes --service to stay non-interactive).`,
|
|
61
61
|
`railway add --service ${serviceName}`,
|
|
62
62
|
``,
|
|
63
|
-
`# Persistent volume at ${MOUNT} — .state (sessions, channel state) + .secrets (
|
|
63
|
+
`# Persistent volume at ${MOUNT} — .state (sessions, channel state) + .secrets (the box's own login).`,
|
|
64
64
|
`railway volume add --mount-path ${MOUNT}`,
|
|
65
65
|
``,
|
|
66
66
|
`# Variables — set BEFORE the first deploy so the box boots with them. Railway injects PORT itself.`,
|
|
@@ -71,14 +71,12 @@ export function planRailwayDeploy(input) {
|
|
|
71
71
|
if (secrets.length > 0) {
|
|
72
72
|
runbook.push(`# Secrets:`, `# ${secrets.map((s) => `${s.name}: ${s.hint}`).join("\n# ")}`, `railway variables set ${secrets.map((s) => `${s.name}=<value>`).join(" ")}`);
|
|
73
73
|
}
|
|
74
|
-
// Model-auth guidance: an env key becomes a variable above.
|
|
75
|
-
if (!isEnvKey(modelAuth)) {
|
|
76
|
-
runbook.push(modelAuth === undefined
|
|
77
|
-
? `# Model auth: none found at the local auth path — a global \`fastagent login\` isn't read here; set FASTAGENT_AUTH_PATH (e.g. ~/.fastagent/.secrets/auth.json), or \`--run\` carries it automatically.`
|
|
78
|
-
: `# Model auth: your local auth is "${modelAuth}" — the plan can't read its value to set as a variable.`, `# Set your provider API key as a variable (railway variables set KEY=...), OR place auth.json on the ${MOUNT} volume.`);
|
|
79
|
-
}
|
|
80
74
|
runbook.push(``, `# OPTIONAL — the build already uses the agent's Dockerfile via RAILWAY_DOCKERFILE_PATH (set above),`, `# and Railway's default restart policy equals what ${configPath} declares (ON_FAILURE).`, `# Pointing the service at ${configPath} (Service → Settings → Config-as-code — dashboard-only) adds`, `# the /health healthcheck gate: a boot-crashing deploy is marked FAILED instead of going live dead.`, `# (Zero-downtime switching doesn't apply either way — the ${MOUNT} volume allows one active deployment.)`);
|
|
81
75
|
runbook.push(``, `# Before a new definition release, run \`fastagent deploy railway\` to refresh the release manifest.`, `# Upload this workspace and build on Railway (no local Docker needed):`, `railway up`);
|
|
76
|
+
// The credential is created on the box, never carried: the box is then the only holder of its grant.
|
|
77
|
+
if (input.boxLogin) {
|
|
78
|
+
runbook.push(``, `# Model auth: once it is up, the deployment logs in to ${input.boxLogin} itself (from this workspace):`, `${deploymentLoginCommand("railway", input.boxLogin)}`);
|
|
79
|
+
}
|
|
82
80
|
runbook.push(``, `# The volume keeps /data/base (including uncommitted work), .state and .secrets across restarts and deploys.`, `# Each generated release replaces only /data/base/${input.agentPrefix}; other workspace files are initialized once.`, input.shipsGit
|
|
83
81
|
? `# Railway uploads may strip .git. Clone inside the persistent workspace when collaboration needs history.`
|
|
84
82
|
: `# To use Git for collaboration, add deploy: { apt: ["git"] }. Storage durability does not require Git.`);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** `fastagent deploy railway --run` — drive the Railway CLI to completion. */
|
|
2
|
+
import type { BoxLoginStep } from "../box-shell.ts";
|
|
2
3
|
import { type PublicHealthProbe, type Registrars } from "../channel-ingress.ts";
|
|
3
4
|
import type { DeclaredChannel } from "../../channels/discover.ts";
|
|
4
|
-
import type
|
|
5
|
+
import { type CliRunner } from "../runner.ts";
|
|
5
6
|
export interface RailwayRunPlan {
|
|
6
7
|
/** Names both the project (`railway init --name`) and the service (`railway add --service`). */
|
|
7
8
|
name: string;
|
|
@@ -10,10 +11,7 @@ export interface RailwayRunPlan {
|
|
|
10
11
|
* in lockstep).
|
|
11
12
|
*/
|
|
12
13
|
mountPath: string;
|
|
13
|
-
/**
|
|
14
|
-
* `KEY=value` secrets set one-per-`variable set --stdin`: the value file's variables + `FASTAGENT_AUTH_SEED` for a
|
|
15
|
-
* file credential.
|
|
16
|
-
*/
|
|
14
|
+
/** `KEY=value` secrets set one-per-`variable set --stdin`: the value file's variables. */
|
|
17
15
|
secrets: Record<string, string>;
|
|
18
16
|
/** Declared names the value file supplies no value for — the run gates on these before any side effect. */
|
|
19
17
|
missingSecrets: string[];
|
|
@@ -25,6 +23,11 @@ export interface RailwayRunPlan {
|
|
|
25
23
|
intoLinked: boolean;
|
|
26
24
|
/** `RAILWAY_DOCKERFILE_PATH` value (`/fastagent/Dockerfile`). */
|
|
27
25
|
dockerfilePath: string;
|
|
26
|
+
/**
|
|
27
|
+
* Log the box in (`fastagent login --deployment`) once it is up and before any webhook is pointed at it: a channel pointed at
|
|
28
|
+
* a box with no model credential answers every message with a failure. Resolves a gate line, or undefined.
|
|
29
|
+
*/
|
|
30
|
+
boxLogin?: BoxLoginStep;
|
|
28
31
|
}
|
|
29
32
|
/**
|
|
30
33
|
* Done (with the live URL), or a gate the operator must clear before re-running (printed + non-zero exit by the CLI).
|
|
@@ -49,7 +52,8 @@ export declare function parseDomainUrl(stdout: string): string | undefined;
|
|
|
49
52
|
* The status of the volume `railway volume list --json` shows mounted at `mountPath` ON `service`, or undefined when
|
|
50
53
|
* that service has none there. The list is the whole PROJECT's: another service's volume, or one a deleted service
|
|
51
54
|
* left behind (`serviceName: null` — deleting a service keeps its volume), sits at the same path and is not this
|
|
52
|
-
* one. Read from any object carrying `mountPath` and `serviceName`, so a wrapper key moving does not matter.
|
|
55
|
+
* one. Read from any object carrying `mountPath` and `serviceName`, so a wrapper key moving does not matter. Output
|
|
56
|
+
* that is not JSON throws: read as "no volume", it would add a second one.
|
|
53
57
|
*/
|
|
54
58
|
export declare function volumeOn(stdout: string, service: string, mountPath: string): string | undefined;
|
|
55
59
|
export declare function deployRailwayRun(plan: RailwayRunPlan, railway: CliRunner, log: (msg: string) => void, registrars: Registrars, healthProbe?: PublicHealthProbe, sleep?: (ms: number) => Promise<void>): Promise<RailwayRunOutcome>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
import { loginGate, publicHealthGate, registerWebhooks, } from "../channel-ingress.js";
|
|
2
|
+
import { readOutput } from "../runner.js";
|
|
3
3
|
import { missingValuesGate } from "../secrets.js";
|
|
4
4
|
/** Whether `railway status --json` shows a linked project: non-empty stdout. */
|
|
5
5
|
export function isLinked(stdout) {
|
|
@@ -49,7 +49,8 @@ export function parseDomainUrl(stdout) {
|
|
|
49
49
|
* The status of the volume `railway volume list --json` shows mounted at `mountPath` ON `service`, or undefined when
|
|
50
50
|
* that service has none there. The list is the whole PROJECT's: another service's volume, or one a deleted service
|
|
51
51
|
* left behind (`serviceName: null` — deleting a service keeps its volume), sits at the same path and is not this
|
|
52
|
-
* one. Read from any object carrying `mountPath` and `serviceName`, so a wrapper key moving does not matter.
|
|
52
|
+
* one. Read from any object carrying `mountPath` and `serviceName`, so a wrapper key moving does not matter. Output
|
|
53
|
+
* that is not JSON throws: read as "no volume", it would add a second one.
|
|
53
54
|
*/
|
|
54
55
|
export function volumeOn(stdout, service, mountPath) {
|
|
55
56
|
const walk = (v) => {
|
|
@@ -62,12 +63,7 @@ export function volumeOn(stdout, service, mountPath) {
|
|
|
62
63
|
return typeof o.status === "string" ? o.status : "";
|
|
63
64
|
return walk(Object.values(o));
|
|
64
65
|
};
|
|
65
|
-
|
|
66
|
-
return walk(JSON.parse(stdout));
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
66
|
+
return walk(JSON.parse(stdout));
|
|
71
67
|
}
|
|
72
68
|
/**
|
|
73
69
|
* How long `--run` waits for a new volume to reach its service before deploying. Railway's `volume add` returns
|
|
@@ -113,7 +109,10 @@ export async function deployRailwayRun(plan, railway, log, registrars, healthPro
|
|
|
113
109
|
// Create + link the service (init makes only a project); it precedes the volume, which has no --service flag and
|
|
114
110
|
// rides the linked service.
|
|
115
111
|
log(`creating service ${plan.name}…`);
|
|
116
|
-
|
|
112
|
+
// `--variables` with one of the machinery variables set just below: in a terminal, a bare `railway add` stops at
|
|
113
|
+
// "Enter a variable <esc to skip>" (5.62.1) and waits for a key nobody was told to press.
|
|
114
|
+
const addArgs = ["add", "--service", plan.name, "--variables", `FASTAGENT_STATE_DIR=${plan.mountPath}/.state`];
|
|
115
|
+
if ((await railway(addArgs)).code !== 0) {
|
|
117
116
|
// Precise recovery, not "fix and re-run": init already created + linked the project, so a plain re-run hits the
|
|
118
117
|
// linked-gate, and --into-linked SKIPS `add` and then fails at the volume (no service to ride).
|
|
119
118
|
return gate(`\`railway add --service\` failed — \`railway init\` already created the project (this directory is now ` +
|
|
@@ -141,8 +140,11 @@ export async function deployRailwayRun(plan, railway, log, registrars, healthPro
|
|
|
141
140
|
}
|
|
142
141
|
}
|
|
143
142
|
// 3c.
|
|
144
|
-
const volumeStatus =
|
|
145
|
-
|
|
143
|
+
const volumeStatus = () => readOutput(railway, "railway", ["volume", "list", "--json"], (out) => volumeOn(out, plan.name, plan.mountPath));
|
|
144
|
+
const existing = await volumeStatus();
|
|
145
|
+
if ("gate" in existing)
|
|
146
|
+
return gate(existing.gate);
|
|
147
|
+
if (existing.value !== undefined) {
|
|
146
148
|
log(`volume at ${plan.mountPath} exists on ${plan.name} — skipping`);
|
|
147
149
|
}
|
|
148
150
|
else {
|
|
@@ -154,7 +156,10 @@ export async function deployRailwayRun(plan, railway, log, registrars, healthPro
|
|
|
154
156
|
// never seed a workspace onto a disk that vanishes on restart).
|
|
155
157
|
let status;
|
|
156
158
|
for (let waited = 0;; waited += VOLUME_POLL_MS) {
|
|
157
|
-
|
|
159
|
+
const read = await volumeStatus();
|
|
160
|
+
if ("gate" in read)
|
|
161
|
+
return gate(read.gate);
|
|
162
|
+
status = read.value;
|
|
158
163
|
if (status === "Ready" || waited >= VOLUME_ATTACH_TIMEOUT_MS)
|
|
159
164
|
break;
|
|
160
165
|
await sleep(VOLUME_POLL_MS);
|
|
@@ -183,9 +188,20 @@ export async function deployRailwayRun(plan, railway, log, registrars, healthPro
|
|
|
183
188
|
log,
|
|
184
189
|
inspectHint: "the service itself deployed — inspect `railway logs`, then re-run once it answers",
|
|
185
190
|
probe: healthProbe,
|
|
191
|
+
...(plan.boxLogin ? { login: plan.boxLogin.command } : {}),
|
|
186
192
|
});
|
|
187
193
|
if (healthGate)
|
|
188
194
|
return gate(healthGate);
|
|
195
|
+
const notLoggedIn = await plan.boxLogin?.run();
|
|
196
|
+
if (notLoggedIn) {
|
|
197
|
+
return gate(loginGate({
|
|
198
|
+
notLoggedIn,
|
|
199
|
+
channels: plan.channels,
|
|
200
|
+
log,
|
|
201
|
+
baseUrl: url,
|
|
202
|
+
afterLogin: "re-run `fastagent deploy railway --run --into-linked` (it keeps the login)",
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
189
205
|
// 7.
|
|
190
206
|
const registrationGateMsg = await registerWebhooks({
|
|
191
207
|
baseUrl: url,
|
package/dist/deploy/runner.d.ts
CHANGED
|
@@ -18,9 +18,25 @@ export type CliRunner = (args: string[], opts?: {
|
|
|
18
18
|
input?: string;
|
|
19
19
|
env?: NodeJS.ProcessEnv;
|
|
20
20
|
}) => Promise<RunResult>;
|
|
21
|
+
/**
|
|
22
|
+
* A read-only host-CLI query (`… list --json`), reduced to the question the next step asks of it — or the gate for an
|
|
23
|
+
* answer we cannot act on. A failed command and unreadable output both stop the run: read as "nothing there", either
|
|
24
|
+
* one would send the next step to create what already exists.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readOutput<T>(run: CliRunner, bin: string, args: string[], read: (stdout: string) => T): Promise<{
|
|
27
|
+
value: T;
|
|
28
|
+
} | {
|
|
29
|
+
gate: string;
|
|
30
|
+
}>;
|
|
21
31
|
/**
|
|
22
32
|
* Production {@link CliRunner}: spawn `bin` in `cwd` (the workspace, so a build/upload context is the agent). stderr
|
|
23
33
|
* is always inherited to the terminal.
|
|
24
34
|
*/
|
|
25
35
|
export declare function spawnRunner(bin: string, cwd: string): CliRunner;
|
|
36
|
+
/**
|
|
37
|
+
* The AWS CLI, as every command here runs it: with its pager off. Given a terminal, AWS CLI v2 pipes any output it
|
|
38
|
+
* prints through `less`, so `deploy agentcore --run` stopped at `(END)` after `ecr create-repository` until someone
|
|
39
|
+
* pressed `q`. ONE runner rather than a `--no-cli-pager` at each call: a call site that forgets is how that hang ships.
|
|
40
|
+
*/
|
|
41
|
+
export declare function awsRunner(cwd: string): CliRunner;
|
|
26
42
|
export {};
|
package/dist/deploy/runner.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
/** The host-CLI dispatcher seam, shared by every `deploy <host> --run` driver (fly/run.ts, railway/run.ts). */
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { log } from "../log.js";
|
|
4
|
+
/**
|
|
5
|
+
* A read-only host-CLI query (`… list --json`), reduced to the question the next step asks of it — or the gate for an
|
|
6
|
+
* answer we cannot act on. A failed command and unreadable output both stop the run: read as "nothing there", either
|
|
7
|
+
* one would send the next step to create what already exists.
|
|
8
|
+
*/
|
|
9
|
+
export async function readOutput(run, bin, args, read) {
|
|
10
|
+
// The command as RUN, not a restatement of it.
|
|
11
|
+
const cmd = `${bin} ${args.join(" ")}`;
|
|
12
|
+
const result = await run(args, { capture: true });
|
|
13
|
+
if (result.code !== 0)
|
|
14
|
+
return { gate: `\`${cmd}\` failed — see the ${bin} output above; fix and re-run` };
|
|
15
|
+
try {
|
|
16
|
+
return { value: read(result.stdout) };
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
// The one place a parse failure is allowed to stop being an exception.
|
|
20
|
+
return {
|
|
21
|
+
gate: `\`${cmd}\` was unreadable (${error instanceof Error ? error.message : String(error)}) — ` +
|
|
22
|
+
`run it yourself and check the ${bin} CLI version; fix and re-run`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
4
26
|
/**
|
|
5
27
|
* Production {@link CliRunner}: spawn `bin` in `cwd` (the workspace, so a build/upload context is the agent). stderr
|
|
6
28
|
* is always inherited to the terminal.
|
|
@@ -39,3 +61,12 @@ export function spawnRunner(bin, cwd) {
|
|
|
39
61
|
child.on("error", () => res({ code: 127, stdout: "", stderr: opts?.captureStderr ? "" : undefined })); // ENOENT
|
|
40
62
|
});
|
|
41
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* The AWS CLI, as every command here runs it: with its pager off. Given a terminal, AWS CLI v2 pipes any output it
|
|
66
|
+
* prints through `less`, so `deploy agentcore --run` stopped at `(END)` after `ecr create-repository` until someone
|
|
67
|
+
* pressed `q`. ONE runner rather than a `--no-cli-pager` at each call: a call site that forgets is how that hang ships.
|
|
68
|
+
*/
|
|
69
|
+
export function awsRunner(cwd) {
|
|
70
|
+
const aws = spawnRunner("aws", cwd);
|
|
71
|
+
return (args, opts) => aws(args, { ...opts, env: { ...opts?.env, AWS_PAGER: "" } });
|
|
72
|
+
}
|
package/dist/deploy/secrets.d.ts
CHANGED
|
@@ -19,21 +19,17 @@ export interface DeploymentSecret {
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function deploymentSecrets(modelAuth: string | undefined, declared: readonly DeclaredSecret[], values: ReadonlyMap<string, string>, valueFile: string): DeploymentSecret[];
|
|
21
21
|
/**
|
|
22
|
-
* Assemble the VALUES a `--run` deploy sets on the host: every variable the value file carries
|
|
23
|
-
* credential. Never minted here.
|
|
22
|
+
* Assemble the VALUES a `--run` deploy sets on the host: every variable the value file carries. Never minted here.
|
|
24
23
|
*
|
|
25
24
|
* `values` is the selected value file, read as data — **the environment running `deploy` is not a source**. A
|
|
26
25
|
* deployment must be reproducible from what it carries, and a variable that happens to be exported on the builder is
|
|
27
26
|
* written down nowhere. CI supplies values by writing the file before running the command.
|
|
27
|
+
*
|
|
28
|
+
* No credentials file travels: a model that does not authenticate through a variable (or its own definition) logs in
|
|
29
|
+
* on the deployment itself (`fastagent login --deployment`), so the box is the only holder of its grant.
|
|
28
30
|
*/
|
|
29
31
|
export declare function assembleSecrets(input: {
|
|
30
32
|
modelAuth: string | undefined;
|
|
31
|
-
/**
|
|
32
|
-
* The definition carries the model key itself (a models.json literal `apiKey` / `!command`): there is no value to
|
|
33
|
-
* carry and no gate to raise.
|
|
34
|
-
*/
|
|
35
|
-
modelKeyInDefinition?: boolean;
|
|
36
|
-
authFile: Buffer | undefined;
|
|
37
33
|
/** Every name the definition declared it needs — what must have a value. */
|
|
38
34
|
declared?: readonly DeclaredSecret[];
|
|
39
35
|
/** The selected value file's contents (`loadEnvValues`). The ONLY source of operator-supplied values. */
|
|
@@ -41,7 +37,6 @@ export declare function assembleSecrets(input: {
|
|
|
41
37
|
}): {
|
|
42
38
|
secrets: Record<string, string>;
|
|
43
39
|
missingSecrets: string[];
|
|
44
|
-
needsModelCredential: boolean;
|
|
45
40
|
};
|
|
46
41
|
/**
|
|
47
42
|
* The ONE refusal for declared names the deployed environment does not supply a value for — every host reaches it
|
|
@@ -49,10 +44,6 @@ export declare function assembleSecrets(input: {
|
|
|
49
44
|
* name missing from it is missing from the deployment, whatever platform receives it.
|
|
50
45
|
*/
|
|
51
46
|
export declare function missingValuesGate(missing: readonly string[], valueFile: string): string | undefined;
|
|
52
|
-
/** The bytes to seed to the auth file, or undefined to leave it alone. */
|
|
53
|
-
export declare function authSeedBytes(seed: string | undefined, fileExists: boolean): Buffer | undefined;
|
|
54
|
-
/** `FASTAGENT_AUTH_SEED` (+ `_2`, …): the base64 auth.json a deploy carries. */
|
|
55
|
-
export declare function collectAuthSeed(env: NodeJS.ProcessEnv): string | undefined;
|
|
56
47
|
/**
|
|
57
48
|
* Expand `FASTAGENT_ENV` into the process environment — what `start` does first on a host that carries the value file
|
|
58
49
|
* that way. The platform's own variables still win, as they do over a value file anywhere.
|
package/dist/deploy/secrets.js
CHANGED
|
@@ -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) || /^
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
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
|
|
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
|
package/dist/dev-supervisor.js
CHANGED
|
@@ -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.
|
|
@@ -33,6 +33,9 @@ export function devWatchIgnored(root, envFile) {
|
|
|
33
33
|
// 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,70 @@
|
|
|
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 { Credential, CredentialStore, Models, Provider } from "@earendil-works/pi-ai";
|
|
9
|
+
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { type AuthLayers, type CredentialSourceOptions, type FastagentAuthOptions } from "./auth.ts";
|
|
11
|
+
/** What authenticates one provider for an agent ({@link AgentModels.authStatus}). */
|
|
12
|
+
interface AuthStatus {
|
|
13
|
+
/** What satisfies it now (`OAuth`, `stored credential`, an env variable's name), after a due refresh. */
|
|
14
|
+
source?: string;
|
|
15
|
+
/** Why resolving it failed (a corrupt entry, a refresh the provider refused), when it did. */
|
|
16
|
+
error?: string;
|
|
17
|
+
/** The file that holds the stored credential, or the one a login would write; absent over a caller's store. */
|
|
18
|
+
path?: string;
|
|
19
|
+
/** The kind of credential the store holds for it, usable or not. */
|
|
20
|
+
stored?: Credential["type"];
|
|
21
|
+
/**
|
|
22
|
+
* An env variable that also authenticates it but goes unused, because pi lets a stored credential own its provider
|
|
23
|
+
* — a key added to a deployment's value file after the box was logged in, typically.
|
|
24
|
+
*/
|
|
25
|
+
shadowed?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface AgentModels {
|
|
28
|
+
/** The credentials files behind {@link credentials}; absent when the caller supplied its own store. */
|
|
29
|
+
auth?: AuthLayers;
|
|
30
|
+
credentials: CredentialStore;
|
|
31
|
+
/**
|
|
32
|
+
* The registry: pi's built-ins, plus (with a directory) the agent's `models.json` and model catalog over the
|
|
33
|
+
* machine's, plus `providers`. Built on first use and shared after, so a malformed file fails the first reader.
|
|
34
|
+
*/
|
|
35
|
+
runtime(): Promise<ModelRuntime>;
|
|
36
|
+
/**
|
|
37
|
+
* What authenticates `provider` here, resolved exactly as a turn resolves it: the ONE answer the startup report and a
|
|
38
|
+
* deployed box's `login --if-missing` both give. `modelId` picks the model to probe with; the provider's first one
|
|
39
|
+
* otherwise (auth is provider-scoped).
|
|
40
|
+
*/
|
|
41
|
+
authStatus(provider: string, modelId?: string): Promise<AuthStatus>;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentModelsOptions {
|
|
44
|
+
/** Extra providers registered on top of the built-ins (a same id replaces one). */
|
|
45
|
+
providers?: readonly Provider[];
|
|
46
|
+
/**
|
|
47
|
+
* Layer the machine's models.json and model catalog under the agent's (default). Off for the registry a DEPLOYED
|
|
48
|
+
* agent has, since neither ships.
|
|
49
|
+
*/
|
|
50
|
+
machineLayer?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The model environment of the agent in `agentDir` (or, without one, of a caller with no directory: pi's built-ins
|
|
54
|
+
* over the named or global credentials file). A supplied `credentialStore` is used as given; otherwise the files:
|
|
55
|
+
* the directory's own layers ({@link resolveAuthLayers}), or the named file or the global one.
|
|
56
|
+
*/
|
|
57
|
+
export declare function agentModels(agentDir: string | undefined, source?: {
|
|
58
|
+
authPath?: string;
|
|
59
|
+
credentialStore?: undefined;
|
|
60
|
+
} & FastagentAuthOptions, options?: AgentModelsOptions): AgentModels & {
|
|
61
|
+
auth: AuthLayers;
|
|
62
|
+
};
|
|
63
|
+
export declare function agentModels(agentDir: string | undefined, source?: CredentialSourceOptions & FastagentAuthOptions, options?: AgentModelsOptions): AgentModels;
|
|
64
|
+
export interface CreatePiModelsOptions extends FastagentAuthOptions, CredentialSourceOptions {
|
|
65
|
+
/** Extra providers registered on top of the built-ins (same id overrides a built-in). */
|
|
66
|
+
providers?: Provider[];
|
|
67
|
+
}
|
|
68
|
+
/** A `Models` with every built-in pi provider, wired to fastagent's auth. */
|
|
69
|
+
export declare function createPiModels(options?: CreatePiModelsOptions): Models;
|
|
70
|
+
export {};
|