@fastagent-sh/fastagent 0.24.4 → 0.26.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 +54 -82
- package/dist/agent-api.d.ts +460 -0
- package/dist/agent-api.js +209 -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 +81 -0
- package/dist/authoring.js +245 -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 +116 -64
- package/dist/cli/auth-view.d.ts +10 -3
- package/dist/cli/auth-view.js +8 -6
- package/dist/cli/box-login.d.ts +18 -7
- package/dist/cli/box-login.js +46 -12
- package/dist/cli/commands/add.d.ts +15 -4
- package/dist/cli/commands/add.js +253 -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 +191 -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 +132 -16
- package/dist/cli/commands/login.d.ts +31 -2
- package/dist/cli/commands/login.js +416 -65
- 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 +6 -4
- package/dist/cli/login-relay.js +19 -7
- 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 +70 -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 +220 -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 +223 -202
- 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 +52 -27
- package/dist/effect-port.d.ts +4 -4
- package/dist/effect-port.js +4 -4
- package/dist/env.d.ts +0 -13
- package/dist/env.js +1 -44
- 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 +51 -0
- package/dist/environment/mise.js +272 -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 +48 -0
- package/dist/harnesses/models/environment.js +88 -0
- package/dist/harnesses/models/host.d.ts +30 -0
- package/dist/harnesses/models/host.js +97 -0
- package/dist/{engines/pi → harnesses/models}/login.d.ts +66 -70
- package/dist/harnesses/models/login.js +439 -0
- package/dist/harnesses/models/models.d.ts +84 -0
- package/dist/harnesses/models/models.js +197 -0
- package/dist/harnesses/pi-durable/agent.d.ts +42 -0
- package/dist/harnesses/pi-durable/agent.js +581 -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 +95 -0
- package/dist/harnesses/pi-durable/open.js +330 -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 +90 -0
- package/dist/harnesses/pi-durable/serve.js +186 -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 +11 -0
- package/dist/harnesses/pi-durable/wake.js +90 -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 +19 -4
- package/dist/index.js +13 -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/sign-in-api.d.ts +147 -0
- package/dist/sign-in-api.js +9 -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 +14 -5
- package/dist/value-file.d.ts +40 -0
- package/dist/value-file.js +164 -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/login.js +0 -331
- 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,44 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Signing in to a MODEL PROVIDER, for any front end: {@link
|
|
2
|
+
* Signing in to a MODEL PROVIDER, for any front end: {@link loginOver} runs one provider's flow over pi-ai's own
|
|
3
3
|
* `AuthInteraction` and persists the credential through the same {@link fastagentCredentialStore} the runtime reads
|
|
4
|
-
* (one writer, one lock/corruption semantics); {@link
|
|
5
|
-
*
|
|
4
|
+
* (one writer, one lock/corruption semantics); {@link loginOptionsOver} is the list a picker offers. An embedding host
|
|
5
|
+
* reaches both through host.ts (`signIn`, `signInOptions`); `fastagent login` is a front end of its own
|
|
6
|
+
* ({@link loginFlow}: terminal menus, then {@link loginOver}); `login --deployment` drives the same flow on a deployed
|
|
7
|
+
* box over a relay (cli/login-relay.ts).
|
|
6
8
|
*/
|
|
7
9
|
import { type AuthInteraction, type CredentialStore, type Provider } from "@earendil-works/pi-ai";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
export declare class
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
provider: string;
|
|
15
|
-
method: LoginMethod;
|
|
16
|
-
/** The method's own name, e.g. "Anthropic (Claude Pro/Max)" or "Anthropic API key". */
|
|
17
|
-
label: string;
|
|
18
|
-
/** An OAuth login backed by a provider subscription. */
|
|
19
|
-
subscription: boolean;
|
|
20
|
-
/**
|
|
21
|
-
* What the store holds for this provider now. It keeps ONE credential per provider, so signing in with the other
|
|
22
|
-
* method replaces it — worth a warning in a client.
|
|
23
|
-
*/
|
|
24
|
-
stored?: LoginMethod;
|
|
10
|
+
import { type SignInMethod, type SignInOption, type SignInResult } from "../../sign-in-api.ts";
|
|
11
|
+
/** A sign-in that cannot start as asked: an unknown provider, or a choice a caller without menus did not make. */
|
|
12
|
+
export declare class LoginRefused extends Error {
|
|
13
|
+
/** The sign-ins it could be instead, for a caller to choose from. */
|
|
14
|
+
readonly choices: readonly SignInOption[];
|
|
15
|
+
constructor(message: string, choices: readonly SignInOption[]);
|
|
25
16
|
}
|
|
26
17
|
/**
|
|
27
|
-
* Every
|
|
28
|
-
*
|
|
18
|
+
* Every sign-in of pi's built-in providers plus `providers` (a same id replaces one), sorted by name as pi's `/login`
|
|
19
|
+
* lists them, with what authenticates each one here: a credential `credentials` (a credentials file, or a store) holds,
|
|
20
|
+
* else this process's environment, which a stored credential outranks.
|
|
29
21
|
*/
|
|
30
|
-
export declare function
|
|
31
|
-
/**
|
|
32
|
-
export declare function
|
|
22
|
+
export declare function loginOptionsOver(credentials: string | CredentialStore, providers?: readonly Provider[]): Promise<SignInOption[]>;
|
|
23
|
+
/** An option's status as pi's picker writes it after the provider's name. */
|
|
24
|
+
export declare function loginStatusText(option: SignInOption): string;
|
|
33
25
|
export type LoginRequest = {
|
|
34
26
|
/**
|
|
35
|
-
* Stable installation identity (Sign in with ChatGPT requires one). With `authPath` it defaults to
|
|
36
|
-
* beside that file
|
|
37
|
-
*
|
|
27
|
+
* Stable installation identity (Sign in with ChatGPT requires one). With `authPath` it defaults to `device.json`
|
|
28
|
+
* beside that file, written before the flow runs; over a caller's `credentialStore` there is no file to keep it
|
|
29
|
+
* beside, so a flow that needs one fails unless this supplies it.
|
|
38
30
|
*/
|
|
39
31
|
getDeviceId?: () => string;
|
|
40
32
|
provider: string;
|
|
41
|
-
method:
|
|
33
|
+
method: SignInMethod;
|
|
42
34
|
/** pi-ai's own interaction: prompts (`text`, `secret`, `select`, `manual_code`) and events (`auth_url`, …). */
|
|
43
35
|
interaction: AuthInteraction;
|
|
44
36
|
} & ({
|
|
@@ -50,15 +42,23 @@ export type LoginRequest = {
|
|
|
50
42
|
credentialStore: CredentialStore;
|
|
51
43
|
authPath?: never;
|
|
52
44
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
/** What the CLI adds to a sign-in. */
|
|
46
|
+
export interface LoginInternals {
|
|
47
|
+
/**
|
|
48
|
+
* The definition's providers, added to pi's built-ins (a same id replaces one): a key is verified against the
|
|
49
|
+
* endpoint its turns will reach, which a provider in code can point at a gateway.
|
|
50
|
+
*/
|
|
51
|
+
providers?: readonly Provider[];
|
|
52
|
+
/**
|
|
53
|
+
* A model spec to verify an entered key with, when the registry has it as one of this provider's models: the
|
|
54
|
+
* first-run picker's choice, the request the agent is about to make. Otherwise pi's default model for the provider
|
|
55
|
+
* is used, then its first. On one registry, any of a provider's models answers the 401 question.
|
|
56
|
+
*/
|
|
57
|
+
verifyWith?: string;
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
* Sign in to one of pi's built-in providers and persist the credential to `authPath`, or to
|
|
61
|
-
* `credentialStore`.
|
|
60
|
+
* Sign in to one of pi's built-in providers (plus the definition's) and persist the credential to `authPath`, or to
|
|
61
|
+
* the caller's `credentialStore`.
|
|
62
62
|
*
|
|
63
63
|
* The store is checked FIRST (a no-op write runs the file store's refuse-corrupt and writability checks, or whatever
|
|
64
64
|
* the caller's store does on a write), so a flow never runs toward a credential that could not be saved. An entered API key is verified with one minimal request to pi's
|
|
@@ -66,32 +66,13 @@ export interface LoginResult {
|
|
|
66
66
|
* written. Credentials are provider-scoped, so any of the provider's models answers the question the check asks (does
|
|
67
67
|
* the provider refuse the key, HTTP 401); a key the provider rejects is never stored, and the provider's key flow runs
|
|
68
68
|
* again (the provider and method were not the mistake). Progress and outcome go out through `interaction.notify`.
|
|
69
|
-
* A provider an agent's models.json points elsewhere is not this call's: store that key with `fastagentCredentialStore`.
|
|
70
69
|
*
|
|
71
|
-
* Aborting `interaction.signal` at any point, the verification included, rejects with {@link
|
|
72
|
-
* writes no credential (an OAuth device ID, the installation's identity, is kept once created). Every prompt carries a
|
|
73
|
-
* beating a `manual_code` prompt), and when the
|
|
70
|
+
* Aborting `interaction.signal` at any point, the verification included, rejects with {@link SignInCancelled} and
|
|
71
|
+
* writes no credential (an OAuth device ID, the installation's identity, is kept once created). Every prompt carries a
|
|
72
|
+
* signal that aborts on it, on the prompt's own signal (a callback server beating a `manual_code` prompt), and when the
|
|
73
|
+
* flow settles with a prompt still pending.
|
|
74
74
|
*/
|
|
75
|
-
export declare function
|
|
76
|
-
/** Not public: what the CLI and the tests add to {@link login}. */
|
|
77
|
-
export interface LoginInternals {
|
|
78
|
-
/** Providers added to pi's built-ins (a same id replaces one): a test seam. */
|
|
79
|
-
providers?: readonly Provider[];
|
|
80
|
-
/**
|
|
81
|
-
* The agent the key is for. Verification then runs on THAT agent's registry (its models.json and the machine's over
|
|
82
|
-
* the built-ins), so the check reaches the endpoint its turns will: a models.json can point a built-in provider at a
|
|
83
|
-
* gateway, where the provider's own endpoint would refuse the gateway's key and loop the person through re-entry.
|
|
84
|
-
*/
|
|
85
|
-
agentDir?: string;
|
|
86
|
-
/**
|
|
87
|
-
* A model spec to verify an entered key with, when the registry has it as one of this provider's models: the
|
|
88
|
-
* first-run picker's choice, the request the agent is about to make. Otherwise pi's default model for the provider
|
|
89
|
-
* is used, then its first. On one registry, any of a provider's models answers the 401 question.
|
|
90
|
-
*/
|
|
91
|
-
verifyWith?: string;
|
|
92
|
-
}
|
|
93
|
-
/** {@link login}, plus {@link LoginInternals}. */
|
|
94
|
-
export declare function loginOver(request: LoginRequest, internals?: LoginInternals): Promise<LoginResult>;
|
|
75
|
+
export declare function loginOver(request: LoginRequest, internals?: LoginInternals): Promise<SignInResult>;
|
|
95
76
|
/**
|
|
96
77
|
* pi's default model per provider (`defaultModelPerProvider` in pi-coding-agent's model-resolver): the general-purpose
|
|
97
78
|
* model pi itself starts on, so the likeliest to answer a minimal request. COPIED, because pi does not export it;
|
|
@@ -108,28 +89,43 @@ export interface IoOption {
|
|
|
108
89
|
export interface LoginIO {
|
|
109
90
|
/** Single-choice picker. */
|
|
110
91
|
select(message: string, options: IoOption[]): Promise<string | undefined>;
|
|
111
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Free-text or hidden input. `redirect`: what is asked is the address the browser ended on after signing in, or the
|
|
94
|
+
* code its page showed (pi-ai's `manual_code`).
|
|
95
|
+
*/
|
|
112
96
|
prompt(message: string, opts?: {
|
|
113
97
|
hidden?: boolean;
|
|
98
|
+
redirect?: boolean;
|
|
114
99
|
signal?: AbortSignal;
|
|
115
100
|
}): Promise<string | undefined>;
|
|
116
101
|
/** Print an informational line (auth URL, device code, progress). */
|
|
117
102
|
note(message: string): void;
|
|
118
|
-
/** Best-effort open a URL in the browser (printed regardless). */
|
|
119
|
-
openUrl(url: string): void;
|
|
103
|
+
/** Best-effort open a URL in the browser (printed regardless); `userCode` is what a device sign-in enters there. */
|
|
104
|
+
openUrl(url: string, userCode?: string): void;
|
|
120
105
|
}
|
|
121
106
|
/**
|
|
122
|
-
* `fastagent login
|
|
123
|
-
*
|
|
107
|
+
* `fastagent login` in a terminal: ask only what was not given, as pi's `/login` asks it (how to sign in, then the
|
|
108
|
+
* provider; a provider named by its id or its name, then how, when it offers both), then {@link loginOver}.
|
|
124
109
|
*/
|
|
125
110
|
export declare function loginFlow(io: LoginIO, options: {
|
|
126
111
|
authPath: string;
|
|
127
112
|
provider?: string;
|
|
128
|
-
method?:
|
|
129
|
-
providers?: Provider[];
|
|
113
|
+
method?: SignInMethod;
|
|
114
|
+
providers?: readonly Provider[];
|
|
130
115
|
signal?: AbortSignal;
|
|
116
|
+
/**
|
|
117
|
+
* Where the menus read what authenticates each provider now: the credentials the agent reads (its file over the
|
|
118
|
+
* global one), which can hold more than the file a sign-in writes. Default that file.
|
|
119
|
+
*/
|
|
120
|
+
statusFrom?: CredentialStore;
|
|
131
121
|
/** {@link LoginInternals.verifyWith}. */
|
|
132
122
|
verifyWith?: string;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
}): Promise<SignInResult>;
|
|
124
|
+
/**
|
|
125
|
+
* The sign-in `given` names, or, with `menus`, the one a person picks; without them a choice not given refuses
|
|
126
|
+
* ({@link LoginRefused}) naming how to give it.
|
|
127
|
+
*/
|
|
128
|
+
export declare function chooseLogin(offered: readonly SignInOption[], given: {
|
|
129
|
+
provider?: string;
|
|
130
|
+
method?: SignInMethod;
|
|
131
|
+
}, menus: LoginIO | undefined): Promise<SignInOption>;
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signing in to a MODEL PROVIDER, for any front end: {@link loginOver} runs one provider's flow over pi-ai's own
|
|
3
|
+
* `AuthInteraction` and persists the credential through the same {@link fastagentCredentialStore} the runtime reads
|
|
4
|
+
* (one writer, one lock/corruption semantics); {@link loginOptionsOver} is the list a picker offers. An embedding host
|
|
5
|
+
* reaches both through host.ts (`signIn`, `signInOptions`); `fastagent login` is a front end of its own
|
|
6
|
+
* ({@link loginFlow}: terminal menus, then {@link loginOver}); `login --deployment` drives the same flow on a deployed
|
|
7
|
+
* box over a relay (cli/login-relay.ts).
|
|
8
|
+
*/
|
|
9
|
+
import { InMemoryCredentialStore, } from "@earendil-works/pi-ai";
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { writeFileAtomic } from "../../atomic-write.js";
|
|
14
|
+
import { SignInCancelled } from "../../sign-in-api.js";
|
|
15
|
+
import { assertOneCredentialSource, fastagentCredentialStore } from "./credentials.js";
|
|
16
|
+
import { UNNAMED_AUTH_SOURCE, interactiveAuth, loginProviders, modelsOver, probeApiKey } from "./models.js";
|
|
17
|
+
/** A sign-in that cannot start as asked: an unknown provider, or a choice a caller without menus did not make. */
|
|
18
|
+
export class LoginRefused extends Error {
|
|
19
|
+
/** The sign-ins it could be instead, for a caller to choose from. */
|
|
20
|
+
choices;
|
|
21
|
+
constructor(message, choices) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.choices = choices;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const ACCOUNT_LABEL = "Sign in with an account";
|
|
27
|
+
const API_KEY_LABEL = "Sign in with an API key";
|
|
28
|
+
/** A credentials file path names fastagent's store over it; a store is used as given. */
|
|
29
|
+
const storeOf = (credentials) => typeof credentials === "string" ? fastagentCredentialStore(credentials) : credentials;
|
|
30
|
+
/**
|
|
31
|
+
* Every sign-in of pi's built-in providers plus `providers` (a same id replaces one), sorted by name as pi's `/login`
|
|
32
|
+
* lists them, with what authenticates each one here: a credential `credentials` (a credentials file, or a store) holds,
|
|
33
|
+
* else this process's environment, which a stored credential outranks.
|
|
34
|
+
*/
|
|
35
|
+
export async function loginOptionsOver(credentials, providers) {
|
|
36
|
+
// ONE read of the store: `list` is metadata only, and reading per provider would repeat a corrupt file's warning for
|
|
37
|
+
// every provider.
|
|
38
|
+
const held = new Map((await storeOf(credentials).list()).map((info) => [info.providerId, info.type]));
|
|
39
|
+
// The environment alone, over no store: what authenticates a provider nothing is stored for.
|
|
40
|
+
const environment = modelsOver(new InMemoryCredentialStore(), providers);
|
|
41
|
+
const offered = [];
|
|
42
|
+
for (const provider of loginProviders(providers)) {
|
|
43
|
+
const methods = ["oauth", "api_key"].filter((method) => provider.auth[method === "oauth" ? "oauth" : "apiKey"]);
|
|
44
|
+
if (methods.length === 0)
|
|
45
|
+
continue;
|
|
46
|
+
const stored = held.get(provider.id);
|
|
47
|
+
let ambient;
|
|
48
|
+
let problem;
|
|
49
|
+
try {
|
|
50
|
+
const found = await environment.checkAuth(provider.id);
|
|
51
|
+
if (found !== undefined)
|
|
52
|
+
ambient = { type: found.type, source: found.source ?? UNNAMED_AUTH_SOURCE };
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
problem = error.message;
|
|
56
|
+
}
|
|
57
|
+
const status = stored === undefined ? ambient : { type: stored, source: stored === "oauth" ? "OAuth" : "stored credential" };
|
|
58
|
+
for (const method of methods) {
|
|
59
|
+
offered.push({
|
|
60
|
+
provider: provider.id,
|
|
61
|
+
name: provider.name,
|
|
62
|
+
method,
|
|
63
|
+
label: method === "oauth" ? (provider.auth.oauth?.loginLabel ?? ACCOUNT_LABEL) : API_KEY_LABEL,
|
|
64
|
+
subscription: method === "oauth" && provider.auth.oauth?.isSubscription === true,
|
|
65
|
+
external: interactiveAuth(provider, method) === undefined,
|
|
66
|
+
...(status ? { status } : {}),
|
|
67
|
+
...(ambient ? { environment: ambient.source } : {}),
|
|
68
|
+
...(problem ? { problem } : {}),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return offered.sort((a, b) => a.name.localeCompare(b.name));
|
|
73
|
+
}
|
|
74
|
+
/** What a credential of `type` is called beside a provider's sign-in, as pi's picker calls it. */
|
|
75
|
+
function credentialKind(type, subscription) {
|
|
76
|
+
if (type === "api_key")
|
|
77
|
+
return "API key";
|
|
78
|
+
return subscription ? "subscription" : "account";
|
|
79
|
+
}
|
|
80
|
+
/** An option's status as pi's picker writes it after the provider's name. */
|
|
81
|
+
export function loginStatusText(option) {
|
|
82
|
+
const { status } = option;
|
|
83
|
+
// A stored credential outranks the environment, so a failed check of the environment does not decide it.
|
|
84
|
+
if (status === undefined)
|
|
85
|
+
return option.problem === undefined ? "not configured" : `auth check failed: ${option.problem}`;
|
|
86
|
+
if (status.type !== option.method)
|
|
87
|
+
return `${credentialKind(status.type, option.subscription)} configured`;
|
|
88
|
+
if (status.source === "OAuth" || status.source === "stored credential")
|
|
89
|
+
return "✓ configured";
|
|
90
|
+
return /^[A-Z][A-Z0-9_]*(?:, [A-Z][A-Z0-9_]*)*$/.test(status.source)
|
|
91
|
+
? `✓ env: ${status.source}`
|
|
92
|
+
: `✓ ${status.source}`;
|
|
93
|
+
}
|
|
94
|
+
/** Combine present abort signals into one (no-op when none/one). */
|
|
95
|
+
function anySignal(...signals) {
|
|
96
|
+
const present = signals.filter((s) => s !== undefined);
|
|
97
|
+
return present.length === 0 ? undefined : present.length === 1 ? present[0] : AbortSignal.any(present);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Sign in to one of pi's built-in providers (plus the definition's) and persist the credential to `authPath`, or to
|
|
101
|
+
* the caller's `credentialStore`.
|
|
102
|
+
*
|
|
103
|
+
* The store is checked FIRST (a no-op write runs the file store's refuse-corrupt and writability checks, or whatever
|
|
104
|
+
* the caller's store does on a write), so a flow never runs toward a credential that could not be saved. An entered API key is verified with one minimal request to pi's
|
|
105
|
+
* default model for the provider (its first model when pi names none), at pi's built-in endpoint, BEFORE it is
|
|
106
|
+
* written. Credentials are provider-scoped, so any of the provider's models answers the question the check asks (does
|
|
107
|
+
* the provider refuse the key, HTTP 401); a key the provider rejects is never stored, and the provider's key flow runs
|
|
108
|
+
* again (the provider and method were not the mistake). Progress and outcome go out through `interaction.notify`.
|
|
109
|
+
*
|
|
110
|
+
* Aborting `interaction.signal` at any point, the verification included, rejects with {@link SignInCancelled} and
|
|
111
|
+
* writes no credential (an OAuth device ID, the installation's identity, is kept once created). Every prompt carries a
|
|
112
|
+
* signal that aborts on it, on the prompt's own signal (a callback server beating a `manual_code` prompt), and when the
|
|
113
|
+
* flow settles with a prompt still pending.
|
|
114
|
+
*/
|
|
115
|
+
export async function loginOver(request, internals = {}) {
|
|
116
|
+
const { providers, verifyWith } = internals;
|
|
117
|
+
const { method, interaction } = request;
|
|
118
|
+
assertOneCredentialSource(request);
|
|
119
|
+
if (request.credentialStore === undefined && typeof request.authPath !== "string") {
|
|
120
|
+
throw new Error("login needs authPath or credentialStore: where the sign-in is written");
|
|
121
|
+
}
|
|
122
|
+
const provider = loginProviders(providers).find((p) => p.id === request.provider);
|
|
123
|
+
if (!provider)
|
|
124
|
+
throw new Error(`unknown provider "${request.provider}"`);
|
|
125
|
+
const auth = interactiveAuth(provider, method);
|
|
126
|
+
if (!auth?.login)
|
|
127
|
+
throw new Error(`provider "${provider.id}" has no interactive ${method} login`);
|
|
128
|
+
const flow = auth.login.bind(auth);
|
|
129
|
+
// The key under test lives here, not in the file, until it passes.
|
|
130
|
+
const trial = new InMemoryCredentialStore();
|
|
131
|
+
// Only a key needs the registry: it is checked with one request before it is kept.
|
|
132
|
+
const models = method === "api_key" ? modelsOver(trial, providers) : undefined;
|
|
133
|
+
const store = request.credentialStore ?? fastagentCredentialStore(request.authPath);
|
|
134
|
+
await store.modify(provider.id, async () => undefined);
|
|
135
|
+
const signal = interaction.signal ?? new AbortController().signal;
|
|
136
|
+
const notify = (event) => interaction.notify(event);
|
|
137
|
+
let getDeviceId = request.getDeviceId;
|
|
138
|
+
if (method === "oauth" && !getDeviceId && request.authPath !== undefined) {
|
|
139
|
+
const deviceId = await deviceIdBeside(request.authPath);
|
|
140
|
+
getDeviceId = () => deviceId;
|
|
141
|
+
}
|
|
142
|
+
for (;;) {
|
|
143
|
+
const credential = await runFlow((io) => flow(io, { getDeviceId }), interaction, signal);
|
|
144
|
+
let verified = "n/a";
|
|
145
|
+
if (models) {
|
|
146
|
+
await trial.modify(provider.id, async () => credential);
|
|
147
|
+
const verdict = await verifyApiKey(models, provider.id, verifyWith, notify, signal);
|
|
148
|
+
if (signal.aborted)
|
|
149
|
+
throw new SignInCancelled("cancelled");
|
|
150
|
+
if (verdict === "rejected")
|
|
151
|
+
continue;
|
|
152
|
+
verified = verdict;
|
|
153
|
+
}
|
|
154
|
+
await store.modify(provider.id, async () => credential);
|
|
155
|
+
return { provider: provider.id, method, verified };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The installation's identity, kept beside the credentials file it signs in to: created once, then read. Settled before
|
|
160
|
+
* the flow runs, so a sign-in never completes with an identity that was not kept.
|
|
161
|
+
*/
|
|
162
|
+
async function deviceIdBeside(authPath) {
|
|
163
|
+
const path = join(dirname(authPath), "device.json");
|
|
164
|
+
try {
|
|
165
|
+
return await readOrCreateDeviceId(path);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
throw new Error(`could not persist login device ID in ${path}: ${error.message}`, { cause: error });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function readOrCreateDeviceId(path) {
|
|
172
|
+
let text;
|
|
173
|
+
try {
|
|
174
|
+
text = await readFile(path, "utf8");
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (error.code !== "ENOENT")
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
if (text !== undefined) {
|
|
181
|
+
const stored = JSON.parse(text).deviceId;
|
|
182
|
+
if (typeof stored !== "string" || stored === "")
|
|
183
|
+
throw new Error("it holds no deviceId");
|
|
184
|
+
return stored;
|
|
185
|
+
}
|
|
186
|
+
const deviceId = randomUUID();
|
|
187
|
+
await mkdir(dirname(path), { recursive: true });
|
|
188
|
+
writeFileAtomic(path, `${JSON.stringify({ deviceId })}\n`);
|
|
189
|
+
return deviceId;
|
|
190
|
+
}
|
|
191
|
+
async function runFlow(flow, interaction, signal) {
|
|
192
|
+
if (signal.aborted)
|
|
193
|
+
throw new SignInCancelled("cancelled");
|
|
194
|
+
const done = new AbortController();
|
|
195
|
+
let backedOut;
|
|
196
|
+
let credential;
|
|
197
|
+
try {
|
|
198
|
+
credential = await flow({
|
|
199
|
+
signal,
|
|
200
|
+
prompt: (prompt) => {
|
|
201
|
+
const asked = anySignal(prompt.signal, signal, done.signal);
|
|
202
|
+
return interaction.prompt({ ...prompt, signal: asked }).catch((error) => {
|
|
203
|
+
// Remembered here: a provider may wrap the rejection it is handed in an error of its own. Only a question
|
|
204
|
+
// still asked: one the flow withdrew (its callback came first) rejects too, and nobody backed out of it.
|
|
205
|
+
if (error instanceof SignInCancelled && !asked?.aborted)
|
|
206
|
+
backedOut = error;
|
|
207
|
+
throw error;
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
notify: (event) => interaction.notify(event),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
// Whatever the provider made of the abort (its own error, a rejected prompt), it was the caller's decision.
|
|
215
|
+
if (signal.aborted)
|
|
216
|
+
throw new SignInCancelled("cancelled");
|
|
217
|
+
if (backedOut !== undefined)
|
|
218
|
+
throw backedOut;
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
done.abort();
|
|
223
|
+
}
|
|
224
|
+
// A provider that ignored the abort and finished anyway: the caller still cancelled.
|
|
225
|
+
if (signal.aborted)
|
|
226
|
+
throw new SignInCancelled("cancelled");
|
|
227
|
+
return credential;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* pi's default model per provider (`defaultModelPerProvider` in pi-coding-agent's model-resolver): the general-purpose
|
|
231
|
+
* model pi itself starts on, so the likeliest to answer a minimal request. COPIED, because pi does not export it;
|
|
232
|
+
* `test/login.test.ts` compares this against pi's own table, so a pi release that changes a default fails there.
|
|
233
|
+
*/
|
|
234
|
+
export const PI_DEFAULT_MODELS = {
|
|
235
|
+
"amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
|
|
236
|
+
"ant-ling": "Ring-2.6-1T",
|
|
237
|
+
anthropic: "claude-opus-4-8",
|
|
238
|
+
openai: "gpt-5.5",
|
|
239
|
+
azure: "gpt-5.4",
|
|
240
|
+
"openai-codex": "gpt-6.1-sol",
|
|
241
|
+
radius: "balanced",
|
|
242
|
+
nvidia: "nvidia/nemotron-3-ultra-550b-a55b",
|
|
243
|
+
deepseek: "deepseek-v4-pro",
|
|
244
|
+
google: "gemini-3.1-pro-preview",
|
|
245
|
+
"google-vertex": "gemini-3.1-pro-preview",
|
|
246
|
+
"github-copilot": "gpt-5.4",
|
|
247
|
+
openrouter: "moonshotai/kimi-k2.6",
|
|
248
|
+
"vercel-ai-gateway": "zai/glm-5.1",
|
|
249
|
+
xai: "grok-4.7",
|
|
250
|
+
groq: "openai/gpt-oss-120b",
|
|
251
|
+
cerebras: "gpt-oss-120b",
|
|
252
|
+
zai: "glm-5.3",
|
|
253
|
+
"zai-coding-cn": "glm-5.3",
|
|
254
|
+
mistral: "devstral-medium-latest",
|
|
255
|
+
minimax: "MiniMax-M2.7",
|
|
256
|
+
"minimax-cn": "MiniMax-M2.7",
|
|
257
|
+
moonshotai: "kimi-k2.6",
|
|
258
|
+
"moonshotai-cn": "kimi-k2.6",
|
|
259
|
+
huggingface: "moonshotai/Kimi-K2.6",
|
|
260
|
+
fireworks: "accounts/fireworks/models/kimi-k3",
|
|
261
|
+
together: "moonshotai/Kimi-K3",
|
|
262
|
+
baseten: "zai-org/GLM-5.2",
|
|
263
|
+
opencode: "kimi-k2.6",
|
|
264
|
+
"opencode-go": "kimi-k3",
|
|
265
|
+
"kimi-coding": "kimi-for-coding",
|
|
266
|
+
meta: "muse-spark-1.3",
|
|
267
|
+
"cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6",
|
|
268
|
+
"cloudflare-ai-gateway": "workers-ai/@cf/moonshotai/kimi-k2.6",
|
|
269
|
+
"qwen-token-plan": "qwen3.7-max",
|
|
270
|
+
"qwen-token-plan-cn": "qwen3.7-max",
|
|
271
|
+
"qwen-token-plan-individual": "qwen3.8-max",
|
|
272
|
+
xiaomi: "mimo-v2.5-pro",
|
|
273
|
+
"xiaomi-token-plan-cn": "mimo-v2.5-pro",
|
|
274
|
+
"xiaomi-token-plan-ams": "mimo-v2.5-pro",
|
|
275
|
+
"xiaomi-token-plan-sgp": "mimo-v2.5-pro",
|
|
276
|
+
};
|
|
277
|
+
/** One minimal request with the key under test. */
|
|
278
|
+
async function verifyApiKey(models, providerId, verifyWith, notify, signal) {
|
|
279
|
+
const chosen = verifyWith?.startsWith(`${providerId}/`)
|
|
280
|
+
? models.getModel(providerId, verifyWith.slice(providerId.length + 1))
|
|
281
|
+
: undefined;
|
|
282
|
+
const piDefault = PI_DEFAULT_MODELS[providerId];
|
|
283
|
+
// The provider's first model is the last resort only: catalogs are alphabetical, and a first model can be a
|
|
284
|
+
// special-purpose one that rejects a minimal request (which still answers the 401 question).
|
|
285
|
+
const model = chosen ??
|
|
286
|
+
(piDefault ? models.getModel(providerId, piDefault) : undefined) ??
|
|
287
|
+
models.getProvider(providerId)?.getModels()[0];
|
|
288
|
+
if (!model) {
|
|
289
|
+
notify({
|
|
290
|
+
type: "info",
|
|
291
|
+
message: `cannot verify the key: provider "${providerId}" lists no models — kept as entered`,
|
|
292
|
+
});
|
|
293
|
+
return "unknown";
|
|
294
|
+
}
|
|
295
|
+
const label = `${model.provider}/${model.id}`;
|
|
296
|
+
notify({ type: "progress", message: `verifying the key with ${label}…` });
|
|
297
|
+
const probe = await probeApiKey(models, model, signal);
|
|
298
|
+
if (signal.aborted)
|
|
299
|
+
return "unknown"; // the caller reports the cancel, not a verdict on the key
|
|
300
|
+
if (probe.state === "ok") {
|
|
301
|
+
notify({ type: "info", message: `key verified — ${label} responded` });
|
|
302
|
+
}
|
|
303
|
+
else if (probe.state === "rejected") {
|
|
304
|
+
notify({
|
|
305
|
+
type: "info",
|
|
306
|
+
message: `${providerId} rejected the API key (HTTP 401): ${probe.message} — enter it again (or cancel)`,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
notify({
|
|
311
|
+
type: "info",
|
|
312
|
+
message: `could not verify the key with ${label}: ${probe.message} — kept; invokes surface the provider's error`,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return probe.state;
|
|
316
|
+
}
|
|
317
|
+
/** {@link LoginIO} as pi-ai's `AuthInteraction`: what {@link loginOver} drives for the terminal. */
|
|
318
|
+
function terminalInteraction(io, signal) {
|
|
319
|
+
return {
|
|
320
|
+
...(signal ? { signal } : {}),
|
|
321
|
+
prompt: async (p) => {
|
|
322
|
+
// A select is UNCANCELLABLE here: `LoginIO.select` takes no signal.
|
|
323
|
+
const v = p.type === "select"
|
|
324
|
+
? await io.select(p.message, p.options.map((o) => ({
|
|
325
|
+
value: o.id,
|
|
326
|
+
label: o.label,
|
|
327
|
+
...(o.description ? { hint: o.description } : {}),
|
|
328
|
+
})))
|
|
329
|
+
: await io.prompt(p.message, {
|
|
330
|
+
hidden: p.type === "secret",
|
|
331
|
+
redirect: p.type === "manual_code",
|
|
332
|
+
...(p.signal ? { signal: p.signal } : {}),
|
|
333
|
+
});
|
|
334
|
+
if (v === undefined)
|
|
335
|
+
throw new SignInCancelled("cancelled");
|
|
336
|
+
return v;
|
|
337
|
+
},
|
|
338
|
+
notify: (e) => {
|
|
339
|
+
if (e.type === "auth_url") {
|
|
340
|
+
io.note(`Open this URL to authorize:\n ${e.url}`);
|
|
341
|
+
if (e.instructions)
|
|
342
|
+
io.note(e.instructions);
|
|
343
|
+
io.openUrl(e.url);
|
|
344
|
+
}
|
|
345
|
+
else if (e.type === "device_code") {
|
|
346
|
+
io.note(`Go to ${e.verificationUri} and enter the code: ${e.userCode}`);
|
|
347
|
+
io.openUrl(e.verificationUri, e.userCode);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
io.note(e.message);
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* `fastagent login` in a terminal: ask only what was not given, as pi's `/login` asks it (how to sign in, then the
|
|
357
|
+
* provider; a provider named by its id or its name, then how, when it offers both), then {@link loginOver}.
|
|
358
|
+
*/
|
|
359
|
+
export async function loginFlow(io, options) {
|
|
360
|
+
const offered = await loginOptionsOver(options.statusFrom ?? options.authPath, options.providers);
|
|
361
|
+
const choice = await chooseLogin(offered, options, io);
|
|
362
|
+
if (choice.external) {
|
|
363
|
+
throw new LoginRefused(`${choice.name} is configured outside fastagent: set its key in the environment`, []);
|
|
364
|
+
}
|
|
365
|
+
return loginOver({
|
|
366
|
+
provider: choice.provider,
|
|
367
|
+
method: choice.method,
|
|
368
|
+
authPath: options.authPath,
|
|
369
|
+
interaction: terminalInteraction(io, options.signal),
|
|
370
|
+
}, {
|
|
371
|
+
...(options.providers ? { providers: options.providers } : {}),
|
|
372
|
+
...(options.verifyWith ? { verifyWith: options.verifyWith } : {}),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
/** The flag that gives a method, as a refusal names it. */
|
|
376
|
+
const METHOD_FLAGS = "--method account or --method api-key";
|
|
377
|
+
/**
|
|
378
|
+
* The sign-in `given` names, or, with `menus`, the one a person picks; without them a choice not given refuses
|
|
379
|
+
* ({@link LoginRefused}) naming how to give it.
|
|
380
|
+
*/
|
|
381
|
+
export async function chooseLogin(offered, given, menus) {
|
|
382
|
+
const picked = async (message, options, from, by) => {
|
|
383
|
+
const value = await menus.select(message, options);
|
|
384
|
+
const option = from.find((o) => by(o) === value);
|
|
385
|
+
if (option === undefined)
|
|
386
|
+
throw new SignInCancelled("nothing selected");
|
|
387
|
+
return option;
|
|
388
|
+
};
|
|
389
|
+
if (given.provider !== undefined) {
|
|
390
|
+
const named = given.provider.trim().toLowerCase();
|
|
391
|
+
const ofProvider = offered.filter((o) => o.provider.toLowerCase() === named || o.name.toLowerCase() === named);
|
|
392
|
+
if (ofProvider.length === 0) {
|
|
393
|
+
throw new LoginRefused(`unknown provider "${given.provider}": ${providerIds(offered)}`, offered);
|
|
394
|
+
}
|
|
395
|
+
const name = ofProvider[0].name;
|
|
396
|
+
const ofMethod = given.method === undefined ? ofProvider : ofProvider.filter((o) => o.method === given.method);
|
|
397
|
+
if (ofMethod.length === 0) {
|
|
398
|
+
const only = ofProvider[0].method === "oauth" ? "an account" : "an API key";
|
|
399
|
+
throw new LoginRefused(`${name} signs in only with ${only}`, ofProvider);
|
|
400
|
+
}
|
|
401
|
+
if (ofMethod.length === 1)
|
|
402
|
+
return ofMethod[0];
|
|
403
|
+
if (menus === undefined)
|
|
404
|
+
throw new LoginRefused(`${name} signs in with an account or an API key: pass ${METHOD_FLAGS}`, ofMethod);
|
|
405
|
+
return picked(`Select authentication method for ${name}:`, ofMethod.map((o) => ({ value: o.method, label: o.label })), ofMethod, (o) => o.method);
|
|
406
|
+
}
|
|
407
|
+
if (menus === undefined) {
|
|
408
|
+
throw new LoginRefused(`name the provider to sign in to (${providerIds(offered)}), and ${METHOD_FLAGS} for one that offers both`, given.method === undefined ? offered : offered.filter((o) => o.method === given.method));
|
|
409
|
+
}
|
|
410
|
+
let method = given.method;
|
|
411
|
+
if (method === undefined) {
|
|
412
|
+
// pi offers its own gateway beside the two ways of signing in.
|
|
413
|
+
const radius = offered.find((o) => o.provider === "radius" && o.method === "oauth");
|
|
414
|
+
const ways = [
|
|
415
|
+
...(offered.some((o) => o.method === "oauth") ? [{ value: "oauth", label: ACCOUNT_LABEL }] : []),
|
|
416
|
+
...(offered.some((o) => o.method === "api_key") ? [{ value: "api_key", label: API_KEY_LABEL }] : []),
|
|
417
|
+
...(radius ? [{ value: "radius", label: `Sign in with ${radius.name}${statusSuffix(radius)}` }] : []),
|
|
418
|
+
];
|
|
419
|
+
const way = await menus.select("Select authentication method:", ways);
|
|
420
|
+
if (way === "radius" && radius)
|
|
421
|
+
return radius;
|
|
422
|
+
if (way !== "oauth" && way !== "api_key")
|
|
423
|
+
throw new SignInCancelled("no authentication method selected");
|
|
424
|
+
method = way;
|
|
425
|
+
}
|
|
426
|
+
const candidates = offered.filter((o) => o.method === method);
|
|
427
|
+
if (candidates.length === 0)
|
|
428
|
+
throw new LoginRefused(`no provider signs in with ${method === "oauth" ? "an account" : "an API key"}`, []);
|
|
429
|
+
return picked("Select provider to configure:", candidates.map((o) => ({ value: o.provider, label: `${o.name}${statusSuffix(o)}` })), candidates, (o) => o.provider);
|
|
430
|
+
}
|
|
431
|
+
/** An option's status after its name on every row, as pi's picker writes it: ` ✓ configured`, ` • not configured`. */
|
|
432
|
+
function statusSuffix(option) {
|
|
433
|
+
const text = loginStatusText(option);
|
|
434
|
+
return text.startsWith("✓") ? ` ${text}` : ` • ${text}`;
|
|
435
|
+
}
|
|
436
|
+
/** Every provider id a sign-in can name, for a refusal that must say which. */
|
|
437
|
+
function providerIds(offered) {
|
|
438
|
+
return `one of ${[...new Set(offered.map((o) => o.provider))].sort().join(", ")}`;
|
|
439
|
+
}
|