@fastagent-sh/fastagent 0.24.3 → 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 -517
- 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 -83
- package/dist/engines/pi/turn-kit.js +0 -275
- 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
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Agent API (SPEC.md) over pi-durable: a Harness opened on the instance's storage, and the operations read
|
|
3
|
+
* from and submitted to it. Admission (a key's lookup, the limits and the submission) is one step per process, since
|
|
4
|
+
* one process owns a storage.
|
|
5
|
+
*/
|
|
6
|
+
import { BACKGROUND_CONTEXT, withAbortSignal } from "@earendil-works/chord/context";
|
|
7
|
+
import { AgentDoc, ConversationBusy, DEFAULT_RETRY_POLICY, Harness, InboxDoc, LiveDoc, SessionFailed, configure, } from "@earendil-works/pi-durable";
|
|
8
|
+
import * as Cause from "effect/Cause";
|
|
9
|
+
import * as Duration from "effect/Duration";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Exit from "effect/Exit";
|
|
12
|
+
import * as Option from "effect/Option";
|
|
13
|
+
import * as Schema from "effect/Schema";
|
|
14
|
+
import * as Scope from "effect/Scope";
|
|
15
|
+
import * as Semaphore from "effect/Semaphore";
|
|
16
|
+
import * as Stream from "effect/Stream";
|
|
17
|
+
import { AgentError, CompactOptions, CreateSessionOptions, EntriesOptions, LIMITS, ListOptions, MessageKey, SendRequest, SessionChanges, Wait, agentError, hasEnded as messageHasEnded, } from "../../agent-api.js";
|
|
18
|
+
import { beginWork } from "../../channels/busy.js";
|
|
19
|
+
import { port, portAbort, portError } from "../../effect-port.js";
|
|
20
|
+
import { log } from "../../log.js";
|
|
21
|
+
import { admitSettings } from "./defaults.js";
|
|
22
|
+
import { USER_ENTRY, imageAt, isPublished, parseImageRef } from "./entries.js";
|
|
23
|
+
import { sessionEvents } from "./events.js";
|
|
24
|
+
import { storageIds } from "./ids.js";
|
|
25
|
+
import { isInput, queuedInputs, requestIdFor, usageOfState } from "./messages.js";
|
|
26
|
+
import { durableReads } from "./reads.js";
|
|
27
|
+
import { releaseWaitingQueues } from "./release.js";
|
|
28
|
+
import { trackSessions } from "./sessions.js";
|
|
29
|
+
import { messageStream } from "./stream.js";
|
|
30
|
+
const OWNERLESS = { ownership: { kind: "ownerless" } };
|
|
31
|
+
const COMMAND = /^\/([^\s]+)(?:\s+([\s\S]*))?$/;
|
|
32
|
+
export async function openDurableAgent(options) {
|
|
33
|
+
const context = BACKGROUND_CONTEXT;
|
|
34
|
+
const harness = await Harness.open(options.storage, {
|
|
35
|
+
models: options.models,
|
|
36
|
+
registry: options.registry,
|
|
37
|
+
...(options.settings === undefined ? {} : { settings: options.settings }),
|
|
38
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
39
|
+
onReport: (error) => log.warn(`[fastagent] pi-durable reported: ${String(error)}`),
|
|
40
|
+
}, context);
|
|
41
|
+
const lifetime = Scope.makeUnsafe();
|
|
42
|
+
try {
|
|
43
|
+
const ids = await storageIds(harness, context);
|
|
44
|
+
const sessions = await Effect.runPromise(Scope.provide(trackSessions(harness, options.storage, context), lifetime));
|
|
45
|
+
// Before `resume()`, which runs an earlier process's work. An abort enables the scheduler too, but commits first, so
|
|
46
|
+
// the aborted work does not run (measured).
|
|
47
|
+
const unsettled = (await harness.inspect(context)).submissions.filter(isInput);
|
|
48
|
+
const unfinished = options.unfinished ?? "resume";
|
|
49
|
+
if (unsettled.length > 0 && unfinished !== "resume") {
|
|
50
|
+
const where = [...new Set(unsettled.map((record) => record.conversationId))]
|
|
51
|
+
.filter((id) => sessions.has(id))
|
|
52
|
+
.map((id) => ids.encode(id));
|
|
53
|
+
const said = `${unsettled.length} message(s) an earlier process accepted and stopped before they ended (sessions: ${where.join(", ")})`;
|
|
54
|
+
if (unfinished === "refuse") {
|
|
55
|
+
throw new Error(`the storage holds ${said}, which this process would not finish: serve the agent until they end, then try again`);
|
|
56
|
+
}
|
|
57
|
+
for (const conversationId of new Set(unsettled.map((record) => record.conversationId))) {
|
|
58
|
+
await (await harness.conversation(conversationId, context))?.abort(context);
|
|
59
|
+
}
|
|
60
|
+
log.warn(`[fastagent] aborted ${said}: none of it runs again`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// What an earlier process accepted and did not settle runs on in this one: it is this process's work now.
|
|
64
|
+
for (const record of unsettled) {
|
|
65
|
+
const submission = await harness.submission(record.id, context);
|
|
66
|
+
if (submission !== undefined)
|
|
67
|
+
countUntilSettled(submission);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
harness.resume();
|
|
71
|
+
await Effect.runPromise(Scope.provide(releaseWaitingQueues(harness, context), lifetime));
|
|
72
|
+
return durableAgent(harness, ids, sessions, lifetime, options);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
await Effect.runPromise(Scope.close(lifetime, Exit.void));
|
|
76
|
+
await harness.close(context);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* A message not yet settled is the process's work in flight (busy.ts): what AgentCore's ping and `dev`'s restart wait
|
|
82
|
+
* for. Whatever ends the wait, settling or the harness closing, ends the count.
|
|
83
|
+
*/
|
|
84
|
+
function countUntilSettled(submission) {
|
|
85
|
+
const done = beginWork();
|
|
86
|
+
submission.wait(BACKGROUND_CONTEXT).then(done, done);
|
|
87
|
+
}
|
|
88
|
+
/** A wait its Caller's signal let go of: the call rejects with the signal's reason, and the message goes on. */
|
|
89
|
+
class Released {
|
|
90
|
+
reason;
|
|
91
|
+
constructor(reason) {
|
|
92
|
+
this.reason = reason;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const released = (signal) => Effect.callback((resume) => {
|
|
96
|
+
const onAbort = () => resume(Effect.fail(new Released(signal.reason)));
|
|
97
|
+
if (signal.aborted)
|
|
98
|
+
return onAbort();
|
|
99
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
100
|
+
return Effect.sync(() => signal.removeEventListener("abort", onAbort));
|
|
101
|
+
});
|
|
102
|
+
/** An input as `schema` reads it, or `invalid_request` saying where it is not. */
|
|
103
|
+
const decode = (schema, input, what) => Exit.mapError(Schema.decodeUnknownExit(schema)(input), (error) => agentError("invalid_request", `${what}: ${error.message.replace(/\s*\n\s*/g, " ")}`));
|
|
104
|
+
function durableAgent(harness, ids, sessions, lifetime, options) {
|
|
105
|
+
const context = BACKGROUND_CONTEXT;
|
|
106
|
+
const reads = durableReads(harness, ids, sessions, context);
|
|
107
|
+
const admission = Semaphore.makeUnsafe(1);
|
|
108
|
+
const commands = options.commands ?? (() => []);
|
|
109
|
+
const inFlightLimit = options.inFlightPerProcess ?? LIMITS.inFlightPerProcess;
|
|
110
|
+
let closing = false;
|
|
111
|
+
harness.subscribeClose(() => {
|
|
112
|
+
closing = true;
|
|
113
|
+
});
|
|
114
|
+
/** A failed call as a Caller's error: once the harness is closing (its storage failed, or it was closed), `unavailable`. */
|
|
115
|
+
const failureOf = (error) => {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
if (closing || error instanceof SessionFailed)
|
|
118
|
+
return agentError("unavailable", `The agent cannot answer now: ${message}`);
|
|
119
|
+
log.error(`[fastagent] the agent failed: ${error instanceof Error ? (error.stack ?? message) : message}`);
|
|
120
|
+
return agentError("internal", message);
|
|
121
|
+
};
|
|
122
|
+
/** An operation as the Promise the API promises: what it names, or a rejection with an AgentError (§4). */
|
|
123
|
+
const settle = (program) => Effect.runPromise(program.pipe(Effect.catchCause((cause) => {
|
|
124
|
+
const error = portError(cause);
|
|
125
|
+
if (error instanceof Released)
|
|
126
|
+
return Effect.fail(error.reason);
|
|
127
|
+
return Effect.fail(error instanceof AgentError ? error : failureOf(error));
|
|
128
|
+
})));
|
|
129
|
+
const conversationOf = (sessionId) => reads
|
|
130
|
+
.conversation(sessionId)
|
|
131
|
+
.pipe(Effect.flatMap((conversation) => conversation === undefined
|
|
132
|
+
? Effect.fail(agentError("not_found", `No session ${sessionId}`))
|
|
133
|
+
: Effect.succeed(conversation)));
|
|
134
|
+
/** The agent's tools by name: the registry's, read for each use, so a tool the definition gains is offered at once. */
|
|
135
|
+
const toolsByName = () => {
|
|
136
|
+
const tools = new Map();
|
|
137
|
+
for (const { tool } of options.registry.snapshot().tools())
|
|
138
|
+
if (!tools.has(tool.name))
|
|
139
|
+
tools.set(tool.name, tool);
|
|
140
|
+
return tools;
|
|
141
|
+
};
|
|
142
|
+
/** What pi-durable stores for `changes`: tools by their registrations, which it stores by name. */
|
|
143
|
+
const changeOf = (changes) => Effect.suspend(() => {
|
|
144
|
+
const { model, thinkingLevel, instructions, tools } = changes;
|
|
145
|
+
let registrations;
|
|
146
|
+
if (tools !== undefined && tools !== null) {
|
|
147
|
+
const known = toolsByName();
|
|
148
|
+
const unknown = tools.filter((name) => !known.has(name));
|
|
149
|
+
if (unknown.length > 0) {
|
|
150
|
+
return Effect.fail(agentError("invalid_request", `This agent has no tool named ${unknown.join(", ")}`, { tools: unknown }));
|
|
151
|
+
}
|
|
152
|
+
registrations = tools.map((name) => known.get(name));
|
|
153
|
+
}
|
|
154
|
+
else
|
|
155
|
+
registrations = tools;
|
|
156
|
+
return Effect.succeed({
|
|
157
|
+
...(model === undefined
|
|
158
|
+
? {}
|
|
159
|
+
: { model: model === null ? null : { provider: model.provider, modelId: model.modelId } }),
|
|
160
|
+
...(thinkingLevel === undefined ? {} : { thinkingLevel }),
|
|
161
|
+
...(instructions === undefined ? {} : { instructions }),
|
|
162
|
+
...(registrations === undefined ? {} : { tools: registrations }),
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
const info = () => settle(Effect.gen(function* () {
|
|
166
|
+
const available = yield* port(() => options.models.getAvailable());
|
|
167
|
+
const named = ({ name, description }) => ({
|
|
168
|
+
name,
|
|
169
|
+
...(description ? { description } : {}),
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
name: options.name,
|
|
173
|
+
...(options.description === undefined ? {} : { description: options.description }),
|
|
174
|
+
commands: commands().map(named),
|
|
175
|
+
tools: [...toolsByName().values()].map(named),
|
|
176
|
+
models: available.map((model) => ({
|
|
177
|
+
provider: model.provider,
|
|
178
|
+
modelId: model.id,
|
|
179
|
+
...(model.name ? { name: model.name } : {}),
|
|
180
|
+
})),
|
|
181
|
+
...(options.model === undefined
|
|
182
|
+
? {}
|
|
183
|
+
: { defaultModel: { provider: options.model.provider, modelId: options.model.modelId } }),
|
|
184
|
+
};
|
|
185
|
+
}));
|
|
186
|
+
const inFlight = port(() => harness.inspect(context)).pipe(Effect.map(({ submissions }) => submissions.filter((record) => record.type === "input").length));
|
|
187
|
+
const defaults = {
|
|
188
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
189
|
+
...(options.thinkingLevel === undefined ? {} : { thinkingLevel: options.thinkingLevel }),
|
|
190
|
+
};
|
|
191
|
+
const has = {
|
|
192
|
+
model: (model) => options.models.getModel(model.provider, model.modelId) !== undefined,
|
|
193
|
+
tool: (name) => toolsByName().has(name),
|
|
194
|
+
};
|
|
195
|
+
/** A message no setting of its session could answer is refused, recording nothing, rather than accepted to fail. */
|
|
196
|
+
const unanswerable = (refused) => {
|
|
197
|
+
if (refused.missing === "tools") {
|
|
198
|
+
const names = refused.tools.join(", ");
|
|
199
|
+
return agentError("missing_tool", `This session enables ${names}, which this agent no longer has`, {
|
|
200
|
+
tools: refused.tools,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const { model } = refused;
|
|
204
|
+
return agentError("missing_model", model === undefined
|
|
205
|
+
? "Neither this session nor the agent names a model"
|
|
206
|
+
: `This session answers with ${model.provider}/${model.modelId}, which is not among this agent's models`, model === undefined ? undefined : { model: `${model.provider}/${model.modelId}` });
|
|
207
|
+
};
|
|
208
|
+
const unconfirmed = (conversation, messageId, error) => agentError("unconfirmed", `${messageId === undefined ? "The message may have been recorded, and that could not be confirmed" : "The message was recorded, and reading it back failed"}: ${failureOf(error).message}`, {
|
|
209
|
+
sessionId: ids.encode(conversation.id),
|
|
210
|
+
...(messageId === undefined ? {} : { messageId: ids.encode(messageId) }),
|
|
211
|
+
});
|
|
212
|
+
/** Admission: a key's lookup, the limits, the settings and the submission, one at a time per process. */
|
|
213
|
+
const admit = (request) => Semaphore.withPermits(admission, 1)(Effect.gen(function* () {
|
|
214
|
+
const requestId = requestIdFor(request.idempotencyKey);
|
|
215
|
+
// Before anything is written: reading or expanding a command reads the definition's files, and may throw.
|
|
216
|
+
const { content, sent } = expandCommand(request.content, commands());
|
|
217
|
+
const conversation = yield* conversationOf(request.sessionId);
|
|
218
|
+
if (request.idempotencyKey !== undefined) {
|
|
219
|
+
const seen = yield* reads.submissionByKey(conversation, requestId);
|
|
220
|
+
if (seen !== undefined)
|
|
221
|
+
return { message: yield* reads.message(seen), replayed: true };
|
|
222
|
+
}
|
|
223
|
+
if ((yield* inFlight) >= inFlightLimit) {
|
|
224
|
+
return yield* agentError("busy", `This agent already holds ${inFlightLimit} messages`, {
|
|
225
|
+
reason: "capacity",
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (request.whenBusy !== "reject") {
|
|
229
|
+
const inbox = yield* reads.inbox(conversation);
|
|
230
|
+
if (queuedInputs(inbox).length >= LIMITS.queuedPerSession) {
|
|
231
|
+
return yield* agentError("busy", `The session already has ${LIMITS.queuedPerSession} queued messages`, {
|
|
232
|
+
reason: "queue_full",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const refused = yield* port(() => conversation.commit((tx) => admitSettings(tx, conversation.id, defaults, has), context));
|
|
237
|
+
if (refused !== undefined)
|
|
238
|
+
return yield* unanswerable(refused);
|
|
239
|
+
const data = { requestId, ...(sent === undefined ? {} : { sent }) };
|
|
240
|
+
let messageId;
|
|
241
|
+
return yield* Effect.gen(function* () {
|
|
242
|
+
const submission = yield* port(() => conversation.submit({
|
|
243
|
+
type: "input",
|
|
244
|
+
content,
|
|
245
|
+
requestId,
|
|
246
|
+
whenBusy: request.whenBusy === "steer" ? "steer" : request.whenBusy === "reject" ? "reject" : "followUp",
|
|
247
|
+
entry: { kind: USER_ENTRY, data },
|
|
248
|
+
}, context));
|
|
249
|
+
messageId = submission.id;
|
|
250
|
+
// Counted once it is read back, whether or not that read succeeds: the wait must not be what reads the
|
|
251
|
+
// storage first, or a failing read would fail it instead of answering this call.
|
|
252
|
+
const record = yield* port(() => submission.status(context)).pipe(Effect.ensuring(Effect.sync(() => countUntilSettled(submission))));
|
|
253
|
+
if (!isInput(record))
|
|
254
|
+
throw new Error(`submission ${record.id} was admitted as a write`);
|
|
255
|
+
return { message: yield* reads.message(record), replayed: false };
|
|
256
|
+
}).pipe(
|
|
257
|
+
// Once submission starts, only pi-durable's explicit refusal proves nothing was recorded.
|
|
258
|
+
Effect.catchCause((cause) => {
|
|
259
|
+
const error = portError(cause);
|
|
260
|
+
return Effect.fail(messageId === undefined && error instanceof ConversationBusy
|
|
261
|
+
? agentError("busy", "The session is answering another message", { reason: "running" })
|
|
262
|
+
: unconfirmed(conversation, messageId, error));
|
|
263
|
+
}));
|
|
264
|
+
}));
|
|
265
|
+
/** The message as it is now. */
|
|
266
|
+
const current = (message) => reads.submission(message.id).pipe(Effect.flatMap((record) => {
|
|
267
|
+
if (record === undefined)
|
|
268
|
+
throw new Error(`message ${message.id} is not in the storage it was accepted into`);
|
|
269
|
+
return reads.message(record);
|
|
270
|
+
}));
|
|
271
|
+
/** The message once it has ended, or after `seconds` as it is then; `signal` lets go of the wait. */
|
|
272
|
+
const awaitEnd = (message, seconds, signal) => {
|
|
273
|
+
if (messageHasEnded(message) || seconds === 0)
|
|
274
|
+
return Effect.succeed(message);
|
|
275
|
+
const ended = port(() => harness.submission(ids.decode(message.id), context)).pipe(Effect.flatMap((handle) => {
|
|
276
|
+
if (handle === undefined)
|
|
277
|
+
throw new Error(`message ${message.id} is not in the storage it was accepted into`);
|
|
278
|
+
const controller = new AbortController();
|
|
279
|
+
return portAbort("a message's wait", () => handle.wait(withAbortSignal(controller.signal, context)), () => controller.abort());
|
|
280
|
+
}), Effect.flatMap((record) => reads.message(record)));
|
|
281
|
+
const timed = ended.pipe(Effect.timeoutOption(Duration.seconds(seconds)), Effect.flatMap(Option.match({ onNone: () => current(message), onSome: Effect.succeed })));
|
|
282
|
+
return signal === undefined ? timed : Effect.raceFirst(timed, released(signal));
|
|
283
|
+
};
|
|
284
|
+
const send = (request, sendOptions) => settle(Effect.gen(function* () {
|
|
285
|
+
const decoded = yield* decode(SendRequest, request, "messages.send");
|
|
286
|
+
const seconds = sendOptions?.wait === undefined ? undefined : yield* decode(Wait, sendOptions.wait, "wait");
|
|
287
|
+
const signal = sendOptions?.signal;
|
|
288
|
+
// A send whose Caller let go before it was made is not made, as `fetch` does not send one.
|
|
289
|
+
if (signal?.aborted)
|
|
290
|
+
return yield* Effect.fail(new Released(signal.reason));
|
|
291
|
+
const { message } = yield* admit(decoded);
|
|
292
|
+
if (seconds === undefined)
|
|
293
|
+
return message;
|
|
294
|
+
return yield* awaitEnd(message, seconds, signal).pipe(Effect.catchCause((cause) => {
|
|
295
|
+
const error = portError(cause);
|
|
296
|
+
if (error instanceof Released)
|
|
297
|
+
return Effect.fail(error);
|
|
298
|
+
return Effect.fail(agentError("unconfirmed", `The message was recorded, and waiting for it failed: ${failureOf(error).message}`, {
|
|
299
|
+
sessionId: message.sessionId,
|
|
300
|
+
messageId: message.id,
|
|
301
|
+
}));
|
|
302
|
+
}));
|
|
303
|
+
}));
|
|
304
|
+
const get = (key, getOptions) => settle(Effect.gen(function* () {
|
|
305
|
+
const decoded = yield* decode(MessageKey, key, "messages.get");
|
|
306
|
+
const seconds = getOptions?.wait === undefined ? undefined : yield* decode(Wait, getOptions.wait, "wait");
|
|
307
|
+
const signal = getOptions?.signal;
|
|
308
|
+
if (signal?.aborted)
|
|
309
|
+
return yield* Effect.fail(new Released(signal.reason));
|
|
310
|
+
const conversation = yield* conversationOf(decoded.sessionId);
|
|
311
|
+
const record = "messageId" in decoded
|
|
312
|
+
? yield* reads.submission(decoded.messageId)
|
|
313
|
+
: yield* reads.submissionByKey(conversation, requestIdFor(decoded.idempotencyKey));
|
|
314
|
+
if (record === undefined || record.conversationId !== conversation.id) {
|
|
315
|
+
const which = "messageId" in decoded ? decoded.messageId : `with key ${decoded.idempotencyKey}`;
|
|
316
|
+
return yield* agentError("not_found", `No message ${which} in session ${decoded.sessionId}`);
|
|
317
|
+
}
|
|
318
|
+
const message = yield* reads.message(record);
|
|
319
|
+
return seconds === undefined ? message : yield* awaitEnd(message, seconds, signal);
|
|
320
|
+
}));
|
|
321
|
+
const create = (createOptions) => settle(Effect.gen(function* () {
|
|
322
|
+
const { forkFrom, ...settings } = yield* decode(CreateSessionOptions, createOptions ?? {}, "sessions.create");
|
|
323
|
+
const creation = { ...OWNERLESS, agent: yield* changeOf(settings) };
|
|
324
|
+
if (forkFrom === undefined) {
|
|
325
|
+
return yield* reads.session(yield* port(() => harness.createConversation(creation, context)));
|
|
326
|
+
}
|
|
327
|
+
const source = yield* conversationOf(forkFrom.sessionId);
|
|
328
|
+
const at = forkFrom.entryId === undefined
|
|
329
|
+
? yield* reads
|
|
330
|
+
.history(source, { order: "descending" })
|
|
331
|
+
.pipe(Stream.runHead, Effect.map(Option.getOrUndefined))
|
|
332
|
+
: yield* reads.visibleEntry(source, forkFrom.entryId);
|
|
333
|
+
if (at === undefined && forkFrom.entryId !== undefined) {
|
|
334
|
+
return yield* agentError("not_found", `No entry ${forkFrom.entryId} in session ${forkFrom.sessionId}`);
|
|
335
|
+
}
|
|
336
|
+
const fork = at === undefined
|
|
337
|
+
? yield* port(async () => {
|
|
338
|
+
// pi-durable forks at an entry; an empty source has only settings to inherit.
|
|
339
|
+
const inherited = await harness.snapshot(AgentDoc, source.id, context);
|
|
340
|
+
return harness.createConversation({
|
|
341
|
+
...creation,
|
|
342
|
+
init: async (tx, id) => {
|
|
343
|
+
Object.assign(await tx.doc(AgentDoc, id), inherited);
|
|
344
|
+
await configure(tx, id, creation.agent);
|
|
345
|
+
},
|
|
346
|
+
}, context);
|
|
347
|
+
})
|
|
348
|
+
: yield* port(() => source.fork(at.id, creation, context));
|
|
349
|
+
return yield* reads.session(fork);
|
|
350
|
+
}));
|
|
351
|
+
const update = (sessionId, changes) => settle(Effect.gen(function* () {
|
|
352
|
+
const decoded = yield* decode(SessionChanges, changes, "sessions.update");
|
|
353
|
+
if (Object.values(decoded).every((value) => value === undefined)) {
|
|
354
|
+
return yield* agentError("invalid_request", "Nothing to update: give a model, a thinking level, instructions or tools");
|
|
355
|
+
}
|
|
356
|
+
const conversation = yield* conversationOf(sessionId);
|
|
357
|
+
const change = yield* changeOf(decoded);
|
|
358
|
+
yield* port(() => conversation.configure(change, context));
|
|
359
|
+
return yield* reads.session(conversation);
|
|
360
|
+
}));
|
|
361
|
+
const list = (listOptions) => settle(Effect.gen(function* () {
|
|
362
|
+
const { after, limit = LIMITS.pageDefault } = yield* decode(ListOptions, listOptions ?? {}, "sessions.list");
|
|
363
|
+
const position = after === undefined ? undefined : ids.decodeCursor(after);
|
|
364
|
+
if (after !== undefined && position === undefined) {
|
|
365
|
+
return yield* agentError("invalid_request", `${JSON.stringify(after)} is not a cursor of this agent's`);
|
|
366
|
+
}
|
|
367
|
+
const ordered = sessions.byActivity();
|
|
368
|
+
const rest = position === undefined ? ordered : ordered.filter(({ activity }) => activity < position);
|
|
369
|
+
const page = rest.slice(0, limit);
|
|
370
|
+
const last = page.at(-1);
|
|
371
|
+
const data = yield* reads.sessionsOf(page.map(({ conversationId }) => conversationId));
|
|
372
|
+
return {
|
|
373
|
+
data,
|
|
374
|
+
...(rest.length > limit && last !== undefined ? { next: ids.encodeCursor(last.activity) } : {}),
|
|
375
|
+
};
|
|
376
|
+
}));
|
|
377
|
+
const entries = (sessionId, entriesOptions) => settle(Effect.gen(function* () {
|
|
378
|
+
const decoded = yield* decode(EntriesOptions, entriesOptions ?? {}, "sessions.entries");
|
|
379
|
+
if (decoded.after !== undefined && decoded.before !== undefined) {
|
|
380
|
+
return yield* agentError("invalid_request", "A page is after an entry or before one, not both");
|
|
381
|
+
}
|
|
382
|
+
return yield* readPage(reads, yield* conversationOf(sessionId), sessionId, decoded);
|
|
383
|
+
}));
|
|
384
|
+
/** One entry the session published, by its id: where `messages.stream` starts a message that is already running. */
|
|
385
|
+
const entry = (sessionId, entryId) => settle(Effect.gen(function* () {
|
|
386
|
+
const record = yield* reads.visibleEntry(yield* conversationOf(sessionId), entryId);
|
|
387
|
+
const [published] = record === undefined || !isPublished(record) ? [] : yield* reads.publish([record]);
|
|
388
|
+
if (published === undefined)
|
|
389
|
+
return yield* agentError("not_found", `No entry ${entryId} in session ${sessionId}`);
|
|
390
|
+
return published;
|
|
391
|
+
}));
|
|
392
|
+
const events = (sessionId, eventsOptions) => {
|
|
393
|
+
const stream = sessionEvents({
|
|
394
|
+
harness,
|
|
395
|
+
reads,
|
|
396
|
+
ids,
|
|
397
|
+
context,
|
|
398
|
+
failureOf,
|
|
399
|
+
maxRetries: options.settings?.retry?.maxRetries ?? DEFAULT_RETRY_POLICY.maxRetries,
|
|
400
|
+
}, sessionId, eventsOptions?.after);
|
|
401
|
+
const signal = eventsOptions?.signal;
|
|
402
|
+
return Stream.toAsyncIterable(signal === undefined
|
|
403
|
+
? stream
|
|
404
|
+
: stream.pipe(Stream.interruptWhen(released(signal).pipe(Effect.mapError((error) => error.reason)))));
|
|
405
|
+
};
|
|
406
|
+
/**
|
|
407
|
+
* Stops everything the session is doing, and answers once that stop is recorded rather than once it has ended
|
|
408
|
+
* (§4). pi-durable runs commits one at a time in the order they are called, and `abort()` calls its commit at once:
|
|
409
|
+
* so the read before it sees what it stops, and the check after it runs once the stop is in the storage.
|
|
410
|
+
*/
|
|
411
|
+
const cancel = (sessionId) => settle(Effect.gen(function* () {
|
|
412
|
+
const conversation = yield* conversationOf(sessionId);
|
|
413
|
+
let abortFailed;
|
|
414
|
+
const recorded = yield* port(() => {
|
|
415
|
+
const before = harness.commit(async (tx) => {
|
|
416
|
+
// Copied out: a document read in a transaction cannot be read once it has settled.
|
|
417
|
+
const run = (await tx.doc(LiveDoc, conversation.id))?.run;
|
|
418
|
+
return {
|
|
419
|
+
run: run === undefined ? undefined : { taskId: run.taskId, inputs: [...run.inputs] },
|
|
420
|
+
queued: queuedInputs(await tx.doc(InboxDoc, conversation.id)),
|
|
421
|
+
};
|
|
422
|
+
}, context);
|
|
423
|
+
conversation.abort(context).catch((error) => {
|
|
424
|
+
abortFailed = error;
|
|
425
|
+
if (!closing)
|
|
426
|
+
log.warn(`[fastagent] stopping session ${sessionId} failed: ${String(error)}`);
|
|
427
|
+
});
|
|
428
|
+
const check = harness.commit(async (tx) => {
|
|
429
|
+
const { run, queued } = await before;
|
|
430
|
+
const task = run === undefined ? undefined : await tx.task(run.taskId);
|
|
431
|
+
const stillQueued = new Set(queuedInputs(await tx.doc(InboxDoc, conversation.id)));
|
|
432
|
+
return {
|
|
433
|
+
marked: (task === undefined || task.abortRequested || task.state.status === "terminal") &&
|
|
434
|
+
queued.every((id) => !stillQueued.has(id)),
|
|
435
|
+
stopped: [...(run?.inputs ?? []), ...queued],
|
|
436
|
+
};
|
|
437
|
+
}, context);
|
|
438
|
+
// Both, so a harness that refuses every commit (closing, or its storage failed) fails this cancel once,
|
|
439
|
+
// leaving neither refusal unheard: the check's callback, which awaits the read, never runs then.
|
|
440
|
+
return Promise.all([before, check]).then(([, checked]) => checked);
|
|
441
|
+
});
|
|
442
|
+
if (!recorded.marked) {
|
|
443
|
+
throw new Error(`the stop of session ${sessionId} was not recorded: ${String(abortFailed ?? "no reason given")}`);
|
|
444
|
+
}
|
|
445
|
+
return { stopped: recorded.stopped.map((id) => ids.encode(id)) };
|
|
446
|
+
}));
|
|
447
|
+
const compact = (sessionId, compactOptions) => settle(Effect.gen(function* () {
|
|
448
|
+
const { instructions } = yield* decode(CompactOptions, compactOptions ?? {}, "sessions.compact");
|
|
449
|
+
const conversation = yield* conversationOf(sessionId);
|
|
450
|
+
yield* port(() => conversation.compact(instructions, context));
|
|
451
|
+
}));
|
|
452
|
+
const attachment = (sessionId, ref) => settle(Effect.gen(function* () {
|
|
453
|
+
const missing = agentError("not_found", `No attachment ${ref} in session ${sessionId}`);
|
|
454
|
+
const parsed = parseImageRef(ref);
|
|
455
|
+
if (parsed === undefined)
|
|
456
|
+
return yield* missing;
|
|
457
|
+
const record = yield* reads.visibleEntry(yield* conversationOf(sessionId), parsed.entryId);
|
|
458
|
+
const image = record === undefined ? undefined : imageAt(record, parsed.index);
|
|
459
|
+
if (image === undefined)
|
|
460
|
+
return yield* missing;
|
|
461
|
+
return { mimeType: image.mimeType, data: new Uint8Array(Buffer.from(image.data, "base64")) };
|
|
462
|
+
}));
|
|
463
|
+
const stream = (request, streamOptions) => {
|
|
464
|
+
const decoded = decode(SendRequest, request, "messages.stream");
|
|
465
|
+
if (Exit.isFailure(decoded))
|
|
466
|
+
return failing(Cause.squash(decoded.cause));
|
|
467
|
+
return messageStream({
|
|
468
|
+
events: (sessionId, after) => events(sessionId, after === undefined ? {} : { after }),
|
|
469
|
+
admit: (sent) => settle(admit(sent)),
|
|
470
|
+
get: (sessionId, messageId) => get({ sessionId, messageId }),
|
|
471
|
+
entry,
|
|
472
|
+
entries,
|
|
473
|
+
}, decoded.value, streamOptions?.signal);
|
|
474
|
+
};
|
|
475
|
+
return {
|
|
476
|
+
harness,
|
|
477
|
+
info,
|
|
478
|
+
usage: () => settle(port(() => harness.usage(context)).pipe(Effect.map((spent) => usageOfState(spent)))),
|
|
479
|
+
sessions: {
|
|
480
|
+
create,
|
|
481
|
+
get: (sessionId) => settle(conversationOf(sessionId).pipe(Effect.flatMap(reads.session))),
|
|
482
|
+
list,
|
|
483
|
+
update,
|
|
484
|
+
entries,
|
|
485
|
+
events,
|
|
486
|
+
cancel,
|
|
487
|
+
compact,
|
|
488
|
+
attachment,
|
|
489
|
+
},
|
|
490
|
+
messages: { send, stream, get },
|
|
491
|
+
conversation: (sessionId) => Effect.runPromise(reads.conversation(sessionId)),
|
|
492
|
+
close: async () => {
|
|
493
|
+
closing = true;
|
|
494
|
+
await Effect.runPromise(Scope.close(lifetime, Exit.void));
|
|
495
|
+
await harness.close(context);
|
|
496
|
+
},
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/** An iterable whose first read rejects with `error`: an operation refused before it began. */
|
|
500
|
+
function failing(error) {
|
|
501
|
+
return {
|
|
502
|
+
[Symbol.asyncIterator]: () => ({
|
|
503
|
+
next: () => Promise.reject(error),
|
|
504
|
+
return: async () => ({ done: true, value: undefined }),
|
|
505
|
+
}),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
/** The content the model sees, and the first text as sent when it was a command the agent expanded. */
|
|
509
|
+
function expandCommand(content, commands) {
|
|
510
|
+
const parts = (typeof content === "string" ? [{ type: "text", text: content }] : content).map((part) => part.type === "text"
|
|
511
|
+
? { type: "text", text: part.text }
|
|
512
|
+
: { type: "image", mimeType: part.mimeType, data: part.data });
|
|
513
|
+
const first = parts[0];
|
|
514
|
+
const match = first?.type === "text" ? COMMAND.exec(first.text) : null;
|
|
515
|
+
const command = match === null ? undefined : commands.find(({ name }) => name === match[1]);
|
|
516
|
+
if (first?.type !== "text" || match === null || command === undefined)
|
|
517
|
+
return { content: parts };
|
|
518
|
+
return { content: [{ type: "text", text: command.expand(match[2] ?? "") }, ...parts.slice(1)], sent: first.text };
|
|
519
|
+
}
|
|
520
|
+
/** One page of a session's published entries (§4): after a cursor, before one, or the newest. */
|
|
521
|
+
function readPage(reads, conversation, sessionId, { after, before, limit = LIMITS.pageDefault }) {
|
|
522
|
+
return Effect.gen(function* () {
|
|
523
|
+
const cursor = after ?? before;
|
|
524
|
+
const at = cursor === undefined ? undefined : yield* reads.visibleEntry(conversation, cursor);
|
|
525
|
+
if (cursor !== undefined && at === undefined) {
|
|
526
|
+
return yield* agentError("not_found", `No entry ${cursor} in session ${sessionId}`);
|
|
527
|
+
}
|
|
528
|
+
const bound = (id) => id;
|
|
529
|
+
const take = (query) => reads.published(conversation, query).pipe(Stream.take(limit + 1), Stream.runCollect);
|
|
530
|
+
if (after !== undefined && at !== undefined) {
|
|
531
|
+
const records = yield* take({ order: "ascending", minEntryId: bound(at.id + 1) });
|
|
532
|
+
const page = records.slice(0, limit);
|
|
533
|
+
const first = page[0];
|
|
534
|
+
if (first === undefined)
|
|
535
|
+
return { data: [] };
|
|
536
|
+
const earlier = yield* reads.newestPublished(conversation, first.id - 1);
|
|
537
|
+
const data = withinBudget(yield* reads.publish(page), "oldest");
|
|
538
|
+
return {
|
|
539
|
+
data,
|
|
540
|
+
...(earlier === undefined ? {} : { prev: data[0].id }),
|
|
541
|
+
...(records.length > limit || data.length < page.length ? { next: data.at(-1).id } : {}),
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
const records = yield* take({
|
|
545
|
+
order: "descending",
|
|
546
|
+
...(at === undefined ? {} : { maxEntryId: bound(at.id - 1) }),
|
|
547
|
+
});
|
|
548
|
+
const page = records.slice(0, limit).reverse();
|
|
549
|
+
if (page.length === 0)
|
|
550
|
+
return { data: [] };
|
|
551
|
+
const older = records[limit];
|
|
552
|
+
const published = yield* reads.publish(page);
|
|
553
|
+
const data = withinBudget(published, "newest");
|
|
554
|
+
const newer = at === undefined
|
|
555
|
+
? undefined
|
|
556
|
+
: yield* reads
|
|
557
|
+
.published(conversation, { order: "ascending", minEntryId: bound(at.id) })
|
|
558
|
+
.pipe(Stream.take(1), Stream.runHead);
|
|
559
|
+
return {
|
|
560
|
+
data,
|
|
561
|
+
...(older !== undefined || data.length < published.length ? { prev: data[0].id } : {}),
|
|
562
|
+
...(newer !== undefined && newer._tag === "Some" ? { next: data.at(-1).id } : {}),
|
|
563
|
+
};
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
/** A page stops at the byte budget (§7), keeping at least one entry: the end nearest its cursor. */
|
|
567
|
+
function withinBudget(entries, keep) {
|
|
568
|
+
const ordered = keep === "oldest" ? entries : [...entries].reverse();
|
|
569
|
+
const kept = [];
|
|
570
|
+
let bytes = 0;
|
|
571
|
+
for (const entry of ordered) {
|
|
572
|
+
const size = Buffer.byteLength(JSON.stringify(entry));
|
|
573
|
+
if (kept.length > 0 && bytes + size > LIMITS.pageBytes)
|
|
574
|
+
break;
|
|
575
|
+
kept.push(entry);
|
|
576
|
+
bytes += size;
|
|
577
|
+
}
|
|
578
|
+
return keep === "oldest" ? kept : kept.reverse();
|
|
579
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deployment's default model and thinking level, for a conversation whose own settings name none. pi-durable reads
|
|
3
|
+
* them only from the conversation's `pi.agent`, so the default is written there when a message is admitted; from then
|
|
4
|
+
* on they are the conversation's own, as one set through pi-durable's API is (architecture.md §6.1).
|
|
5
|
+
*/
|
|
6
|
+
import type { ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
7
|
+
import { type ConversationId, type ModelRef, type Tx } from "@earendil-works/pi-durable";
|
|
8
|
+
export interface Defaults {
|
|
9
|
+
model?: ModelRef;
|
|
10
|
+
thinkingLevel?: ModelThinkingLevel;
|
|
11
|
+
}
|
|
12
|
+
/** Why a session's settings cannot answer a message: a model the agent lacks, or tools it no longer has. */
|
|
13
|
+
export type Unanswerable = {
|
|
14
|
+
missing: "model";
|
|
15
|
+
model: ModelRef | undefined;
|
|
16
|
+
} | {
|
|
17
|
+
missing: "tools";
|
|
18
|
+
tools: string[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Take the defaults the conversation's settings lack, unless the settings cannot answer a message: the model it would
|
|
22
|
+
* answer with is not among the agent's models, or a tool it enables is not among the agent's tools. Then nothing is
|
|
23
|
+
* written, and what is missing is returned so the message can be refused.
|
|
24
|
+
*/
|
|
25
|
+
export declare function admitSettings(tx: Tx, conversationId: ConversationId, defaults: Defaults, has: {
|
|
26
|
+
model(model: ModelRef): boolean;
|
|
27
|
+
tool(name: string): boolean;
|
|
28
|
+
}): Promise<Unanswerable | undefined>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AgentDoc, configure } from "@earendil-works/pi-durable";
|
|
2
|
+
/**
|
|
3
|
+
* Take the defaults the conversation's settings lack, unless the settings cannot answer a message: the model it would
|
|
4
|
+
* answer with is not among the agent's models, or a tool it enables is not among the agent's tools. Then nothing is
|
|
5
|
+
* written, and what is missing is returned so the message can be refused.
|
|
6
|
+
*/
|
|
7
|
+
export async function admitSettings(tx, conversationId, defaults, has) {
|
|
8
|
+
const agent = await tx.doc(AgentDoc, conversationId);
|
|
9
|
+
const model = agent.model ?? defaults.model;
|
|
10
|
+
if (model === undefined || !has.model(model)) {
|
|
11
|
+
return {
|
|
12
|
+
missing: "model",
|
|
13
|
+
model: model === undefined ? undefined : { provider: model.provider, modelId: model.modelId },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const missing = Array.isArray(agent.tools) ? agent.tools.filter((name) => !has.tool(name)) : [];
|
|
17
|
+
if (missing.length > 0)
|
|
18
|
+
return { missing: "tools", tools: missing };
|
|
19
|
+
await configure(tx, conversationId, {
|
|
20
|
+
...(agent.model === undefined ? { model } : {}),
|
|
21
|
+
...(agent.thinkingLevel === undefined && defaults.thinkingLevel !== undefined
|
|
22
|
+
? { thinkingLevel: defaults.thinkingLevel }
|
|
23
|
+
: {}),
|
|
24
|
+
});
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|