@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/deploy/fly/run.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** `fastagent deploy fly --run` — drive flyctl 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 FlyRunPlan {
|
|
6
7
|
appName: string;
|
|
7
|
-
/** `KEY=value` secrets to set on Fly: the value file's variables
|
|
8
|
+
/** `KEY=value` secrets to set on Fly: the value file's variables. */
|
|
8
9
|
secrets: Record<string, string>;
|
|
9
10
|
/** Declared names the value file supplies no value for — the run gates on these before any side effect. */
|
|
10
11
|
missingSecrets: string[];
|
|
@@ -19,6 +20,11 @@ export interface FlyRunPlan {
|
|
|
19
20
|
* resolve it relative to the config's own directory).
|
|
20
21
|
*/
|
|
21
22
|
dockerfile: string;
|
|
23
|
+
/**
|
|
24
|
+
* Log the box in (`fastagent login --deployment`) once it is up and before any webhook is pointed at it: a channel pointed at
|
|
25
|
+
* a box with no model credential answers every message with a failure. Resolves a gate line, or undefined.
|
|
26
|
+
*/
|
|
27
|
+
boxLogin?: BoxLoginStep;
|
|
22
28
|
}
|
|
23
29
|
/** Done, or a gate the operator must clear before re-running (printed + non-zero exit by the CLI). */
|
|
24
30
|
export type FlyRunOutcome = {
|
package/dist/deploy/fly/run.js
CHANGED
|
@@ -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
|
/**
|
|
5
5
|
* The `Type` values that put an app on `https://<app>.fly.dev`, BY FAMILY — one entry per allocate command, because
|
|
@@ -28,27 +28,6 @@ export function listHasName(stdout, name) {
|
|
|
28
28
|
throw new Error(`expected a JSON array, got ${typeof entries}`);
|
|
29
29
|
return entries.some((o) => o.Name === name);
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
32
|
-
* A read-only `fly … list --json`, reduced to the question the next step asks of it — or the gate for a list we cannot
|
|
33
|
-
* act on.
|
|
34
|
-
*/
|
|
35
|
-
async function readList(fly, args, read) {
|
|
36
|
-
// The command as RUN, not a restatement of it.
|
|
37
|
-
const cmd = `fly ${args.join(" ")}`;
|
|
38
|
-
const result = await fly(args, { capture: true });
|
|
39
|
-
if (result.code !== 0)
|
|
40
|
-
return { gate: `\`${cmd}\` failed — see the flyctl output above; fix and re-run` };
|
|
41
|
-
try {
|
|
42
|
-
return { value: read(result.stdout) };
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
// The one place a parse failure is allowed to stop being an exception.
|
|
46
|
-
return {
|
|
47
|
-
gate: `\`${cmd}\` was unreadable (${error instanceof Error ? error.message : String(error)}) — ` +
|
|
48
|
-
`run it yourself and check the flyctl version; fix and re-run`,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
31
|
export async function deployFlyRun(plan, fly, log, registrars, healthProbe) {
|
|
53
32
|
const gate = (g) => ({ ok: false, gate: g });
|
|
54
33
|
// 1.
|
|
@@ -60,7 +39,7 @@ export async function deployFlyRun(plan, fly, log, registrars, healthProbe) {
|
|
|
60
39
|
if (missingValues)
|
|
61
40
|
return gate(missingValues);
|
|
62
41
|
// 3.
|
|
63
|
-
const appExists = await
|
|
42
|
+
const appExists = await readOutput(fly, "fly", ["apps", "list", "--json"], (out) => listHasName(out, plan.appName));
|
|
64
43
|
if ("gate" in appExists)
|
|
65
44
|
return gate(appExists.gate);
|
|
66
45
|
if (appExists.value) {
|
|
@@ -77,7 +56,7 @@ export async function deployFlyRun(plan, fly, log, registrars, healthProbe) {
|
|
|
77
56
|
// which machine (guest size + image) must fit on the host the volume is pinned to. Pre-creating one here placed it
|
|
78
57
|
// blind, and a host with disk but no compute failed the deploy with `insufficient resources … with existing volume`.
|
|
79
58
|
// 5.
|
|
80
|
-
const addresses = await
|
|
59
|
+
const addresses = await readOutput(fly, "fly", ["ips", "list", "-a", plan.appName, "--json"], ingressAddresses);
|
|
81
60
|
if ("gate" in addresses)
|
|
82
61
|
return gate(addresses.gate);
|
|
83
62
|
// Check-then-act PER FAMILY: an app that already holds one must still be given the other, or the gate between the
|
|
@@ -131,9 +110,20 @@ export async function deployFlyRun(plan, fly, log, registrars, healthProbe) {
|
|
|
131
110
|
inspectHint: `the app itself deployed — inspect \`fly logs -a ${plan.appName}\`, then re-run once it answers ` +
|
|
132
111
|
`(a re-run repeats the remote build)`,
|
|
133
112
|
probe: healthProbe,
|
|
113
|
+
...(plan.boxLogin ? { login: plan.boxLogin.command } : {}),
|
|
134
114
|
});
|
|
135
115
|
if (healthGate)
|
|
136
116
|
return gate(healthGate);
|
|
117
|
+
const notLoggedIn = await plan.boxLogin?.run();
|
|
118
|
+
if (notLoggedIn) {
|
|
119
|
+
return gate(loginGate({
|
|
120
|
+
notLoggedIn,
|
|
121
|
+
channels: plan.channels,
|
|
122
|
+
log,
|
|
123
|
+
baseUrl,
|
|
124
|
+
afterLogin: "re-run `fastagent deploy fly --run` (it keeps the login)",
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
137
127
|
// 9.
|
|
138
128
|
const registrationGateMsg = await registerWebhooks({
|
|
139
129
|
baseUrl,
|
|
@@ -26,20 +26,22 @@ interface DeployFacts {
|
|
|
26
26
|
values: ReadonlyMap<string, string>;
|
|
27
27
|
/** That file, workspace-relative — the name every "set it here" message must use. */
|
|
28
28
|
valueFile: string;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* The variable the value file carries the model's key in (the provider's own, or a models.json `"$NAME"`), or
|
|
31
|
+
* undefined when no key travels that way ({@link credentialRoute}).
|
|
32
|
+
*/
|
|
30
33
|
modelAuth: string | undefined;
|
|
31
34
|
/**
|
|
32
|
-
* The
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
+
* The provider the deployment must log in to itself (`fastagent login --deployment`): the model's credential does
|
|
36
|
+
* not travel, because it is neither a variable in the value file nor carried by the definition (a models.json
|
|
37
|
+
* literal `apiKey` or `!command`). Nothing from this machine's credentials file is ever copied, so a login on the
|
|
38
|
+
* box is its only holder.
|
|
35
39
|
*/
|
|
36
|
-
|
|
40
|
+
boxLogin: string | undefined;
|
|
37
41
|
/** Every tool/routine/channel declaration — the names the value file must supply, by declaring file. */
|
|
38
42
|
declaredSecrets: DeclaredSecret[];
|
|
39
43
|
/** The runbook's variable list: the declared names, then everything else the value file carries. */
|
|
40
44
|
secrets: DeploymentSecret[];
|
|
41
|
-
/** The project-level auth file `--run` reads to carry the credential (probed with the same path). */
|
|
42
|
-
authPath: string;
|
|
43
45
|
/** Container facts shared by the plan and the generated Dockerfile — ONE source, so they can't drift. */
|
|
44
46
|
container: ContainerInput;
|
|
45
47
|
port: number;
|
package/dist/deploy/preflight.js
CHANGED
|
@@ -6,13 +6,16 @@ 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";
|
|
16
|
+
import { agentModels } from "../engines/pi/agent-models.js";
|
|
14
17
|
import { allSecrets } from "../declared-secrets.js";
|
|
15
|
-
import { createPiModelRuntime, literalKeyProviders, isBuiltinProvider, machineModels,
|
|
18
|
+
import { createPiModelRuntime, definitionKeyOf, environmentAuthSource, literalKeyProviders, isBuiltinProvider, machineModels, globalCatalogPath, inGlobalCatalog, interactiveLoginKind, loginProviders, } from "../engines/pi/models.js";
|
|
16
19
|
import { CHANNEL_KINDS } from "../scaffold/add-channel.js";
|
|
17
20
|
import { detectRuntime, readPackageJson } from "../runtime.js";
|
|
18
21
|
import { fastagentVersion } from "../version.js";
|
|
@@ -147,21 +150,38 @@ async function gatherFacts(input, report) {
|
|
|
147
150
|
: ""));
|
|
148
151
|
}
|
|
149
152
|
await checkMachineModels(agentDir, modelSpec, report);
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
+
// The registry the DEPLOYED agent has (its own models.json and model catalog, nothing of the machine's), built first
|
|
154
|
+
// so a malformed file stops the deploy with pi's own reason before anything below reads the file raw. No credential
|
|
155
|
+
// is read for that.
|
|
156
|
+
const deployed = await createPiModelRuntime({
|
|
157
|
+
agentDir,
|
|
158
|
+
credentials: new InMemoryCredentialStore(),
|
|
159
|
+
machineLayer: false,
|
|
160
|
+
});
|
|
161
|
+
if (modelSpec)
|
|
162
|
+
await checkGlobalCatalogModel(modelSpec, deployed, report);
|
|
153
163
|
const authPath = resolveAuthPath(agentDir);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
// A model only the definition's extensions declare (a virtual model, or a provider one registers) is authenticated
|
|
165
|
+
// by that code on the box, per request: a virtual model's credential is whichever physical model it routes to.
|
|
166
|
+
// Asked of the catalog the box builds, extensions included, so deploy judges the model the box will run.
|
|
167
|
+
const fromExtension = modelSpec ? await extensionDeclared(agentDir, workspace, modelSpec, deployed) : false;
|
|
168
|
+
if (fromExtension && modelSpec) {
|
|
169
|
+
report.note(`${modelSpec} is declared by the definition's extensions, which resolve its credentials on the box — deploy ` +
|
|
170
|
+
`cannot check them. Set the keys the models it uses need in ${valueFile}`);
|
|
171
|
+
}
|
|
172
|
+
const route = modelSpec && !fromExtension ? await credentialRoute(agentDir, modelSpec, values) : {};
|
|
173
|
+
const modelAuth = route.envVar;
|
|
174
|
+
const boxLogin = route.boxLogin;
|
|
175
|
+
if (boxLogin !== undefined) {
|
|
176
|
+
if (!hasLogin(boxLogin)) {
|
|
177
|
+
report.issue(`no credential for ${modelSpec} reaches the deployment, and "${boxLogin}" has no login to run there — set ` +
|
|
178
|
+
`its API key in ${valueFile}`);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
report.note(`${modelSpec}: no credential ships with this deploy (this machine's logins and shell stay here) — once the ` +
|
|
182
|
+
`box is up, \`--run\` asks it: it keeps what it already authenticates ${boxLogin} with, else logs in ` +
|
|
183
|
+
`(\`fastagent login ${boxLogin} --deployment\`). Or set ${boxLogin}'s API key in ${valueFile}`);
|
|
184
|
+
}
|
|
165
185
|
}
|
|
166
186
|
// Reported, not refused. Whether a string is a credential is the AUTHOR's knowledge: pi's docs prescribe
|
|
167
187
|
// `"apiKey": "ollama"` for a keyless local server, and no static rule separates that from a leaked key. The
|
|
@@ -254,14 +274,52 @@ async function gatherFacts(input, report) {
|
|
|
254
274
|
values,
|
|
255
275
|
valueFile,
|
|
256
276
|
modelAuth,
|
|
257
|
-
|
|
258
|
-
authPath,
|
|
277
|
+
boxLogin,
|
|
259
278
|
container,
|
|
260
279
|
port,
|
|
261
280
|
declaredSecrets,
|
|
262
281
|
secrets: deploymentSecrets(modelAuth, declaredSecrets, values, valueFile),
|
|
263
282
|
};
|
|
264
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* HOW THE MODEL'S CREDENTIAL REACHES THE BOX, from what the deploy ships and nothing else. The box is the one authority
|
|
286
|
+
* on what it authenticates with (pi ranks a stored login above the environment, and only the box knows what it has
|
|
287
|
+
* stored, which platform variables it was given, or what role it runs as), so this never asks what authenticates the
|
|
288
|
+
* model on THIS machine: its stored logins and the shell running deploy do not travel. In order:
|
|
289
|
+
*
|
|
290
|
+
* 1. the definition's own models.json: a `"$NAME"` reference is a variable the value file must hold (the values gate
|
|
291
|
+
* asks for it by name), and a literal or `"!command"` travels in the image;
|
|
292
|
+
* 2. a credential pi reads for the provider from the value file alone: its key variable (which the values gate then
|
|
293
|
+
* requires, as it does every declared name), or a keyless one such as `AWS_ACCESS_KEY_ID` — never one that needs a
|
|
294
|
+
* file, which would be this machine's;
|
|
295
|
+
* 3. otherwise the box answers — `boxLogin`: after readiness, `--run` asks it, and it logs in only if it cannot
|
|
296
|
+
* already authenticate the provider.
|
|
297
|
+
*/
|
|
298
|
+
async function credentialRoute(agentDir, spec, values) {
|
|
299
|
+
const provider = providerOf(spec);
|
|
300
|
+
const declared = await definitionKeyOf(agentDir, provider);
|
|
301
|
+
if (declared)
|
|
302
|
+
return "reference" in declared ? { envVar: declared.reference } : {};
|
|
303
|
+
// No file of this machine's travels, so a source that needs one (Google ADC, an AWS profile) is the box's to find.
|
|
304
|
+
const fromValues = await environmentAuthSource(provider, Object.fromEntries(values), async () => false);
|
|
305
|
+
if (fromValues !== undefined)
|
|
306
|
+
return isEnvKey(fromValues) ? { envVar: fromValues } : {};
|
|
307
|
+
return { boxLogin: provider };
|
|
308
|
+
}
|
|
309
|
+
/** Whether `spec` exists only once the definition's extensions have registered their models. */
|
|
310
|
+
async function extensionDeclared(agentDir, workspace, spec, deployed) {
|
|
311
|
+
const provider = providerOf(spec);
|
|
312
|
+
const id = spec.slice(provider.length + 1);
|
|
313
|
+
if (deployed.getModel(provider, id))
|
|
314
|
+
return false;
|
|
315
|
+
const catalog = await agentModels(agentDir, { credentialStore: new InMemoryCredentialStore() }, { machineLayer: false, cwd: workspace }).runtime();
|
|
316
|
+
return catalog.getModel(provider, id) !== undefined;
|
|
317
|
+
}
|
|
318
|
+
/** Does `provider` offer an interactive login, i.e. can `fastagent login --deployment` authenticate it on the box? */
|
|
319
|
+
function hasLogin(providerId) {
|
|
320
|
+
const provider = loginProviders().find((p) => p.id === providerId);
|
|
321
|
+
return provider !== undefined && interactiveLoginKind(provider) !== "none";
|
|
322
|
+
}
|
|
265
323
|
/**
|
|
266
324
|
* The machine's models.json is this box's environment, not the artifact: whatever the model takes from it is
|
|
267
325
|
* absent wherever the agent is deployed. Said in so many words here; the credential probe already reads the deployed
|
|
@@ -285,6 +343,20 @@ async function checkMachineModels(agentDir, modelSpec, report) {
|
|
|
285
343
|
report.issue(issue);
|
|
286
344
|
}
|
|
287
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* The machine's model catalog is this box's environment, like its models.json: a model only it knows (newer than the
|
|
348
|
+
* catalog bundled with pi) is unknown wherever the agent is deployed. The agent's own catalog ships, so the remedy is
|
|
349
|
+
* one refresh in the agent.
|
|
350
|
+
*/
|
|
351
|
+
async function checkGlobalCatalogModel(modelSpec, deployed, report) {
|
|
352
|
+
const provider = providerOf(modelSpec);
|
|
353
|
+
const id = modelSpec.slice(provider.length + 1);
|
|
354
|
+
if (deployed.getModel(provider, id) || !(await inGlobalCatalog(provider, id)))
|
|
355
|
+
return;
|
|
356
|
+
report.issue(`model "${modelSpec}" is known only from ${globalCatalogPath()}, the machine's model catalog, which does not ` +
|
|
357
|
+
"ship — the deployed agent would fail with an unknown model. Run `fastagent models --refresh` in the agent " +
|
|
358
|
+
`to record it in its own ${AGENT_MODEL_CATALOG_FILE}, which ships.`);
|
|
359
|
+
}
|
|
288
360
|
/**
|
|
289
361
|
* What a KEPT hand-written Dockerfile drops. `deploy.apt` is the obvious one; the resolved model is the one that
|
|
290
362
|
* looks safe and is not: the manifest is always written, but only the generated Dockerfile sets
|
|
@@ -341,7 +413,7 @@ valueFile) {
|
|
|
341
413
|
const source = fromEnv ? valueFile : "fastagent.config";
|
|
342
414
|
// BOTH sources are checked, at the one point that reads them: a `provider`-less spec resolves to nothing on the
|
|
343
415
|
// box, so letting `config.model` through would ship exactly the crash-loop this resolution exists to prevent —
|
|
344
|
-
//
|
|
416
|
+
// the auth probe reports "unconfigured" for it here and the failure only appears after deployment.
|
|
345
417
|
const spec = fromEnv || config.model;
|
|
346
418
|
if (spec && !isModelSpec(spec))
|
|
347
419
|
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.
|