@fastagent-sh/fastagent 0.24.4 → 0.25.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 +53 -82
- package/dist/agent-api.d.ts +452 -0
- package/dist/agent-api.js +201 -0
- package/dist/answer-text.d.ts +8 -0
- package/dist/answer-text.js +18 -0
- package/dist/atomic-write.d.ts +2 -2
- package/dist/atomic-write.js +2 -2
- package/dist/authoring.d.ts +77 -0
- package/dist/authoring.js +238 -0
- package/dist/channel.d.ts +29 -15
- package/dist/channel.js +4 -1
- package/dist/channels/agentcore-protocol.d.ts +36 -50
- package/dist/channels/agentcore-protocol.js +3 -3
- package/dist/channels/agentcore-service.d.ts +8 -18
- package/dist/channels/agentcore-service.js +108 -127
- package/dist/channels/agentcore.d.ts +8 -24
- package/dist/channels/agentcore.js +32 -65
- package/dist/channels/body.d.ts +0 -16
- package/dist/channels/body.js +0 -25
- package/dist/channels/busy.js +6 -1
- package/dist/channels/context.d.ts +4 -0
- package/dist/channels/context.js +35 -0
- package/dist/channels/discover.d.ts +18 -20
- package/dist/channels/discover.js +41 -99
- package/dist/channels/feishu/feishu-api.d.ts +54 -4
- package/dist/channels/feishu/feishu-api.js +192 -149
- package/dist/channels/feishu/feishu.d.ts +3 -0
- package/dist/channels/feishu/feishu.js +117 -106
- package/dist/channels/feishu/history.d.ts +55 -0
- package/dist/channels/feishu/history.js +199 -0
- package/dist/channels/feishu/invoke-turn.d.ts +13 -11
- package/dist/channels/feishu/invoke-turn.js +9 -10
- package/dist/channels/feishu/model.d.ts +2 -1
- package/dist/channels/feishu/normalize.js +100 -0
- package/dist/channels/feishu/preview.d.ts +2 -2
- package/dist/channels/feishu/register-webhook.d.ts +1 -0
- package/dist/channels/feishu/register-webhook.js +3 -2
- package/dist/channels/feishu/scaffold/channel.ts +65 -46
- package/dist/channels/feishu/scaffold/feishu-send.ts +38 -33
- package/dist/channels/feishu/scaffold/feishu-threads.ts +25 -0
- package/dist/channels/feishu/setup-mode.d.ts +45 -25
- package/dist/channels/feishu/setup-mode.js +74 -23
- package/dist/channels/feishu/shared-api.d.ts +24 -6
- package/dist/channels/feishu/shared-api.js +30 -24
- package/dist/channels/kit/context-buffer.d.ts +11 -2
- package/dist/channels/kit/context-buffer.js +7 -4
- package/dist/channels/kit/delivery.d.ts +19 -3
- package/dist/channels/kit/delivery.js +74 -15
- package/dist/channels/kit/event-stream.js +2 -1
- package/dist/channels/kit/invoke-turn-kit.d.ts +41 -41
- package/dist/channels/kit/invoke-turn-kit.js +62 -71
- package/dist/channels/kit/place-history.d.ts +107 -0
- package/dist/channels/kit/place-history.js +182 -0
- package/dist/channels/kit/preview-kit.d.ts +9 -5
- package/dist/channels/kit/preview-kit.js +36 -15
- package/dist/channels/kit/room-threads.d.ts +46 -0
- package/dist/channels/kit/room-threads.js +81 -0
- package/dist/channels/kit/seen.js +1 -1
- package/dist/channels/kit/stop-command.d.ts +6 -3
- package/dist/channels/kit/stop-command.js +20 -18
- package/dist/channels/kit/thread-fork.d.ts +17 -0
- package/dist/channels/kit/thread-fork.js +43 -0
- package/dist/channels/kit/thread-participants.js +1 -1
- package/dist/channels/kit/transport.d.ts +5 -0
- package/dist/channels/kit/transport.js +5 -0
- package/dist/channels/kit/turn-runner.d.ts +10 -9
- package/dist/channels/kit/turn-runner.js +17 -19
- package/dist/channels/kit/turn-store.d.ts +5 -4
- package/dist/channels/kit/turn-store.js +5 -5
- package/dist/channels/lark/lark.d.ts +3 -0
- package/dist/channels/lark/lark.js +5 -0
- package/dist/channels/lark/onboard.d.ts +5 -3
- package/dist/channels/lark/onboard.js +14 -14
- package/dist/channels/lark/scaffold/channel.ts +67 -44
- package/dist/channels/lark/scaffold/lark-send.ts +38 -33
- package/dist/channels/lark/scaffold/lark-threads.ts +25 -0
- package/dist/channels/respond.d.ts +0 -4
- package/dist/channels/respond.js +1 -1
- package/dist/channels/router.d.ts +18 -0
- package/dist/channels/router.js +68 -0
- package/dist/channels/serve.d.ts +5 -70
- package/dist/channels/serve.js +105 -287
- package/dist/channels/slack/history.d.ts +42 -0
- package/dist/channels/slack/history.js +282 -0
- package/dist/channels/slack/invoke-turn.d.ts +12 -5
- package/dist/channels/slack/invoke-turn.js +7 -5
- package/dist/channels/slack/manifest.d.ts +1 -4
- package/dist/channels/slack/manifest.js +22 -10
- package/dist/channels/slack/model.d.ts +2 -1
- package/dist/channels/slack/onboard.d.ts +0 -2
- package/dist/channels/slack/onboard.js +2 -4
- package/dist/channels/slack/onboarding-state.d.ts +0 -2
- package/dist/channels/slack/onboarding-state.js +0 -1
- package/dist/channels/slack/parse.d.ts +0 -5
- package/dist/channels/slack/parse.js +1 -16
- package/dist/channels/slack/preview.d.ts +2 -3
- package/dist/channels/slack/preview.js +78 -52
- package/dist/channels/slack/register-webhook.d.ts +1 -0
- package/dist/channels/slack/register-webhook.js +6 -5
- package/dist/channels/slack/scaffold/channel.ts +23 -24
- package/dist/channels/slack/scaffold/slack-send.ts +50 -43
- package/dist/channels/slack/scaffold/slack-threads.ts +29 -0
- package/dist/channels/slack/setup-server.d.ts +19 -0
- package/dist/channels/slack/setup-server.js +113 -11
- package/dist/channels/slack/shared-api.d.ts +15 -4
- package/dist/channels/slack/shared-api.js +19 -14
- package/dist/channels/slack/slack-api.d.ts +61 -2
- package/dist/channels/slack/slack-api.js +28 -5
- package/dist/channels/slack/slack.js +97 -52
- package/dist/channels/slack/welcomed.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +6 -1
- package/dist/channels/telegram/context-buffer.js +19 -0
- package/dist/channels/telegram/invoke-turn.d.ts +8 -4
- package/dist/channels/telegram/invoke-turn.js +7 -7
- package/dist/channels/telegram/parse.d.ts +7 -2
- package/dist/channels/telegram/parse.js +7 -0
- package/dist/channels/telegram/preview.d.ts +2 -2
- package/dist/channels/telegram/register-webhook.d.ts +1 -0
- package/dist/channels/telegram/register-webhook.js +3 -2
- package/dist/channels/telegram/scaffold/channel.ts +27 -28
- package/dist/channels/telegram/scaffold/telegram-send.ts +61 -108
- package/dist/channels/telegram/shared-api.d.ts +34 -0
- package/dist/channels/telegram/shared-api.js +79 -0
- package/dist/channels/telegram/telegram-api.d.ts +15 -5
- package/dist/channels/telegram/telegram-api.js +10 -6
- package/dist/channels/telegram/telegram.js +27 -21
- package/dist/channels/telegram/turn-store.js +1 -1
- package/dist/channels/{kit/turn-queue.d.ts → turn-queue.d.ts} +2 -2
- package/dist/channels/{kit/turn-queue.js → turn-queue.js} +10 -10
- package/dist/cli/add-feishu.d.ts +42 -12
- package/dist/cli/add-feishu.js +177 -124
- package/dist/cli/add-slack.d.ts +8 -3
- package/dist/cli/add-slack.js +115 -63
- package/dist/cli/auth-view.d.ts +10 -3
- package/dist/cli/auth-view.js +8 -6
- package/dist/cli/box-login.d.ts +17 -7
- package/dist/cli/box-login.js +46 -12
- package/dist/cli/commands/add.d.ts +15 -4
- package/dist/cli/commands/add.js +252 -143
- package/dist/cli/commands/auth.d.ts +6 -0
- package/dist/cli/commands/auth.js +50 -0
- package/dist/cli/commands/content.d.ts +6 -0
- package/dist/cli/commands/content.js +45 -0
- package/dist/cli/commands/deploy/agentcore.js +50 -77
- package/dist/cli/commands/deploy/docker.js +38 -39
- package/dist/cli/commands/deploy/fly.js +21 -18
- package/dist/cli/commands/deploy/host-login.d.ts +26 -0
- package/dist/cli/commands/deploy/host-login.js +90 -0
- package/dist/cli/commands/deploy/railway.js +21 -26
- package/dist/cli/commands/deploy/shared.d.ts +45 -17
- package/dist/cli/commands/deploy/shared.js +34 -15
- package/dist/cli/commands/deploy.d.ts +0 -4
- package/dist/cli/commands/deploy.js +237 -50
- package/dist/cli/commands/destroy.js +8 -6
- package/dist/cli/commands/dev.d.ts +0 -2
- package/dist/cli/commands/dev.js +20 -19
- package/dist/cli/commands/env.d.ts +12 -0
- package/dist/cli/commands/env.js +190 -0
- package/dist/cli/commands/info.js +95 -156
- package/dist/cli/commands/init.d.ts +2 -2
- package/dist/cli/commands/init.js +35 -33
- package/dist/cli/commands/invoke.d.ts +6 -0
- package/dist/cli/commands/invoke.js +131 -16
- package/dist/cli/commands/login.d.ts +31 -2
- package/dist/cli/commands/login.js +413 -64
- package/dist/cli/commands/logout.d.ts +4 -0
- package/dist/cli/commands/logout.js +53 -0
- package/dist/cli/commands/logs.js +8 -6
- package/dist/cli/commands/models.d.ts +2 -1
- package/dist/cli/commands/models.js +39 -37
- package/dist/cli/commands/schedules.d.ts +21 -0
- package/dist/cli/commands/schedules.js +143 -0
- package/dist/cli/commands/start.d.ts +11 -10
- package/dist/cli/commands/start.js +35 -62
- package/dist/cli/content-view.d.ts +9 -0
- package/dist/cli/content-view.js +26 -0
- package/dist/cli/fail.d.ts +9 -12
- package/dist/cli/fail.js +17 -14
- package/dist/cli/invoke-stream.d.ts +12 -3
- package/dist/cli/invoke-stream.js +85 -23
- package/dist/cli/kernel.d.ts +9 -2
- package/dist/cli/kernel.js +15 -6
- package/dist/cli/login-relay.d.ts +3 -2
- package/dist/cli/login-relay.js +18 -6
- package/dist/cli/models-view.d.ts +5 -1
- package/dist/cli/models-view.js +1 -1
- package/dist/cli/person.d.ts +10 -0
- package/dist/cli/person.js +23 -0
- package/dist/cli/program.js +421 -203
- package/dist/cli/serve.d.ts +13 -63
- package/dist/cli/serve.js +31 -121
- package/dist/cli/shared.d.ts +21 -48
- package/dist/cli/shared.js +68 -89
- package/dist/cli/ticket.d.ts +136 -0
- package/dist/cli/ticket.js +476 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +2 -0
- package/dist/content/declare.d.ts +70 -0
- package/dist/content/declare.js +163 -0
- package/dist/content/file.d.ts +32 -0
- package/dist/content/file.js +119 -0
- package/dist/content/git.d.ts +41 -0
- package/dist/content/git.js +217 -0
- package/dist/content/mount.d.ts +2 -0
- package/dist/content/mount.js +15 -0
- package/dist/content/resolve.d.ts +52 -0
- package/dist/content/resolve.js +151 -0
- package/dist/content/source.d.ts +27 -0
- package/dist/content/source.js +53 -0
- package/dist/conversation-sessions.d.ts +26 -0
- package/dist/conversation-sessions.js +99 -0
- package/dist/declared-secrets.d.ts +19 -37
- package/dist/declared-secrets.js +16 -58
- package/dist/deploy/agentcore/destroy.d.ts +3 -3
- package/dist/deploy/agentcore/destroy.js +46 -28
- package/dist/deploy/agentcore/forwarder.js +105 -22
- package/dist/deploy/agentcore/logs.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +10 -24
- package/dist/deploy/agentcore/plan.js +31 -135
- package/dist/deploy/agentcore/run.d.ts +1 -1
- package/dist/deploy/agentcore/run.js +8 -5
- package/dist/deploy/agentcore/shell.d.ts +1 -1
- package/dist/deploy/agentcore/shell.js +5 -5
- package/dist/deploy/api.d.ts +34 -0
- package/dist/deploy/api.js +50 -0
- package/dist/deploy/box-shell.d.ts +6 -0
- package/dist/deploy/box-shell.js +7 -0
- package/dist/deploy/build-context.d.ts +7 -5
- package/dist/deploy/build-context.js +41 -35
- package/dist/deploy/channel-ingress.d.ts +6 -1
- package/dist/deploy/channel-ingress.js +18 -5
- package/dist/deploy/container.d.ts +22 -9
- package/dist/deploy/container.js +96 -55
- package/dist/deploy/docker/plan.d.ts +3 -2
- package/dist/deploy/docker/plan.js +13 -14
- package/dist/deploy/docker/run.d.ts +2 -2
- package/dist/deploy/docker/run.js +8 -4
- package/dist/deploy/fly/plan.d.ts +1 -1
- package/dist/deploy/fly/plan.js +12 -21
- package/dist/deploy/fly/run.d.ts +10 -6
- package/dist/deploy/fly/run.js +11 -6
- package/dist/deploy/hosts.d.ts +2 -1
- package/dist/deploy/hosts.js +2 -1
- package/dist/deploy/preflight.d.ts +21 -23
- package/dist/deploy/preflight.js +222 -201
- package/dist/deploy/railway/plan.d.ts +8 -6
- package/dist/deploy/railway/plan.js +15 -58
- package/dist/deploy/railway/run.d.ts +8 -3
- package/dist/deploy/railway/run.js +12 -8
- package/dist/deploy/residency.d.ts +5 -11
- package/dist/deploy/residency.js +0 -6
- package/dist/deploy/runner.d.ts +1 -1
- package/dist/deploy/runner.js +1 -1
- package/dist/deploy/secrets.d.ts +16 -2
- package/dist/deploy/secrets.js +33 -6
- package/dist/deploy/workspace.d.ts +11 -6
- package/dist/deploy/workspace.js +55 -38
- package/dist/dev-supervisor.d.ts +11 -2
- package/dist/dev-supervisor.js +51 -26
- package/dist/effect-port.d.ts +4 -4
- package/dist/effect-port.js +4 -4
- package/dist/environment/apt.d.ts +4 -0
- package/dist/environment/apt.js +32 -0
- package/dist/environment/declare.d.ts +59 -0
- package/dist/environment/declare.js +210 -0
- package/dist/environment/mise.d.ts +50 -0
- package/dist/environment/mise.js +268 -0
- package/dist/feishu.d.ts +2 -2
- package/dist/feishu.js +2 -2
- package/dist/git.d.ts +13 -0
- package/dist/git.js +55 -0
- package/dist/harnesses/models/catalog.d.ts +20 -0
- package/dist/harnesses/models/catalog.js +254 -0
- package/dist/{engines/pi/auth.d.ts → harnesses/models/credentials.d.ts} +3 -3
- package/dist/{engines/pi/auth.js → harnesses/models/credentials.js} +3 -3
- package/dist/harnesses/models/environment.d.ts +43 -0
- package/dist/harnesses/models/environment.js +82 -0
- package/dist/{engines/pi → harnesses/models}/login.d.ts +57 -26
- package/dist/{engines/pi → harnesses/models}/login.js +187 -79
- package/dist/harnesses/models/models.d.ts +83 -0
- package/dist/harnesses/models/models.js +158 -0
- package/dist/harnesses/pi-durable/agent.d.ts +42 -0
- package/dist/harnesses/pi-durable/agent.js +579 -0
- package/dist/harnesses/pi-durable/defaults.d.ts +28 -0
- package/dist/harnesses/pi-durable/defaults.js +26 -0
- package/dist/harnesses/pi-durable/define.d.ts +77 -0
- package/dist/harnesses/pi-durable/define.js +28 -0
- package/dist/harnesses/pi-durable/entries.d.ts +32 -0
- package/dist/harnesses/pi-durable/entries.js +126 -0
- package/dist/harnesses/pi-durable/events.d.ts +25 -0
- package/dist/harnesses/pi-durable/events.js +238 -0
- package/dist/harnesses/pi-durable/ids.d.ts +16 -0
- package/dist/harnesses/pi-durable/ids.js +55 -0
- package/dist/harnesses/pi-durable/messages.d.ts +41 -0
- package/dist/harnesses/pi-durable/messages.js +116 -0
- package/dist/harnesses/pi-durable/open.d.ts +88 -0
- package/dist/harnesses/pi-durable/open.js +329 -0
- package/dist/harnesses/pi-durable/reads.d.ts +30 -0
- package/dist/harnesses/pi-durable/reads.js +94 -0
- package/dist/harnesses/pi-durable/registry.d.ts +18 -0
- package/dist/harnesses/pi-durable/registry.js +56 -0
- package/dist/harnesses/pi-durable/release.d.ts +13 -0
- package/dist/harnesses/pi-durable/release.js +40 -0
- package/dist/harnesses/pi-durable/serve.d.ts +80 -0
- package/dist/harnesses/pi-durable/serve.js +156 -0
- package/dist/harnesses/pi-durable/sessions.d.ts +27 -0
- package/dist/harnesses/pi-durable/sessions.js +47 -0
- package/dist/harnesses/pi-durable/storage.d.ts +4 -0
- package/dist/harnesses/pi-durable/storage.js +49 -0
- package/dist/harnesses/pi-durable/stream.d.ts +20 -0
- package/dist/harnesses/pi-durable/stream.js +221 -0
- package/dist/harnesses/pi-durable/text.d.ts +30 -0
- package/dist/harnesses/pi-durable/text.js +292 -0
- package/dist/harnesses/pi-durable/wake.d.ts +4 -0
- package/dist/harnesses/pi-durable/wake.js +81 -0
- package/dist/http/client.d.ts +6 -0
- package/dist/http/client.js +256 -0
- package/dist/http/contract.d.ts +541 -0
- package/dist/http/contract.js +179 -0
- package/dist/http/server.d.ts +34 -0
- package/dist/http/server.js +194 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +10 -5
- package/dist/lark.d.ts +2 -2
- package/dist/lark.js +2 -2
- package/dist/locked-file.d.ts +17 -0
- package/dist/{engines/pi/locked-file.js → locked-file.js} +16 -10
- package/dist/once.d.ts +6 -0
- package/dist/once.js +23 -0
- package/dist/paths.d.ts +36 -52
- package/dist/paths.js +66 -182
- package/dist/runtime.d.ts +25 -2
- package/dist/runtime.js +27 -0
- package/dist/scaffold/add-channel.d.ts +14 -27
- package/dist/scaffold/add-channel.js +82 -180
- package/dist/scaffold/init.d.ts +17 -18
- package/dist/scaffold/init.js +87 -99
- package/dist/scaffold/templates/SYSTEM.md +12 -0
- package/dist/scaffold/templates/env.example +4 -10
- package/dist/scaffold/templates/fastagent.config.ts +21 -33
- package/dist/scaffold/templates/gitignore +8 -1
- package/dist/scaffold/templates/skills/writing-great-skills/SKILL.md +1 -2
- package/dist/scaffold/templates.d.ts +9 -4
- package/dist/scaffold/templates.js +7 -5
- package/dist/scaffold/vendor-skill.d.ts +1 -4
- package/dist/scaffold/vendor-skill.js +5 -6
- package/dist/schedule/cron.d.ts +13 -0
- package/dist/schedule/cron.js +52 -0
- package/dist/schedule/eventbridge-cron.d.ts +17 -0
- package/dist/schedule/eventbridge-cron.js +88 -0
- package/dist/schedule/schedule.d.ts +23 -0
- package/dist/schedule/schedule.js +34 -0
- package/dist/schedule/scheduler.d.ts +34 -33
- package/dist/schedule/scheduler.js +251 -137
- package/dist/schedule/state.d.ts +4 -14
- package/dist/schedule/state.js +7 -7
- package/dist/schedule/wake-alarm.d.ts +22 -2
- package/dist/schedule/wake-alarm.js +66 -20
- package/dist/schedule/wakeups.d.ts +16 -15
- package/dist/schedule/wakeups.js +47 -45
- package/dist/service.d.ts +56 -169
- package/dist/service.js +91 -187
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +16 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/{channels/kit/state.js → state-file.js} +6 -6
- package/dist/telegram.d.ts +1 -0
- package/dist/telegram.js +1 -0
- package/dist/tunnel.d.ts +3 -0
- package/dist/tunnel.js +12 -4
- package/dist/value-file.d.ts +21 -0
- package/dist/value-file.js +108 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +14 -1
- package/package.json +18 -28
- package/dist/agent.d.ts +0 -106
- package/dist/agent.js +0 -21
- package/dist/channels/control.d.ts +0 -31
- package/dist/channels/control.js +0 -329
- package/dist/channels/define-channel.d.ts +0 -39
- package/dist/channels/define-channel.js +0 -9
- package/dist/channels/feishu/context-buffer.d.ts +0 -35
- package/dist/channels/feishu/context-buffer.js +0 -65
- package/dist/channels/feishu/scaffold/channel.websocket.ts +0 -31
- package/dist/channels/http.d.ts +0 -23
- package/dist/channels/http.js +0 -57
- package/dist/channels/lark/scaffold/channel.websocket.ts +0 -31
- package/dist/channels/slack/context-buffer.d.ts +0 -23
- package/dist/channels/slack/context-buffer.js +0 -37
- package/dist/channels/sse.d.ts +0 -4
- package/dist/channels/sse.js +0 -66
- package/dist/cli/commands/chat.d.ts +0 -3
- package/dist/cli/commands/chat.js +0 -14
- package/dist/cli/commands/routine-run.d.ts +0 -6
- package/dist/cli/commands/routine-run.js +0 -46
- package/dist/cli/commands/routine.d.ts +0 -25
- package/dist/cli/commands/routine.js +0 -122
- package/dist/cli/commands/tool.d.ts +0 -1
- package/dist/cli/commands/tool.js +0 -57
- package/dist/collect.d.ts +0 -63
- package/dist/collect.js +0 -90
- package/dist/core.d.ts +0 -13
- package/dist/core.js +0 -15
- package/dist/engines/pi/agent-models.d.ts +0 -83
- package/dist/engines/pi/agent-models.js +0 -120
- package/dist/engines/pi/agent-session-factory.d.ts +0 -121
- package/dist/engines/pi/agent-session-factory.js +0 -535
- package/dist/engines/pi/chat.d.ts +0 -3
- package/dist/engines/pi/chat.js +0 -8
- package/dist/engines/pi/config.d.ts +0 -71
- package/dist/engines/pi/config.js +0 -199
- package/dist/engines/pi/create.d.ts +0 -170
- package/dist/engines/pi/create.js +0 -343
- package/dist/engines/pi/definition.d.ts +0 -74
- package/dist/engines/pi/definition.js +0 -236
- package/dist/engines/pi/entry-images.d.ts +0 -14
- package/dist/engines/pi/entry-images.js +0 -23
- package/dist/engines/pi/invoke-session.d.ts +0 -18
- package/dist/engines/pi/invoke-session.js +0 -403
- package/dist/engines/pi/locked-file.d.ts +0 -9
- package/dist/engines/pi/machine.d.ts +0 -53
- package/dist/engines/pi/machine.js +0 -137
- package/dist/engines/pi/models.d.ts +0 -200
- package/dist/engines/pi/models.js +0 -533
- package/dist/engines/pi/open.d.ts +0 -180
- package/dist/engines/pi/open.js +0 -270
- package/dist/engines/pi/openai-account-models.d.ts +0 -35
- package/dist/engines/pi/openai-account-models.js +0 -116
- package/dist/engines/pi/report.d.ts +0 -18
- package/dist/engines/pi/report.js +0 -59
- package/dist/engines/pi/retry-event.d.ts +0 -6
- package/dist/engines/pi/retry-event.js +0 -15
- package/dist/engines/pi/service.d.ts +0 -9
- package/dist/engines/pi/service.js +0 -14
- package/dist/engines/pi/session-builder.d.ts +0 -13
- package/dist/engines/pi/session-builder.js +0 -117
- package/dist/engines/pi/session-control.d.ts +0 -77
- package/dist/engines/pi/session-control.js +0 -908
- package/dist/engines/pi/session-effects.d.ts +0 -14
- package/dist/engines/pi/session-effects.js +0 -30
- package/dist/engines/pi/session-inheritance.d.ts +0 -21
- package/dist/engines/pi/session-inheritance.js +0 -204
- package/dist/engines/pi/session-markers.d.ts +0 -49
- package/dist/engines/pi/session-markers.js +0 -60
- package/dist/engines/pi/session-settings.d.ts +0 -67
- package/dist/engines/pi/session-settings.js +0 -119
- package/dist/engines/pi/session-store.d.ts +0 -69
- package/dist/engines/pi/session-store.js +0 -430
- package/dist/engines/pi/tool-context.d.ts +0 -45
- package/dist/engines/pi/tool-context.js +0 -31
- package/dist/engines/pi/tool.d.ts +0 -99
- package/dist/engines/pi/tool.js +0 -118
- package/dist/engines/pi/turn-kit.d.ts +0 -92
- package/dist/engines/pi/turn-kit.js +0 -288
- package/dist/engines/pi/wake-tool.d.ts +0 -9
- package/dist/engines/pi/wake-tool.js +0 -92
- package/dist/loader.d.ts +0 -41
- package/dist/loader.js +0 -116
- package/dist/node.d.ts +0 -3
- package/dist/node.js +0 -4
- package/dist/observe.d.ts +0 -6
- package/dist/observe.js +0 -47
- package/dist/pi.d.ts +0 -25
- package/dist/pi.js +0 -15
- package/dist/scaffold/templates/APPEND_SYSTEM.md +0 -12
- package/dist/scaffold/templates/tools/fetch-url.ts +0 -25
- package/dist/schedule/discover.d.ts +0 -17
- package/dist/schedule/discover.js +0 -61
- package/dist/schedule/routine.d.ts +0 -43
- package/dist/schedule/routine.js +0 -32
- package/dist/schedule/run.d.ts +0 -55
- package/dist/schedule/run.js +0 -146
- package/dist/secrets-gate.d.ts +0 -32
- package/dist/secrets-gate.js +0 -33
- package/dist/session-remote.d.ts +0 -47
- package/dist/session-remote.js +0 -460
- package/dist/session.d.ts +0 -466
- package/dist/session.js +0 -57
- /package/dist/{channels/kit/state.d.ts → state-file.d.ts} +0 -0
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Building pi's model registry: the built-ins, an agent's `models.json` and model catalog layered over the machine's,
|
|
3
|
-
* the catalog refresh, and the reads of those files a report or a deploy check needs. Which credential store an agent
|
|
4
|
-
* runs it over is agent-models.ts's; registry and auth are one collection, so the model's provider auth is in scope.
|
|
5
|
-
*/
|
|
6
|
-
import { createHash } from "node:crypto";
|
|
7
|
-
import { existsSync } from "node:fs";
|
|
8
|
-
import { readFile } from "node:fs/promises";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { InMemoryCredentialStore, InMemoryModelsStore, defaultProviderAuthContext, } from "@earendil-works/pi-ai";
|
|
11
|
-
import { builtinModels, builtinProviders, getBuiltinModelDataGeneratedAt } from "@earendil-works/pi-ai/providers/all";
|
|
12
|
-
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import { AGENT_MODEL_CATALOG_FILE, AGENT_MODELS_FILE, globalHome, resolveOverridePath } from "../../paths.js";
|
|
14
|
-
import { writeFileAtomic } from "../../atomic-write.js";
|
|
15
|
-
import { withLockedFile } from "./locked-file.js";
|
|
16
|
-
import { OPENAI_PROVIDER, registerAccountModels, withAccountModels } from "./openai-account-models.js";
|
|
17
|
-
/** The built-ins plus `providers` (a same id replaces a built-in), over any credential store. */
|
|
18
|
-
export function piModelsOver(credentials, providers = []) {
|
|
19
|
-
const models = builtinModels({ credentials, authContext: defaultProviderAuthContext() });
|
|
20
|
-
const openai = models.getProvider(OPENAI_PROVIDER);
|
|
21
|
-
if (openai)
|
|
22
|
-
models.setProvider(withAccountModels(openai));
|
|
23
|
-
for (const provider of providers)
|
|
24
|
-
models.setProvider(provider);
|
|
25
|
-
return models;
|
|
26
|
-
}
|
|
27
|
-
/** The serving default for reasoning effort, pinned to what pi's TUI defaults to (its own DEFAULT_THINKING_LEVEL). */
|
|
28
|
-
export const DEFAULT_THINKING_LEVEL = "medium";
|
|
29
|
-
/**
|
|
30
|
-
* The MACHINE's custom-endpoint file, in the same schema as an agent's own: endpoints a person set up for this machine
|
|
31
|
-
* (a local Ollama, a company gateway), which every agent here inherits the way it inherits the machine's skills.
|
|
32
|
-
* `FASTAGENT_MODELS_PATH` moves it. It never ships: a deployed agent has only its definition's file.
|
|
33
|
-
*/
|
|
34
|
-
function machineModelsPath(env = process.env) {
|
|
35
|
-
return resolveOverridePath(env.FASTAGENT_MODELS_PATH) ?? join(globalHome(), AGENT_MODELS_FILE);
|
|
36
|
-
}
|
|
37
|
-
/** A custom-endpoint file's providers by id, or undefined when there is no file. Strict JSON: see {@link modelLayers}. */
|
|
38
|
-
async function readProviders(path) {
|
|
39
|
-
let raw;
|
|
40
|
-
try {
|
|
41
|
-
raw = await readFile(path, "utf8");
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
if (error.code === "ENOENT")
|
|
45
|
-
return undefined;
|
|
46
|
-
throw new Error(`could not read ${path}: ${error.message}`);
|
|
47
|
-
}
|
|
48
|
-
let parsed;
|
|
49
|
-
try {
|
|
50
|
-
parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
throw new Error(`${path} is not valid JSON: ${error.message}`);
|
|
54
|
-
}
|
|
55
|
-
const providers = parsed?.providers;
|
|
56
|
-
if (typeof providers !== "object" || providers === null || Array.isArray(providers)) {
|
|
57
|
-
throw new Error(`${path} must hold { "providers": { ... } }`);
|
|
58
|
-
}
|
|
59
|
-
return providers;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* The two custom-endpoint layers of an agent, read ONCE for every reader: the file pi loads and the report of where a
|
|
63
|
-
* provider came from both come from this, so what runs and what `info`/`deploy` say cannot disagree. Undefined when
|
|
64
|
-
* the machine has no file: then the agent's own file goes to pi untouched.
|
|
65
|
-
*
|
|
66
|
-
* Read here rather than by pi, because pi loads exactly one path and its comment stripping is not reachable: while a
|
|
67
|
-
* machine file exists, both files must be plain JSON.
|
|
68
|
-
*/
|
|
69
|
-
async function modelLayers(agentDir) {
|
|
70
|
-
const machinePath = machineModelsPath();
|
|
71
|
-
const machine = await readProviders(machinePath);
|
|
72
|
-
if (!machine)
|
|
73
|
-
return undefined;
|
|
74
|
-
return { machinePath, machine, own: (await readProviders(join(agentDir, AGENT_MODELS_FILE))) ?? {} };
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* The models.json pi loads for an agent: its own file, layered over the machine's when `machine` is on. The agent's
|
|
78
|
-
* file wins a provider id outright, as a definition's skill wins a name: an agent that pins an endpoint keeps it.
|
|
79
|
-
*
|
|
80
|
-
* Always a SNAPSHOT named by its content, in fastagent's own home: pi reloads `modelsPath` on every refresh, and every
|
|
81
|
-
* session builds its own runtime, so a live file would let turns run on an edit the control plane never read. Not in
|
|
82
|
-
* the agent (so `info` writes nothing there), and not in a shared temp dir, which the OS clears while a long-running
|
|
83
|
-
* process still re-reads it. Content addressing is what lets every process share the directory: a snapshot is never
|
|
84
|
-
* rewritten, so no process can change what another reads. A running process keeps the content it started with; an
|
|
85
|
-
* edit to either file takes effect on the next start.
|
|
86
|
-
*
|
|
87
|
-
* ponytail: snapshots are never pruned (one per distinct content, a few KB each) because a running process may still
|
|
88
|
-
* read an old one; delete the directory while no fastagent process runs if it ever matters.
|
|
89
|
-
*/
|
|
90
|
-
async function modelsFileFor(agentDir, machine) {
|
|
91
|
-
const definition = join(agentDir, AGENT_MODELS_FILE);
|
|
92
|
-
const layers = machine ? await modelLayers(agentDir) : undefined;
|
|
93
|
-
if (layers) {
|
|
94
|
-
const content = JSON.stringify({ providers: { ...layers.machine, ...layers.own } }, null, 2);
|
|
95
|
-
return { path: snapshotFile(content), merged: { machine: layers.machinePath, definition } };
|
|
96
|
-
}
|
|
97
|
-
let own;
|
|
98
|
-
try {
|
|
99
|
-
own = await readFile(definition, "utf8");
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
if (error.code === "ENOENT")
|
|
103
|
-
return { path: null };
|
|
104
|
-
throw new Error(`could not read ${definition}: ${error.message}`);
|
|
105
|
-
}
|
|
106
|
-
// Verbatim, so pi parses it exactly as it would the file itself.
|
|
107
|
-
return { path: snapshotFile(own), snapshotOf: definition };
|
|
108
|
-
}
|
|
109
|
-
function snapshotFile(content) {
|
|
110
|
-
const hash = createHash("sha256").update(content).digest("hex").slice(0, 32);
|
|
111
|
-
const path = join(globalHome(), ".cache", "models", `${hash}.json`);
|
|
112
|
-
// 0600: it may carry literal keys. Several processes may create the same snapshot at once.
|
|
113
|
-
if (!existsSync(path))
|
|
114
|
-
writeFileAtomic(path, content, 0o600, true);
|
|
115
|
-
return path;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Where an agent's custom endpoints come from, for a report: the machine file, the providers the agent inherits from
|
|
119
|
-
* it, and the ones its own file overrides. Undefined when the machine has no file.
|
|
120
|
-
*/
|
|
121
|
-
export async function machineModels(agentDir) {
|
|
122
|
-
const layers = await modelLayers(agentDir);
|
|
123
|
-
if (!layers)
|
|
124
|
-
return undefined;
|
|
125
|
-
const ids = Object.keys(layers.machine).sort();
|
|
126
|
-
return {
|
|
127
|
-
path: layers.machinePath,
|
|
128
|
-
inherited: ids.filter((id) => !(id in layers.own)),
|
|
129
|
-
overridden: ids.filter((id) => id in layers.own),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
/** Whether pi ships this provider id itself, so an agent resolves it without any models.json entry. */
|
|
133
|
-
export function isBuiltinProvider(providerId) {
|
|
134
|
-
return builtinProviders().some((provider) => provider.id === providerId);
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* The MACHINE's model catalog: the models pi.dev lists that are newer than the catalog bundled with pi, fetched by
|
|
138
|
-
* `fastagent models --refresh -g`. Every agent here reads it under its own; like `~/.fastagent/models.json`, it
|
|
139
|
-
* never ships.
|
|
140
|
-
*/
|
|
141
|
-
export function globalCatalogPath() {
|
|
142
|
-
return join(globalHome(), AGENT_MODEL_CATALOG_FILE);
|
|
143
|
-
}
|
|
144
|
-
/** A catalog file as pi writes it (entries by provider), or `{}` when there is none. Read without a lock: its one
|
|
145
|
-
* writer ({@link catalogFileStore}) replaces the file whole, so a read sees the old catalog or the new one. */
|
|
146
|
-
async function readCatalog(path) {
|
|
147
|
-
if (!existsSync(path))
|
|
148
|
-
return {};
|
|
149
|
-
return parseCatalog(await readFile(path, "utf8"), path);
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* The one reading of a catalog file's content, for reads and writes alike. Empty is an empty catalog, as pi reads it:
|
|
153
|
-
* the writer creates the file exclusively an instant before its first content, and pi's own store, which wrote these
|
|
154
|
-
* files before, left one behind whenever a refresh it began wrote no entry. That is safe only because nothing writes
|
|
155
|
-
* these files in place any more: a rewrite in place is read mostly as empty while it runs. Anything else that does not
|
|
156
|
-
* parse is a corrupt file, and a WRITE refuses it too: serializing over it would drop every other provider's entries.
|
|
157
|
-
*/
|
|
158
|
-
function parseCatalog(content, path) {
|
|
159
|
-
if (content === "")
|
|
160
|
-
return {};
|
|
161
|
-
try {
|
|
162
|
-
return JSON.parse(content);
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
throw new Error(`model catalog ${path} is not valid JSON (${error.message}): refresh or delete it`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* The catalog file a refresh writes, as pi's store. It replaces pi's own file store, which rewrites the file in place
|
|
170
|
-
* under its lock: a reader that takes no lock (every catalog read here, and anything else reading the file) could open
|
|
171
|
-
* it cut short mid-write and blame a healthy file. Same lock as pi's, so concurrent refreshes still queue; reading
|
|
172
|
-
* creates nothing, so a refresh that is refused writes no file.
|
|
173
|
-
*/
|
|
174
|
-
export function catalogFileStore(path) {
|
|
175
|
-
const change = (update, signal) => withLockedFile(path, async (current) => {
|
|
176
|
-
const catalog = parseCatalog(current ?? "", path);
|
|
177
|
-
update(catalog);
|
|
178
|
-
return { result: undefined, next: JSON.stringify(catalog, null, 2) };
|
|
179
|
-
}, signal ? { signal } : {});
|
|
180
|
-
return {
|
|
181
|
-
async read(providerId, options) {
|
|
182
|
-
options?.signal?.throwIfAborted();
|
|
183
|
-
return (await readCatalog(path))[providerId];
|
|
184
|
-
},
|
|
185
|
-
async write(providerId, entry, options) {
|
|
186
|
-
options?.signal?.throwIfAborted();
|
|
187
|
-
await change((catalog) => {
|
|
188
|
-
catalog[providerId] = structuredClone(entry);
|
|
189
|
-
}, options?.signal);
|
|
190
|
-
},
|
|
191
|
-
async delete(providerId, options) {
|
|
192
|
-
options?.signal?.throwIfAborted();
|
|
193
|
-
await change((catalog) => {
|
|
194
|
-
delete catalog[providerId];
|
|
195
|
-
}, options?.signal);
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* The catalog files layered into one read-only store, later files winning a (type, model id) pair. An entry pi would ignore (no
|
|
201
|
-
* newer than the catalog bundled with it) is dropped before the merge, so a stale layer cannot ride a newer one's
|
|
202
|
-
* date past pi's rule.
|
|
203
|
-
*
|
|
204
|
-
* Read here rather than handed to pi as a file: pi takes one store, and its file store creates the file (and a lock)
|
|
205
|
-
* on read, which would drop an empty catalog into every agent dir that was never refreshed.
|
|
206
|
-
*/
|
|
207
|
-
async function layeredCatalog(paths) {
|
|
208
|
-
return catalogStore(await readLayeredCatalog(paths));
|
|
209
|
-
}
|
|
210
|
-
/** A fresh store over a catalog read once: pi may write into the store it is given, so runtimes never share one. */
|
|
211
|
-
async function catalogStore(entries) {
|
|
212
|
-
const store = new InMemoryModelsStore();
|
|
213
|
-
for (const [provider, entry] of entries)
|
|
214
|
-
await store.write(provider, structuredClone(entry));
|
|
215
|
-
return store;
|
|
216
|
-
}
|
|
217
|
-
async function readLayeredCatalog(paths) {
|
|
218
|
-
const bundledAt = getBuiltinModelDataGeneratedAt();
|
|
219
|
-
const merged = new Map();
|
|
220
|
-
for (const path of paths) {
|
|
221
|
-
for (const [provider, entry] of Object.entries(await readCatalog(path))) {
|
|
222
|
-
if (bundledAt !== undefined && (entry.lastModified === undefined || entry.lastModified <= bundledAt))
|
|
223
|
-
continue;
|
|
224
|
-
const held = merged.get(provider);
|
|
225
|
-
const key = (model) => `${model.type ?? "chat"}\u0000${model.id}`;
|
|
226
|
-
const ids = new Set(entry.models.map(key));
|
|
227
|
-
merged.set(provider, held
|
|
228
|
-
? {
|
|
229
|
-
models: [...held.models.filter((model) => !ids.has(key(model))), ...entry.models],
|
|
230
|
-
lastModified: Math.max(held.lastModified ?? 0, entry.lastModified ?? 0),
|
|
231
|
-
}
|
|
232
|
-
: entry);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return merged;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Whether the machine's model catalog supplies this model, by the same rule every registry reads it with
|
|
239
|
-
* ({@link layeredCatalog}): an entry pi would ignore supplies nothing. The one layer a deployed agent does not have.
|
|
240
|
-
*/
|
|
241
|
-
export async function inGlobalCatalog(provider, id) {
|
|
242
|
-
const entry = await (await layeredCatalog([globalCatalogPath()])).read(provider);
|
|
243
|
-
return entry?.models.some((model) => (model.type ?? "chat") === "chat" && model.id === id) ?? false;
|
|
244
|
-
}
|
|
245
|
-
/** The models.json a runtime for these options loads, and which model catalog it reads. */
|
|
246
|
-
export async function modelRuntimeFiles(options) {
|
|
247
|
-
const { agentDir } = options;
|
|
248
|
-
const machine = options.machineLayer !== false;
|
|
249
|
-
const models = agentDir ? await modelsFileFor(agentDir, machine) : undefined;
|
|
250
|
-
const catalogs = !agentDir
|
|
251
|
-
? []
|
|
252
|
-
: [...(machine ? [globalCatalogPath()] : []), join(agentDir, AGENT_MODEL_CATALOG_FILE)];
|
|
253
|
-
const catalog = !options.catalogFile && agentDir ? await readLayeredCatalog(catalogs) : undefined;
|
|
254
|
-
return {
|
|
255
|
-
...(models ? { models } : {}),
|
|
256
|
-
create: async () => ({
|
|
257
|
-
modelsPath: models?.path ?? null,
|
|
258
|
-
// Always a store of our own whenever modelsPath is set: pi's default is a file at
|
|
259
|
-
// `<dirname(modelsPath)>/models-store.json`. Without a directory, pi keeps an empty one in memory.
|
|
260
|
-
...(options.catalogFile
|
|
261
|
-
? { modelsStore: catalogFileStore(options.catalogFile) }
|
|
262
|
-
: catalog
|
|
263
|
-
? { modelsStore: await catalogStore(catalog) }
|
|
264
|
-
: {}),
|
|
265
|
-
// Never fetched while a runtime is built: serving stays offline and reproducible. A refresh is asked for.
|
|
266
|
-
allowModelNetwork: false,
|
|
267
|
-
...(options.catalogBaseUrl ? { catalogBaseUrl: options.catalogBaseUrl } : {}),
|
|
268
|
-
}),
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* This machine's registry, without an agent: pi's built-ins, the machine's models.json and its model catalog. What
|
|
273
|
-
* `fastagent models` lists outside an agent; {@link catalogFile} is the refresh's (it writes the file).
|
|
274
|
-
*/
|
|
275
|
-
export async function machineModelRuntime(options = {}) {
|
|
276
|
-
const runtime = await ModelRuntime.create({
|
|
277
|
-
credentials: options.credentials ?? new InMemoryCredentialStore(),
|
|
278
|
-
modelsPath: machineModelsPath(),
|
|
279
|
-
modelsStore: options.catalogFile
|
|
280
|
-
? catalogFileStore(options.catalogFile)
|
|
281
|
-
: await layeredCatalog([globalCatalogPath()]),
|
|
282
|
-
allowModelNetwork: false,
|
|
283
|
-
...(options.catalogBaseUrl ? { catalogBaseUrl: options.catalogBaseUrl } : {}),
|
|
284
|
-
});
|
|
285
|
-
const error = runtime.getError();
|
|
286
|
-
if (error)
|
|
287
|
-
throw new Error(error);
|
|
288
|
-
// A machine catalog refresh may refresh an OAuth token, which must carry the account's catalog over.
|
|
289
|
-
await withModelRegistration(runtime, async () => registerAccountModels(runtime));
|
|
290
|
-
return runtime;
|
|
291
|
-
}
|
|
292
|
-
/** How long a catalog refresh may take, as `pi update --models` allows. */
|
|
293
|
-
const CATALOG_REFRESH_TIMEOUT_MS = 15_000;
|
|
294
|
-
/**
|
|
295
|
-
* Fetch the model catalog of every provider the runtime can authenticate into `catalogFile` ({@link
|
|
296
|
-
* catalogFileStore}): the refresh `pi update --models` runs. `build` makes the runtime over that file, and runs only
|
|
297
|
-
* once the refresh is known to go ahead. A refusal writes nothing: the store creates the file only when pi writes an
|
|
298
|
-
* entry. pi asks pi.dev only for a provider with a usable credential, and may refresh an expired OAuth token of the
|
|
299
|
-
* runtime's store to get one. Rejects, naming each provider that failed, when any part fails, when it outlasts 15
|
|
300
|
-
* seconds, when `PI_OFFLINE` is set, when the catalog file is corrupt, and when no provider has a usable credential (the
|
|
301
|
-
* refresh would ask for nothing).
|
|
302
|
-
*/
|
|
303
|
-
export async function refreshCatalog(catalogFile, build, options = {}) {
|
|
304
|
-
// pi skips its own background refresh under PI_OFFLINE, but an explicit `allowNetwork: true` overrides that.
|
|
305
|
-
if (process.env.PI_OFFLINE !== undefined)
|
|
306
|
-
throw new Error("PI_OFFLINE is set, so the model catalog is not refreshed");
|
|
307
|
-
// A corrupt file fails here, once, by its own message: past this point every provider's refresh reads the store and
|
|
308
|
-
// would repeat it, worded as that provider's failure.
|
|
309
|
-
await readCatalog(catalogFile);
|
|
310
|
-
const runtime = await build(catalogFile);
|
|
311
|
-
// pi skips a provider it cannot authenticate without recording anything, so with no usable credential at all the
|
|
312
|
-
// refresh would "succeed" having asked for nothing.
|
|
313
|
-
const refreshable = runtime.getProviders().filter((provider) => provider.refreshModels !== undefined);
|
|
314
|
-
const usable = await Promise.all(refreshable.map(async (provider) => (await runtime.checkAuth(provider.id)) !== undefined));
|
|
315
|
-
if (!usable.includes(true)) {
|
|
316
|
-
throw new Error("no provider has a usable credential here, so there is no model catalog to fetch — log in " +
|
|
317
|
-
"(`fastagent login`) or set a provider's API key first");
|
|
318
|
-
}
|
|
319
|
-
const timeout = AbortSignal.timeout(CATALOG_REFRESH_TIMEOUT_MS);
|
|
320
|
-
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
321
|
-
const result = await runtime.refresh({ allowNetwork: true, force: true, signal });
|
|
322
|
-
if (result.aborted) {
|
|
323
|
-
throw new Error(options.signal?.aborted
|
|
324
|
-
? "model catalog refresh cancelled"
|
|
325
|
-
: `model catalog refresh did not finish within ${CATALOG_REFRESH_TIMEOUT_MS / 1000}s`);
|
|
326
|
-
}
|
|
327
|
-
if (result.errors.size > 0) {
|
|
328
|
-
const details = [...result.errors].map(([provider, error]) => `${provider}: ${error.message}`).join("; ");
|
|
329
|
-
throw new Error(`could not refresh the model catalog: ${details}`);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* What satisfies a provider from `env` alone (its API-key variable, or an ambient source such as an AWS profile), by
|
|
334
|
-
* pi's own check, or undefined. Passed explicitly rather than read from `process.env`: only the environment a SHELL
|
|
335
|
-
* hands down is shared by every agent on the machine, and a process that has loaded an agent's `.secrets/.env` holds
|
|
336
|
-
* that agent's variables too. What the shell shares outranks the global credentials file (agent-models.ts),
|
|
337
|
-
* so a global login for such a provider is not used.
|
|
338
|
-
*
|
|
339
|
-
* `fileExists` answers for the files a source points at (an AWS profile, Google ADC): this machine's by default, which
|
|
340
|
-
* is right wherever the answer is about THIS machine. A deployment passes one that finds nothing, because no file of
|
|
341
|
-
* the builder's travels with it.
|
|
342
|
-
*/
|
|
343
|
-
export async function environmentAuthSource(providerId, env, fileExists = defaultProviderAuthContext().fileExists) {
|
|
344
|
-
const models = builtinModels({
|
|
345
|
-
credentials: new InMemoryCredentialStore(),
|
|
346
|
-
authContext: {
|
|
347
|
-
// pi's own reading of a variable (a blank one is unset), over the given environment.
|
|
348
|
-
env: async (name) => (env[name]?.trim() ? env[name] : undefined),
|
|
349
|
-
fileExists,
|
|
350
|
-
},
|
|
351
|
-
});
|
|
352
|
-
return (await models.checkAuth(providerId))?.source;
|
|
353
|
-
}
|
|
354
|
-
/** Pi registration starts background refreshes; wait for all of them before publishing this fresh runtime. */
|
|
355
|
-
export async function withModelRegistration(runtime, register) {
|
|
356
|
-
const refresh = runtime.refresh;
|
|
357
|
-
const refreshes = [];
|
|
358
|
-
runtime.refresh = (...args) => {
|
|
359
|
-
const pending = refresh.apply(runtime, args);
|
|
360
|
-
refreshes.push(pending);
|
|
361
|
-
return pending;
|
|
362
|
-
};
|
|
363
|
-
try {
|
|
364
|
-
const result = await register();
|
|
365
|
-
await Promise.all(refreshes);
|
|
366
|
-
return result;
|
|
367
|
-
}
|
|
368
|
-
finally {
|
|
369
|
-
runtime.refresh = refresh;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* A registry over the given credential store. What an agent runs on is built through `agentModels`
|
|
374
|
-
* (agent-models.ts); this is for a registry over some OTHER store: none at all (`models`, a deploy check of what
|
|
375
|
-
* ships), a trial key under test (login), or the catalog file a refresh writes.
|
|
376
|
-
*/
|
|
377
|
-
export async function createPiModelRuntime(options) {
|
|
378
|
-
const { models, create } = options.files ?? (await modelRuntimeFiles(options));
|
|
379
|
-
const runtime = await ModelRuntime.create({ credentials: options.credentials, ...(await create()) });
|
|
380
|
-
// A malformed models.json does NOT throw upstream — `create` resolves with the built-ins and parks the reason in
|
|
381
|
-
// getError().
|
|
382
|
-
const error = runtime.getError();
|
|
383
|
-
if (error) {
|
|
384
|
-
const origin = models?.merged
|
|
385
|
-
? `\n\nThat file merges ${models.merged.machine} with ${models.merged.definition} (the agent's own wins a provider id).`
|
|
386
|
-
: models?.snapshotOf
|
|
387
|
-
? `\n\nThat file is ${models.snapshotOf} as read at startup.`
|
|
388
|
-
: "";
|
|
389
|
-
throw new Error(`${error}${origin}`);
|
|
390
|
-
}
|
|
391
|
-
await withModelRegistration(runtime, async () => {
|
|
392
|
-
registerAccountModels(runtime);
|
|
393
|
-
for (const provider of options.providers ?? [])
|
|
394
|
-
runtime.registerNativeProvider(provider);
|
|
395
|
-
});
|
|
396
|
-
return runtime;
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* EVERY provider whose `models.json` entry writes its key as a LITERAL rather than a `"$NAME"` reference or a
|
|
400
|
-
* `"!cmd"`. The file ships inside the image, so a literal there is a credential in a readable layer.
|
|
401
|
-
*
|
|
402
|
-
* Read from the FILE, not from `getProviderAuthStatus`: that answers "what satisfies this provider right now" and
|
|
403
|
-
* returns `stored` first, so a provider that has both an `auth.json` entry and a literal in the file would report
|
|
404
|
-
* `stored` and the literal would go unreported. The question is what the definition DECLARES, and only the file
|
|
405
|
-
* answers it. The three-way split mirrors pi's `configuredRequestAuthStatus`, which is not exported.
|
|
406
|
-
*
|
|
407
|
-
* The caller WARNS on this; it does not refuse. Whether a given string is a credential is the author's knowledge,
|
|
408
|
-
* not the framework's — pi's own docs prescribe `"apiKey": "ollama"` for a keyless local server, and no static rule
|
|
409
|
-
* separates that from a leaked key (an endpoint's reachability is not decidable from its URL either). FastAgent
|
|
410
|
-
* gates what IT causes; what the author wrote into their own committed file, it reports.
|
|
411
|
-
*/
|
|
412
|
-
export async function literalKeyProviders(agentDir) {
|
|
413
|
-
return Object.entries(await definitionApiKeys(agentDir))
|
|
414
|
-
.filter(([, apiKey]) => isLiteralKey(apiKey))
|
|
415
|
-
.map(([id]) => id);
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* How the agent's OWN models.json supplies `providerId`'s key, read from the file for the reason
|
|
419
|
-
* {@link literalKeyProviders} gives: `reference` is the variable a `"$NAME"` reads (the value file must hold it),
|
|
420
|
-
* `inFile` a key that travels with the definition itself (a literal, or a `"!command"` run on the box), and undefined
|
|
421
|
-
* no apiKey there at all.
|
|
422
|
-
*/
|
|
423
|
-
export async function definitionKeyOf(agentDir, providerId) {
|
|
424
|
-
const apiKey = (await definitionApiKeys(agentDir))[providerId];
|
|
425
|
-
if (typeof apiKey !== "string" || apiKey === "")
|
|
426
|
-
return undefined;
|
|
427
|
-
if (apiKey.startsWith("!") || isLiteralKey(apiKey))
|
|
428
|
-
return { inFile: true };
|
|
429
|
-
// Not a literal, so isLiteralKey's own pattern matched: there is a name to read.
|
|
430
|
-
return { reference: apiKey.replaceAll("$$", "").match(/\$\{?([A-Za-z_][A-Za-z0-9_]*)/)?.[1] };
|
|
431
|
-
}
|
|
432
|
-
/** Each provider's `apiKey` as the agent's own models.json writes it. */
|
|
433
|
-
async function definitionApiKeys(agentDir) {
|
|
434
|
-
const file = join(agentDir, AGENT_MODELS_FILE);
|
|
435
|
-
let raw;
|
|
436
|
-
try {
|
|
437
|
-
raw = await readFile(file, "utf8");
|
|
438
|
-
}
|
|
439
|
-
catch (error) {
|
|
440
|
-
// No custom endpoints is the normal case; anything else (unreadable, a directory) is the caller's problem.
|
|
441
|
-
if (error.code === "ENOENT")
|
|
442
|
-
return {};
|
|
443
|
-
throw error;
|
|
444
|
-
}
|
|
445
|
-
// Malformed JSON already threw out of createPiModelRuntime before this runs, so a parse failure here would be a
|
|
446
|
-
// genuine surprise and must not be swallowed.
|
|
447
|
-
const providers = JSON.parse(raw).providers ?? {};
|
|
448
|
-
return Object.fromEntries(Object.entries(providers).map(([id, provider]) => [id, provider?.apiKey]));
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* `!cmd` runs on the box; `$NAME` / `${NAME}` reads the environment; anything else is the key itself.
|
|
452
|
-
*
|
|
453
|
-
* `$$` is pi's escape for a literal `$`, so it is removed BEFORE looking for a reference — otherwise `"sk$$abc"`
|
|
454
|
-
* (which resolves to the literal `sk$abc`) would read as a reference and go unmentioned.
|
|
455
|
-
*/
|
|
456
|
-
function isLiteralKey(apiKey) {
|
|
457
|
-
if (typeof apiKey !== "string" || apiKey === "" || apiKey.startsWith("!"))
|
|
458
|
-
return false;
|
|
459
|
-
return !/\$\{?[A-Za-z_]/.test(apiKey.replaceAll("$$", ""));
|
|
460
|
-
}
|
|
461
|
-
export function interactiveLoginKind(p) {
|
|
462
|
-
if (interactiveAuth(p, "oauth"))
|
|
463
|
-
return "oauth";
|
|
464
|
-
return interactiveAuth(p, "api_key") ? "api_key" : "none";
|
|
465
|
-
}
|
|
466
|
-
/** THE rule for "can this method be signed in to interactively": the auth to run for it, or undefined. */
|
|
467
|
-
export function interactiveAuth(provider, method) {
|
|
468
|
-
return method === "oauth" ? provider.auth.oauth : provider.auth.apiKey?.login ? provider.auth.apiKey : undefined;
|
|
469
|
-
}
|
|
470
|
-
/** The providers a sign-in can target: pi's built-ins plus `extra`, composed exactly as {@link piModelsOver} does. */
|
|
471
|
-
export function loginProviders(extra) {
|
|
472
|
-
return piModelsOver(new InMemoryCredentialStore(), extra).getProviders();
|
|
473
|
-
}
|
|
474
|
-
/** Probe every provider's auth once (auth is provider-scoped, so any of its models works as the probe). */
|
|
475
|
-
export async function providerAuthStatuses(models) {
|
|
476
|
-
const statuses = new Map();
|
|
477
|
-
for (const provider of models.getProviders()) {
|
|
478
|
-
const [probe] = provider.getModels();
|
|
479
|
-
if (!probe)
|
|
480
|
-
continue;
|
|
481
|
-
const login = interactiveLoginKind(provider);
|
|
482
|
-
try {
|
|
483
|
-
const auth = await models.getAuth(probe);
|
|
484
|
-
statuses.set(provider.id, auth ? { state: "ready", source: auth.source } : { state: "unconfigured", login });
|
|
485
|
-
}
|
|
486
|
-
catch (error) {
|
|
487
|
-
statuses.set(provider.id, { state: "broken", message: error.message, login });
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return statuses;
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Which source currently satisfies auth for `spec`, or undefined when none does (or the spec is not in `models`).
|
|
494
|
-
* Rejects when resolving fails — a corrupt entry, a refresh the provider refused — which is a different answer from
|
|
495
|
-
* "nothing configured" and must reach whoever reports it.
|
|
496
|
-
*/
|
|
497
|
-
export async function probeAuthSource(models, spec) {
|
|
498
|
-
const slash = spec.indexOf("/");
|
|
499
|
-
if (slash < 1)
|
|
500
|
-
return undefined;
|
|
501
|
-
const model = models.getModel(spec.slice(0, slash), spec.slice(slash + 1));
|
|
502
|
-
if (!model)
|
|
503
|
-
return undefined;
|
|
504
|
-
// A virtual selection authenticates the physical model after routing, not its catalog entry.
|
|
505
|
-
if (model.api === "pi-virtual")
|
|
506
|
-
return "virtual";
|
|
507
|
-
return (await models.getAuth(model))?.source;
|
|
508
|
-
}
|
|
509
|
-
/** Quick-fail probe for a just-stored API key. */
|
|
510
|
-
export async function probeApiKey(models, model, signal) {
|
|
511
|
-
let status;
|
|
512
|
-
let reply;
|
|
513
|
-
try {
|
|
514
|
-
reply = await models.complete(model, { messages: [{ role: "user", content: "ping", timestamp: Date.now() }] }, {
|
|
515
|
-
maxTokens: 16,
|
|
516
|
-
timeoutMs: 15_000,
|
|
517
|
-
maxRetries: 0,
|
|
518
|
-
...(signal ? { signal } : {}),
|
|
519
|
-
onResponse: (r) => {
|
|
520
|
-
status = r.status;
|
|
521
|
-
},
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
catch (error) {
|
|
525
|
-
// Thrown = before/around the request (auth resolution, transport setup) — not a provider verdict.
|
|
526
|
-
return { state: "unknown", message: error.message };
|
|
527
|
-
}
|
|
528
|
-
if (reply.stopReason !== "error" && reply.stopReason !== "aborted")
|
|
529
|
-
return { state: "ok" };
|
|
530
|
-
const message = reply.errorMessage ?? `stopReason "${reply.stopReason}"`;
|
|
531
|
-
const unauthorized = status === 401 || (status === undefined && /(^|\D)401(\D|$)/.test(message));
|
|
532
|
-
return { state: unauthorized ? "rejected" : "unknown", message };
|
|
533
|
-
}
|