@fastagent-sh/fastagent 0.14.0 → 0.16.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/README.md +16 -6
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -0
- package/dist/channels/agentcore-limits.d.ts +7 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-state.d.ts +83 -0
- package/dist/channels/agentcore-state.js +258 -0
- package/dist/channels/agentcore.d.ts +98 -0
- package/dist/channels/agentcore.js +260 -0
- package/dist/channels/busy.d.ts +23 -0
- package/dist/channels/busy.js +53 -0
- package/dist/channels/context-buffer.d.ts +35 -0
- package/dist/channels/context-buffer.js +103 -0
- package/dist/channels/control.d.ts +28 -0
- package/dist/channels/control.js +214 -0
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +15 -13
- package/dist/channels/feishu/context-buffer.js +62 -69
- package/dist/channels/feishu/feishu-api.d.ts +11 -3
- package/dist/channels/feishu/feishu-api.js +24 -3
- package/dist/channels/feishu/feishu.d.ts +27 -27
- package/dist/channels/feishu/feishu.js +318 -207
- package/dist/channels/feishu/invoke-turn.d.ts +7 -20
- package/dist/channels/feishu/invoke-turn.js +44 -79
- package/dist/channels/feishu/model.d.ts +2 -2
- package/dist/channels/feishu/normalize.d.ts +2 -1
- package/dist/channels/feishu/normalize.js +0 -1
- package/dist/channels/feishu/parse.d.ts +22 -8
- package/dist/channels/feishu/parse.js +24 -7
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +27 -142
- package/dist/channels/feishu/register-app.d.ts +2 -1
- package/dist/channels/feishu/register-webhook.d.ts +2 -1
- package/dist/channels/feishu/scaffold/channel.ts +9 -8
- package/dist/channels/feishu/scaffold/feishu-send.ts +6 -4
- package/dist/channels/feishu/setup-mode.d.ts +37 -0
- package/dist/channels/feishu/setup-mode.js +28 -0
- package/dist/channels/feishu/ws-ingress.d.ts +28 -0
- package/dist/channels/feishu/ws-ingress.js +136 -0
- package/dist/channels/github/github.js +16 -7
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +36 -3
- package/dist/channels/invoke-turn-kit.d.ts +65 -0
- package/dist/channels/invoke-turn-kit.js +87 -0
- package/dist/channels/lark/lark.d.ts +4 -2
- package/dist/channels/lark/lark.js +4 -1
- package/dist/channels/lark/onboard.d.ts +8 -4
- package/dist/channels/lark/onboard.js +8 -0
- package/dist/channels/lark/scaffold/channel.ts +9 -8
- package/dist/channels/lark/scaffold/lark-send.ts +6 -4
- package/dist/channels/preview-kit.d.ts +115 -0
- package/dist/channels/preview-kit.js +184 -0
- package/dist/channels/seen.d.ts +5 -0
- package/dist/channels/seen.js +35 -0
- package/dist/channels/slack/bot-auth.d.ts +15 -0
- package/dist/channels/slack/bot-auth.js +146 -0
- package/dist/channels/slack/config-api.d.ts +60 -0
- package/dist/channels/slack/config-api.js +149 -0
- package/dist/channels/slack/context-buffer.d.ts +24 -0
- package/dist/channels/slack/context-buffer.js +37 -0
- package/dist/channels/slack/invoke-turn.d.ts +19 -0
- package/dist/channels/slack/invoke-turn.js +63 -0
- package/dist/channels/slack/manifest.d.ts +49 -0
- package/dist/channels/slack/manifest.js +69 -0
- package/dist/channels/slack/model.d.ts +67 -0
- package/dist/channels/slack/model.js +2 -0
- package/dist/channels/slack/onboard.d.ts +41 -0
- package/dist/channels/slack/onboard.js +120 -0
- package/dist/channels/slack/onboarding-state.d.ts +31 -0
- package/dist/channels/slack/onboarding-state.js +69 -0
- package/dist/channels/slack/parse.d.ts +38 -0
- package/dist/channels/slack/parse.js +124 -0
- package/dist/channels/slack/preview.d.ts +23 -0
- package/dist/channels/slack/preview.js +403 -0
- package/dist/channels/slack/reaction.d.ts +24 -0
- package/dist/channels/slack/reaction.js +62 -0
- package/dist/channels/slack/register-webhook.d.ts +10 -0
- package/dist/channels/slack/register-webhook.js +49 -0
- package/dist/channels/slack/scaffold/channel.ts +33 -0
- package/dist/channels/slack/scaffold/slack-send.ts +171 -0
- package/dist/channels/slack/setup-server.d.ts +17 -0
- package/dist/channels/slack/setup-server.js +103 -0
- package/dist/channels/slack/slack-api.d.ts +57 -0
- package/dist/channels/slack/slack-api.js +399 -0
- package/dist/channels/slack/slack.d.ts +51 -0
- package/dist/channels/slack/slack.js +496 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.d.ts +11 -4
- package/dist/channels/state.js +22 -15
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +7 -0
- package/dist/channels/tasks.js +25 -0
- package/dist/channels/telegram/context-buffer.d.ts +8 -17
- package/dist/channels/telegram/context-buffer.js +6 -85
- package/dist/channels/telegram/invoke-turn.d.ts +5 -22
- package/dist/channels/telegram/invoke-turn.js +11 -58
- package/dist/channels/telegram/parse.d.ts +0 -7
- package/dist/channels/telegram/parse.js +4 -2
- package/dist/channels/telegram/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
- package/dist/channels/telegram/telegram.js +24 -10
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +15 -1
- package/dist/channels/{feishu/text.js → text.js} +15 -1
- package/dist/channels/thread-participants.d.ts +21 -0
- package/dist/channels/thread-participants.js +132 -0
- package/dist/channels/turn-queue.js +8 -1
- package/dist/channels/turn-store.d.ts +1 -1
- package/dist/channels/turn-store.js +2 -3
- package/dist/cli/add-feishu.d.ts +30 -0
- package/dist/cli/add-feishu.js +366 -0
- package/dist/cli/add-slack.d.ts +11 -0
- package/dist/cli/add-slack.js +199 -0
- package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
- package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
- package/dist/cli/commands/add.d.ts +4 -0
- package/dist/cli/commands/add.js +191 -60
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +563 -0
- package/dist/cli/commands/chat.d.ts +1 -0
- package/dist/cli/commands/chat.js +12 -7
- package/dist/cli/commands/deploy.d.ts +3 -1
- package/dist/cli/commands/deploy.js +384 -82
- package/dist/cli/commands/dev.js +34 -26
- package/dist/cli/commands/fire.js +17 -18
- package/dist/cli/commands/info.js +36 -29
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.js +65 -53
- package/dist/cli/commands/invoke.js +11 -9
- package/dist/cli/commands/login.js +48 -45
- package/dist/cli/commands/models.js +1 -1
- package/dist/cli/commands/schedule.js +6 -8
- package/dist/cli/commands/start.js +102 -39
- package/dist/cli/commands/tool.js +23 -18
- package/dist/cli/fail.d.ts +17 -2
- package/dist/cli/fail.js +25 -1
- package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
- package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
- package/dist/cli/kernel.d.ts +3 -2
- package/dist/cli/models-view.d.ts +21 -0
- package/dist/cli/models-view.js +66 -0
- package/dist/cli/program.js +135 -56
- package/dist/cli/serve.d.ts +60 -20
- package/dist/cli/serve.js +252 -47
- package/dist/cli/shared.d.ts +40 -7
- package/dist/cli/shared.js +180 -38
- package/dist/collect.d.ts +14 -3
- package/dist/collect.js +24 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +2 -0
- package/dist/deploy/agentcore/plan.d.ts +117 -0
- package/dist/deploy/agentcore/plan.js +721 -0
- package/dist/deploy/agentcore/run.d.ts +73 -0
- package/dist/deploy/agentcore/run.js +412 -0
- package/dist/deploy/agentcore/zip.d.ts +17 -0
- package/dist/deploy/agentcore/zip.js +68 -0
- package/dist/deploy/container.d.ts +26 -25
- package/dist/deploy/container.js +93 -89
- package/dist/deploy/docker/plan.d.ts +3 -3
- package/dist/deploy/docker/plan.js +20 -23
- package/dist/deploy/fly/plan.d.ts +5 -1
- package/dist/deploy/fly/plan.js +39 -26
- package/dist/deploy/fly/run.d.ts +17 -5
- package/dist/deploy/fly/run.js +52 -7
- package/dist/deploy/preflight.d.ts +20 -8
- package/dist/deploy/preflight.js +267 -71
- package/dist/deploy/railway/plan.d.ts +10 -1
- package/dist/deploy/railway/plan.js +53 -20
- package/dist/deploy/railway/run.d.ts +13 -5
- package/dist/deploy/railway/run.js +22 -7
- package/dist/deploy/runner.d.ts +7 -3
- package/dist/deploy/runner.js +9 -3
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +11 -10
- package/dist/dev-supervisor.js +56 -50
- package/dist/engines/pi/auth.d.ts +8 -7
- package/dist/engines/pi/auth.js +12 -10
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +92 -62
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -261
- package/dist/engines/pi/config.d.ts +20 -55
- package/dist/engines/pi/config.js +49 -121
- package/dist/engines/pi/create.d.ts +30 -10
- package/dist/engines/pi/create.js +33 -26
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- package/dist/engines/pi/harness.d.ts +49 -0
- package/dist/engines/pi/harness.js +90 -2
- package/dist/engines/pi/invoke.d.ts +55 -2
- package/dist/engines/pi/invoke.js +304 -19
- package/dist/engines/pi/login.d.ts +12 -1
- package/dist/engines/pi/login.js +16 -4
- package/dist/engines/pi/models.d.ts +59 -13
- package/dist/engines/pi/models.js +62 -24
- package/dist/engines/pi/open.d.ts +114 -0
- package/dist/engines/pi/open.js +127 -0
- package/dist/engines/pi/session-builder.d.ts +16 -0
- package/dist/engines/pi/session-builder.js +308 -0
- package/dist/engines/pi/session-control.d.ts +50 -0
- package/dist/engines/pi/session-control.js +604 -0
- package/dist/engines/pi/sessions.d.ts +17 -2
- package/dist/engines/pi/sessions.js +9 -0
- package/dist/engines/pi/tool-context.d.ts +18 -11
- package/dist/engines/pi/tool-context.js +3 -9
- package/dist/engines/pi/tool.d.ts +6 -8
- package/dist/engines/pi/tool.js +10 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +25 -8
- package/dist/host/node.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/loader.d.ts +2 -2
- package/dist/loader.js +3 -3
- package/dist/log.d.ts +1 -1
- package/dist/log.js +1 -1
- package/dist/observe.js +3 -0
- package/dist/paths.d.ts +138 -0
- package/dist/paths.js +326 -0
- package/dist/pi.d.ts +6 -5
- package/dist/pi.js +4 -3
- package/dist/runtime.d.ts +7 -5
- package/dist/runtime.js +2 -2
- package/dist/scaffold/add-channel.d.ts +26 -9
- package/dist/scaffold/add-channel.js +175 -40
- package/dist/scaffold/init.d.ts +32 -41
- package/dist/scaffold/init.js +161 -185
- package/dist/scaffold/templates/env.example +15 -6
- package/dist/scaffold/templates/fastagent.config.mjs +5 -4
- package/dist/scaffold/templates/gitignore +14 -6
- package/dist/scaffold/templates/persona.md +4 -2
- package/dist/scaffold/templates/secrets.gitignore +5 -0
- package/dist/scaffold/templates.d.ts +1 -7
- package/dist/scaffold/templates.js +3 -25
- package/dist/scaffold/vendor-skill.d.ts +2 -2
- package/dist/scaffold/vendor-skill.js +13 -13
- package/dist/schedule/discover.js +4 -4
- package/dist/schedule/scheduler.d.ts +40 -1
- package/dist/schedule/scheduler.js +89 -56
- package/dist/schedule/state.js +1 -1
- package/dist/schedule/wake-alarm.d.ts +47 -0
- package/dist/schedule/wake-alarm.js +136 -0
- package/dist/schedule/wakeups.d.ts +1 -3
- package/dist/schedule/wakeups.js +19 -1
- package/dist/session-remote.d.ts +53 -0
- package/dist/session-remote.js +336 -0
- package/dist/session.d.ts +265 -0
- package/dist/session.js +37 -0
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +2 -0
- package/dist/tunnel.d.ts +7 -4
- package/dist/tunnel.js +23 -12
- package/package.json +22 -7
- package/dist/channels/feishu/owned-threads.d.ts +0 -11
- package/dist/channels/feishu/owned-threads.js +0 -47
- package/dist/channels/feishu/seen.d.ts +0 -5
- package/dist/channels/feishu/seen.js +0 -47
- package/dist/cli-add-feishu.d.ts +0 -8
- package/dist/cli-add-feishu.js +0 -223
- package/dist/cli-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
- package/dist/engines/pi/workspace.d.ts +0 -56
- package/dist/engines/pi/workspace.js +0 -72
- package/dist/scaffold/templates/gitignore.agentdir-root +0 -5
- package/dist/scaffold/templates/gitignore.kit +0 -2
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.js +0 -45
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { loadDotEnv } from "../../env.js";
|
|
8
|
-
import {
|
|
8
|
+
import { resolveStateRoot } from "../../paths.js";
|
|
9
9
|
import { reportModuleLoadFailures } from "../../engines/pi/report.js";
|
|
10
10
|
import { readRuns } from "../../schedule/audit.js";
|
|
11
11
|
import { nextRun } from "../../schedule/cron.js";
|
|
12
12
|
import { loadSchedules } from "../../schedule/discover.js";
|
|
13
13
|
import { listWakeups, removeWakeup } from "../../schedule/wakeups.js";
|
|
14
|
-
import { failStartup } from "../fail.js";
|
|
14
|
+
import { failStartup, placementOrExit } from "../fail.js";
|
|
15
15
|
/**
|
|
16
16
|
* `fastagent schedule history <name> [dir]`: print the run audit for one schedule (or "wake") — fired
|
|
17
17
|
* time, outcome, duration, reply/error. Read-only (reads `<stateRoot>/schedule/runs.jsonl`); the answer
|
|
18
18
|
* to "did last night's run silently fail?". Text mode previews the reply/error; --json is the full record.
|
|
19
19
|
*/
|
|
20
20
|
export function runScheduleHistory(name, dirArg, json) {
|
|
21
|
-
const target = resolve(dirArg);
|
|
21
|
+
const { agentDir: target } = placementOrExit(resolve(dirArg));
|
|
22
22
|
loadDotEnv(target); // FASTAGENT_STATE_DIR may live in .env — read the SAME state root the scheduler wrote
|
|
23
23
|
const runs = readRuns(resolveStateRoot(target), name);
|
|
24
24
|
if (json) {
|
|
@@ -45,11 +45,9 @@ export function runScheduleHistory(name, dirArg, json) {
|
|
|
45
45
|
/** `fastagent schedule list [dir]`: everything that will fire — BOTH producers: the static `schedules/`
|
|
46
46
|
* files (with their next instant) and the agent's pending self-scheduled wake-ups. Read-only. */
|
|
47
47
|
export async function runScheduleList(dirArg, json) {
|
|
48
|
-
const target = resolve(dirArg);
|
|
48
|
+
const { agentDir: target } = placementOrExit(resolve(dirArg));
|
|
49
49
|
loadDotEnv(target);
|
|
50
|
-
const {
|
|
51
|
-
const agentDir = resolveAgentDir(target, config);
|
|
52
|
-
const { schedules, failures } = await loadSchedules(agentDir).catch(failStartup);
|
|
50
|
+
const { schedules, failures } = await loadSchedules(target).catch(failStartup);
|
|
53
51
|
reportModuleLoadFailures(failures);
|
|
54
52
|
const wakeups = listWakeups(resolveStateRoot(target));
|
|
55
53
|
if (json) {
|
|
@@ -75,7 +73,7 @@ export async function runScheduleList(dirArg, json) {
|
|
|
75
73
|
/** `fastagent schedule cancel <id> [dir]`: remove a pending wake-up — the operator's kill switch (the
|
|
76
74
|
* agent's own is the `unwake` tool). Unlike unwake it is NOT session-scoped: the operator owns the box. */
|
|
77
75
|
export function runScheduleCancel(id, dirArg) {
|
|
78
|
-
const target = resolve(dirArg);
|
|
76
|
+
const { agentDir: target } = placementOrExit(resolve(dirArg));
|
|
79
77
|
loadDotEnv(target);
|
|
80
78
|
if (removeWakeup(resolveStateRoot(target), id)) {
|
|
81
79
|
// ponytail: the store's load→save is lock-free — a serving scheduler's claim-advance can race this
|
|
@@ -4,84 +4,147 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import { dirname, resolve } from "node:path";
|
|
7
|
-
import { authSeedBytes } from "../../deploy/fly/run.js";
|
|
7
|
+
import { authSeedBytes, collectAuthSeed } from "../../deploy/fly/run.js";
|
|
8
8
|
import { loadDotEnv } from "../../env.js";
|
|
9
|
-
import {
|
|
9
|
+
import { resolveAuthPath, resolveSessionsDirOverride } from "../../engines/pi/config.js";
|
|
10
|
+
import { resolveSecretsDir, workspaceHint } from "../../paths.js";
|
|
10
11
|
import { isUnderDir } from "../../engines/pi/definition.js";
|
|
11
12
|
import { reportDefinitionWarnings, reportModuleLoadFailures, reportToolCollisions } from "../../engines/pi/report.js";
|
|
12
|
-
import {
|
|
13
|
+
import { createPiAgentFromDir } from "../../engines/pi/open.js";
|
|
13
14
|
import { log, setLogLevel } from "../../log.js";
|
|
15
|
+
import { createWakeAlarmSink, reconcileWakeAlarms } from "../../schedule/wake-alarm.js";
|
|
16
|
+
import { setWakeupsSink } from "../../schedule/wakeups.js";
|
|
14
17
|
import { logAgentLoop } from "../../observe.js";
|
|
15
18
|
import { installProxyFetch } from "../../proxy.js";
|
|
16
|
-
import { exists } from "../../
|
|
17
|
-
import { failStartup } from "../fail.js";
|
|
18
|
-
import { maybeTunnel, routesFor, serve, startSchedules } from "../serve.js";
|
|
19
|
-
import { parsePort, reportAuth, resolveFirstRunModel } from "../shared.js";
|
|
19
|
+
import { exists } from "../../paths.js";
|
|
20
|
+
import { failStartup, placementOrExit } from "../fail.js";
|
|
21
|
+
import { maybeTunnel, mountAgentcore, mountSessionControl, routesFor, serve, startSchedules } from "../serve.js";
|
|
22
|
+
import { parsePort, reportAuth, reportLine, resolveFirstRunModel, reportWorkspaceHint } from "../shared.js";
|
|
20
23
|
export async function runStart(dirArg, opts) {
|
|
21
24
|
const dir = resolve(dirArg);
|
|
22
|
-
|
|
25
|
+
// Flag validation first: a bad --port is a USAGE error (exit 2), and reporting it must not depend on
|
|
26
|
+
// the directory being an agent (which is a runtime/environment failure, exit 1).
|
|
23
27
|
const portFlag = parsePort(opts.port, "--port", "flag");
|
|
24
|
-
|
|
28
|
+
const placement = placementOrExit(dir);
|
|
29
|
+
setLogLevel("info"); // production posture: info+, the debug turn trace (and its end-user content) gated out
|
|
30
|
+
loadDotEnv(placement.agentDir);
|
|
25
31
|
installProxyFetch();
|
|
26
|
-
await resolveFirstRunModel(
|
|
32
|
+
await resolveFirstRunModel(placement.agentDir, opts);
|
|
27
33
|
// A `deploy --run` may carry the operator's local credential as FASTAGENT_AUTH_SEED —
|
|
28
|
-
// materialize it
|
|
29
|
-
|
|
30
|
-
await maybeSeedAuth(
|
|
34
|
+
// materialize it into the writable secrets dir BEFORE the opener resolves auth (once, absent-only).
|
|
35
|
+
// Same resolveAuthPath the opener uses — ONE owner of the flag > env > default chain.
|
|
36
|
+
await maybeSeedAuth(resolveAuthPath(placement.agentDir, opts.authPath));
|
|
31
37
|
// The same opener dev uses (single assembly source), just no watch.
|
|
32
38
|
const sessionsDirOverride = resolveSessionsDirOverride(opts.sessionsDir);
|
|
33
|
-
const { agent, definition, agentDir, config, modelSpec, stateRoot, sessionsDir, authPath, toolNames, deferredToolNames, toolCollisions, toolFailures, } = await
|
|
39
|
+
const { agent, definition, agentDir, workspace, config, modelSpec, stateRoot, sessionsDir, authPath, toolNames, deferredToolNames, toolCollisions, toolFailures, sessionControl, } = await createPiAgentFromDir(dir, {
|
|
34
40
|
model: opts.model,
|
|
35
41
|
sessionsDir: sessionsDirOverride,
|
|
36
|
-
authPath:
|
|
42
|
+
authPath: opts.authPath,
|
|
37
43
|
serving: true, // long-running serve: the scheduler poller runs (wake mounts iff config.selfSchedule)
|
|
38
44
|
}).catch(failStartup);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
reportLine("agent", agentDir);
|
|
46
|
+
reportLine("workspace", workspace);
|
|
47
|
+
reportWorkspaceHint(workspaceHint({ agentDir, workspace }));
|
|
48
|
+
reportLine("model", `${modelSpec}${config.thinkingLevel ? ` (thinking: ${config.thinkingLevel})` : ""}`);
|
|
43
49
|
await reportAuth(modelSpec, authPath);
|
|
44
|
-
|
|
50
|
+
reportLine("context", definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
|
|
45
51
|
if (definition.persona)
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
reportLine("persona", "persona.md");
|
|
53
|
+
reportLine("skills", definition.skills.map((s) => s.name).join(", ") || "(none)");
|
|
48
54
|
if (toolNames.length > 0)
|
|
49
|
-
|
|
55
|
+
reportLine("tools", toolNames.join(", "));
|
|
50
56
|
if (deferredToolNames.length > 0) {
|
|
51
|
-
|
|
57
|
+
reportLine("deferred", `${deferredToolNames.join(", ")} (activated via search_tools)`);
|
|
52
58
|
}
|
|
53
59
|
reportToolCollisions(toolCollisions);
|
|
54
60
|
reportModuleLoadFailures(toolFailures);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// State defaults under the
|
|
58
|
-
// root ACTUALLY resolved (
|
|
59
|
-
// as unset (resolveStateRoot) and still lands in-
|
|
60
|
-
// silence the warning. A sessions
|
|
61
|
-
// telegram turn/context files replay depends on) is still in-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
reportLine("state", stateRoot);
|
|
62
|
+
reportLine("sessions", sessionsDir);
|
|
63
|
+
// State defaults under the agent dir, which a redeploy may replace wholesale. Gate on where the
|
|
64
|
+
// state root ACTUALLY resolved (inside the agent dir?), not on the raw env var: an empty
|
|
65
|
+
// `FASTAGENT_STATE_DIR=""` reads as unset (resolveStateRoot) and still lands in-agent, so a raw
|
|
66
|
+
// `=== undefined` check would wrongly silence the warning. A sessions override to a volume does not
|
|
67
|
+
// help — channel state (the telegram turn/context files replay depends on) is still in-agent.
|
|
68
|
+
// (auth.json is NOT under the state root — it lives in the secrets dir, resolveSecretsDir.)
|
|
69
|
+
if (isUnderDir(stateRoot, agentDir)) {
|
|
70
|
+
log.info(`[fastagent] note: state (sessions, channel state) lives under the definition dir; point ` +
|
|
64
71
|
`FASTAGENT_STATE_DIR at a persistent volume so a redeploy that replaces the dir does not wipe it.`);
|
|
65
72
|
}
|
|
73
|
+
// The secrets dir is the SAME trap on a different lifecycle: auth.json is MACHINE-WRITTEN (OAuth
|
|
74
|
+
// rotation), so the copy under the definition dir is the only valid one — a redeploy that replaces
|
|
75
|
+
// the dir loses a credential no re-seed can restore. An independent check on purpose: moving ONE
|
|
76
|
+
// of the two to a volume must not silence the note about the other.
|
|
77
|
+
if (isUnderDir(resolveSecretsDir(agentDir), agentDir)) {
|
|
78
|
+
log.info(`[fastagent] note: secrets (.env, rotated auth.json) live under the definition dir; point ` +
|
|
79
|
+
`FASTAGENT_SECRETS_DIR at a persistent volume so a redeploy that replaces the dir does not wipe them.`);
|
|
80
|
+
}
|
|
66
81
|
reportDefinitionWarnings(definition.collisions, definition.diagnostics);
|
|
82
|
+
// AgentCore Runtime posture (FASTAGENT_AGENTCORE=1, set by the generated deploy artifacts): the
|
|
83
|
+
// adapter (POST /invocations + GET /ping) is the container's only reachable surface, and cron
|
|
84
|
+
// slots arrive from the external clock through it — so no resident cron timers. In particular,
|
|
85
|
+
// do NOT mount the ordinary unauthenticated POST /invoke fallback: a selfSchedule-only topology
|
|
86
|
+
// needs a public Function URL for wake callbacks, and its forwarder can relay arbitrary paths.
|
|
87
|
+
const agentcore = process.env.FASTAGENT_AGENTCORE === "1";
|
|
67
88
|
// Same debug turn trace as dev; gated out here by the info level (see dev.ts serveOnce).
|
|
68
89
|
const traced = logAgentLoop(agent);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
const routed = await routesFor(agentDir, traced, stateRoot, sessionControl, { builtinInvoke: !agentcore }).catch(failStartup);
|
|
91
|
+
const withControl = mountSessionControl(routed.routes, sessionControl, stateRoot, {
|
|
92
|
+
tunnel: opts.tunnel ?? false,
|
|
93
|
+
agent: traced,
|
|
94
|
+
});
|
|
95
|
+
// AgentCore + selfSchedule: register the wake-ALARM sink BEFORE the scheduler starts — the boot
|
|
96
|
+
// wake pump may advance a recurring entry (a store save) and that save must already re-arm its
|
|
97
|
+
// alarm. The secret arrives via the stack (FASTAGENT_WAKE_SECRET); without it the deployment
|
|
98
|
+
// degrades to awake-only wakes — warned, never silent.
|
|
99
|
+
let onStateReady;
|
|
100
|
+
if (agentcore && config.selfSchedule) {
|
|
101
|
+
const wakeSecret = process.env.FASTAGENT_WAKE_SECRET;
|
|
102
|
+
if (wakeSecret) {
|
|
103
|
+
const sink = createWakeAlarmSink({ secret: wakeSecret });
|
|
104
|
+
setWakeupsSink(sink);
|
|
105
|
+
// NOT here: at boot the state mount is whatever the platform just provisioned — after a runtime
|
|
106
|
+
// version update that is EMPTY, so a reconcile now would see no pending wake-ups and conclude
|
|
107
|
+
// there is nothing to re-arm. It runs once the snapshot has been restored (mountAgentcore).
|
|
108
|
+
onStateReady = () => reconcileWakeAlarms(stateRoot, sink);
|
|
109
|
+
log.info(`[fastagent] wake alarms: EventBridge-backed via the forwarder`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
log.warn(`[fastagent] FASTAGENT_WAKE_SECRET is not set — wake-ups fire only while a session is awake ` +
|
|
113
|
+
`(redeploy with the current template to fix)`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const schedules = await startSchedules(agentDir, traced, stateRoot, config.selfSchedule ?? false, {
|
|
117
|
+
externalClock: agentcore,
|
|
118
|
+
});
|
|
119
|
+
let routes = withControl.routes;
|
|
120
|
+
if (agentcore) {
|
|
121
|
+
try {
|
|
122
|
+
routes = mountAgentcore(routes, { agent: traced, stateRoot, schedules, onStateReady });
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
failStartup(e);
|
|
126
|
+
}
|
|
127
|
+
log.info(`[fastagent] agentcore: serving POST /invocations + GET /ping (FASTAGENT_AGENTCORE=1)`);
|
|
128
|
+
}
|
|
129
|
+
serve({ ...routed, routes }, portFlag ?? parsePort(process.env.PORT, "PORT env", "env") ?? config.http?.port ?? 8787, (p) => {
|
|
130
|
+
withControl.announce(p);
|
|
131
|
+
maybeTunnel(agentDir, routed.routeChannels, p, opts.tunnel ?? false, stateRoot);
|
|
132
|
+
});
|
|
72
133
|
// No graceful drain: webhook turns run fire-and-forget; SIGTERM just exits mid-turn. Whether an
|
|
73
134
|
// in-flight turn is LOST depends on the channel: the Telegram channel persists turn intent pre-ACK
|
|
74
135
|
// and replays it next start (turn-store.ts, L1 durable execution, at-least-once); HTTP and other
|
|
75
136
|
// channels have no such layer, so their in-flight turns are still lost (the asker re-invokes).
|
|
76
137
|
}
|
|
77
138
|
/**
|
|
78
|
-
* Materialize `FASTAGENT_AUTH_SEED` (base64 of an auth.json, set by `deploy --run`)
|
|
79
|
-
* writable
|
|
139
|
+
* Materialize `FASTAGENT_AUTH_SEED` (base64 of an auth.json, set by `deploy --run`) into the
|
|
140
|
+
* writable secrets dir ONCE — only when the seed is set AND the auth file is absent, so a refreshed
|
|
80
141
|
* volume copy is never clobbered by the stale seed. Lets a deploy carry the operator's local
|
|
81
142
|
* OAuth/API credential so the box runs on the SAME subscription. No-op locally (the seed is unset).
|
|
82
143
|
*/
|
|
83
144
|
async function maybeSeedAuth(authPath) {
|
|
84
|
-
|
|
145
|
+
// collectAuthSeed: the seed may arrive CHUNKED (FASTAGENT_AUTH_SEED + _2…) on hosts with a small
|
|
146
|
+
// env-value max length (AgentCore); single-var hosts are unchanged.
|
|
147
|
+
const bytes = authSeedBytes(collectAuthSeed(process.env), await exists(authPath));
|
|
85
148
|
if (!bytes)
|
|
86
149
|
return;
|
|
87
150
|
await mkdir(dirname(authPath), { recursive: true });
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/** `fastagent tool <name> '<json>' [dir]`: run one tool's body directly with JSON args — no model. */
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { loadDotEnv } from "../../env.js";
|
|
4
|
-
import { loadConfig
|
|
5
|
-
import {
|
|
4
|
+
import { loadConfig } from "../../engines/pi/config.js";
|
|
5
|
+
import { resolveAgentTools } from "../../engines/pi/create.js";
|
|
6
6
|
import { reportModuleLoadFailures } from "../../engines/pi/report.js";
|
|
7
|
-
import {
|
|
7
|
+
import { turnContext } from "../../engines/pi/tool-context.js";
|
|
8
|
+
import { failStartup, failUsage, placementOrExit } from "../fail.js";
|
|
8
9
|
export async function runTool(name, argsJson, dirArg) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
10
|
+
// Argument shape first: malformed JSON is a USAGE error (exit 2), independent of whether the
|
|
11
|
+
// directory is an agent (a runtime failure, exit 1).
|
|
12
|
+
const args = parseToolArgs(argsJson);
|
|
13
|
+
const { agentDir, workspace } = placementOrExit(resolve(dirArg));
|
|
14
|
+
loadDotEnv(agentDir); // a tool may read a key from .env
|
|
15
|
+
const { config } = await loadConfig(agentDir).catch(failStartup);
|
|
12
16
|
// The same tool set dev/start mount (defaults + config.tools + discovered, deduped), so the runner
|
|
13
|
-
// exercises exactly what gets served — a shadowed tool is surfaced, not silently run. Resolve
|
|
14
|
-
// like the openers so `fastagent tool` finds the SAME tools/ as dev/start
|
|
15
|
-
const
|
|
16
|
-
const { tools, toolCollisions, toolFailures } = await resolveWorkspaceTools(config, agentDir, toolDir).catch(failStartup);
|
|
17
|
+
// exercises exactly what gets served — a shadowed tool is surfaced, not silently run. Resolve the
|
|
18
|
+
// placement like the openers, so `fastagent tool` finds the SAME tools/ as dev/start.
|
|
19
|
+
const { tools, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir, workspace).catch(failStartup);
|
|
17
20
|
for (const c of toolCollisions) {
|
|
18
21
|
console.error(`[fastagent] warn: tool "${c.name}" (${c.source}) is shadowed by a default/config tool — not mounted`);
|
|
19
22
|
}
|
|
@@ -22,16 +25,18 @@ export async function runTool(name, argsJson, dirArg) {
|
|
|
22
25
|
if (!tool) {
|
|
23
26
|
failStartup(new Error(`unknown tool "${name}". available: ${tools.map((t) => t.name).join(", ") || "(none)"}`));
|
|
24
27
|
}
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
args = JSON.parse(argsJson);
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
failUsage(`invalid JSON args: ${argsJson}`); // malformed input syntax = usage error, exit 2
|
|
31
|
-
}
|
|
32
|
-
const result = await tool.execute(`cli-${name}`, args).catch(failStartup);
|
|
28
|
+
const result = await turnContext.run({ cwd: workspace }, () => tool.execute(`cli-${name}`, args)).catch(failStartup);
|
|
33
29
|
const out = result?.details !== undefined
|
|
34
30
|
? result.details
|
|
35
31
|
: (result?.content ?? []).map((c) => ("text" in c ? c.text : "")).join("");
|
|
36
32
|
console.log(typeof out === "string" ? out : JSON.stringify(out, null, 2));
|
|
37
33
|
}
|
|
34
|
+
/** Parse the CLI's JSON args blob; malformed input syntax is a usage error (exit 2). */
|
|
35
|
+
function parseToolArgs(argsJson) {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(argsJson);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
failUsage(`invalid JSON args: ${argsJson}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/cli/fail.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function stderrHasColors(): boolean;
|
|
1
|
+
import { type ResolvedPlacement } from "../paths.ts";
|
|
3
2
|
/**
|
|
4
3
|
* The ONE error prefix every error message carries — bold red when stderr renders color, plain
|
|
5
4
|
* otherwise. Errors are the only place the CLI uses color at all.
|
|
@@ -11,6 +10,22 @@ export declare function errorPrefix(colors?: boolean): string;
|
|
|
11
10
|
* Shared by the kernel and the command modules; exit 1 (runtime failure).
|
|
12
11
|
*/
|
|
13
12
|
export declare function failStartup(error: unknown): never;
|
|
13
|
+
/**
|
|
14
|
+
* THE placement entry point for commands: resolve `dir`, or exit 1 with the one-line refusal. Every
|
|
15
|
+
* command that needs an agent goes through this — the try/catch was hand-repeated at sixteen call
|
|
16
|
+
* sites, which is sixteen chances to forget it (`dev`'s supervisor did, and surfaced a raw stack).
|
|
17
|
+
*
|
|
18
|
+
* The catch is what makes it a function at all: placement resolves SYNCHRONOUSLY, before any promise
|
|
19
|
+
* exists, so it cannot ride the `.catch(failStartup)` every async startup chain carries — and its
|
|
20
|
+
* refusals ("not a fastagent agent", plus the two dead ends with their own exits) are user-fixable,
|
|
21
|
+
* which means a one-line `Error:` and exit 1, never a stack.
|
|
22
|
+
*
|
|
23
|
+
* Not every command calls it — and the exceptions are the useful part of this note. `init` CREATES the
|
|
24
|
+
* agent (it must run where there is none). `models` is directory-independent. `login` needs one only for
|
|
25
|
+
* the project-level credential and falls back to the global one, and `attach --url/--token` reads
|
|
26
|
+
* nothing local. Everything else needs an agent, so it comes through here.
|
|
27
|
+
*/
|
|
28
|
+
export declare function placementOrExit(dir: string): ResolvedPlacement;
|
|
14
29
|
/**
|
|
15
30
|
* A usage error the parser could not catch (a bad value shape, an invalid flag/argument combination
|
|
16
31
|
* discovered in a command body): print the message and exit 2 — the same class as a parse error.
|
package/dist/cli/fail.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { resolvePlacement } from "../paths.js";
|
|
1
2
|
/** stderr renders color: a color TTY, with Node's `hasColors()` carrying the NO_COLOR/TERM=dumb veto. */
|
|
2
|
-
|
|
3
|
+
function stderrHasColors() {
|
|
3
4
|
return process.stderr.isTTY === true && (process.stderr.hasColors?.() ?? false);
|
|
4
5
|
}
|
|
5
6
|
/**
|
|
@@ -21,6 +22,29 @@ export function failStartup(error) {
|
|
|
21
22
|
console.error(errorPrefix(), error);
|
|
22
23
|
process.exit(1);
|
|
23
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* THE placement entry point for commands: resolve `dir`, or exit 1 with the one-line refusal. Every
|
|
27
|
+
* command that needs an agent goes through this — the try/catch was hand-repeated at sixteen call
|
|
28
|
+
* sites, which is sixteen chances to forget it (`dev`'s supervisor did, and surfaced a raw stack).
|
|
29
|
+
*
|
|
30
|
+
* The catch is what makes it a function at all: placement resolves SYNCHRONOUSLY, before any promise
|
|
31
|
+
* exists, so it cannot ride the `.catch(failStartup)` every async startup chain carries — and its
|
|
32
|
+
* refusals ("not a fastagent agent", plus the two dead ends with their own exits) are user-fixable,
|
|
33
|
+
* which means a one-line `Error:` and exit 1, never a stack.
|
|
34
|
+
*
|
|
35
|
+
* Not every command calls it — and the exceptions are the useful part of this note. `init` CREATES the
|
|
36
|
+
* agent (it must run where there is none). `models` is directory-independent. `login` needs one only for
|
|
37
|
+
* the project-level credential and falls back to the global one, and `attach --url/--token` reads
|
|
38
|
+
* nothing local. Everything else needs an agent, so it comes through here.
|
|
39
|
+
*/
|
|
40
|
+
export function placementOrExit(dir) {
|
|
41
|
+
try {
|
|
42
|
+
return resolvePlacement(dir);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
failStartup(error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
24
48
|
/**
|
|
25
49
|
* A usage error the parser could not catch (a bad value shape, an invalid flag/argument combination
|
|
26
50
|
* discovered in a command body): print the message and exit 2 — the same class as a parse error.
|
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
* An errored tool inside an otherwise-completed turn still surfaces (the diagnostic the operator
|
|
7
7
|
* needs); tool_ended carries no name, so it is remembered from the matching tool_started.
|
|
8
8
|
*/
|
|
9
|
-
import type { AgentEvent } from "
|
|
9
|
+
import type { AgentEvent } from "../agent.ts";
|
|
10
10
|
export declare function runInvokeStream(events: AsyncIterable<AgentEvent>, out: (text: string) => void, err: (line: string) => void): Promise<number>;
|
|
@@ -14,6 +14,10 @@ export async function runInvokeStream(events, out, err) {
|
|
|
14
14
|
if (event.isError)
|
|
15
15
|
err(`[tool] ${toolName.get(event.id) ?? event.id} failed`);
|
|
16
16
|
break;
|
|
17
|
+
case "retrying":
|
|
18
|
+
// Operator-facing: include the reason (channels show a neutral customer line instead).
|
|
19
|
+
err(`[fastagent] transient failure — retrying (${event.attempt}/${event.maxAttempts} in ${event.delayMs}ms): ${event.reason}`);
|
|
20
|
+
break;
|
|
17
21
|
case "failed":
|
|
18
22
|
err(`[fastagent] failed: ${event.details}${event.retryable ? " (retryable)" : ""}`);
|
|
19
23
|
exitCode = 1;
|
package/dist/cli/kernel.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { Command } from "commander";
|
|
15
15
|
/** One positional argument, in commander syntax: `<name>` required, `[dir]` optional. */
|
|
16
|
-
|
|
16
|
+
interface ArgSpec {
|
|
17
17
|
name: string;
|
|
18
18
|
description: string;
|
|
19
19
|
default?: string;
|
|
@@ -29,7 +29,7 @@ export interface FlagSpec {
|
|
|
29
29
|
/** Mutually exclusive with these {@link optionKey} values — validated at build time. */
|
|
30
30
|
conflicts?: string[];
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
interface ExampleSpec {
|
|
33
33
|
cmd: string;
|
|
34
34
|
note?: string;
|
|
35
35
|
}
|
|
@@ -87,3 +87,4 @@ export interface ProgramOptions {
|
|
|
87
87
|
export declare function optionKey(flags: string): string;
|
|
88
88
|
/** Build the commander program for `specs`. The CLI entry parses with it; tests inject the IO seams. */
|
|
89
89
|
export declare function buildProgram(specs: readonly CommandSpec[], options?: ProgramOptions): Command;
|
|
90
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProviderAuthStatus } from "../engines/pi/models.ts";
|
|
2
|
+
/** One first-run picker entry (@clack/prompts option shape). */
|
|
3
|
+
export interface ModelPickerOption {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
hint: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The first-run picker menu: the FULL model catalog, each spec annotated with its provider's auth
|
|
10
|
+
* status — ready first (usable now, with the credential source so "which account pays" is visible at
|
|
11
|
+
* the decision point), then the rest with their remedy. Order within each group preserves `specs`
|
|
12
|
+
* (sorted by the caller). A broken provider (expired/corrupt credential) is annotated, not dropped —
|
|
13
|
+
* fail visibly.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildModelPickerOptions(specs: string[], statuses: Map<string, ProviderAuthStatus>): ModelPickerOption[];
|
|
16
|
+
/** The output of `fastagent models [search]`: the spec `lines` to print to stdout (a case-insensitive
|
|
17
|
+
* substring filter; no search → all), and an stderr `error` diagnostic when a search matches nothing. */
|
|
18
|
+
export declare function formatModelsCommand(specs: string[], search?: string): {
|
|
19
|
+
lines: string[];
|
|
20
|
+
error?: string;
|
|
21
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI presenter for the model-facing commands: `fastagent models [search]` output and the first-run
|
|
3
|
+
* picker's option list. Output DECISIONS live here, out of the engine config layer (config.ts owns
|
|
4
|
+
* model resolution / listModels), so this process-boundary behavior is unit-testable without
|
|
5
|
+
* spawning the CLI.
|
|
6
|
+
*/
|
|
7
|
+
import { providerOf } from "../engines/pi/config.js";
|
|
8
|
+
/** The remedy hint for a non-ready provider, by what picking it actually does: an OAuth flow →
|
|
9
|
+
* "login required"; an interactive key prompt → "API key required"; no flow at all → the env var. */
|
|
10
|
+
function remedy(login) {
|
|
11
|
+
if (login === "oauth")
|
|
12
|
+
return "login required";
|
|
13
|
+
return login === "api_key" ? "API key required" : "API key required — set the provider's env var";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The first-run picker menu: the FULL model catalog, each spec annotated with its provider's auth
|
|
17
|
+
* status — ready first (usable now, with the credential source so "which account pays" is visible at
|
|
18
|
+
* the decision point), then the rest with their remedy. Order within each group preserves `specs`
|
|
19
|
+
* (sorted by the caller). A broken provider (expired/corrupt credential) is annotated, not dropped —
|
|
20
|
+
* fail visibly.
|
|
21
|
+
*/
|
|
22
|
+
export function buildModelPickerOptions(specs, statuses) {
|
|
23
|
+
const ready = [];
|
|
24
|
+
const rest = [];
|
|
25
|
+
for (const spec of specs) {
|
|
26
|
+
const status = statuses.get(providerOf(spec));
|
|
27
|
+
if (status?.state === "ready") {
|
|
28
|
+
ready.push({ value: spec, label: spec, hint: status.source ? `ready — ${status.source}` : "ready" });
|
|
29
|
+
}
|
|
30
|
+
else if (status?.state === "broken") {
|
|
31
|
+
// A broken stored credential OWNS the provider (env is consulted only when nothing is stored),
|
|
32
|
+
// so with no login flow the remedy is fixing the store — not the env var (which can't win here).
|
|
33
|
+
rest.push({
|
|
34
|
+
value: spec,
|
|
35
|
+
label: spec,
|
|
36
|
+
hint: status.login === "none"
|
|
37
|
+
? `stored auth unusable: ${status.message} — fix or remove the stored credential`
|
|
38
|
+
: `${remedy(status.login)} — stored auth unusable: ${status.message}`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else if (status) {
|
|
42
|
+
rest.push({ value: spec, label: spec, hint: remedy(status.login) });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Unreachable when `specs` and `statuses` come from the same Models (every listed provider is
|
|
46
|
+
// probed); if a caller ever mixes sources, promise nothing — neutral wording, no login claim.
|
|
47
|
+
rest.push({ value: spec, label: spec, hint: "auth required" });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [...ready, ...rest];
|
|
51
|
+
}
|
|
52
|
+
/** The output of `fastagent models [search]`: the spec `lines` to print to stdout (a case-insensitive
|
|
53
|
+
* substring filter; no search → all), and an stderr `error` diagnostic when a search matches nothing. */
|
|
54
|
+
export function formatModelsCommand(specs, search) {
|
|
55
|
+
if (!search)
|
|
56
|
+
return { lines: specs };
|
|
57
|
+
const q = search.toLowerCase();
|
|
58
|
+
const matches = specs.filter((spec) => spec.toLowerCase().includes(q));
|
|
59
|
+
if (matches.length === 0)
|
|
60
|
+
return { lines: matches, error: `no model matches "${search}"` };
|
|
61
|
+
// Rank a PROVIDER-name match (query in the part before "/") above an incidental model-id match, so
|
|
62
|
+
// `models anthropic` leads with anthropic/* rather than burying it under amazon-bedrock/anthropic.*
|
|
63
|
+
// and google-vertex/…-anthropic-… (which only match in the model id). Order within each group is kept.
|
|
64
|
+
const providerMatch = (spec) => providerOf(spec).toLowerCase().includes(q);
|
|
65
|
+
return { lines: [...matches.filter(providerMatch), ...matches.filter((s) => !providerMatch(s))] };
|
|
66
|
+
}
|