@gjermundgaraba/effect-actions 0.10.0 → 0.12.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 +103 -43
- package/dist/Action.d.mts +31 -118
- package/dist/Action.mjs +15 -53
- package/dist/ActionCli.d.mts +60 -28
- package/dist/ActionCli.mjs +54 -48
- package/dist/ActionHttp.d.mts +18 -67
- package/dist/ActionHttp.mjs +20 -70
- package/dist/ActionMcp.d.mts +25 -31
- package/dist/ActionMcp.mjs +140 -74
- package/dist/ActionRpc.d.mts +147 -0
- package/dist/ActionRpc.mjs +113 -0
- package/dist/ActionToolkit.d.mts +5 -8
- package/dist/ActionToolkit.mjs +3 -3
- package/dist/Authentication.d.mts +15 -15
- package/dist/Authentication.mjs +42 -96
- package/dist/Testing.d.mts +32 -25
- package/dist/Testing.mjs +45 -30
- package/dist/{internal/authentication.d.mts → authentication/provider.d.mts} +41 -9
- package/dist/authentication/provider.mjs +12 -0
- package/dist/authentication/refusal.mjs +36 -0
- package/dist/{internal/cli.d.mts → cli/commands.d.mts} +16 -12
- package/dist/cli/commands.mjs +302 -0
- package/dist/{internal → contract}/call.d.mts +2 -2
- package/dist/contract/call.mjs +10 -0
- package/dist/{internal → contract}/errors.d.mts +13 -10
- package/dist/{internal → contract}/errors.mjs +9 -14
- package/dist/{internal → contract}/implementation.d.mts +37 -21
- package/dist/{internal → contract}/implementation.mjs +18 -49
- package/dist/contract/media.d.mts +13 -0
- package/dist/contract/media.mjs +18 -0
- package/dist/contract/rules.d.mts +25 -0
- package/dist/{internal/actions.mjs → contract/rules.mjs} +6 -42
- package/dist/{internal → http}/client.d.mts +7 -9
- package/dist/http/client.mjs +17 -0
- package/dist/http/declared.mjs +20 -0
- package/dist/http/schema-errors.mjs +9 -0
- package/dist/mcp/protocol.d.mts +17 -0
- package/dist/mcp/protocol.mjs +149 -0
- package/dist/stdio/console.mjs +64 -0
- package/dist/{internal → testing}/memory.d.mts +1 -1
- package/dist/{internal → testing}/memory.mjs +5 -17
- package/dist/toolkit/tools.mjs +29 -0
- package/docs/Action-failures.md +47 -0
- package/docs/Action-in-process.md +93 -0
- package/docs/Action.md +206 -424
- package/docs/ActionCli-failures.md +102 -0
- package/docs/ActionCli.md +206 -292
- package/docs/ActionHttp-failures.md +83 -0
- package/docs/ActionHttp.md +202 -281
- package/docs/ActionMcp-failures.md +31 -0
- package/docs/ActionMcp.md +229 -193
- package/docs/ActionRpc-failures.md +21 -0
- package/docs/ActionRpc.md +178 -0
- package/docs/ActionToolkit-failures.md +15 -0
- package/docs/ActionToolkit.md +100 -117
- package/docs/Authentication-advanced.md +139 -0
- package/docs/Authentication-failures.md +99 -0
- package/docs/Authentication.md +142 -392
- package/docs/CONTEXT.md +39 -23
- package/docs/README.md +71 -35
- package/docs/Testing-failures.md +20 -0
- package/docs/Testing.md +186 -151
- package/docs/guarantees.md +35 -95
- package/docs/setup-failures.md +34 -0
- package/docs/setup.md +88 -87
- package/examples/README.md +68 -0
- package/examples/assistant/README.md +92 -0
- package/examples/assistant/src/agent.ts +92 -0
- package/examples/assistant/src/cli.ts +34 -0
- package/examples/assistant/src/commands.ts +25 -0
- package/examples/assistant/src/identity/Identity.ts +33 -0
- package/examples/assistant/src/identity/authorize.ts +24 -0
- package/examples/assistant/src/identity/handlers.ts +10 -0
- package/examples/assistant/src/identity/members.ts +9 -0
- package/examples/assistant/src/identity/permissions.ts +23 -0
- package/examples/assistant/src/scripted.ts +23 -0
- package/examples/assistant/src/tasks/Tasks.ts +63 -0
- package/examples/assistant/src/tasks/handlers.ts +25 -0
- package/examples/assistant/src/tasks/store.ts +96 -0
- package/examples/assistant/src/toolkit.ts +30 -0
- package/examples/assistant/test/commands.test.ts +57 -0
- package/examples/assistant/test/identity/identity.test.ts +43 -0
- package/examples/assistant/test/tasks/store.test.ts +48 -0
- package/examples/assistant/test/tasks/tasks.test.ts +75 -0
- package/examples/assistant/test/toolkit.test.ts +121 -0
- package/examples/kitchen-sink/README.md +317 -0
- package/examples/kitchen-sink/src/Actions.ts +19 -0
- package/examples/kitchen-sink/src/Api.ts +24 -0
- package/examples/kitchen-sink/src/Rpc.ts +12 -0
- package/examples/kitchen-sink/src/admin/Admin.ts +59 -0
- package/examples/kitchen-sink/src/admin/authenticate.ts +18 -0
- package/examples/kitchen-sink/src/admin/authorize.ts +27 -0
- package/examples/kitchen-sink/src/admin/grants.ts +22 -0
- package/examples/kitchen-sink/src/admin/handlers.ts +21 -0
- package/examples/kitchen-sink/src/admin/keys.ts +8 -0
- package/examples/kitchen-sink/src/agent.ts +47 -0
- package/examples/kitchen-sink/src/assistant/Assistant.ts +82 -0
- package/examples/kitchen-sink/src/assistant/conversations.ts +71 -0
- package/examples/kitchen-sink/src/assistant/demo-model.ts +33 -0
- package/examples/kitchen-sink/src/assistant/handlers.ts +146 -0
- package/examples/kitchen-sink/src/attachments/Attachments.ts +48 -0
- package/examples/kitchen-sink/src/attachments/download.ts +49 -0
- package/examples/kitchen-sink/src/attachments/handlers.ts +38 -0
- package/examples/kitchen-sink/src/attachments/store.ts +58 -0
- package/examples/kitchen-sink/src/browser.ts +33 -0
- package/examples/kitchen-sink/src/cli.ts +85 -0
- package/examples/kitchen-sink/src/config.ts +18 -0
- package/examples/kitchen-sink/src/identity/Identity.ts +61 -0
- package/examples/kitchen-sink/src/identity/authenticate.ts +87 -0
- package/examples/kitchen-sink/src/identity/authorize.ts +16 -0
- package/examples/kitchen-sink/src/identity/directory.ts +52 -0
- package/examples/kitchen-sink/src/identity/handlers.ts +9 -0
- package/examples/kitchen-sink/src/identity/members.ts +13 -0
- package/examples/kitchen-sink/src/identity/quota.ts +62 -0
- package/examples/kitchen-sink/src/issues/Issues.ts +109 -0
- package/examples/kitchen-sink/src/issues/assistant.ts +23 -0
- package/examples/kitchen-sink/src/issues/guide.ts +37 -0
- package/examples/kitchen-sink/src/issues/handlers.ts +37 -0
- package/examples/kitchen-sink/src/issues/store.ts +96 -0
- package/examples/kitchen-sink/src/issues/write-limit.ts +41 -0
- package/examples/kitchen-sink/src/memory.ts +30 -0
- package/examples/kitchen-sink/src/ops.ts +42 -0
- package/examples/kitchen-sink/src/request-policy.ts +63 -0
- package/examples/kitchen-sink/src/routes.ts +120 -0
- package/examples/kitchen-sink/src/sdk.ts +46 -0
- package/examples/kitchen-sink/src/seed.ts +65 -0
- package/examples/kitchen-sink/src/server.ts +62 -0
- package/examples/kitchen-sink/src/stdio.ts +34 -0
- package/examples/kitchen-sink/src/workspaces/Workspaces.ts +28 -0
- package/examples/kitchen-sink/src/workspaces/handlers.ts +23 -0
- package/examples/kitchen-sink/src/workspaces/registry.ts +46 -0
- package/examples/kitchen-sink/src/workspaces/tenant.ts +17 -0
- package/examples/kitchen-sink/test/admin/admin.test.ts +67 -0
- package/examples/kitchen-sink/test/assistant/assistant.test.ts +435 -0
- package/examples/kitchen-sink/test/attachments/attachments.test.ts +161 -0
- package/examples/kitchen-sink/test/identity/identity.test.ts +137 -0
- package/examples/kitchen-sink/test/identity/session.test.ts +110 -0
- package/examples/kitchen-sink/test/issues/assistant.test.ts +75 -0
- package/examples/kitchen-sink/test/issues/issues.test.ts +135 -0
- package/examples/kitchen-sink/test/request-policy.test.ts +125 -0
- package/examples/kitchen-sink/test/routes.test.ts +123 -0
- package/examples/kitchen-sink/test/support/scripted.ts +40 -0
- package/examples/live-rpc/README.md +107 -0
- package/examples/live-rpc/src/Rpc.ts +8 -0
- package/examples/live-rpc/src/browser.ts +98 -0
- package/examples/live-rpc/src/chat/Chat.ts +75 -0
- package/examples/live-rpc/src/chat/authorize.ts +16 -0
- package/examples/live-rpc/src/chat/handlers.ts +50 -0
- package/examples/live-rpc/src/chat/room.ts +23 -0
- package/examples/live-rpc/src/chat/store.ts +54 -0
- package/examples/live-rpc/src/client.ts +37 -0
- package/examples/live-rpc/src/identity/Identity.ts +19 -0
- package/examples/live-rpc/src/identity/members.ts +8 -0
- package/examples/live-rpc/src/identity/verifier.ts +23 -0
- package/examples/live-rpc/src/routes.ts +22 -0
- package/examples/live-rpc/src/server.ts +43 -0
- package/examples/live-rpc/test/browser.test.ts +105 -0
- package/examples/live-rpc/test/chat/chat.test.ts +69 -0
- package/examples/live-rpc/test/routes.test.ts +99 -0
- package/examples/live-rpc/test/server.test.ts +75 -0
- package/examples/mcp-tools/README.md +136 -0
- package/examples/mcp-tools/src/instructions.ts +12 -0
- package/examples/mcp-tools/src/printers/Printers.ts +119 -0
- package/examples/mcp-tools/src/printers/fake.ts +115 -0
- package/examples/mcp-tools/src/printers/farm.ts +136 -0
- package/examples/mcp-tools/src/printers/handlers.ts +29 -0
- package/examples/mcp-tools/src/routes.ts +52 -0
- package/examples/mcp-tools/src/server.ts +30 -0
- package/examples/mcp-tools/src/stdio.ts +26 -0
- package/examples/mcp-tools/test/printers/printers.test.ts +189 -0
- package/examples/mcp-tools/test/routes.test.ts +202 -0
- package/examples/notes-service/README.md +108 -0
- package/examples/notes-service/src/Api.ts +13 -0
- package/examples/notes-service/src/browser.ts +41 -0
- package/examples/notes-service/src/cli.ts +22 -0
- package/examples/notes-service/src/commands.ts +89 -0
- package/examples/notes-service/src/identity/Identity.ts +31 -0
- package/examples/notes-service/src/identity/authorize.ts +18 -0
- package/examples/notes-service/src/identity/handlers.ts +10 -0
- package/examples/notes-service/src/identity/users.ts +9 -0
- package/examples/notes-service/src/identity/verifier.ts +36 -0
- package/examples/notes-service/src/notes/Notes.ts +91 -0
- package/examples/notes-service/src/notes/handlers.ts +33 -0
- package/examples/notes-service/src/notes/store.ts +73 -0
- package/examples/notes-service/src/routes.ts +45 -0
- package/examples/notes-service/src/server.ts +26 -0
- package/examples/notes-service/src/status/Status.ts +10 -0
- package/examples/notes-service/src/status/handlers.ts +14 -0
- package/examples/notes-service/src/stdio.ts +25 -0
- package/examples/notes-service/test/commands.test.ts +134 -0
- package/examples/notes-service/test/notes/notes.test.ts +103 -0
- package/examples/notes-service/test/routes.test.ts +84 -0
- package/examples/quickstart/README.md +31 -0
- package/examples/quickstart/src/Api.ts +5 -0
- package/examples/quickstart/src/client.ts +13 -0
- package/examples/quickstart/src/greeting/Greeting.ts +12 -0
- package/examples/quickstart/src/greeting/handlers.ts +6 -0
- package/examples/quickstart/src/routes.ts +11 -0
- package/examples/quickstart/src/server.ts +16 -0
- package/examples/quickstart/test/greeting/greeting.test.ts +18 -0
- package/package.json +32 -14
- package/dist/internal/actions.d.mts +0 -9
- package/dist/internal/authentication.mjs +0 -11
- package/dist/internal/call.mjs +0 -12
- package/dist/internal/cli.mjs +0 -328
- package/dist/internal/client.mjs +0 -25
- package/dist/internal/console.mjs +0 -77
- package/dist/internal/declared.d.mts +0 -13
- package/dist/internal/declared.mjs +0 -34
- package/dist/internal/mcp.d.mts +0 -11
- package/dist/internal/mcp.mjs +0 -52
- package/dist/internal/refusal.mjs +0 -67
- package/dist/internal/schema-errors.mjs +0 -20
- package/dist/internal/tools.mjs +0 -44
package/dist/internal/cli.mjs
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import { InvalidInput } from "./errors.mjs";
|
|
2
|
-
import { assertDistinct, literalValues, members, projectedErrors, unsuspended } from "./actions.mjs";
|
|
3
|
-
import { logToStderr } from "./console.mjs";
|
|
4
|
-
import { Console, Effect, Option, Predicate, Record, Runtime, Schema, SchemaAST } from "effect";
|
|
5
|
-
import { CliError, Command, Flag, Param } from "effect/cli";
|
|
6
|
-
//#region src/internal/cli.ts
|
|
7
|
-
/**
|
|
8
|
-
* `getUser` as a command or flag name: `get-user`; `getHTTPUser`: `get-http-user`; `_id`:
|
|
9
|
-
* `id`, since a flag already starts with its dashes. A name of dashes and underscores alone,
|
|
10
|
-
* which would be left empty, is kept as it is.
|
|
11
|
-
*/
|
|
12
|
-
const kebab = (name) => {
|
|
13
|
-
const kebabbed = name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
14
|
-
return kebabbed === "" ? name : kebabbed;
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* A command with a renderer takes `--json` as a flag of its own, so nothing is
|
|
18
|
-
* claimed tree-wide and a host's `--json`, global or not, is never contested.
|
|
19
|
-
* Without a renderer the output is JSON already and there is no flag.
|
|
20
|
-
*/
|
|
21
|
-
const jsonFlag = Flag.Boolean("json").pipe(Flag.withDefault(false), Flag.withDescription("Print machine-readable JSON"));
|
|
22
|
-
/** The kinds of JSON value there are. */
|
|
23
|
-
const jsonKinds = [
|
|
24
|
-
"null",
|
|
25
|
-
"boolean",
|
|
26
|
-
"number",
|
|
27
|
-
"string",
|
|
28
|
-
"array",
|
|
29
|
-
"object"
|
|
30
|
-
];
|
|
31
|
-
/** The kind of a JSON value, or of a literal's, which JSON has no bigint for. */
|
|
32
|
-
const kindOf = (value) => value === null ? "null" : Predicate.isBoolean(value) ? "boolean" : Predicate.isNumber(value) ? "number" : Predicate.isString(value) ? "string" : Array.isArray(value) ? "array" : "object";
|
|
33
|
-
/**
|
|
34
|
-
* The kinds of JSON value an encoded type accepts at its top, each member's of a union and a
|
|
35
|
-
* suspended type's: its checks and what it nests aside, which the action's schema decodes.
|
|
36
|
-
* A type of no fixed kind accepts all.
|
|
37
|
-
*/
|
|
38
|
-
const kindsOf = (ast) => members(ast).flatMap((member) => {
|
|
39
|
-
if (SchemaAST.isLiteral(member)) return [kindOf(member.literal)];
|
|
40
|
-
if (SchemaAST.isEnum(member)) return member.enums.map(([, value]) => kindOf(value));
|
|
41
|
-
if (SchemaAST.isNull(member)) return ["null"];
|
|
42
|
-
if (SchemaAST.isBoolean(member)) return ["boolean"];
|
|
43
|
-
if (SchemaAST.isNumber(member)) return ["number"];
|
|
44
|
-
if (SchemaAST.isString(member) || SchemaAST.isTemplateLiteral(member)) return ["string"];
|
|
45
|
-
if (SchemaAST.isArrays(member)) return ["array"];
|
|
46
|
-
if (SchemaAST.isObjects(member)) return ["object"];
|
|
47
|
-
return jsonKinds;
|
|
48
|
-
});
|
|
49
|
-
/**
|
|
50
|
-
* A flag's text as the JSON it holds when the field's `encoded` type accepts that kind of
|
|
51
|
-
* value, or else as itself, for the action's schema to decode: for a number `21` parses,
|
|
52
|
-
* while for `"auto" | string` the text `true` stays text. Only the kind is checked, so JSON
|
|
53
|
-
* breaking a rule of the schema, such as a maximum length, stays JSON, and the action's
|
|
54
|
-
* schema reports the rule and the path to what breaks it.
|
|
55
|
-
*/
|
|
56
|
-
const jsonOrText = (encoded) => {
|
|
57
|
-
const accepted = new Set(kindsOf(encoded));
|
|
58
|
-
return Schema.Union([Schema.fromJsonString(Schema.Json).check(Schema.makeFilter((json) => accepted.has(kindOf(json)))), Schema.String]);
|
|
59
|
-
};
|
|
60
|
-
/** The strings a union of string literals or a string enum accepts, if it is one. */
|
|
61
|
-
const choices = (ast) => {
|
|
62
|
-
const accepted = members(ast).flatMap(literalValues);
|
|
63
|
-
return accepted.every(Predicate.isString) ? accepted : void 0;
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* The native flag or argument parsing one field's encoded JSON value: a string or boolean
|
|
67
|
-
* for those, a choice for string literals and string enums, and JSON or text for anything
|
|
68
|
-
* else, numbers included. A template literal is a string; the action's schema checks its
|
|
69
|
-
* pattern. A suspended type is read as the type it stands for. JSON or text, a flag's or an
|
|
70
|
-
* argument's, is shown as `value`, not as the string it is parsed from.
|
|
71
|
-
*/
|
|
72
|
-
const valueParam = (kind, name, field) => {
|
|
73
|
-
const encoded = unsuspended(field);
|
|
74
|
-
const literals = choices(encoded);
|
|
75
|
-
if (literals !== void 0) return Param.Literals(kind, name, literals);
|
|
76
|
-
if (SchemaAST.isString(encoded) || SchemaAST.isTemplateLiteral(encoded)) return Param.String(kind, name);
|
|
77
|
-
if (SchemaAST.isBoolean(encoded)) return Param.Boolean(kind, name);
|
|
78
|
-
return Param.String(kind, name).pipe(Param.withSchema(jsonOrText(encoded)), Param.withMetavar("value"));
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* An optional field's value without the absence `Schema.optional` adds to it: `undefined`
|
|
82
|
-
* declared, `null` encoded. Omitting the flag already leaves the field out, so
|
|
83
|
-
* `optional(Schema.String)` takes a plain string.
|
|
84
|
-
*/
|
|
85
|
-
const present = (ast) => {
|
|
86
|
-
const members = SchemaAST.isUnion(ast) ? ast.types.filter((member) => !SchemaAST.isNull(member) && !SchemaAST.isUndefined(member)) : [];
|
|
87
|
-
return members.length === 1 && members[0] !== void 0 ? members[0] : ast;
|
|
88
|
-
};
|
|
89
|
-
/**
|
|
90
|
-
* Whether `Schema.optional` added the field's JSON `null`: encoded without the JSON codec,
|
|
91
|
-
* its value admits `undefined` but not `null`. A `null` of the field's own schema, as
|
|
92
|
-
* `NullOr` or `OptionFromNullOr` encode, stays.
|
|
93
|
-
*/
|
|
94
|
-
const addsNull = (plain) => {
|
|
95
|
-
const types = members(plain);
|
|
96
|
-
return types.some(SchemaAST.isUndefined) && !types.some(SchemaAST.isNull);
|
|
97
|
-
};
|
|
98
|
-
/**
|
|
99
|
-
* The element of an encoded array a flag repeats, and how many times it must: an array of
|
|
100
|
-
* strings or numbers, choices included, whose fixed first elements, if any, are its rest's,
|
|
101
|
-
* as `Schema.NonEmptyArray` writes one. `undefined` for any other array, which takes JSON.
|
|
102
|
-
*/
|
|
103
|
-
const repeated = (ast) => {
|
|
104
|
-
const encoded = unsuspended(ast);
|
|
105
|
-
if (!SchemaAST.isArrays(encoded)) return void 0;
|
|
106
|
-
const [element, ...after] = encoded.rest;
|
|
107
|
-
if (element === void 0 || after.length > 0 || !encoded.elements.every((first) => first === element) || !kindsOf(element).every((kind) => kind === "string" || kind === "number")) return;
|
|
108
|
-
return {
|
|
109
|
-
element,
|
|
110
|
-
min: encoded.elements.length
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
/** The occurrence of a repeated flag that adds no element, so `--tag '[]'` alone is `[]`. */
|
|
114
|
-
const empty = "[]";
|
|
115
|
-
/**
|
|
116
|
-
* A field's flag or argument, `None` when omitted, from its JSON encoding and its plain one.
|
|
117
|
-
* A required field's is required, so the parser reports it missing; a required boolean flag
|
|
118
|
-
* is a switch instead: omitted, it is `false`, as a switch reads. A boolean argument takes
|
|
119
|
-
* `true` or `false`. A flag or argument of an array of strings or numbers is repeated, one
|
|
120
|
-
* element per occurrence but `[]`, which adds none, and a choice among the element's: none
|
|
121
|
-
* is `[]`, or leaves an optional field out, which `[]` gives instead.
|
|
122
|
-
*/
|
|
123
|
-
const fieldParam = (kind, name, encoded, plain) => {
|
|
124
|
-
const optional = SchemaAST.isOptional(encoded);
|
|
125
|
-
const value = optional && plain !== void 0 && addsNull(plain) ? present(encoded) : encoded;
|
|
126
|
-
const repeats = repeated(value);
|
|
127
|
-
if (repeats !== void 0) {
|
|
128
|
-
const literals = choices(unsuspended(repeats.element));
|
|
129
|
-
const elements = literals === void 0 ? valueParam(kind, name, repeats.element) : Param.Literals(kind, name, [...literals, empty]);
|
|
130
|
-
const listed = (values) => Option.some(values.filter((value) => value !== empty));
|
|
131
|
-
return {
|
|
132
|
-
param: optional ? Param.variadic(elements).pipe(Param.map((values) => values.length === 0 ? Option.none() : listed(values))) : Param.variadic(elements, { min: repeats.min }).pipe(Param.map(listed)),
|
|
133
|
-
repeats: true
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
if (optional) return {
|
|
137
|
-
param: Param.optional(valueParam(kind, name, value)),
|
|
138
|
-
repeats: false
|
|
139
|
-
};
|
|
140
|
-
return {
|
|
141
|
-
param: kind === Param.flagKind && SchemaAST.isBoolean(unsuspended(encoded)) ? Flag.Boolean(name).pipe(Flag.withDefault(false), Flag.map(Option.some)) : valueParam(kind, name, encoded).pipe(Param.map(Option.some)),
|
|
142
|
-
repeats: false
|
|
143
|
-
};
|
|
144
|
-
};
|
|
145
|
-
/**
|
|
146
|
-
* The fields of a struct, or of a class, which declares them as its one type parameter, by name.
|
|
147
|
-
*/
|
|
148
|
-
const fieldsOf = (ast) => {
|
|
149
|
-
const fields = SchemaAST.isDeclaration(ast) ? ast.typeParameters[0] : ast;
|
|
150
|
-
return new Map(fields !== void 0 && SchemaAST.isObjects(fields) ? fields.propertySignatures.map((property) => [property.name, property.type]) : []);
|
|
151
|
-
};
|
|
152
|
-
/** A struct, or a class's, transformed as a whole, as `Schema.encodeKeys` transforms one. */
|
|
153
|
-
const transformedWhole = (ast) => (SchemaAST.isDeclaration(ast) ? ast.typeParameters[0] : ast)?.encoding !== void 0;
|
|
154
|
-
/** A field's description: its own, or the first a suspension it stands for has. */
|
|
155
|
-
const descriptionOf = (ast) => SchemaAST.resolveDescription(ast) ?? (SchemaAST.isSuspend(ast) ? descriptionOf(ast.thunk()) : void 0);
|
|
156
|
-
/**
|
|
157
|
-
* One flag or argument per top-level field of a struct input, named after the field in
|
|
158
|
-
* kebab case, parsed as its encoded value and described by its declared schema: an
|
|
159
|
-
* argument for a field listed in `positional`, a flag otherwise. A field required once
|
|
160
|
-
* encoded is required; the action's schema still decodes what they parse.
|
|
161
|
-
*/
|
|
162
|
-
const fieldParams = (encoded, input, positional, aliases) => {
|
|
163
|
-
const plain = fieldsOf(SchemaAST.toEncoded(input));
|
|
164
|
-
const described = fieldsOf(transformedWhole(input) ? encoded : SchemaAST.toType(input));
|
|
165
|
-
return encoded.propertySignatures.map((property) => {
|
|
166
|
-
const field = String(property.name);
|
|
167
|
-
const declaredField = described.get(property.name);
|
|
168
|
-
const kind = positional.includes(field) ? Param.argumentKind : Param.flagKind;
|
|
169
|
-
const { param, repeats } = fieldParam(kind, kebab(field), property.type, plain.get(property.name));
|
|
170
|
-
const alias = kind === Param.flagKind ? aliases[field] : void 0;
|
|
171
|
-
const aliased = alias === void 0 ? param : Param.withAlias(param, alias);
|
|
172
|
-
const documented = declaredField ?? property.type;
|
|
173
|
-
const description = descriptionOf(documented) ?? descriptionOf(present(documented));
|
|
174
|
-
return {
|
|
175
|
-
field,
|
|
176
|
-
param: description === void 0 ? aliased : Param.withDescription(aliased, description),
|
|
177
|
-
optional: SchemaAST.isOptional(property.type),
|
|
178
|
-
repeats
|
|
179
|
-
};
|
|
180
|
-
});
|
|
181
|
-
};
|
|
182
|
-
/**
|
|
183
|
-
* The positional fields in order, checked against the fields there are: each one a field,
|
|
184
|
-
* listed once, and every required one before any optional one, as a parser reads them.
|
|
185
|
-
*/
|
|
186
|
-
const positionalOrder = (params, positional) => {
|
|
187
|
-
assertDistinct("positional argument", positional, (field) => field);
|
|
188
|
-
const ordered = positional.map((field) => {
|
|
189
|
-
const found = params.find((param) => param.field === field);
|
|
190
|
-
if (found === void 0) throw new Error(`Not an input field: ${field}`);
|
|
191
|
-
return found;
|
|
192
|
-
});
|
|
193
|
-
const firstOptional = ordered.findIndex((param) => param.optional);
|
|
194
|
-
const misplaced = ordered.slice(firstOptional === -1 ? ordered.length : firstOptional).find((param) => !param.optional);
|
|
195
|
-
if (misplaced !== void 0) throw new Error(`Required positional argument after an optional one: ${misplaced.field}`);
|
|
196
|
-
const greedy = ordered.slice(0, -1).find((param) => param.repeats);
|
|
197
|
-
if (greedy !== void 0) throw new Error(`Repeated positional argument before another one: ${greedy.field}`);
|
|
198
|
-
return ordered;
|
|
199
|
-
};
|
|
200
|
-
/**
|
|
201
|
-
* `--input`, the whole encoded input as JSON, or text, for an input that is not a struct of
|
|
202
|
-
* fields. It is never required: left off, it is `{}`, which the action's schema decodes
|
|
203
|
-
* when the command runs, never when it is built.
|
|
204
|
-
*/
|
|
205
|
-
const inputFlag = (encoded) => Flag.String("input").pipe(Flag.withSchema(jsonOrText(encoded)), Flag.withDescription("Whole action input as JSON; omitted, {}"), Flag.optional);
|
|
206
|
-
/**
|
|
207
|
-
* What a success prints on stdout: its JSON, or `render`'s text. It is validated and encoded
|
|
208
|
-
* first, so human output cannot conceal an invalid success, which is a defect, as on a server.
|
|
209
|
-
* An action that returns nothing prints nothing, rather than its encoding, `null`.
|
|
210
|
-
*/
|
|
211
|
-
const output = (action, value, render) => Effect.map(Effect.orDie(Schema.encodeEffect(Schema.toCodecJson(action.success))(value)), (encoded) => render === void 0 && SchemaAST.isVoid(action.success.ast) ? Option.none() : Option.some(render === void 0 ? JSON.stringify(encoded, null, 2) : render(value)));
|
|
212
|
-
/**
|
|
213
|
-
* What a command fails with when its action fails: Effect CLI's own error for a handler's
|
|
214
|
-
* failure, which `Command.run` prints on stderr and marks reported, so `runMain` does not
|
|
215
|
-
* print it again. Its `cause` is the action's failure. The process exits with the cause's
|
|
216
|
-
* `Runtime.errorExitCode`, 1 by default.
|
|
217
|
-
*/
|
|
218
|
-
var UserError = class extends CliError.UserError {
|
|
219
|
-
/**
|
|
220
|
-
* The action's failure, as Effect names an error's nested one, so the native
|
|
221
|
-
* `Effect.catchReason("UserError", "UserNotFound", f)` and `catchReasons` match it by tag.
|
|
222
|
-
*/
|
|
223
|
-
get reason() {
|
|
224
|
-
return this.cause;
|
|
225
|
-
}
|
|
226
|
-
get [Runtime.errorExitCode]() {
|
|
227
|
-
return Runtime.getErrorExitCode(this.cause);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
/** `error`'s own `_tag` or `message`, when it is a string. */
|
|
231
|
-
const text = (error, key) => {
|
|
232
|
-
if (!Predicate.hasProperty(error, key)) return void 0;
|
|
233
|
-
const value = error[key];
|
|
234
|
-
return Predicate.isString(value) ? value : void 0;
|
|
235
|
-
};
|
|
236
|
-
/**
|
|
237
|
-
* A failure no projected schema encodes, without a stack: its tag or an error's name, and
|
|
238
|
-
* its message, or the failure itself when it is a string, then each cause's, so a transport
|
|
239
|
-
* error keeps the refused connection beneath it, up to one already described, as a cycle
|
|
240
|
-
* repeats it. Its other fields are left out, a plain object's `name` too: no schema says
|
|
241
|
-
* they are safe to show, and a builder's failure may hold a connection string.
|
|
242
|
-
*/
|
|
243
|
-
const described = (error, seen = /* @__PURE__ */ new Set()) => {
|
|
244
|
-
const name = text(error, "_tag") ?? (error instanceof Error ? error.name : "Error");
|
|
245
|
-
const message = Predicate.isString(error) ? error : text(error, "message") ?? "";
|
|
246
|
-
const named = message === "" ? name : `${name}: ${message}`;
|
|
247
|
-
seen.add(error);
|
|
248
|
-
return Predicate.hasProperty(error, "cause") && error.cause !== void 0 && !seen.has(error.cause) ? `${named}: ${described(error.cause, seen)}` : named;
|
|
249
|
-
};
|
|
250
|
-
/**
|
|
251
|
-
* The `UserError` of each failure of `action`, its message the JSON HTTP sends for it: encoded
|
|
252
|
-
* by the first schema that takes it among the built-in errors, the action's and the
|
|
253
|
-
* surface's own `errors`, as an Effect, as HTTP encodes it. A failure none takes, such as a
|
|
254
|
-
* builder's or the transport's, is described instead.
|
|
255
|
-
*/
|
|
256
|
-
const failureOf = (action, errors) => {
|
|
257
|
-
const encode = Schema.encodeUnknownEffect(Schema.fromJsonString(Schema.toCodecJson(Schema.Union(projectedErrors(action, errors)))));
|
|
258
|
-
return (error) => encode(error).pipe(Effect.orElseSucceed(() => described(error)), Effect.flatMap((userMessage) => Effect.fail(new UserError({
|
|
259
|
-
cause: error,
|
|
260
|
-
userMessage
|
|
261
|
-
}))));
|
|
262
|
-
};
|
|
263
|
-
/**
|
|
264
|
-
* The action's input as native flags: one per field of a struct input, named after it,
|
|
265
|
-
* or `--input` taking the whole input as JSON otherwise. The `positional` fields of a
|
|
266
|
-
* struct are arguments instead, in that order.
|
|
267
|
-
*/
|
|
268
|
-
const inputConfig = (action, positional, aliases) => {
|
|
269
|
-
const codec = Schema.toCodecJson(action.input);
|
|
270
|
-
const encoded = unsuspended(SchemaAST.toEncoded(codec.ast));
|
|
271
|
-
const decode = Schema.decodeUnknownEffect(codec, { onExcessProperty: "error" });
|
|
272
|
-
if (SchemaAST.isObjects(encoded) && encoded.indexSignatures.every((signature) => SchemaAST.isNever(signature.type))) {
|
|
273
|
-
const params = fieldParams(encoded, unsuspended(action.input.ast), positional, aliases);
|
|
274
|
-
const flagged = new Set(params.map(({ field }) => field).filter((field) => !positional.includes(field)));
|
|
275
|
-
const stray = Object.keys(aliases).find((field) => !flagged.has(field));
|
|
276
|
-
if (stray !== void 0) throw new Error(`Not a flag's input field: ${stray}`);
|
|
277
|
-
return {
|
|
278
|
-
flags: Object.fromEntries(params.flatMap(({ field, param }) => positional.includes(field) ? [] : [[field, param]])),
|
|
279
|
-
positional: positionalOrder(params, positional),
|
|
280
|
-
decode: (parsed) => decode(Record.getSomes(parsed))
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
if (positional.length > 0) throw new Error(`Positional arguments need named input fields: ${positional.join(", ")}`);
|
|
284
|
-
if (Object.keys(aliases).length > 0) throw new Error(`Aliases need named input fields: ${Object.keys(aliases).join(", ")}`);
|
|
285
|
-
return {
|
|
286
|
-
flags: { input: inputFlag(encoded) },
|
|
287
|
-
positional: [],
|
|
288
|
-
decode: (parsed) => decode(Option.getOrElse(parsed["input"] ?? Option.none(), () => ({})))
|
|
289
|
-
};
|
|
290
|
-
};
|
|
291
|
-
/**
|
|
292
|
-
* One native command around an action-bound operation, its flags and positional
|
|
293
|
-
* arguments derived from the action's input. A field's flag shadows a global flag of the
|
|
294
|
-
* same name; two flags of the command itself with one name are refused when it is built.
|
|
295
|
-
* Input that does not decode is `InvalidInput`, as over HTTP, and the operation writes its
|
|
296
|
-
* logs and console output to stderr, since stdout carries the result. Every failure is a
|
|
297
|
-
* `UserError`, encoded with the surface's `errors` too, such as a binding's.
|
|
298
|
-
*/
|
|
299
|
-
const command = (action, execute, options, errors = []) => {
|
|
300
|
-
const name = options?.name ?? kebab(action.name);
|
|
301
|
-
const render = options?.render;
|
|
302
|
-
const { flags, positional, decode } = inputConfig(action, options?.positional ?? [], options?.aliases ?? {});
|
|
303
|
-
const failure = failureOf(action, errors);
|
|
304
|
-
assertDistinct("flag", [
|
|
305
|
-
...Object.keys(flags).map((field) => [`--${kebab(field)}`, `field ${field}`]),
|
|
306
|
-
...render === void 0 ? [] : [["--json", "render's --json"]],
|
|
307
|
-
...Object.entries(options?.aliases ?? {}).flatMap(([field, alias]) => Predicate.isString(alias) ? [[`--${alias.replace(/^-+/, "")}`, `alias of field ${field}`]] : [])
|
|
308
|
-
], ([flag]) => flag, ([, claimant]) => claimant);
|
|
309
|
-
const args = positional.map(({ param }) => param);
|
|
310
|
-
const parsedFields = (input, values) => ({
|
|
311
|
-
...input,
|
|
312
|
-
...Object.fromEntries(positional.map(({ field }, index) => [field, values[index] ?? Option.none()]))
|
|
313
|
-
});
|
|
314
|
-
const run = (parsed, rendered) => decode(parsed).pipe(Effect.mapError(InvalidInput.fromSchemaError), Effect.flatMap(execute), Effect.catch(failure), Effect.flatMap((value) => output(action, value, rendered)), logToStderr, Effect.flatMap(Option.match({
|
|
315
|
-
onNone: () => Effect.void,
|
|
316
|
-
onSome: Console.log
|
|
317
|
-
})));
|
|
318
|
-
return (render === void 0 ? Command.make(name, {
|
|
319
|
-
input: flags,
|
|
320
|
-
args
|
|
321
|
-
}, ({ input, args: values }) => run(parsedFields(input, values), void 0)) : Command.make(name, {
|
|
322
|
-
input: flags,
|
|
323
|
-
args,
|
|
324
|
-
json: jsonFlag
|
|
325
|
-
}, ({ input, args: values, json }) => run(parsedFields(input, values), json ? void 0 : render))).pipe(Command.withDescription(action.description));
|
|
326
|
-
};
|
|
327
|
-
//#endregion
|
|
328
|
-
export { UserError, command, kebab };
|
package/dist/internal/client.mjs
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { InvalidInput } from "./errors.mjs";
|
|
2
|
-
import { inputOf } from "./call.mjs";
|
|
3
|
-
import { Effect, Schema } from "effect";
|
|
4
|
-
import { HttpApiClient } from "effect/http-api";
|
|
5
|
-
//#region src/internal/client.ts
|
|
6
|
-
/** Refuse an action a binding of `actions` does not hold, matched by identity. */
|
|
7
|
-
const assertInBinding = (actions, action) => {
|
|
8
|
-
if (!actions.includes(action)) throw new Error(`Action "${action.name}" is not in this HTTP binding`);
|
|
9
|
-
};
|
|
10
|
-
/**
|
|
11
|
-
* Build the native client of a binding once and look up each action's method, taking the
|
|
12
|
-
* action's input directly. Every binding API is a native `HttpApi` of one top-level
|
|
13
|
-
* group built from `http.actions`, so each action has a native method of its name.
|
|
14
|
-
*/
|
|
15
|
-
const methods = (http, options = {}) => Effect.map(HttpApiClient.make(http.api, options), (client) => {
|
|
16
|
-
const native = client;
|
|
17
|
-
return (action) => {
|
|
18
|
-
const method = native[action.name];
|
|
19
|
-
if (method === void 0) throw new Error(`No client method for ${action.name}`);
|
|
20
|
-
const encode = Schema.encodeUnknownEffect(Schema.toCodecJson(action.input), { errors: "all" });
|
|
21
|
-
return (...args) => inputOf(action, args).pipe(Effect.tap(encode), Effect.mapError(InvalidInput.fromSchemaError), Effect.flatMap((payload) => method({ payload })));
|
|
22
|
-
};
|
|
23
|
-
});
|
|
24
|
-
//#endregion
|
|
25
|
-
export { assertInBinding, methods };
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { Clock, Console, Effect, Function, Predicate } from "effect";
|
|
2
|
-
//#region src/internal/console.ts
|
|
3
|
-
/**
|
|
4
|
-
* The console of code whose program's stdout carries data: a stdio server's protocol, or a
|
|
5
|
-
* CLI command's result. Every method writes through `console.error` alone, so every console
|
|
6
|
-
* logger, `Console.log` and the default logger alike, leaves stdout intact, and so do
|
|
7
|
-
* counters, timers and group labels, which Node's console prints on stdout. A counter or a
|
|
8
|
-
* timer prints its label as given, never as a format, then its count or the milliseconds
|
|
9
|
-
* since it started, read on `clock` and rounded to three decimals; a group prints its label
|
|
10
|
-
* without indenting what follows, and `clear` clears nothing.
|
|
11
|
-
*/
|
|
12
|
-
const stderrConsole = (console, clock) => {
|
|
13
|
-
const counts = /* @__PURE__ */ new Map();
|
|
14
|
-
const timers = /* @__PURE__ */ new Map();
|
|
15
|
-
const write = (...args) => console.error(...args);
|
|
16
|
-
const elapsed = (label, data) => {
|
|
17
|
-
const start = timers.get(label);
|
|
18
|
-
if (start !== void 0) {
|
|
19
|
-
const millis = Number(clock.monotonicTimeNanosUnsafe() - start) / 1e6;
|
|
20
|
-
write("%s: %sms", label, Number(millis.toFixed(3)), ...data);
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
const labeled = (label, args) => {
|
|
24
|
-
const [first, ...rest] = args;
|
|
25
|
-
write(...Predicate.isString(first) ? [`${label}: ${first}`, ...rest] : [label, ...args]);
|
|
26
|
-
};
|
|
27
|
-
const group = (...label) => {
|
|
28
|
-
if (label.some((part) => part !== void 0)) write(...label);
|
|
29
|
-
};
|
|
30
|
-
return {
|
|
31
|
-
assert: (condition, ...args) => {
|
|
32
|
-
if (!condition) labeled("Assertion failed", args);
|
|
33
|
-
},
|
|
34
|
-
clear: Function.constVoid,
|
|
35
|
-
count: (label = "default") => {
|
|
36
|
-
const count = (counts.get(label) ?? 0) + 1;
|
|
37
|
-
counts.set(label, count);
|
|
38
|
-
write("%s: %d", label, count);
|
|
39
|
-
},
|
|
40
|
-
countReset: (label = "default") => {
|
|
41
|
-
counts.delete(label);
|
|
42
|
-
},
|
|
43
|
-
debug: write,
|
|
44
|
-
dir: (item) => write("%O", item),
|
|
45
|
-
dirxml: write,
|
|
46
|
-
error: write,
|
|
47
|
-
group,
|
|
48
|
-
groupCollapsed: group,
|
|
49
|
-
groupEnd: Function.constVoid,
|
|
50
|
-
info: write,
|
|
51
|
-
log: write,
|
|
52
|
-
table: (data) => write(data),
|
|
53
|
-
time: (label = "default") => {
|
|
54
|
-
timers.set(label, clock.monotonicTimeNanosUnsafe());
|
|
55
|
-
},
|
|
56
|
-
timeEnd: (label = "default") => {
|
|
57
|
-
elapsed(label, []);
|
|
58
|
-
timers.delete(label);
|
|
59
|
-
},
|
|
60
|
-
timeLog: (label = "default", ...data) => elapsed(label, data),
|
|
61
|
-
trace: (...args) => {
|
|
62
|
-
const { stack = "" } = /* @__PURE__ */ new Error();
|
|
63
|
-
const frames = stack.split("\n").slice(2).join("\n");
|
|
64
|
-
labeled("Trace", args);
|
|
65
|
-
if (frames !== "") write(frames);
|
|
66
|
-
},
|
|
67
|
-
warn: write
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* `effect` writing its console output and Effect logs to stderr alone, through the console
|
|
72
|
-
* it runs with: for what a surface runs where stdout carries data, a stdio server's program
|
|
73
|
-
* or a CLI command's builder, authorizer and handler.
|
|
74
|
-
*/
|
|
75
|
-
const logToStderr = (effect) => Effect.provideServiceEffect(effect, Console.Console, Effect.zipWith(Effect.service(Console.Console), Effect.service(Clock.Clock), stderrConsole));
|
|
76
|
-
//#endregion
|
|
77
|
-
export { logToStderr };
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Action_d_exports, Errors } from "../Action.mjs";
|
|
2
|
-
import { HttpServerResponse } from "effect/http";
|
|
3
|
-
//#region src/internal/declared.d.ts
|
|
4
|
-
/** The list an `error` option of type `G` stands for: one schema is a list of one. */
|
|
5
|
-
type ListOf<G> = G extends Errors ? G : readonly [G];
|
|
6
|
-
/**
|
|
7
|
-
* The errors options `O` declare, as at run time: those `error` always gives, or, where it may
|
|
8
|
-
* be absent, those it gives or none, so clients decode each that may arrive.
|
|
9
|
-
*/
|
|
10
|
-
export type ErrorsOf<O> = O extends unknown ? "error" extends keyof O ? O extends {
|
|
11
|
-
readonly error: infer G extends Errors | Errors[number];
|
|
12
|
-
} ? ListOf<G> : ListOf<Extract<O["error" & keyof O], Errors | Errors[number]>> | [] : [] : never;
|
|
13
|
-
//#endregion
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { unsuspended } from "./actions.mjs";
|
|
2
|
-
import { Schema, SchemaAST } from "effect";
|
|
3
|
-
import { HttpServerResponse } from "effect/http";
|
|
4
|
-
import { HttpApiSchema } from "effect/http-api";
|
|
5
|
-
//#region src/internal/declared.ts
|
|
6
|
-
/** The status an error schema states, as `HttpApi` reads it. */
|
|
7
|
-
const statusOf = SchemaAST.resolveAt("httpApiStatus");
|
|
8
|
-
/** The status a schema states, or the first one a suspension it wraps states. */
|
|
9
|
-
const statusThrough = (ast) => statusOf(ast) ?? (SchemaAST.isSuspend(ast) ? statusThrough(ast.thunk()) : void 0);
|
|
10
|
-
/**
|
|
11
|
-
* The schemas an endpoint declares for one error, each with the status it is sent with. `HttpApi`
|
|
12
|
-
* reads a status off each declared schema, never off a union's members nor through a suspension, so
|
|
13
|
-
* a plain union without a status of its own declares each member, and a suspended error, as a
|
|
14
|
-
* recursive one is written, without a status of its own states the status of what it suspends. An
|
|
15
|
-
* error without a status is an outcome the action expects, not a server fault: it is sent as 422,
|
|
16
|
-
* rather than `HttpApi`'s 500, which clients and proxies read as the server failing.
|
|
17
|
-
*/
|
|
18
|
-
const declared = (error) => {
|
|
19
|
-
const status = statusThrough(error.ast);
|
|
20
|
-
if (status !== void 0) return [statusOf(error.ast) === void 0 ? HttpApiSchema.status(status)(error) : error];
|
|
21
|
-
const resolved = unsuspended(error.ast);
|
|
22
|
-
if (SchemaAST.isUnion(resolved) && resolved.checks === void 0 && resolved.encoding === void 0) return resolved.types.flatMap((member) => declared(Schema.make(member)));
|
|
23
|
-
return [HttpApiSchema.status(422)(error)];
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* `error` as an endpoint declaring `errors` sends it: its JSON, encoded by the first declared
|
|
27
|
-
* schema that accepts it, with that schema's status. None for a value no schema accepts.
|
|
28
|
-
*/
|
|
29
|
-
const declaredResponse = (errors, error) => {
|
|
30
|
-
const schema = errors.flatMap(declared).find((each) => Schema.is(each)(error));
|
|
31
|
-
return schema === void 0 ? void 0 : HttpServerResponse.jsonUnsafe(Schema.encodeUnknownSync(Schema.toCodecJson(schema))(error), { status: statusOf(schema.ast) ?? 422 });
|
|
32
|
-
};
|
|
33
|
-
//#endregion
|
|
34
|
-
export { declared, declaredResponse };
|
package/dist/internal/mcp.d.mts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Schema } from "effect";
|
|
2
|
-
import { McpProtocol } from "effect/ai";
|
|
3
|
-
//#region src/internal/mcp.d.ts
|
|
4
|
-
/** An MCP request's parameters: JSON, leaving out an `undefined` one, as a caller may. */
|
|
5
|
-
export interface Params {
|
|
6
|
-
readonly _meta?: {
|
|
7
|
-
readonly [key: string]: Schema.Json;
|
|
8
|
-
};
|
|
9
|
-
readonly [key: string]: Schema.Json | undefined;
|
|
10
|
-
}
|
|
11
|
-
//#endregion
|
package/dist/internal/mcp.mjs
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { Predicate } from "effect";
|
|
2
|
-
import { McpProtocol } from "effect/ai";
|
|
3
|
-
//#region src/internal/mcp.ts
|
|
4
|
-
/**
|
|
5
|
-
* The one protocol revision served over HTTP. 2026-07-28 is stateless: every request
|
|
6
|
-
* stands alone. The stateful revisions keep a session per `initialize`, which the native
|
|
7
|
-
* HTTP runtime never expires and which no identity owns. A mixed endpoint's gate relies on it
|
|
8
|
-
* too: it decides from the routing headers, which the native runtime checks against the body
|
|
9
|
-
* of a stateless request alone, so serving a stateful revision over HTTP would open the gate.
|
|
10
|
-
*/
|
|
11
|
-
const httpProtocol = McpProtocol.v2026_07_28;
|
|
12
|
-
/** Where `ActionMcp.layerHttp` serves, and `Testing.mcpClient` calls, by default. */
|
|
13
|
-
const defaultPath = "/mcp";
|
|
14
|
-
/** The parameter a method's `mcp-name` header repeats, as the native runtime routes it. */
|
|
15
|
-
const routed = (method, params) => method === "resources/read" ? params.uri : params.name;
|
|
16
|
-
/**
|
|
17
|
-
* One stateless MCP request of `method` with `params`, as `ActionMcp.layerHttp` serves it:
|
|
18
|
-
* the headers routing it, which repeat what its body says, and its JSON-RPC body. The client
|
|
19
|
-
* metadata goes in `_meta`, under any given, such as a `progressToken`; the protocol version
|
|
20
|
-
* is always the request's own.
|
|
21
|
-
*/
|
|
22
|
-
const statelessRequest = (method, params) => {
|
|
23
|
-
const { protocolVersion } = httpProtocol;
|
|
24
|
-
const name = routed(method, params);
|
|
25
|
-
return {
|
|
26
|
-
headers: {
|
|
27
|
-
accept: "application/json, text/event-stream",
|
|
28
|
-
"mcp-protocol-version": protocolVersion,
|
|
29
|
-
"mcp-method": method,
|
|
30
|
-
...Predicate.isString(name) ? { "mcp-name": name } : {}
|
|
31
|
-
},
|
|
32
|
-
body: {
|
|
33
|
-
jsonrpc: "2.0",
|
|
34
|
-
id: 1,
|
|
35
|
-
method,
|
|
36
|
-
params: {
|
|
37
|
-
...params,
|
|
38
|
-
_meta: {
|
|
39
|
-
"io.modelcontextprotocol/clientCapabilities": {},
|
|
40
|
-
"io.modelcontextprotocol/clientInfo": {
|
|
41
|
-
name: "effect-actions",
|
|
42
|
-
version: "0"
|
|
43
|
-
},
|
|
44
|
-
...params._meta,
|
|
45
|
-
"io.modelcontextprotocol/protocolVersion": protocolVersion
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
//#endregion
|
|
52
|
-
export { defaultPath, httpProtocol, statelessRequest };
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { Refusal, statuses } from "./errors.mjs";
|
|
2
|
-
import { Cause, Context, Effect, Exit, Option, Predicate, Ref, Schema } from "effect";
|
|
3
|
-
import { HttpServerResponse } from "effect/http";
|
|
4
|
-
//#region src/internal/refusal.ts
|
|
5
|
-
/** An RFC 6750 error description: printable ASCII but `"` and `\`. */
|
|
6
|
-
const description = /^[\x20\x21\x23-\x5B\x5D-\x7E]+$/;
|
|
7
|
-
/**
|
|
8
|
-
* An RFC 9110 challenge of `scheme` and the parameters given, each a quoted string whose `"`
|
|
9
|
-
* and `\` are escaped: a metadata URL's query may hold a `\`.
|
|
10
|
-
*/
|
|
11
|
-
const challenge = (scheme, parameters) => {
|
|
12
|
-
const given = parameters.flatMap(([name, value]) => value === void 0 ? [] : [`${name}="${value.replace(/["\\]/g, "\\$&")}"`]);
|
|
13
|
-
return given.length === 0 ? scheme : `${scheme} ${given.join(", ")}`;
|
|
14
|
-
};
|
|
15
|
-
/** An RFC 6750 `Bearer` challenge of the parameters given. */
|
|
16
|
-
const bearer = (parameters) => challenge("Bearer", parameters);
|
|
17
|
-
const encode = Schema.encodeSync(Refusal);
|
|
18
|
-
/** Whether `error` is a built-in refusal, `Unauthenticated` or `Forbidden`. */
|
|
19
|
-
const isRefusal = Schema.is(Refusal);
|
|
20
|
-
/**
|
|
21
|
-
* The RFC 6750 `insufficient_scope` challenge an OAuth client steps up on, for a `Forbidden`
|
|
22
|
-
* naming scopes, naming the resource's metadata URL when there is one.
|
|
23
|
-
*/
|
|
24
|
-
const insufficientScope = (error, metadataUrl) => Predicate.isTagged(error, "Forbidden") && error.scopes !== void 0 ? bearer([
|
|
25
|
-
["error", "insufficient_scope"],
|
|
26
|
-
["scope", error.scopes.join(" ")],
|
|
27
|
-
["resource_metadata", metadataUrl],
|
|
28
|
-
["error_description", description.test(error.message) ? error.message : void 0]
|
|
29
|
-
]) : void 0;
|
|
30
|
-
/** A refusal as its JSON with its status alone, as a scheme no OAuth client acts on answers it. */
|
|
31
|
-
const plain = (error) => HttpServerResponse.jsonUnsafe(encode(error), { status: statuses[error._tag] });
|
|
32
|
-
/**
|
|
33
|
-
* A refusal as its JSON with its status, as every endpoint declares it, and its
|
|
34
|
-
* `insufficient_scope` challenge if it has one.
|
|
35
|
-
*/
|
|
36
|
-
const answer = (error, metadataUrl) => {
|
|
37
|
-
const response = plain(error);
|
|
38
|
-
const scoped = insufficientScope(error, metadataUrl);
|
|
39
|
-
return scoped === void 0 ? response : HttpServerResponse.setHeader(response, "www-authenticate", scoped);
|
|
40
|
-
};
|
|
41
|
-
/** The step-up refusal a tool's call under `answerStepUp` failed with, which answers its request. */
|
|
42
|
-
var SteppedUp = class extends Context.Service()("effect-actions/SteppedUp") {};
|
|
43
|
-
/**
|
|
44
|
-
* Whether `error` is a refusal an OAuth client acts on: `Unauthenticated`, or `Forbidden`
|
|
45
|
-
* naming scopes.
|
|
46
|
-
*/
|
|
47
|
-
const isStepUp = (error) => isRefusal(error) && (!Predicate.isTagged(error, "Forbidden") || error.scopes !== void 0);
|
|
48
|
-
/**
|
|
49
|
-
* A protected MCP tool's `call`, whose step-up refusal answers the request under a Bearer
|
|
50
|
-
* descriptor's `answerStepUp`. Elsewhere it is `call` as it is.
|
|
51
|
-
*/
|
|
52
|
-
const recordStepUp = (call) => Effect.tapError(call, (error) => isStepUp(error) ? Effect.flatMap(Effect.serviceOption(SteppedUp), (slot) => Option.isSome(slot) ? Ref.set(slot.value, Option.some(error)) : Effect.void) : Effect.void);
|
|
53
|
-
/**
|
|
54
|
-
* An MCP request, answered with the step-up refusal a tool's call in it failed with, its
|
|
55
|
-
* status, JSON and challenge naming `metadataUrl`, however the request ended short of a defect
|
|
56
|
-
* or an interruption: a tool's failure is a successful result, and MCP authorization requires
|
|
57
|
-
* 401 or 403.
|
|
58
|
-
*/
|
|
59
|
-
const answerStepUp = (route, metadataUrl) => Effect.gen(function* () {
|
|
60
|
-
const slot = yield* Ref.make(Option.none());
|
|
61
|
-
const exit = yield* Effect.exit(Effect.provideService(route, SteppedUp, slot));
|
|
62
|
-
const refused = yield* Ref.get(slot);
|
|
63
|
-
const broken = Exit.isFailure(exit) && (Cause.hasDies(exit.cause) || Cause.hasInterrupts(exit.cause));
|
|
64
|
-
return Option.isSome(refused) && !broken ? answer(refused.value, metadataUrl) : yield* exit;
|
|
65
|
-
});
|
|
66
|
-
//#endregion
|
|
67
|
-
export { answer, answerStepUp, bearer, challenge, isRefusal, isStepUp, plain, recordStepUp };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { InvalidInput } from "./errors.mjs";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
|
-
import { HttpApiMiddleware } from "effect/http-api";
|
|
4
|
-
//#region src/internal/schema-errors.ts
|
|
5
|
-
/**
|
|
6
|
-
* `HttpApiBuilder` reports these kinds while encoding the handler's answer, after the
|
|
7
|
-
* handler ran; every other kind (`Payload`, `Params`, `Headers`, `Query`) comes from
|
|
8
|
-
* decoding the request before it.
|
|
9
|
-
*/
|
|
10
|
-
const responseKinds = /* @__PURE__ */ new Set(["Body", "ResponseHeaders"]);
|
|
11
|
-
/**
|
|
12
|
-
* The native middleware answering schema failures: a request that does not decode with
|
|
13
|
-
* `InvalidInput`, the schema's own message and issues, a result that does not encode as a defect,
|
|
14
|
-
* the empty 500 of any other server bug.
|
|
15
|
-
*/
|
|
16
|
-
var SchemaErrors = class extends HttpApiMiddleware.Service()("effect-actions/http/SchemaErrors", { error: [InvalidInput] }) {};
|
|
17
|
-
/** `SchemaErrors`' server side, built by `ActionHttp.layer` alone, so a client's bundle leaves it out. */
|
|
18
|
-
const schemaErrors = () => HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrors, (failure) => responseKinds.has(failure.kind) ? Effect.die(failure.cause) : Effect.fail(InvalidInput.fromSchemaError(failure.cause)));
|
|
19
|
-
//#endregion
|
|
20
|
-
export { SchemaErrors, schemaErrors };
|