@gjermundgaraba/effect-actions 0.20.0 → 0.21.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 +19 -41
- package/dist/Action.d.mts +140 -46
- package/dist/Action.mjs +87 -42
- package/dist/ActionCli.d.mts +69 -33
- package/dist/ActionCli.mjs +51 -32
- package/dist/ActionHttp.d.mts +69 -35
- package/dist/ActionHttp.mjs +75 -23
- package/dist/ActionMcp.d.mts +56 -22
- package/dist/ActionMcp.mjs +94 -69
- package/dist/ActionRpc.d.mts +222 -55
- package/dist/ActionRpc.mjs +299 -46
- package/dist/ActionToolkit.d.mts +22 -5
- package/dist/ActionToolkit.mjs +36 -12
- package/dist/Authentication.d.mts +42 -10
- package/dist/Authentication.mjs +37 -30
- package/dist/Event.d.mts +370 -0
- package/dist/Event.mjs +103 -0
- package/dist/Testing.mjs +1 -1
- package/dist/Webhook.d.mts +146 -0
- package/dist/Webhook.mjs +72 -0
- package/dist/authentication/provider.d.mts +35 -10
- package/dist/authentication/provider.mjs +1 -1
- package/dist/authentication/refusal.mjs +2 -2
- package/dist/cli/commands.d.mts +9 -5
- package/dist/cli/commands.mjs +84 -33
- package/dist/contract/ast.mjs +4 -1
- package/dist/contract/authorizer.d.mts +8 -0
- package/dist/contract/authorizer.mjs +20 -0
- package/dist/contract/call.d.mts +9 -6
- package/dist/contract/call.mjs +48 -6
- package/dist/contract/callback.d.mts +7 -0
- package/dist/contract/callback.mjs +27 -0
- package/dist/contract/caller.d.mts +13 -2
- package/dist/contract/caller.mjs +22 -3
- package/dist/contract/canonical.mjs +10 -0
- package/dist/contract/codec.d.mts +9 -0
- package/dist/contract/codec.mjs +9 -0
- package/dist/contract/codes.mjs +81 -0
- package/dist/contract/declared.mjs +25 -9
- package/dist/contract/digest.mjs +6 -0
- package/dist/contract/errors.d.mts +9 -3
- package/dist/contract/errors.mjs +1 -1
- package/dist/contract/feed.d.mts +127 -0
- package/dist/contract/feed.mjs +249 -0
- package/dist/contract/hub.d.mts +94 -0
- package/dist/contract/hub.mjs +133 -0
- package/dist/contract/idempotency.d.mts +47 -0
- package/dist/contract/idempotency.mjs +99 -0
- package/dist/contract/implementation.d.mts +73 -47
- package/dist/contract/implementation.mjs +92 -58
- package/dist/contract/input-check.d.mts +15 -0
- package/dist/contract/input-check.mjs +16 -0
- package/dist/contract/lease.mjs +28 -0
- package/dist/contract/listed.d.mts +4 -0
- package/dist/contract/listed.mjs +22 -0
- package/dist/contract/log.d.mts +41 -0
- package/dist/contract/log.mjs +144 -0
- package/dist/contract/names.mjs +14 -4
- package/dist/contract/occurrence.d.mts +29 -0
- package/dist/contract/occurrence.mjs +45 -0
- package/dist/contract/pages.d.mts +13 -0
- package/dist/contract/pages.mjs +39 -0
- package/dist/contract/progress.d.mts +26 -0
- package/dist/contract/progress.mjs +72 -0
- package/dist/contract/push.d.mts +20 -0
- package/dist/contract/push.mjs +96 -0
- package/dist/contract/receiving.d.mts +29 -0
- package/dist/contract/receiving.mjs +42 -0
- package/dist/contract/recent.mjs +12 -0
- package/dist/contract/reported.mjs +16 -0
- package/dist/contract/serving.mjs +63 -0
- package/dist/contract/status.mjs +14 -5
- package/dist/contract/stream.d.mts +34 -0
- package/dist/contract/stream.mjs +31 -0
- package/dist/contract/subscribe.d.mts +45 -0
- package/dist/contract/subscribe.mjs +50 -0
- package/dist/contract/transient.mjs +28 -0
- package/dist/contract/watch.d.mts +18 -0
- package/dist/contract/watch.mjs +105 -0
- package/dist/contract/webhooks.d.mts +42 -0
- package/dist/contract/webhooks.mjs +53 -0
- package/dist/http/client.d.mts +37 -12
- package/dist/http/client.mjs +51 -12
- package/dist/http/events.d.mts +56 -0
- package/dist/http/events.mjs +102 -0
- package/dist/http/placed.d.mts +36 -0
- package/dist/http/placed.mjs +71 -0
- package/dist/http/schema-errors.mjs +1 -1
- package/dist/http/streamed.d.mts +21 -0
- package/dist/http/streamed.mjs +94 -0
- package/dist/http/watch.mjs +50 -0
- package/dist/mcp/bridge.mjs +283 -0
- package/dist/mcp/client.d.mts +30 -97
- package/dist/mcp/client.mjs +59 -132
- package/dist/mcp/events.mjs +142 -0
- package/dist/mcp/progress.mjs +34 -0
- package/dist/mcp/protocol.mjs +39 -13
- package/dist/mcp/reply.d.mts +79 -0
- package/dist/mcp/reply.mjs +99 -0
- package/dist/mcp/request.d.mts +40 -0
- package/dist/mcp/request.mjs +51 -0
- package/dist/mcp/watch.mjs +115 -0
- package/dist/rpc/watch.mjs +39 -0
- package/dist/stdio/console.mjs +13 -5
- package/dist/testing/memory.d.mts +1 -1
- package/dist/testing/memory.mjs +3 -3
- package/dist/webhook/address.mjs +47 -0
- package/dist/webhook/attempt.mjs +65 -0
- package/dist/webhook/receiver.mjs +67 -0
- package/dist/webhook/signing.mjs +28 -0
- package/dist/webhook/stored.mjs +57 -0
- package/dist/webhook/subscriptions.mjs +342 -0
- package/docs/Action-failures.md +2 -2
- package/docs/Action-streams.md +115 -0
- package/docs/Action.md +58 -124
- package/docs/ActionCli-failures.md +1 -2
- package/docs/ActionCli.md +83 -88
- package/docs/ActionHttp-failures.md +3 -3
- package/docs/ActionHttp.md +90 -70
- package/docs/ActionMcp-failures.md +2 -1
- package/docs/ActionMcp.md +120 -92
- package/docs/ActionRpc-events.md +126 -0
- package/docs/ActionRpc-failures.md +1 -2
- package/docs/ActionRpc.md +108 -79
- package/docs/ActionToolkit-failures.md +1 -2
- package/docs/ActionToolkit.md +33 -90
- package/docs/Authentication-advanced.md +52 -60
- package/docs/Authentication-failures.md +5 -4
- package/docs/Authentication.md +23 -58
- package/docs/Event-failures.md +15 -0
- package/docs/Event.md +281 -0
- package/docs/README.md +51 -82
- package/docs/Testing-failures.md +1 -5
- package/docs/Testing.md +126 -86
- package/docs/Webhook-failures.md +8 -0
- package/docs/Webhook.md +319 -0
- package/docs/setup-failures.md +2 -3
- package/docs/setup.md +32 -52
- package/examples/README.md +12 -37
- package/examples/assistant/README.md +21 -26
- package/examples/assistant/src/agent.ts +66 -24
- package/examples/assistant/src/cli.ts +7 -4
- package/examples/assistant/src/commands.ts +9 -4
- package/examples/assistant/src/identity/Identity.ts +5 -4
- package/examples/assistant/src/identity/authorize.ts +13 -5
- package/examples/assistant/src/identity/handlers.ts +3 -2
- package/examples/assistant/src/identity/permissions.ts +9 -3
- package/examples/assistant/src/platform/tasks-file.ts +15 -11
- package/examples/assistant/src/scripted.ts +7 -4
- package/examples/assistant/src/tasks/Tasks.ts +20 -6
- package/examples/assistant/src/tasks/handlers.ts +9 -5
- package/examples/assistant/src/tasks/store.ts +38 -11
- package/examples/assistant/src/toolkit.ts +11 -7
- package/examples/assistant/test/commands.test.ts +53 -27
- package/examples/assistant/test/identity/identity.test.ts +27 -11
- package/examples/assistant/test/platform/tasks-file.test.ts +39 -23
- package/examples/assistant/test/tasks/tasks.test.ts +62 -34
- package/examples/assistant/test/toolkit.test.ts +104 -75
- package/examples/build-events/README.md +83 -0
- package/examples/build-events/src/Api.ts +27 -0
- package/examples/build-events/src/Rpc.ts +26 -0
- package/examples/build-events/src/builds/Builds.ts +108 -0
- package/examples/build-events/src/builds/events.ts +54 -0
- package/examples/build-events/src/builds/handlers.ts +58 -0
- package/examples/build-events/src/builds/log.ts +30 -0
- package/examples/build-events/src/builds/status.ts +28 -0
- package/examples/build-events/src/builds/store.ts +130 -0
- package/examples/build-events/src/cli.ts +18 -0
- package/examples/build-events/src/commands.ts +32 -0
- package/examples/build-events/src/deploys/Deploys.ts +44 -0
- package/examples/build-events/src/deploys/approvers.ts +55 -0
- package/examples/build-events/src/deploys/events.ts +32 -0
- package/examples/build-events/src/deploys/handlers.ts +38 -0
- package/examples/build-events/src/identity/Identity.ts +29 -0
- package/examples/build-events/src/identity/members.ts +7 -0
- package/examples/build-events/src/identity/verifier.ts +22 -0
- package/examples/build-events/src/notifier/Notices.ts +26 -0
- package/examples/build-events/src/notifier/routes.ts +71 -0
- package/examples/build-events/src/notifier.ts +48 -0
- package/examples/build-events/src/routes.ts +71 -0
- package/examples/build-events/src/server.ts +62 -0
- package/examples/build-events/test/builds/events.test.ts +68 -0
- package/examples/build-events/test/builds/log.test.ts +71 -0
- package/examples/build-events/test/builds/progress.test.ts +57 -0
- package/examples/build-events/test/builds/status.test.ts +42 -0
- package/examples/build-events/test/commands.test.ts +103 -0
- package/examples/build-events/test/deploys/events.test.ts +52 -0
- package/examples/build-events/test/http.test.ts +95 -0
- package/examples/build-events/test/notifier.test.ts +80 -0
- package/examples/build-events/test/routes.test.ts +96 -0
- package/examples/build-events/test/rpc.test.ts +84 -0
- package/examples/build-events/test/support/as.ts +8 -0
- package/examples/build-events/test/support/memory.ts +43 -0
- package/examples/build-events/test/webhooks.test.ts +143 -0
- package/examples/kitchen-sink/README.md +178 -166
- package/examples/kitchen-sink/src/Actions.ts +11 -3
- package/examples/kitchen-sink/src/Api.ts +12 -6
- package/examples/kitchen-sink/src/Rpc.ts +7 -5
- package/examples/kitchen-sink/src/admin/Admin.ts +21 -10
- package/examples/kitchen-sink/src/admin/authenticate.ts +8 -4
- package/examples/kitchen-sink/src/admin/authorize.ts +13 -5
- package/examples/kitchen-sink/src/admin/grants.ts +5 -3
- package/examples/kitchen-sink/src/admin/handlers.ts +9 -5
- package/examples/kitchen-sink/src/agent.ts +14 -6
- package/examples/kitchen-sink/src/assistant/Assistant.ts +21 -15
- package/examples/kitchen-sink/src/assistant/conversations.ts +14 -9
- package/examples/kitchen-sink/src/assistant/demo-model.ts +2 -2
- package/examples/kitchen-sink/src/assistant/handlers.ts +42 -22
- package/examples/kitchen-sink/src/attachments/Attachments.ts +6 -6
- package/examples/kitchen-sink/src/attachments/download.ts +7 -6
- package/examples/kitchen-sink/src/attachments/handlers.ts +9 -4
- package/examples/kitchen-sink/src/attachments/store.ts +25 -18
- package/examples/kitchen-sink/src/browser.ts +10 -4
- package/examples/kitchen-sink/src/cli.ts +28 -12
- package/examples/kitchen-sink/src/config.ts +1 -1
- package/examples/kitchen-sink/src/identity/Identity.ts +24 -10
- package/examples/kitchen-sink/src/identity/authenticate.ts +19 -11
- package/examples/kitchen-sink/src/identity/authorize.ts +14 -4
- package/examples/kitchen-sink/src/identity/directory.ts +16 -7
- package/examples/kitchen-sink/src/identity/handlers.ts +1 -0
- package/examples/kitchen-sink/src/identity/members.ts +7 -2
- package/examples/kitchen-sink/src/identity/quota.ts +23 -20
- package/examples/kitchen-sink/src/issues/Issues.ts +91 -15
- package/examples/kitchen-sink/src/issues/assistant.ts +4 -2
- package/examples/kitchen-sink/src/issues/events.ts +46 -0
- package/examples/kitchen-sink/src/issues/export.ts +35 -0
- package/examples/kitchen-sink/src/issues/guide.ts +4 -3
- package/examples/kitchen-sink/src/issues/handlers.ts +20 -7
- package/examples/kitchen-sink/src/issues/store.ts +156 -67
- package/examples/kitchen-sink/src/issues/write-limit.ts +44 -18
- package/examples/kitchen-sink/src/memory.ts +11 -2
- package/examples/kitchen-sink/src/ops.ts +10 -5
- package/examples/kitchen-sink/src/request-policy.ts +21 -9
- package/examples/kitchen-sink/src/routes.ts +67 -27
- package/examples/kitchen-sink/src/sdk.ts +22 -12
- package/examples/kitchen-sink/src/seed.ts +15 -9
- package/examples/kitchen-sink/src/server.ts +24 -10
- package/examples/kitchen-sink/src/stdio.ts +9 -3
- package/examples/kitchen-sink/src/workspaces/Workspaces.ts +10 -7
- package/examples/kitchen-sink/src/workspaces/handlers.ts +3 -2
- package/examples/kitchen-sink/src/workspaces/registry.ts +16 -10
- package/examples/kitchen-sink/src/workspaces/tenant.ts +12 -5
- package/examples/kitchen-sink/test/admin/admin.test.ts +38 -23
- package/examples/kitchen-sink/test/assistant/assistant.test.ts +471 -299
- package/examples/kitchen-sink/test/attachments/attachments.test.ts +135 -94
- package/examples/kitchen-sink/test/identity/identity.test.ts +110 -82
- package/examples/kitchen-sink/test/identity/session.test.ts +65 -43
- package/examples/kitchen-sink/test/issues/assistant.test.ts +50 -31
- package/examples/kitchen-sink/test/issues/events.test.ts +273 -0
- package/examples/kitchen-sink/test/issues/export.test.ts +120 -0
- package/examples/kitchen-sink/test/issues/issues.test.ts +105 -75
- package/examples/kitchen-sink/test/request-policy.test.ts +108 -47
- package/examples/kitchen-sink/test/routes.test.ts +102 -57
- package/examples/kitchen-sink/test/support/as.ts +9 -0
- package/examples/kitchen-sink/test/support/scripted.ts +6 -5
- package/examples/kitchen-sink/test/support/services.ts +17 -0
- package/examples/live-rpc/README.md +33 -35
- package/examples/live-rpc/src/Rpc.ts +9 -5
- package/examples/live-rpc/src/browser.ts +39 -11
- package/examples/live-rpc/src/chat/Chat.ts +30 -16
- package/examples/live-rpc/src/chat/authorize.ts +2 -1
- package/examples/live-rpc/src/chat/events.ts +74 -0
- package/examples/live-rpc/src/chat/handlers.ts +13 -5
- package/examples/live-rpc/src/chat/room.ts +9 -5
- package/examples/live-rpc/src/chat/store.ts +51 -14
- package/examples/live-rpc/src/client.ts +22 -5
- package/examples/live-rpc/src/identity/Identity.ts +3 -3
- package/examples/live-rpc/src/identity/verifier.ts +8 -4
- package/examples/live-rpc/src/page.ts +58 -12
- package/examples/live-rpc/src/routes.ts +14 -10
- package/examples/live-rpc/src/server.ts +15 -5
- package/examples/live-rpc/test/browser.test.ts +127 -50
- package/examples/live-rpc/test/chat/chat.test.ts +215 -23
- package/examples/live-rpc/test/routes.test.ts +130 -46
- package/examples/live-rpc/test/server.test.ts +94 -33
- package/examples/mcp-tools/README.md +24 -38
- package/examples/mcp-tools/src/platform/fake-farm.ts +63 -24
- package/examples/mcp-tools/src/platform/print-farm.ts +59 -29
- package/examples/mcp-tools/src/printers/Printers.ts +18 -7
- package/examples/mcp-tools/src/printers/farm.ts +15 -5
- package/examples/mcp-tools/src/printers/handlers.ts +12 -6
- package/examples/mcp-tools/src/routes.ts +8 -6
- package/examples/mcp-tools/src/server.ts +12 -5
- package/examples/mcp-tools/src/stdio.ts +3 -2
- package/examples/mcp-tools/test/printers/printers.test.ts +132 -89
- package/examples/mcp-tools/test/routes.test.ts +140 -81
- package/examples/notes-service/README.md +30 -45
- package/examples/notes-service/src/Api.ts +1 -0
- package/examples/notes-service/src/browser.ts +15 -14
- package/examples/notes-service/src/cli.ts +8 -3
- package/examples/notes-service/src/commands.ts +46 -20
- package/examples/notes-service/src/identity/Identity.ts +2 -2
- package/examples/notes-service/src/identity/authorize.ts +6 -2
- package/examples/notes-service/src/identity/handlers.ts +3 -2
- package/examples/notes-service/src/identity/users.ts +2 -1
- package/examples/notes-service/src/identity/verifier.ts +12 -6
- package/examples/notes-service/src/notes/Notes.ts +46 -22
- package/examples/notes-service/src/notes/handlers.ts +18 -9
- package/examples/notes-service/src/notes/store.ts +70 -43
- package/examples/notes-service/src/routes.ts +7 -5
- package/examples/notes-service/src/server.ts +12 -5
- package/examples/notes-service/src/status/Status.ts +1 -1
- package/examples/notes-service/src/status/handlers.ts +5 -3
- package/examples/notes-service/src/stdio.ts +7 -3
- package/examples/notes-service/src/worker.ts +6 -3
- package/examples/notes-service/test/commands.test.ts +164 -91
- package/examples/notes-service/test/notes/as.ts +3 -1
- package/examples/notes-service/test/notes/idempotency.test.ts +97 -67
- package/examples/notes-service/test/notes/in-process.test.ts +26 -15
- package/examples/notes-service/test/notes/notes.test.ts +49 -31
- package/examples/notes-service/test/routes.test.ts +106 -71
- package/examples/notes-service/test/worker.test.ts +21 -13
- package/examples/quickstart/README.md +2 -6
- package/examples/quickstart/src/Api.ts +1 -0
- package/examples/quickstart/src/client.ts +6 -3
- package/examples/quickstart/src/greeting/Greeting.ts +1 -1
- package/examples/quickstart/src/greeting/handlers.ts +5 -2
- package/examples/quickstart/src/routes.ts +3 -2
- package/examples/quickstart/src/server.ts +6 -2
- package/examples/quickstart/test/routes.test.ts +3 -2
- package/package.json +10 -4
package/docs/setup.md
CHANGED
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
# Setup
|
|
2
2
|
|
|
3
|
-
Install the package, import its modules, write the smallest program, lay out an application and
|
|
4
|
-
bundle a browser client.
|
|
3
|
+
Install the package, import its modules, write the smallest program, lay out an application and bundle a browser client.
|
|
5
4
|
|
|
6
5
|
A message the library throws says its fix; for a compiler diagnostic or Effect's own error, search [setup-failures.md](setup-failures.md).
|
|
7
6
|
|
|
8
7
|
## Install
|
|
9
8
|
|
|
10
9
|
```sh
|
|
11
|
-
pnpm add @gjermundgaraba/effect-actions effect@~4.0.
|
|
10
|
+
pnpm add @gjermundgaraba/effect-actions effect@~4.0.5
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
| Need
|
|
15
|
-
|
|
|
16
|
-
| Node HTTP server, stdio host or CLI | `@effect/platform-node@~4.0.
|
|
17
|
-
| Tests
|
|
18
|
-
| Type checking
|
|
19
|
-
| Node's types, for `node:` imports
|
|
20
|
-
| Running a TypeScript entrypoint
|
|
13
|
+
| Need | Add |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Node HTTP server, stdio host or CLI | `@effect/platform-node@~4.0.5` |
|
|
16
|
+
| Tests | `vitest@^5` and `@effect/vitest@~4.0.5`, as dev dependencies |
|
|
17
|
+
| Type checking | `typescript@^7.0.2`, as a dev dependency |
|
|
18
|
+
| Node's types, for `node:` imports | `@types/node`, as a dev dependency |
|
|
19
|
+
| Running a TypeScript entrypoint | `tsx`, as a dev dependency: `node --import tsx <entrypoint>.ts` |
|
|
21
20
|
|
|
22
|
-
The `effect` peer admits the 4.0.x patches from the oldest whose APIs the package uses, and no
|
|
23
|
-
minor release: a minor release may change the unstable Effect modules the surfaces build on.
|
|
21
|
+
The `effect` peer admits the 4.0.x patches from the oldest whose APIs the package uses, and no minor release: a minor release may change the unstable Effect modules the surfaces build on.
|
|
24
22
|
|
|
25
|
-
Keep the tilde in `package.json`: `^4.0.
|
|
26
|
-
release outside the peer range. `npm install effect@~4.0.4` keeps it.
|
|
23
|
+
Keep the tilde in `package.json`: `^4.0.5`, which npm writes for `effect@4.0.5`, admits a minor release outside the peer range. `npm install effect@~4.0.5` keeps it.
|
|
27
24
|
|
|
28
|
-
Node `>=26`: the package's `engines`, the Node the library is developed and tested on. TypeScript 7 or newer. ESM only. The repository checks the
|
|
29
|
-
examples with a stricter `tsconfig.json`; this is the minimum a project needs. The package's modules resolve only through its
|
|
30
|
-
`exports`, which `"module": "nodenext"` reads, as a bundler project's
|
|
31
|
-
`"moduleResolution": "bundler"` does. `"strict": true` is required, by Effect:
|
|
25
|
+
Node `>=26.12.0`: the package's `engines`, the Node the library is developed and tested on. TypeScript 7 or newer. ESM only. The repository checks the examples with a stricter `tsconfig.json`; this is the minimum a project needs. The package's modules resolve only through its `exports`, which `"module": "nodenext"` reads, as a bundler project's `"moduleResolution": "bundler"` does. `"strict": true` is required, by Effect:
|
|
32
26
|
|
|
33
27
|
```json
|
|
34
28
|
{
|
|
@@ -47,24 +41,17 @@ examples with a stricter `tsconfig.json`; this is the minimum a project needs. T
|
|
|
47
41
|
|
|
48
42
|
## Entry points
|
|
49
43
|
|
|
50
|
-
There is no package root. Import one module per subpath, as a namespace,
|
|
51
|
-
`import * as Action from "@gjermundgaraba/effect-actions/Action"`.
|
|
52
|
-
The modules: `Action`, `ActionHttp`, `ActionMcp`, `ActionRpc`, `ActionToolkit`, `ActionCli`,
|
|
53
|
-
`Authentication`, `Testing`.
|
|
44
|
+
There is no package root. Import one module per subpath, as a namespace, `import * as Action from "@gjermundgaraba/effect-actions/Action"`. The modules: `Action`, `Event`, `ActionHttp`, `ActionMcp`, `ActionRpc`, `ActionToolkit`, `ActionCli`, `Authentication`, `Webhook`, `Testing`.
|
|
54
45
|
|
|
55
|
-
Effect's own modules are subpaths of `effect` too: `effect/http` (`HttpRouter`, `HttpClient`,
|
|
56
|
-
`FetchHttpClient`), `effect/http-api` (`OpenApi`, `HttpApiMiddleware`), `effect/rpc`
|
|
57
|
-
(`RpcServer`, `RpcClient`), `effect/ai` (`LanguageModel`, `Chat`), `effect/cli` (`Command`) and
|
|
58
|
-
`effect/socket` (`Socket`).
|
|
46
|
+
Effect's own modules are subpaths of `effect` too: `effect/http` (`HttpRouter`, `HttpClient`, `FetchHttpClient`), `effect/http-api` (`OpenApi`, `HttpApiMiddleware`), `effect/rpc` (`RpcServer`, `RpcClient`), `effect/ai` (`LanguageModel`, `Chat`), `effect/cli` (`Command`) and `effect/socket` (`Socket`).
|
|
59
47
|
|
|
60
48
|
## Minimal program
|
|
61
49
|
|
|
62
|
-
One action, served as a JSON route and as an MCP tool: four declarations, each in a file of
|
|
63
|
-
[quickstart/src/](../examples/quickstart/src/).
|
|
50
|
+
One action, served as a JSON route and as an MCP tool: four declarations, each in a file of [quickstart/src/](../examples/quickstart/src/).
|
|
64
51
|
|
|
65
52
|
```ts example=quickstart/src/greeting/Greeting.ts
|
|
66
|
-
import { Schema } from "effect";
|
|
67
53
|
import * as Action from "@gjermundgaraba/effect-actions/Action";
|
|
54
|
+
import { Schema } from "effect";
|
|
68
55
|
|
|
69
56
|
// A contract: what the action takes and answers, whether it changes anything, and who may
|
|
70
57
|
// call it. It imports no server code, so any client can import it, a browser page included.
|
|
@@ -78,16 +65,20 @@ export const Greet = Action.make("greet", {
|
|
|
78
65
|
```
|
|
79
66
|
|
|
80
67
|
```ts example=quickstart/src/greeting/handlers.ts
|
|
81
|
-
import { Effect } from "effect";
|
|
82
68
|
import * as Action from "@gjermundgaraba/effect-actions/Action";
|
|
69
|
+
import { Effect } from "effect";
|
|
70
|
+
|
|
83
71
|
import { Greet } from "./Greeting.js";
|
|
84
72
|
|
|
85
73
|
// The implementation: the contract bound to its handler. Every surface takes this one value.
|
|
86
|
-
export const greeting = Action.implement(Greet, ({ name }) =>
|
|
74
|
+
export const greeting = Action.implement(Greet, ({ name }) =>
|
|
75
|
+
Effect.succeed(`Hello, ${name}!`)
|
|
76
|
+
);
|
|
87
77
|
```
|
|
88
78
|
|
|
89
79
|
```ts example=quickstart/src/Api.ts
|
|
90
80
|
import * as ActionHttp from "@gjermundgaraba/effect-actions/ActionHttp";
|
|
81
|
+
|
|
91
82
|
import { Greet } from "./greeting/Greeting.js";
|
|
92
83
|
|
|
93
84
|
// The HTTP binding: `POST /api/greet`. The server serves it and the client calls it.
|
|
@@ -95,41 +86,30 @@ export const Http = ActionHttp.make([Greet]);
|
|
|
95
86
|
```
|
|
96
87
|
|
|
97
88
|
```ts example=quickstart/src/routes.ts
|
|
98
|
-
import { Layer } from "effect";
|
|
99
89
|
import * as ActionHttp from "@gjermundgaraba/effect-actions/ActionHttp";
|
|
100
90
|
import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
|
|
91
|
+
import { Layer } from "effect";
|
|
92
|
+
|
|
101
93
|
import { Http } from "./Api.js";
|
|
102
94
|
import { greeting } from "./greeting/handlers.js";
|
|
103
95
|
|
|
104
96
|
// One implementation, two surfaces: a JSON route and an MCP tool at `/mcp`.
|
|
105
97
|
export const routes = Layer.mergeAll(
|
|
106
98
|
ActionHttp.layer(Http, greeting),
|
|
107
|
-
ActionMcp.layerHttp(greeting, { name: "greetings", version: "1.0.0" })
|
|
99
|
+
ActionMcp.layerHttp(greeting, { name: "greetings", version: "1.0.0" })
|
|
108
100
|
);
|
|
109
101
|
```
|
|
110
102
|
|
|
111
|
-
Serve `routes` with `HttpRouter.serve` and a platform server layer
|
|
112
|
-
([quickstart/src/server.ts](../examples/quickstart/src/server.ts)), call the route with the
|
|
113
|
-
binding's typed client ([quickstart/src/client.ts](../examples/quickstart/src/client.ts)), and
|
|
114
|
-
test both in memory ([quickstart/test/routes.test.ts](../examples/quickstart/test/routes.test.ts)).
|
|
115
|
-
[notes-service](../examples/notes-service/) is this program grown into a service, in the same
|
|
116
|
-
layout.
|
|
103
|
+
Serve `routes` with `HttpRouter.serve` and a platform server layer ([quickstart/src/server.ts](../examples/quickstart/src/server.ts)), call the route with the binding's typed client ([quickstart/src/client.ts](../examples/quickstart/src/client.ts)), and test both in memory ([quickstart/test/routes.test.ts](../examples/quickstart/test/routes.test.ts)). [notes-service](../examples/notes-service/) is this program grown into a service, in the same layout.
|
|
117
104
|
|
|
118
105
|
## Lay out an application
|
|
119
106
|
|
|
120
|
-
Copy the examples' layout:
|
|
121
|
-
[examples/README.md](../examples/README.md#how-they-are-laid-out).
|
|
107
|
+
Copy the examples' layout: [examples/README.md](../examples/README.md#how-they-are-laid-out).
|
|
122
108
|
|
|
123
109
|
## Bundle for a browser
|
|
124
110
|
|
|
125
|
-
Import into the page only the contract files, the binding and a client module of your own:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
- `
|
|
130
|
-
use nothing of Node or a server platform.
|
|
131
|
-
- Keep `Action.implement` and `Authentication.layer` out of every file the page imports: a
|
|
132
|
-
bundler keeps such a call, and what its handlers or verifier import, even unused.
|
|
133
|
-
- Those four modules import Effect through `effect`, `effect/http` and `effect/http-api` alone,
|
|
134
|
-
and `ActionRpc` through `effect/rpc` too. A page loading Effect from an import map serves
|
|
135
|
-
those, and `effect/socket` for a WebSocket client.
|
|
111
|
+
Import into the page only the contract files, the binding and a client module of your own: [notes-service/src/browser.ts](../examples/notes-service/src/browser.ts) over HTTP, with promises, [live-rpc/src/browser.ts](../examples/live-rpc/src/browser.ts) over a WebSocket.
|
|
112
|
+
|
|
113
|
+
- `Action`, `Event`, `ActionHttp`, `ActionRpc` and `Authentication` are the browser-safe modules: they use nothing of Node or a server platform.
|
|
114
|
+
- Keep `Action.implement` and `Authentication.layer` out of every file the page imports: a bundler keeps such a call, and what its handlers or verifier import, even unused.
|
|
115
|
+
- Those five modules import Effect through `effect`, `effect/http` and `effect/http-api` alone, and `ActionRpc` through `effect/rpc` too. A page loading Effect from an import map serves those, and `effect/socket` for a WebSocket client.
|
package/examples/README.md
CHANGED
|
@@ -1,33 +1,23 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
Complete applications built with `@gjermundgaraba/effect-actions`. Each is a directory you can
|
|
4
|
-
run, with its own tests and a README that says what it shows and where. They are type-checked,
|
|
5
|
-
linted and tested with the library, and they ship in the package, at
|
|
6
|
-
`node_modules/@gjermundgaraba/effect-actions/examples`.
|
|
3
|
+
Complete applications built with `@gjermundgaraba/effect-actions`. Each is a directory you can run, with its own tests and a README that says what it shows and where. They are type-checked, linted and tested with the library, and they ship in the package, at `node_modules/@gjermundgaraba/effect-actions/examples`.
|
|
7
4
|
|
|
8
|
-
[quickstart](quickstart/), [notes-service](notes-service/), [mcp-tools](mcp-tools/),
|
|
9
|
-
[assistant](assistant/), [live-rpc](live-rpc/) and [kitchen-sink](kitchen-sink/): which to start
|
|
10
|
-
from, by what you are building, is the table in [docs/README.md](../docs/README.md#examples).
|
|
5
|
+
[quickstart](quickstart/), [notes-service](notes-service/), [mcp-tools](mcp-tools/), [assistant](assistant/), [live-rpc](live-rpc/), [build-events](build-events/) and [kitchen-sink](kitchen-sink/): which to start from, by what you are building, is the table in [docs/README.md](../docs/README.md#examples).
|
|
11
6
|
|
|
12
7
|
## Run one
|
|
13
8
|
|
|
14
|
-
From the repository root, after `vp install`, each README's commands work as written, for
|
|
15
|
-
example:
|
|
9
|
+
From the repository root, after `vp install`, each README's commands work as written, for example:
|
|
16
10
|
|
|
17
11
|
```sh
|
|
18
12
|
node --import tsx examples/notes-service/src/server.ts
|
|
19
13
|
vp test examples/notes-service
|
|
20
14
|
```
|
|
21
15
|
|
|
22
|
-
In a project that installs the package, copy an example's directory out of `node_modules` and
|
|
23
|
-
run it the same way: the examples import the package by its name. They need `tsx`,
|
|
24
|
-
`@effect/platform-node` and `@types/node` beside `effect`, and `vitest` with `@effect/vitest` for
|
|
25
|
-
their tests.
|
|
16
|
+
In a project that installs the package, copy an example's directory out of `node_modules` and run it the same way: the examples import the package by its name. They need `tsx`, `@effect/platform-node` and `@types/node` beside `effect`, and `vitest` with `@effect/vitest` for their tests.
|
|
26
17
|
|
|
27
18
|
## How they are laid out
|
|
28
19
|
|
|
29
|
-
Every example follows one layout, which is the one we recommend for an application. Each has
|
|
30
|
-
the parts of it that it needs:
|
|
20
|
+
Every example follows one layout, which is the one we recommend for an application. Each has the parts of it that it needs:
|
|
31
21
|
|
|
32
22
|
```text
|
|
33
23
|
<example>/
|
|
@@ -46,27 +36,12 @@ the parts of it that it needs:
|
|
|
46
36
|
└── routes.test.ts what the surfaces add, called in memory
|
|
47
37
|
```
|
|
48
38
|
|
|
49
|
-
- **Folders are domains, not mechanisms.** Everything about notes is in `notes/`; there is no
|
|
50
|
-
|
|
51
|
-
- **
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
a disk that ignores case.
|
|
55
|
-
- **Technology stays out of domains.** A domain's service declares what its handlers need; a
|
|
56
|
-
Layer that reaches a file system, a database or another service's API is an adapter, in
|
|
57
|
-
`platform/`, named for what it reaches (`tasks-file.ts`, `print-farm.ts`), and turns that
|
|
58
|
-
technology's failures into the domain's declared errors, or into defects where no caller can
|
|
59
|
-
act on them. A Layer in memory, which reaches
|
|
60
|
-
nothing, stays beside its service.
|
|
61
|
-
- **An entrypoint is where implementations are chosen**: which store, which platform, and on a
|
|
62
|
-
local surface which caller. Nothing below it knows what it got, so a test chooses its own.
|
|
63
|
-
- **Tests mirror the source.** A domain's tests sit in `test/<domain>/`, most calling its
|
|
64
|
-
implementation in process with `Action.client`; a test of a root file, such as `routes.ts`,
|
|
65
|
-
sits beside them and calls the real routes in memory with `Testing.layer`. One opens a port:
|
|
66
|
-
live-rpc's `server.test.ts`, for the WebSocket no in-memory test reaches.
|
|
39
|
+
- **Folders are domains, not mechanisms.** Everything about notes is in `notes/`; there is no `contracts/`, `handlers/` or `tools/` folder. `platform/`, the adapters, is the one exception.
|
|
40
|
+
- **The PascalCase file in a domain is its contract.** It imports no server code, so a browser or a CLI can import it. Lowercase files beside it run on the server, each named for its role (`handlers.ts`, `store.ts`), never for the domain: `notes.ts` beside `Notes.ts` is one file on a disk that ignores case.
|
|
41
|
+
- **Technology stays out of domains.** A domain's service declares what its handlers need; a Layer that reaches a file system, a database or another service's API is an adapter, in `platform/`, named for what it reaches (`tasks-file.ts`, `print-farm.ts`), and turns that technology's failures into the domain's declared errors, or into defects where no caller can act on them. A Layer in memory, which reaches nothing, stays beside its service.
|
|
42
|
+
- **An entrypoint is where implementations are chosen**: which store, which platform, and on a local surface which caller. Nothing below it knows what it got, so a test chooses its own.
|
|
43
|
+
- **Tests mirror the source.** A domain's tests sit in `test/<domain>/`, most calling its implementation in process with `Action.client`; a test of a root file, such as `routes.ts`, sits beside them and calls the real routes in memory with `Testing.layer`. One opens a port: live-rpc's `server.test.ts`, for the WebSocket no in-memory test reaches.
|
|
67
44
|
|
|
68
|
-
In a monorepo, the contract files and the binding are the package that the server, the CLI and
|
|
69
|
-
the website all depend on; the rest is the server.
|
|
45
|
+
In a monorepo, the contract files and the binding are the package that the server, the CLI and the website all depend on; the rest is the server.
|
|
70
46
|
|
|
71
|
-
Every token, user and store in these examples is a demo. Each place that must change before a
|
|
72
|
-
deployment is marked `DEMO ONLY`.
|
|
47
|
+
Every token, user and store in these examples is a demo. Each place that must change before a deployment is marked `DEMO ONLY`.
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
# assistant
|
|
2
2
|
|
|
3
|
-
An application with no server: its actions run in the process that calls them. A team's task
|
|
4
|
-
list is worked on by a model, through tools, and by whoever runs the machine, through commands.
|
|
5
|
-
Both are the same implementations behind the same authorization. It opens no port.
|
|
3
|
+
An application with no server: its actions run in the process that calls them. A team's task list is worked on by a model, through tools, and by whoever runs the machine, through commands. Both are the same implementations behind the same authorization. It opens no port.
|
|
6
4
|
|
|
7
5
|
## Run it
|
|
8
6
|
|
|
9
|
-
From the repository root, after `vp install` (from an installed package, see
|
|
10
|
-
[Run one](../README.md#run-one)):
|
|
7
|
+
From the repository root, after `vp install` (from an installed package, see [Run one](../README.md#run-one)):
|
|
11
8
|
|
|
12
9
|
```sh
|
|
13
10
|
node --import tsx examples/assistant/src/agent.ts # a scripted model: no API key
|
|
@@ -22,9 +19,7 @@ node --import tsx examples/assistant/src/cli.ts list-tasks -a grace --json 2>/de
|
|
|
22
19
|
vp test examples/assistant
|
|
23
20
|
```
|
|
24
21
|
|
|
25
|
-
The agent and each command are separate processes. They share one JSON file, named by
|
|
26
|
-
`ASSISTANT_TASKS` and kept in the system's temporary directory by default. Running the agent
|
|
27
|
-
again adds the same tasks again: delete the file to start over.
|
|
22
|
+
The agent and each command are separate processes. They share one JSON file, named by `ASSISTANT_TASKS` and kept in the system's temporary directory by default. Running the agent again adds the same tasks again: delete the file to start over.
|
|
28
23
|
|
|
29
24
|
## Layout
|
|
30
25
|
|
|
@@ -57,22 +52,22 @@ test/
|
|
|
57
52
|
|
|
58
53
|
## Where to look
|
|
59
54
|
|
|
60
|
-
| To see
|
|
61
|
-
|
|
|
62
|
-
| Implementations as a native Effect AI `Toolkit`
|
|
63
|
-
| Native tool titles in the approval display
|
|
64
|
-
| One approval rule reading the call and its caller
|
|
65
|
-
| A turn run as a member, and an approval answered through a `Chat`
|
|
66
|
-
| Where a real `LanguageModel` plugs in
|
|
67
|
-
| Local commands with `positional`, `aliases`, `render`, `stdin`
|
|
68
|
-
| Services provided on the command, and the host naming the caller
|
|
69
|
-
| A command's result alone on stdout, its handler's logs on stderr
|
|
70
|
-
| `logToStderr` moving a defect's report from stdout to stderr
|
|
71
|
-
| An identity only the host builds: the trusted operator
|
|
72
|
-
| An authorizer built once from a service, reading the caller per call
|
|
55
|
+
| To see | Read |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| Implementations as a native Effect AI `Toolkit` | `src/toolkit.ts` |
|
|
58
|
+
| Native tool titles in the approval display | `src/toolkit.ts`, `src/agent.ts` |
|
|
59
|
+
| One approval rule reading the call and its caller | `src/toolkit.ts` |
|
|
60
|
+
| A turn run as a member, and an approval answered through a `Chat` | `src/agent.ts` |
|
|
61
|
+
| Where a real `LanguageModel` plugs in | `src/agent.ts`, `src/scripted.ts` |
|
|
62
|
+
| Local commands with `positional`, `aliases`, `render`, `stdin` | `src/commands.ts` |
|
|
63
|
+
| Services provided on the command, and the host naming the caller | `src/cli.ts` |
|
|
64
|
+
| A command's result alone on stdout, its handler's logs on stderr | `src/cli.ts`, `src/platform/tasks-file.ts` |
|
|
65
|
+
| `logToStderr` moving a defect's report from stdout to stderr | `src/cli.ts` |
|
|
66
|
+
| An identity only the host builds: the trusted operator | `src/identity/Identity.ts` |
|
|
67
|
+
| An authorizer built once from a service, reading the caller per call | `src/identity/authorize.ts` |
|
|
73
68
|
| One service with two Layers: a file for entrypoints, memory for tests | `src/tasks/store.ts`, `src/platform/tasks-file.ts` |
|
|
74
|
-
| Changes serialized, since a turn's tool calls run concurrently
|
|
75
|
-
| Testing handlers and authorization, each caller around its calls
|
|
76
|
-
| Testing a Layer over a temporary file, its path set through config
|
|
77
|
-
| Testing a tool call, an approval required, an approval answered
|
|
78
|
-
| Testing a command's syntax and output without a process
|
|
69
|
+
| Changes serialized, since a turn's tool calls run concurrently | `src/tasks/store.ts` |
|
|
70
|
+
| Testing handlers and authorization, each caller around its calls | `test/tasks/tasks.test.ts` |
|
|
71
|
+
| Testing a Layer over a temporary file, its path set through config | `test/platform/tasks-file.test.ts` |
|
|
72
|
+
| Testing a tool call, an approval required, an approval answered | `test/toolkit.test.ts` |
|
|
73
|
+
| Testing a command's syntax and output without a process | `test/commands.test.ts` |
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
2
2
|
import { Console, Context, Effect, Layer } from "effect";
|
|
3
|
-
import { Chat,
|
|
3
|
+
import { Chat, Tool } from "effect/ai";
|
|
4
|
+
import type { LanguageModel, Prompt } from "effect/ai";
|
|
5
|
+
|
|
4
6
|
import type { Member } from "./identity/Identity.js";
|
|
5
7
|
import { members } from "./identity/members.js";
|
|
6
8
|
import { Permissions } from "./identity/permissions.js";
|
|
7
|
-
import { scripted } from "./scripted.js";
|
|
8
9
|
import { tasksFile } from "./platform/tasks-file.js";
|
|
10
|
+
import { scripted } from "./scripted.js";
|
|
9
11
|
import { actingFor, layer, toolkit } from "./toolkit.js";
|
|
10
12
|
|
|
11
13
|
// Entrypoint: `node --import tsx examples/assistant/src/agent.ts`.
|
|
@@ -13,29 +15,39 @@ import { actingFor, layer, toolkit } from "./toolkit.js";
|
|
|
13
15
|
// port. Every tool call passes through the same `authorize` and handlers as the commands.
|
|
14
16
|
|
|
15
17
|
// What a turn answers with: its text, the calls the model made and what they returned.
|
|
16
|
-
type Response = LanguageModel.GenerateTextResponse<
|
|
18
|
+
type Response = LanguageModel.GenerateTextResponse<
|
|
19
|
+
typeof toolkit.tools,
|
|
20
|
+
"opaque"
|
|
21
|
+
>;
|
|
17
22
|
|
|
18
23
|
// Printing only.
|
|
19
24
|
const report = (member: Member) => (response: Response) => {
|
|
20
|
-
const waiting =
|
|
21
|
-
|
|
25
|
+
const waiting = new Set(
|
|
26
|
+
response.content.flatMap((part) =>
|
|
27
|
+
part.type === "tool-approval-request" ? [part.toolCallId] : []
|
|
28
|
+
)
|
|
22
29
|
);
|
|
23
30
|
|
|
24
31
|
return Console.log(
|
|
25
32
|
[
|
|
26
33
|
`As ${member.id}:`,
|
|
27
34
|
...response.toolResults.map(
|
|
28
|
-
({ name, encodedResult }) =>
|
|
35
|
+
({ name, encodedResult }) =>
|
|
36
|
+
` ${name} -> ${JSON.stringify(encodedResult)}`
|
|
29
37
|
),
|
|
30
38
|
...response.toolCalls.flatMap(({ id, name, params }) => {
|
|
31
|
-
const title = Context.getOrElse(
|
|
39
|
+
const title = Context.getOrElse(
|
|
40
|
+
toolkit.tools[name].annotations,
|
|
41
|
+
Tool.Title,
|
|
42
|
+
() => name
|
|
43
|
+
);
|
|
32
44
|
|
|
33
|
-
return waiting.
|
|
45
|
+
return waiting.has(id)
|
|
34
46
|
? [` ${title} ${JSON.stringify(params)} waits for approval`]
|
|
35
47
|
: [];
|
|
36
48
|
}),
|
|
37
49
|
...(response.text === "" ? [] : [` "${response.text}"`]),
|
|
38
|
-
].join("\n")
|
|
50
|
+
].join("\n")
|
|
39
51
|
);
|
|
40
52
|
};
|
|
41
53
|
|
|
@@ -43,7 +55,9 @@ const report = (member: Member) => (response: Response) => {
|
|
|
43
55
|
// provided around the whole turn, so the approval rule, `authorize` and the handlers of every
|
|
44
56
|
// call in it see that member.
|
|
45
57
|
const turn = (chat: Chat.Chat, member: Member, prompt: Prompt.RawInput) =>
|
|
46
|
-
chat
|
|
58
|
+
chat
|
|
59
|
+
.generateText({ prompt, toolkit })
|
|
60
|
+
.pipe(actingFor(member), Effect.tap(report(member)));
|
|
47
61
|
|
|
48
62
|
const conversations = Effect.gen(function* () {
|
|
49
63
|
// A `Chat` keeps the conversation's history, and approval depends on it: an answer is matched
|
|
@@ -54,7 +68,11 @@ const conversations = Effect.gen(function* () {
|
|
|
54
68
|
|
|
55
69
|
// Ada's own task is added at once. The task for Grace is not run: the turn ends with a
|
|
56
70
|
// request for approval in place of that call's result.
|
|
57
|
-
const asked = yield* turn(
|
|
71
|
+
const asked = yield* turn(
|
|
72
|
+
withAda,
|
|
73
|
+
members.ada,
|
|
74
|
+
"Add the venue for me, invitations for Grace."
|
|
75
|
+
);
|
|
58
76
|
|
|
59
77
|
// A real host shows each request to the person and waits. Their answers start the next turn,
|
|
60
78
|
// in which an approved call runs before the model is asked again. It runs as whoever that
|
|
@@ -64,32 +82,56 @@ const conversations = Effect.gen(function* () {
|
|
|
64
82
|
role: "tool",
|
|
65
83
|
content: asked.content.flatMap((part) =>
|
|
66
84
|
part.type === "tool-approval-request"
|
|
67
|
-
? [
|
|
68
|
-
|
|
85
|
+
? [
|
|
86
|
+
{
|
|
87
|
+
type: "tool-approval-response",
|
|
88
|
+
approvalId: part.approvalId,
|
|
89
|
+
approved: true,
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
: []
|
|
69
93
|
),
|
|
70
94
|
},
|
|
71
95
|
]);
|
|
72
96
|
|
|
73
97
|
// The same toolkit, another member. The guest may read, so the write comes back refused.
|
|
74
|
-
yield* turn(
|
|
98
|
+
yield* turn(
|
|
99
|
+
yield* Chat.empty,
|
|
100
|
+
members.guest,
|
|
101
|
+
"What is there to do? Add lunch for me."
|
|
102
|
+
);
|
|
75
103
|
});
|
|
76
104
|
|
|
77
105
|
const adding = (id: string, title: string, assignee: string) =>
|
|
78
|
-
({
|
|
106
|
+
({
|
|
107
|
+
type: "tool-call",
|
|
108
|
+
id,
|
|
109
|
+
name: "addTask",
|
|
110
|
+
params: { title, assignee },
|
|
111
|
+
}) as const;
|
|
79
112
|
|
|
80
113
|
// DEMO ONLY: what the model answers, request by request, so this runs without an API key.
|
|
81
114
|
const model = scripted(
|
|
82
|
-
[
|
|
115
|
+
[
|
|
116
|
+
adding("1", "Book the venue", "ada"),
|
|
117
|
+
adding("2", "Send invitations", "grace"),
|
|
118
|
+
],
|
|
83
119
|
[{ type: "text", text: "Both tasks are on the list." }],
|
|
84
|
-
[
|
|
120
|
+
[
|
|
121
|
+
{ type: "tool-call", id: "3", name: "listTasks", params: {} },
|
|
122
|
+
adding("4", "Lunch", "guest"),
|
|
123
|
+
]
|
|
85
124
|
);
|
|
86
125
|
|
|
87
126
|
conversations.pipe(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
127
|
+
Effect.provide(
|
|
128
|
+
Layer.mergeAll(
|
|
129
|
+
// A real `LanguageModel` plugs in here: the layer of a provider's model, with the layer
|
|
130
|
+
// of that provider's client, in place of the scripted one. Nothing else changes.
|
|
131
|
+
model,
|
|
132
|
+
// Startup services only: where tasks and permissions are kept.
|
|
133
|
+
layer.pipe(Layer.provide([tasksFile, Permissions.layerMemory]))
|
|
134
|
+
).pipe(Layer.provideMerge(NodeServices.layer))
|
|
135
|
+
),
|
|
136
|
+
NodeRuntime.runMain()
|
|
95
137
|
);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { userInfo } from "node:os";
|
|
2
|
+
|
|
2
3
|
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
4
|
+
import * as ActionCli from "@gjermundgaraba/effect-actions/ActionCli";
|
|
3
5
|
import { Effect, Layer } from "effect";
|
|
4
6
|
import { Command } from "effect/cli";
|
|
5
|
-
|
|
7
|
+
|
|
6
8
|
import { commands } from "./commands.js";
|
|
7
|
-
import { CurrentCaller
|
|
9
|
+
import { CurrentCaller } from "./identity/Identity.js";
|
|
10
|
+
import type { Operator } from "./identity/Identity.js";
|
|
8
11
|
import { Permissions } from "./identity/permissions.js";
|
|
9
12
|
import { tasksFile } from "./platform/tasks-file.js";
|
|
10
13
|
|
|
@@ -16,7 +19,7 @@ const operator: Operator = { kind: "operator", id: userInfo().username };
|
|
|
16
19
|
|
|
17
20
|
const assistant = commands.pipe(
|
|
18
21
|
Command.provide(Layer.mergeAll(tasksFile, Permissions.layerMemory)),
|
|
19
|
-
Command.provideSync(CurrentCaller, operator)
|
|
22
|
+
Command.provideSync(CurrentCaller, operator)
|
|
20
23
|
);
|
|
21
24
|
|
|
22
25
|
// A command's defect, such as a tasks file that does not decode, `runMain` reports on stdout.
|
|
@@ -25,5 +28,5 @@ const assistant = commands.pipe(
|
|
|
25
28
|
Command.run(assistant, { version: "1.0.0" }).pipe(
|
|
26
29
|
Effect.provide(NodeServices.layer),
|
|
27
30
|
ActionCli.logToStderr,
|
|
28
|
-
NodeRuntime.runMain
|
|
31
|
+
NodeRuntime.runMain()
|
|
29
32
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as ActionCli from "@gjermundgaraba/effect-actions/ActionCli";
|
|
2
|
+
|
|
2
3
|
import { identity } from "./identity/handlers.js";
|
|
3
4
|
import { tasks } from "./tasks/handlers.js";
|
|
4
5
|
|
|
@@ -12,11 +13,15 @@ export const commands = ActionCli.make([tasks, identity], {
|
|
|
12
13
|
// per task for a person, and adds `--json` for the JSON a script reads. `description` is the
|
|
13
14
|
// command's help, saying what a line holds; the action's stays what a model reads.
|
|
14
15
|
listTasks: {
|
|
15
|
-
description:
|
|
16
|
+
description:
|
|
17
|
+
"Print one line per task: [x] when done, then its id, title and assignee.",
|
|
16
18
|
aliases: { assignee: "a" },
|
|
17
|
-
render: ({ tasks }) =>
|
|
18
|
-
|
|
19
|
-
.map(
|
|
19
|
+
render: ({ tasks: listed }) =>
|
|
20
|
+
listed
|
|
21
|
+
.map(
|
|
22
|
+
(task) =>
|
|
23
|
+
`${task.done ? "[x]" : "[ ]"} ${task.id} ${task.title} (${task.assignee})`
|
|
24
|
+
)
|
|
20
25
|
.join("\n"),
|
|
21
26
|
},
|
|
22
27
|
// `assistant complete-task 2`: an argument instead of `--id 2`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Context, Schema } from "effect";
|
|
2
1
|
import * as Action from "@gjermundgaraba/effect-actions/Action";
|
|
2
|
+
import { Context, Schema } from "effect";
|
|
3
3
|
|
|
4
4
|
/** A teammate: what a sign-in can name. The assistant acts for one in each turn. */
|
|
5
5
|
export interface Member {
|
|
@@ -21,9 +21,10 @@ export interface Operator {
|
|
|
21
21
|
* authenticates anyone: the host provides it around each call, as the member a model acts for
|
|
22
22
|
* or as the operator at the terminal.
|
|
23
23
|
*/
|
|
24
|
-
export class CurrentCaller extends Context.Service<
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
export class CurrentCaller extends Context.Service<
|
|
25
|
+
CurrentCaller,
|
|
26
|
+
Member | Operator
|
|
27
|
+
>()("assistant/CurrentCaller") {}
|
|
27
28
|
|
|
28
29
|
export const WhoAmI = Action.make("whoAmI", {
|
|
29
30
|
description: "Show who the assistant is acting for.",
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
1
|
import * as Action from "@gjermundgaraba/effect-actions/Action";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
|
|
3
4
|
import { CurrentCaller } from "./Identity.js";
|
|
4
|
-
import {
|
|
5
|
+
import { Permissions } from "./permissions.js";
|
|
6
|
+
import type { Permission } from "./permissions.js";
|
|
5
7
|
|
|
6
8
|
// The one rule, for a model's tool call and for a command alike. The permission store is a
|
|
7
9
|
// startup service, so this Effect gets it; the caller differs per call, so the function it
|
|
@@ -13,12 +15,18 @@ export const authorize = Effect.gen(function* () {
|
|
|
13
15
|
const caller = yield* CurrentCaller;
|
|
14
16
|
|
|
15
17
|
// The operator is trusted, and has no entry in the store. Only a host can name one.
|
|
16
|
-
if (caller.kind === "operator")
|
|
18
|
+
if (caller.kind === "operator") {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
17
21
|
|
|
18
|
-
const permission: Permission = action.readOnly
|
|
22
|
+
const permission: Permission = action.readOnly
|
|
23
|
+
? "tasks:read"
|
|
24
|
+
: "tasks:write";
|
|
19
25
|
|
|
20
26
|
if (!(yield* permissions.granted(caller.id)).includes(permission)) {
|
|
21
|
-
return yield* new Action.Forbidden({
|
|
27
|
+
return yield* new Action.Forbidden({
|
|
28
|
+
message: `Requires ${permission}.`,
|
|
29
|
+
});
|
|
22
30
|
}
|
|
23
31
|
});
|
|
24
32
|
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
1
|
import * as Action from "@gjermundgaraba/effect-actions/Action";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
|
|
3
4
|
import { authorize } from "./authorize.js";
|
|
4
5
|
import { CurrentCaller, WhoAmI } from "./Identity.js";
|
|
5
6
|
|
|
6
7
|
export const identity = Action.implement(
|
|
7
8
|
WhoAmI,
|
|
8
9
|
() => Effect.map(CurrentCaller, ({ id, kind }) => ({ id, kind })),
|
|
9
|
-
{ authorize }
|
|
10
|
+
{ authorize }
|
|
10
11
|
);
|
|
@@ -8,16 +8,22 @@ export type Permission = "tasks:read" | "tasks:write";
|
|
|
8
8
|
*/
|
|
9
9
|
export class Permissions extends Context.Service<
|
|
10
10
|
Permissions,
|
|
11
|
-
{
|
|
11
|
+
{
|
|
12
|
+
readonly granted: (
|
|
13
|
+
memberId: string
|
|
14
|
+
) => Effect.Effect<readonly Permission[]>;
|
|
15
|
+
}
|
|
12
16
|
>()("assistant/Permissions") {
|
|
13
17
|
// DEMO ONLY: a fixed table. A real application reads its database or its directory.
|
|
14
18
|
static readonly layerMemory = Layer.sync(Permissions, () => {
|
|
15
|
-
const table = new Map<string,
|
|
19
|
+
const table = new Map<string, readonly Permission[]>([
|
|
16
20
|
["ada", ["tasks:read", "tasks:write"]],
|
|
17
21
|
["grace", ["tasks:read", "tasks:write"]],
|
|
18
22
|
["guest", ["tasks:read"]],
|
|
19
23
|
]);
|
|
20
24
|
|
|
21
|
-
return Permissions.of({
|
|
25
|
+
return Permissions.of({
|
|
26
|
+
granted: (memberId) => Effect.sync(() => table.get(memberId) ?? []),
|
|
27
|
+
});
|
|
22
28
|
});
|
|
23
29
|
}
|