@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
|
@@ -2,10 +2,11 @@ import { Config, Effect, Layer, Redacted, Schema } from "effect";
|
|
|
2
2
|
import {
|
|
3
3
|
HttpBody,
|
|
4
4
|
HttpClient,
|
|
5
|
-
type HttpClientError,
|
|
6
5
|
HttpClientRequest,
|
|
7
6
|
HttpClientResponse,
|
|
8
7
|
} from "effect/http";
|
|
8
|
+
import type { HttpClientError } from "effect/http";
|
|
9
|
+
|
|
9
10
|
import { PrintFarm } from "../printers/farm.js";
|
|
10
11
|
import { Printer, PrintFarmError } from "../printers/Printers.js";
|
|
11
12
|
|
|
@@ -13,16 +14,24 @@ const fail = (message: string) => Effect.fail(new PrintFarmError({ message }));
|
|
|
13
14
|
|
|
14
15
|
const unreadable = "The print farm sent an answer this server could not read.";
|
|
15
16
|
|
|
17
|
+
// An id is written into the path as it is: the contract admits only letters, digits,
|
|
18
|
+
// `_` and `-`, so it is always one segment. Encoding would not make other text safe:
|
|
19
|
+
// `encodeURIComponent` leaves `..` alone, and a URL parser then walks up the path.
|
|
20
|
+
const at = (id: string, rest = "") => `/api/printers/${id}${rest}`;
|
|
21
|
+
|
|
16
22
|
// The farm answers a request it refuses with a status that is not 2xx and `{ "error": "..." }`.
|
|
17
23
|
// Its reason is passed on to the model, which can act on "The printer is busy with a job".
|
|
18
24
|
const refuseUnlessOk = HttpClientResponse.matchStatus({
|
|
19
25
|
"2xx": (response) => Effect.succeed(response),
|
|
20
26
|
orElse: (response) =>
|
|
21
|
-
HttpClientResponse.schemaBodyJson(Schema.Struct({ error: Schema.String }))(
|
|
27
|
+
HttpClientResponse.schemaBodyJson(Schema.Struct({ error: Schema.String }))(
|
|
28
|
+
response
|
|
29
|
+
).pipe(
|
|
22
30
|
Effect.matchEffect({
|
|
23
|
-
onFailure: () =>
|
|
31
|
+
onFailure: () =>
|
|
32
|
+
fail(`The print farm refused the request (HTTP ${response.status}).`),
|
|
24
33
|
onSuccess: ({ error }) => fail(`The print farm refused: ${error}`),
|
|
25
|
-
})
|
|
34
|
+
})
|
|
26
35
|
),
|
|
27
36
|
});
|
|
28
37
|
|
|
@@ -30,24 +39,34 @@ const refuseUnlessOk = HttpClientResponse.matchStatus({
|
|
|
30
39
|
// answer of another shape than expected, or none in time. Nothing of the HTTP client's own
|
|
31
40
|
// errors leaves this file, so no tool can fail with something its contract does not declare.
|
|
32
41
|
const asPrintFarmError = <A>(
|
|
33
|
-
effect: Effect.Effect<
|
|
42
|
+
effect: Effect.Effect<
|
|
43
|
+
A,
|
|
44
|
+
PrintFarmError | HttpClientError.HttpClientError | Schema.SchemaError
|
|
45
|
+
>
|
|
34
46
|
) =>
|
|
35
47
|
effect.pipe(
|
|
36
|
-
Effect.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
Effect.catchTags({
|
|
49
|
+
HttpClientError: ({ response }) =>
|
|
50
|
+
fail(
|
|
51
|
+
response === undefined
|
|
52
|
+
? "The print farm could not be reached."
|
|
53
|
+
: unreadable
|
|
54
|
+
),
|
|
55
|
+
SchemaError: () => fail(unreadable),
|
|
56
|
+
}),
|
|
40
57
|
Effect.timeoutOrElse({
|
|
41
58
|
duration: "30 seconds",
|
|
42
59
|
orElse: () => fail("The print farm did not answer in time."),
|
|
43
|
-
})
|
|
60
|
+
})
|
|
44
61
|
);
|
|
45
62
|
|
|
46
63
|
const asImage = (response: HttpClientResponse.HttpClientResponse) =>
|
|
47
64
|
Effect.gen(function* () {
|
|
48
65
|
const mimeType = response.headers["content-type"];
|
|
49
66
|
|
|
50
|
-
if (mimeType === undefined || !mimeType.startsWith("image/"))
|
|
67
|
+
if (mimeType === undefined || !mimeType.startsWith("image/")) {
|
|
68
|
+
return yield* fail(unreadable);
|
|
69
|
+
}
|
|
51
70
|
|
|
52
71
|
return { data: new Uint8Array(yield* response.arrayBuffer), mimeType };
|
|
53
72
|
});
|
|
@@ -67,7 +86,7 @@ export const printFarm = Layer.effect(
|
|
|
67
86
|
|
|
68
87
|
const http = (yield* HttpClient.HttpClient).pipe(
|
|
69
88
|
HttpClient.mapRequest(HttpClientRequest.prependUrl(url.href)),
|
|
70
|
-
HttpClient.transformResponse(Effect.flatMap(refuseUnlessOk))
|
|
89
|
+
HttpClient.transformResponse(Effect.flatMap(refuseUnlessOk))
|
|
71
90
|
);
|
|
72
91
|
|
|
73
92
|
// Signs in once, here, and every call below reuses the token, kept `Redacted` so no log
|
|
@@ -81,38 +100,49 @@ export const printFarm = Layer.effect(
|
|
|
81
100
|
.pipe(
|
|
82
101
|
Effect.flatMap(
|
|
83
102
|
HttpClientResponse.schemaBodyJson(
|
|
84
|
-
Schema.Struct({ token: Schema.RedactedFromValue(Schema.String) })
|
|
85
|
-
)
|
|
103
|
+
Schema.Struct({ token: Schema.RedactedFromValue(Schema.String) })
|
|
104
|
+
)
|
|
86
105
|
),
|
|
87
|
-
asPrintFarmError
|
|
106
|
+
asPrintFarmError
|
|
88
107
|
);
|
|
89
108
|
|
|
90
|
-
yield* Effect.logInfo(
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
yield* Effect.logInfo(
|
|
110
|
+
`Signed in to the print farm at ${url.host} as ${user}.`
|
|
111
|
+
);
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
const api = HttpClient.mapRequest(
|
|
114
|
+
http,
|
|
115
|
+
HttpClientRequest.bearerToken(session.token)
|
|
116
|
+
);
|
|
98
117
|
|
|
99
118
|
const printer = (answer: ReturnType<typeof api.get>) =>
|
|
100
|
-
answer.pipe(
|
|
119
|
+
answer.pipe(
|
|
120
|
+
Effect.flatMap(HttpClientResponse.schemaBodyJson(Printer)),
|
|
121
|
+
asPrintFarmError
|
|
122
|
+
);
|
|
101
123
|
|
|
102
124
|
return PrintFarm.of({
|
|
103
125
|
printers: api.get("/api/printers").pipe(
|
|
104
126
|
Effect.flatMap(
|
|
105
|
-
HttpClientResponse.schemaBodyJson(
|
|
127
|
+
HttpClientResponse.schemaBodyJson(
|
|
128
|
+
Schema.Struct({ printers: Schema.Array(Printer) })
|
|
129
|
+
)
|
|
106
130
|
),
|
|
107
131
|
Effect.map(({ printers }) => printers),
|
|
108
|
-
asPrintFarmError
|
|
132
|
+
asPrintFarmError
|
|
109
133
|
),
|
|
110
134
|
printer: (id) => printer(api.get(at(id))),
|
|
111
|
-
snapshot: (id) =>
|
|
135
|
+
snapshot: (id) =>
|
|
136
|
+
api
|
|
137
|
+
.get(at(id, "/camera"))
|
|
138
|
+
.pipe(Effect.flatMap(asImage), asPrintFarmError),
|
|
112
139
|
start: (id, file) =>
|
|
113
|
-
printer(
|
|
114
|
-
|
|
140
|
+
printer(
|
|
141
|
+
api.post(at(id, "/job"), { body: HttpBody.jsonUnsafe({ file }) })
|
|
142
|
+
),
|
|
143
|
+
setPaused: (id, paused) =>
|
|
144
|
+
printer(api.post(at(id, paused ? "/pause" : "/resume"))),
|
|
115
145
|
cancel: (id) => printer(api.del(at(id, "/job"))),
|
|
116
146
|
});
|
|
117
|
-
})
|
|
147
|
+
})
|
|
118
148
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Schema } from "effect";
|
|
2
1
|
import * as Action from "@gjermundgaraba/effect-actions/Action";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
3
|
|
|
4
4
|
export const Printer = Schema.Struct({
|
|
5
5
|
id: Schema.String,
|
|
@@ -8,8 +8,10 @@ export const Printer = Schema.Struct({
|
|
|
8
8
|
job: Schema.NullOr(
|
|
9
9
|
Schema.Struct({
|
|
10
10
|
file: Schema.String,
|
|
11
|
-
progress: Schema.Finite.annotate({
|
|
12
|
-
|
|
11
|
+
progress: Schema.Finite.annotate({
|
|
12
|
+
description: "Percent done, 0 to 100.",
|
|
13
|
+
}),
|
|
14
|
+
})
|
|
13
15
|
),
|
|
14
16
|
});
|
|
15
17
|
|
|
@@ -18,7 +20,10 @@ export type Printer = typeof Printer.Type;
|
|
|
18
20
|
// The one error every tool declares: the farm could not do what was asked. A model reads its
|
|
19
21
|
// `message` and decides what to do next, so the message says what went wrong in plain words.
|
|
20
22
|
// A handler failing with an error its action does not declare does not compile.
|
|
21
|
-
export class PrintFarmError extends Schema.TaggedError<
|
|
23
|
+
export class PrintFarmError extends Schema.TaggedError<
|
|
24
|
+
PrintFarmError,
|
|
25
|
+
{ readonly brand: unique symbol }
|
|
26
|
+
>()("PrintFarmError", {
|
|
22
27
|
message: Schema.String,
|
|
23
28
|
}) {}
|
|
24
29
|
|
|
@@ -27,7 +32,9 @@ export class PrintFarmError extends Schema.TaggedError<PrintFarmError>()("PrintF
|
|
|
27
32
|
// from a model. An id that does not match is refused as invalid input, on every surface,
|
|
28
33
|
// before a handler runs, so `..` or `a/b` never reaches the farm. The `u` flag is what puts the
|
|
29
34
|
// pattern in the tool's schema, where the model reads it.
|
|
30
|
-
const PrinterId = Schema.String.check(
|
|
35
|
+
const PrinterId = Schema.String.check(
|
|
36
|
+
Schema.isPattern(/^[A-Za-z0-9_-]+$/u)
|
|
37
|
+
).annotate({
|
|
31
38
|
description: "A printer's id, as list_printers gives it, such as mk4-1.",
|
|
32
39
|
});
|
|
33
40
|
|
|
@@ -74,7 +81,9 @@ export const StartPrint = Action.make("start_print", {
|
|
|
74
81
|
"named as the farm stores it. Refused when the printer is not idle.",
|
|
75
82
|
input: {
|
|
76
83
|
printer_id: PrinterId,
|
|
77
|
-
file: Schema.String.annotate({
|
|
84
|
+
file: Schema.String.annotate({
|
|
85
|
+
description: "A G-code file's name, such as bracket.gcode.",
|
|
86
|
+
}),
|
|
78
87
|
},
|
|
79
88
|
success: Printer,
|
|
80
89
|
readOnly: false,
|
|
@@ -89,7 +98,9 @@ export const SetPaused = Action.make("set_paused", {
|
|
|
89
98
|
"pausing is the safe move while deciding what to do with a print that looks wrong.",
|
|
90
99
|
input: {
|
|
91
100
|
printer_id: PrinterId,
|
|
92
|
-
paused: Schema.Boolean.annotate({
|
|
101
|
+
paused: Schema.Boolean.annotate({
|
|
102
|
+
description: "true to pause the job, false to resume it.",
|
|
103
|
+
}),
|
|
93
104
|
},
|
|
94
105
|
success: Printer,
|
|
95
106
|
readOnly: false,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Context, type Effect } from "effect";
|
|
2
1
|
import type * as Action from "@gjermundgaraba/effect-actions/Action";
|
|
2
|
+
import { Context } from "effect";
|
|
3
|
+
import type { Effect } from "effect";
|
|
4
|
+
|
|
3
5
|
import type { Printer, PrintFarmError } from "./Printers.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -10,11 +12,19 @@ import type { Printer, PrintFarmError } from "./Printers.js";
|
|
|
10
12
|
export class PrintFarm extends Context.Service<
|
|
11
13
|
PrintFarm,
|
|
12
14
|
{
|
|
13
|
-
readonly printers: Effect.Effect<
|
|
15
|
+
readonly printers: Effect.Effect<readonly Printer[], PrintFarmError>;
|
|
14
16
|
readonly printer: (id: string) => Effect.Effect<Printer, PrintFarmError>;
|
|
15
|
-
readonly snapshot: (
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
readonly snapshot: (
|
|
18
|
+
id: string
|
|
19
|
+
) => Effect.Effect<typeof Action.Image.Type, PrintFarmError>;
|
|
20
|
+
readonly start: (
|
|
21
|
+
id: string,
|
|
22
|
+
file: string
|
|
23
|
+
) => Effect.Effect<Printer, PrintFarmError>;
|
|
24
|
+
readonly setPaused: (
|
|
25
|
+
id: string,
|
|
26
|
+
paused: boolean
|
|
27
|
+
) => Effect.Effect<Printer, PrintFarmError>;
|
|
18
28
|
readonly cancel: (id: string) => Effect.Effect<Printer, PrintFarmError>;
|
|
19
29
|
}
|
|
20
30
|
>()("mcp-tools/PrintFarm") {}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
1
|
import * as Action from "@gjermundgaraba/effect-actions/Action";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
|
|
3
4
|
import { PrintFarm } from "./farm.js";
|
|
4
5
|
import { Printers } from "./Printers.js";
|
|
5
6
|
|
|
@@ -14,16 +15,21 @@ export const printers = Action.implement(
|
|
|
14
15
|
const farm = yield* PrintFarm;
|
|
15
16
|
|
|
16
17
|
return {
|
|
17
|
-
list_printers: () =>
|
|
18
|
+
list_printers: () =>
|
|
19
|
+
Effect.map(farm.printers, (all) => ({ printers: all })),
|
|
18
20
|
// Returned as `{ data, mimeType }` like any other field: the MCP surface makes the block.
|
|
19
21
|
get_camera_snapshot: ({ printer_id }) =>
|
|
20
22
|
Effect.all(
|
|
21
|
-
{
|
|
22
|
-
|
|
23
|
+
{
|
|
24
|
+
printer: farm.printer(printer_id),
|
|
25
|
+
image: farm.snapshot(printer_id),
|
|
26
|
+
},
|
|
27
|
+
{ concurrency: "unbounded" }
|
|
23
28
|
),
|
|
24
29
|
start_print: ({ printer_id, file }) => farm.start(printer_id, file),
|
|
25
|
-
set_paused: ({ printer_id, paused }) =>
|
|
30
|
+
set_paused: ({ printer_id, paused }) =>
|
|
31
|
+
farm.setPaused(printer_id, paused),
|
|
26
32
|
cancel_print: ({ printer_id }) => farm.cancel(printer_id),
|
|
27
33
|
};
|
|
28
|
-
})
|
|
34
|
+
})
|
|
29
35
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
|
|
1
2
|
import { Config, Effect, Layer, Schema } from "effect";
|
|
2
3
|
import { HttpRouter, HttpServerResponse } from "effect/http";
|
|
3
|
-
|
|
4
|
+
|
|
4
5
|
import { serverInfo } from "./instructions.js";
|
|
5
6
|
import { printers } from "./printers/handlers.js";
|
|
6
7
|
|
|
@@ -11,9 +12,10 @@ export const routes = Layer.unwrap(
|
|
|
11
12
|
// A browser sends its page's `Origin`, and the endpoint refuses every origin not listed
|
|
12
13
|
// here, so a web page a user happens to visit cannot call the tools. Other MCP clients
|
|
13
14
|
// send none and pass. `ALLOWED_ORIGINS=https://a.example,https://b.example`; none by default.
|
|
14
|
-
const allowedOrigins = yield* Config.Array(
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const allowedOrigins = yield* Config.Array(
|
|
16
|
+
Schema.String,
|
|
17
|
+
"ALLOWED_ORIGINS"
|
|
18
|
+
).pipe(Config.withDefault([]));
|
|
17
19
|
|
|
18
20
|
// The Origin check only refuses. A browser also needs CORS: an answer to its preflight, and
|
|
19
21
|
// headers on the endpoint's answers. `HttpRouter.cors` is global to the router and, given
|
|
@@ -39,7 +41,7 @@ export const routes = Layer.unwrap(
|
|
|
39
41
|
return Layer.mergeAll(
|
|
40
42
|
// No `authentication`: every tool is public, so the endpoint authenticates nothing.
|
|
41
43
|
ActionMcp.layerHttp(printers, { ...serverInfo, allowedOrigins }),
|
|
42
|
-
HttpRouter.add("GET", "/healthz", HttpServerResponse.text("ok"))
|
|
44
|
+
HttpRouter.add("GET", "/healthz", HttpServerResponse.text("ok"))
|
|
43
45
|
).pipe(Layer.provide(cors));
|
|
44
|
-
})
|
|
46
|
+
})
|
|
45
47
|
);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
|
|
2
3
|
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node";
|
|
3
4
|
import { ByteSize, Config, Effect, Layer } from "effect";
|
|
4
5
|
import { HttpRouter, HttpServerRequest } from "effect/http";
|
|
6
|
+
|
|
5
7
|
import { fakeFarm } from "./platform/fake-farm.js";
|
|
6
8
|
import { printFarm } from "./platform/print-farm.js";
|
|
7
9
|
import { routes } from "./routes.js";
|
|
@@ -13,19 +15,24 @@ import { routes } from "./routes.js";
|
|
|
13
15
|
// that need a caller are protected as docs/ActionMcp.md's "Protect tools" shows.
|
|
14
16
|
const server = Layer.unwrap(
|
|
15
17
|
Effect.gen(function* () {
|
|
16
|
-
const host = yield* Config.String("HOST").pipe(
|
|
18
|
+
const host = yield* Config.String("HOST").pipe(
|
|
19
|
+
Config.withDefault("127.0.0.1")
|
|
20
|
+
);
|
|
21
|
+
|
|
17
22
|
const port = yield* Config.Port("PORT").pipe(Config.withDefault(3000));
|
|
18
23
|
|
|
19
24
|
return HttpRouter.serve(routes).pipe(
|
|
20
25
|
Layer.provide(NodeHttpServer.layer(createServer, { host, port })),
|
|
21
26
|
// Request bodies have no size limit unless the host sets one. A tool call is a few
|
|
22
27
|
// short fields, so anything larger is not one.
|
|
23
|
-
Layer.provide(
|
|
28
|
+
Layer.provide(
|
|
29
|
+
Layer.succeed(HttpServerRequest.MaxBodySize, ByteSize.kibibytes(64))
|
|
30
|
+
),
|
|
24
31
|
// DEMO ONLY: `fakeFarm` answers the farm's API in memory. A real server provides
|
|
25
32
|
// `FetchHttpClient.layer` in its place, and `printFarm` calls the farm at FARM_URL.
|
|
26
|
-
Layer.provide(printFarm.pipe(Layer.provide(fakeFarm)))
|
|
33
|
+
Layer.provide(printFarm.pipe(Layer.provide(fakeFarm)))
|
|
27
34
|
);
|
|
28
|
-
})
|
|
35
|
+
})
|
|
29
36
|
);
|
|
30
37
|
|
|
31
|
-
Layer.launch(server).pipe(NodeRuntime.runMain);
|
|
38
|
+
Layer.launch(server).pipe(NodeRuntime.runMain());
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { NodeRuntime, NodeStdio } from "@effect/platform-node";
|
|
2
|
-
import { Effect, Layer } from "effect";
|
|
3
2
|
import * as ActionCli from "@gjermundgaraba/effect-actions/ActionCli";
|
|
4
3
|
import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
|
+
|
|
5
6
|
import { serverInfo } from "./instructions.js";
|
|
6
7
|
import { fakeFarm } from "./platform/fake-farm.js";
|
|
7
8
|
import { printFarm } from "./platform/print-farm.js";
|
|
@@ -22,5 +23,5 @@ ActionMcp.runStdio(printers, serverInfo).pipe(
|
|
|
22
23
|
// stdout carries the protocol alone. Applied last, this sends every log to stderr, the
|
|
23
24
|
// sign-in line `printFarm` logs included, and a startup failure too.
|
|
24
25
|
ActionCli.logToStderr,
|
|
25
|
-
NodeRuntime.runMain
|
|
26
|
+
NodeRuntime.runMain()
|
|
26
27
|
);
|