@alpacakit/channels 0.1.0-beta.37 → 0.2.0-beta.2

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.
Files changed (99) hide show
  1. package/README.md +204 -723
  2. package/dist/binding-compiler.d.ts +12 -2
  3. package/dist/binding-compiler.d.ts.map +1 -1
  4. package/dist/binding-compiler.js +25 -0
  5. package/dist/cli-coercion.d.ts +1 -1
  6. package/dist/cli-coercion.d.ts.map +1 -1
  7. package/dist/cli-coercion.js +14 -29
  8. package/dist/cli-compiler.d.ts +4 -17
  9. package/dist/cli-compiler.d.ts.map +1 -1
  10. package/dist/cli-compiler.js +78 -39
  11. package/dist/cli-help-projection.d.ts.map +1 -1
  12. package/dist/cli-help-projection.js +41 -36
  13. package/dist/cli-model.d.ts +50 -65
  14. package/dist/cli-model.d.ts.map +1 -1
  15. package/dist/cli-model.js +26 -34
  16. package/dist/cli-tree.d.ts +6 -3
  17. package/dist/cli-tree.d.ts.map +1 -1
  18. package/dist/cli-tree.js +161 -387
  19. package/dist/cli.d.ts +1 -5
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +1 -5
  22. package/dist/codecs.d.ts +20 -18
  23. package/dist/codecs.d.ts.map +1 -1
  24. package/dist/codecs.js +21 -31
  25. package/dist/contract.d.ts.map +1 -1
  26. package/dist/contract.js +8 -3
  27. package/dist/definition-error.d.ts +5 -0
  28. package/dist/definition-error.d.ts.map +1 -0
  29. package/dist/definition-error.js +8 -0
  30. package/dist/factory.d.ts +25 -11
  31. package/dist/factory.d.ts.map +1 -1
  32. package/dist/factory.js +28 -18
  33. package/dist/http-compiler.d.ts +15 -0
  34. package/dist/http-compiler.d.ts.map +1 -0
  35. package/dist/http-compiler.js +37 -0
  36. package/dist/http-model.d.ts +42 -0
  37. package/dist/http-model.d.ts.map +1 -0
  38. package/dist/http-model.js +20 -0
  39. package/dist/http-tree.d.ts +7 -0
  40. package/dist/http-tree.d.ts.map +1 -0
  41. package/dist/http-tree.js +181 -0
  42. package/dist/http.d.ts +3 -0
  43. package/dist/http.d.ts.map +1 -0
  44. package/dist/http.js +2 -0
  45. package/dist/index.d.ts +2 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1 -1
  48. package/dist/input-compiler.d.ts +13 -0
  49. package/dist/input-compiler.d.ts.map +1 -0
  50. package/dist/input-compiler.js +122 -0
  51. package/dist/introspection.d.ts +1 -1
  52. package/dist/introspection.d.ts.map +1 -1
  53. package/dist/introspection.js +30 -16
  54. package/dist/mcp.d.ts +25 -24
  55. package/dist/mcp.d.ts.map +1 -1
  56. package/dist/mcp.js +87 -65
  57. package/dist/model.d.ts +44 -42
  58. package/dist/model.d.ts.map +1 -1
  59. package/dist/model.js +44 -9
  60. package/dist/owned.d.ts +12 -0
  61. package/dist/owned.d.ts.map +1 -0
  62. package/dist/owned.js +61 -0
  63. package/dist/registration.d.ts +41 -0
  64. package/dist/registration.d.ts.map +1 -0
  65. package/dist/registration.js +14 -0
  66. package/dist/runtime.d.ts +2 -35
  67. package/dist/runtime.d.ts.map +1 -1
  68. package/dist/runtime.js +2 -127
  69. package/dist/schema.d.ts +11 -8
  70. package/dist/schema.d.ts.map +1 -1
  71. package/dist/schema.js +110 -47
  72. package/dist/string-coercion.d.ts +4 -0
  73. package/dist/string-coercion.d.ts.map +1 -0
  74. package/dist/string-coercion.js +24 -0
  75. package/dist/tree-internal.d.ts +4 -2
  76. package/dist/tree-internal.d.ts.map +1 -1
  77. package/dist/tree-internal.js +5 -2
  78. package/dist/values.d.ts +1 -6
  79. package/dist/values.d.ts.map +1 -1
  80. package/dist/values.js +1 -6
  81. package/examples/README.md +28 -0
  82. package/examples/book-search.ts +151 -0
  83. package/examples/greeting.ts +193 -0
  84. package/package.json +9 -4
  85. package/dist/cli-application.d.ts +0 -40
  86. package/dist/cli-application.d.ts.map +0 -1
  87. package/dist/cli-application.js +0 -90
  88. package/dist/cli-executor.d.ts +0 -25
  89. package/dist/cli-executor.d.ts.map +0 -1
  90. package/dist/cli-executor.js +0 -44
  91. package/dist/cli-presentation.d.ts +0 -38
  92. package/dist/cli-presentation.d.ts.map +0 -1
  93. package/dist/cli-presentation.js +0 -301
  94. package/dist/cli-result-envelope.d.ts +0 -104
  95. package/dist/cli-result-envelope.d.ts.map +0 -1
  96. package/dist/cli-result-envelope.js +0 -137
  97. package/dist/cli-schema.d.ts +0 -4
  98. package/dist/cli-schema.d.ts.map +0 -1
  99. package/dist/cli-schema.js +0 -7
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./codecs.js";
2
- export * from "./contract.js";
2
+ export { createOutputContract, EntryOutputContractError, requireEntryOutput, verifyEntryOutput, } from "./contract.js";
3
3
  export * from "./factory.js";
4
4
  export * from "./introspection.js";
5
5
  export * from "./model.js";
@@ -0,0 +1,13 @@
1
+ import type { CompiledParameterBinding } from "./binding-compiler.js";
2
+ import type { ErasedEntryDefinition, ErasedParameterDefinition, ParameterValues, RawValueMap } from "./model.js";
3
+ import type { InputPlan } from "./registration.js";
4
+ export type InputCoercer = (parameter: ErasedParameterDefinition, raw: unknown) => unknown;
5
+ export type CompiledInputPlan<Values extends RawValueMap = RawValueMap, Input = unknown> = InputPlan<Values, Input> & {
6
+ readonly parameterNames: readonly string[];
7
+ readonly parseCoerced: (raw: RawValueMap, coerce: InputCoercer) => Input;
8
+ };
9
+ export declare function compileInputPlan<const Entry extends ErasedEntryDefinition, Values extends RawValueMap = ParameterValues<Entry["parameters"]>>(input: {
10
+ readonly entry: Entry;
11
+ readonly bindings: readonly CompiledParameterBinding[];
12
+ }): CompiledInputPlan<Values, ReturnType<Entry["project"]>>;
13
+ //# sourceMappingURL=input-compiler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-compiler.d.ts","sourceRoot":"","sources":["../src/input-compiler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEtE,OAAO,KAAK,EACV,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,EACf,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAOnD,MAAM,MAAM,YAAY,GAAG,CACzB,SAAS,EAAE,yBAAyB,EACpC,GAAG,EAAE,OAAO,KACT,OAAO,CAAC;AAEb,MAAM,MAAM,iBAAiB,CAC3B,MAAM,SAAS,WAAW,GAAG,WAAW,EACxC,KAAK,GAAG,OAAO,IACb,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG;IAC7B,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,KAAK,KAAK,CAAC;CAC1E,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,KAAK,CAAC,KAAK,SAAS,qBAAqB,EACzC,MAAM,SAAS,WAAW,GAAG,eAAe,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EACjE,KAAK,EAAE;IACP,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACxD,GAAG,iBAAiB,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CA4B1D"}
@@ -0,0 +1,122 @@
1
+ import { z } from "zod";
2
+ import { EntryTreeDefinitionError } from "./definition-error.js";
3
+ import { copyJsonValue, ownedRecord } from "./owned.js";
4
+ import { EntryInputError } from "./runtime.js";
5
+ import { ENTRY_INPUT_TARGET_KIND, PARAMETER_REQUIREMENT_KIND, } from "./values.js";
6
+ export function compileInputPlan(input) {
7
+ validateDefaults(input.entry);
8
+ const exposedNames = input.bindings.map((binding) => binding.parameter.name);
9
+ const exposed = new Set(exposedNames);
10
+ const schema = buildInputSchema(input.bindings);
11
+ const project = (values) => projectEntry(input.entry, exposed, values);
12
+ const parseValues = (raw, coerce) => {
13
+ rejectUnknownKeys(raw, exposed);
14
+ const candidate = coerce
15
+ ? coerceRawValues(raw, input.bindings, coerce)
16
+ : raw;
17
+ const parsed = schema.safeParse(candidate);
18
+ if (!parsed.success) {
19
+ throw entryInputError(parsed.error);
20
+ }
21
+ return project(parsed.data);
22
+ };
23
+ return ownedRecord({
24
+ schema: schema,
25
+ parse: (raw) => parseValues(raw),
26
+ parseCoerced: (raw, coerce) => parseValues(raw, coerce),
27
+ project,
28
+ parameterNames: Object.freeze(exposedNames),
29
+ });
30
+ }
31
+ function buildInputSchema(bindings) {
32
+ const shape = Object.create(null);
33
+ for (const { parameter } of bindings) {
34
+ let schema = parameter.codec.schema;
35
+ switch (parameter.requirement.kind) {
36
+ case PARAMETER_REQUIREMENT_KIND.required:
37
+ break;
38
+ case PARAMETER_REQUIREMENT_KIND.optional:
39
+ schema = schema.optional();
40
+ break;
41
+ case PARAMETER_REQUIREMENT_KIND.optionalWithDefault: {
42
+ const authoredDefault = parameter.requirement.defaultValue;
43
+ schema = schema.prefault(() => copyJsonValue(authoredDefault));
44
+ break;
45
+ }
46
+ }
47
+ schema = schema.describe(parameter.description);
48
+ Reflect.set(shape, parameter.name, schema);
49
+ }
50
+ return z.object(shape).strict();
51
+ }
52
+ function validateDefaults(entry) {
53
+ const issues = [];
54
+ for (const parameter of entry.parameters) {
55
+ if (parameter.requirement.kind !==
56
+ PARAMETER_REQUIREMENT_KIND.optionalWithDefault) {
57
+ continue;
58
+ }
59
+ const parsed = parameter.codec.schema.safeParse(copyJsonValue(parameter.requirement.defaultValue));
60
+ if (!parsed.success) {
61
+ issues.push(`${entry.name}#${parameter.name}: invalid authored default: ${formatZodIssues(parsed.error)}`);
62
+ }
63
+ }
64
+ if (issues.length > 0) {
65
+ throw new EntryTreeDefinitionError(issues);
66
+ }
67
+ }
68
+ function projectEntry(entry, exposed, publicValues) {
69
+ const values = Object.create(null);
70
+ for (const parameter of entry.parameters) {
71
+ if (exposed.has(parameter.name)) {
72
+ if (Object.hasOwn(publicValues, parameter.name)) {
73
+ Reflect.set(values, parameter.name, Reflect.get(publicValues, parameter.name));
74
+ }
75
+ continue;
76
+ }
77
+ if (parameter.requirement.kind ===
78
+ PARAMETER_REQUIREMENT_KIND.optionalWithDefault) {
79
+ const parsed = parameter.codec.schema.safeParse(copyJsonValue(parameter.requirement.defaultValue));
80
+ if (!parsed.success) {
81
+ throw new Error(`Compiled default became invalid for ${entry.name}#${parameter.name}`);
82
+ }
83
+ Reflect.set(values, parameter.name, parsed.data);
84
+ }
85
+ }
86
+ const project = entry.project;
87
+ return project({ values: values });
88
+ }
89
+ function rejectUnknownKeys(raw, exposed) {
90
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
91
+ throw new EntryInputError(["Entry input must be an object"]);
92
+ }
93
+ const unknown = Object.keys(raw).filter((key) => !exposed.has(key));
94
+ if (unknown.length > 0) {
95
+ throw new EntryInputError(unknown.map((key) => `Unknown parameter ${key}`));
96
+ }
97
+ }
98
+ function coerceRawValues(raw, bindings, coerce) {
99
+ const output = Object.create(null);
100
+ for (const binding of bindings) {
101
+ const name = binding.parameter.name;
102
+ if (!Object.hasOwn(raw, name)) {
103
+ continue;
104
+ }
105
+ Reflect.set(output, name, coerce(binding.parameter, Reflect.get(raw, name)));
106
+ }
107
+ return output;
108
+ }
109
+ function entryInputError(error) {
110
+ const first = error.issues[0];
111
+ const firstPath = first?.path[0];
112
+ const target = typeof firstPath === "string"
113
+ ? {
114
+ kind: ENTRY_INPUT_TARGET_KIND.parameter,
115
+ parameterName: firstPath,
116
+ }
117
+ : { kind: ENTRY_INPUT_TARGET_KIND.entry };
118
+ return new EntryInputError(error.issues.map((issue) => issue.message), target);
119
+ }
120
+ function formatZodIssues(error) {
121
+ return error.issues.map((issue) => issue.message).join("; ");
122
+ }
@@ -1,6 +1,6 @@
1
1
  import type { JsonValue } from "@alpacakit/core";
2
2
  import type { CommandDocumentation, EntryTreeDefinition, ErasedEntryDefinition, ValueShape } from "./model.js";
3
- import { ENTRY_OUTPUT_DESCRIPTION_KIND, type PARAMETER_REQUIREMENT_KIND } from "./values.js";
3
+ import { ENTRY_OUTPUT_DESCRIPTION_KIND, PARAMETER_REQUIREMENT_KIND } from "./values.js";
4
4
  export type CommandPath = readonly string[];
5
5
  export type ParameterRequirementDescription = {
6
6
  readonly kind: typeof PARAMETER_REQUIREMENT_KIND.required;
@@ -1 +1 @@
1
- {"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../src/introspection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EAEnB,qBAAqB,EACrB,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,6BAA6B,EAC7B,KAAK,0BAA0B,EAChC,MAAM,aAAa,CAAC;AAOrB,MAAM,MAAM,WAAW,GAAG,SAAS,MAAM,EAAE,CAAC;AAE5C,MAAM,MAAM,+BAA+B,GACvC;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC,QAAQ,CAAA;CAAE,GAC7D;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC,QAAQ,CAAC;CAC3D,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC,mBAAmB,CAAC;IACrE,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC;CAClC,CAAC;AAEN,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,+BAA+B,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,6BAA6B,CAAC,KAAK,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,6BAA6B,CAAC,IAAI,CAAA;CAAE,CAAC;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAClD,CAAC;AAOF,wBAAgB,aAAa,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,CAgB5E;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,mBAAmB,GACxB,kBAAkB,CAiCpB;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,GACtE,+BAA+B,CAEjC"}
1
+ {"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../src/introspection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EAEnB,qBAAqB,EACrB,UAAU,EACX,MAAM,YAAY,CAAC;AAYpB,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AAOrB,MAAM,MAAM,WAAW,GAAG,SAAS,MAAM,EAAE,CAAC;AAE5C,MAAM,MAAM,+BAA+B,GACvC;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC,QAAQ,CAAA;CAAE,GAC7D;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC,QAAQ,CAAC;CAC3D,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC,mBAAmB,CAAC;IACrE,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC;CAClC,CAAC;AAEN,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,+BAA+B,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,6BAA6B,CAAC,KAAK,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,6BAA6B,CAAC,IAAI,CAAA;CAAE,CAAC;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAClD,CAAC;AAOF,wBAAgB,aAAa,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,CAoB5E;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,mBAAmB,GACxB,kBAAkB,CA4CpB;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,GACtE,+BAA+B,CAWjC"}
@@ -1,49 +1,54 @@
1
- import { visitEntryTree } from "./schema.js";
2
- import { ENTRY_OUTPUT_DESCRIPTION_KIND, } from "./values.js";
1
+ import { ownedArray, ownedRecord, snapshotJsonValue, snapshotValueShape, } from "./owned.js";
2
+ import { EntryTreeDefinitionError, validateEntryTreeStructure, visitEntryTree, } from "./schema.js";
3
+ import { ENTRY_OUTPUT_DESCRIPTION_KIND, PARAMETER_REQUIREMENT_KIND, } from "./values.js";
3
4
  const ENTRY_INTROSPECTION_ERROR = {
4
5
  missingChild: "Entry tree child could not be described",
5
6
  missingRoot: "Entry tree root could not be described",
6
7
  };
7
8
  export function describeEntry(entry) {
8
- return {
9
+ return ownedRecord({
9
10
  name: entry.name,
10
- parameters: entry.parameters.map((parameter) => ({
11
+ parameters: ownedArray(entry.parameters.map((parameter) => ownedRecord({
11
12
  name: parameter.name,
12
13
  description: parameter.description,
13
14
  requirement: describeParameterRequirement(parameter.requirement),
14
- shape: parameter.codec.shape,
15
- })),
16
- output: {
15
+ shape: snapshotValueShape(parameter.codec.shape),
16
+ }))),
17
+ output: ownedRecord({
17
18
  kind: entry.output === null
18
19
  ? ENTRY_OUTPUT_DESCRIPTION_KIND.none
19
20
  : ENTRY_OUTPUT_DESCRIPTION_KIND.typed,
20
- },
21
- };
21
+ }),
22
+ });
22
23
  }
23
24
  export function describeEntryTree(tree) {
25
+ const issues = validateEntryTreeStructure(tree);
26
+ if (issues.length > 0) {
27
+ throw new EntryTreeDefinitionError(issues);
28
+ }
24
29
  const frames = new Map();
25
30
  const entries = [];
26
31
  visitEntryTree(tree, {
27
32
  visitNode(node, path) {
28
- entries.push({ node, path: path.slice(1) });
33
+ entries.push({ node, path: ownedArray(path.slice(1)) });
29
34
  },
30
35
  visitEndpoint() { },
31
36
  });
32
37
  for (const current of entries.reverse()) {
33
- frames.set(current.node, {
38
+ frames.set(current.node, ownedRecord({
34
39
  path: current.path,
35
40
  name: current.node.name,
36
41
  description: current.node.description,
37
42
  documentation: current.node.documentation,
38
- endpoints: current.node.endpoints.map((endpoint) => describeEntry(endpoint.entry)),
39
- children: current.node.children.map((child) => {
43
+ endpoints: ownedArray(current.node.endpoints.map((endpoint) => describeEntry(endpoint.entry))),
44
+ children: ownedArray(current.node.children.map((child) => {
40
45
  const description = frames.get(child);
41
46
  if (description === undefined) {
42
47
  throw new Error(ENTRY_INTROSPECTION_ERROR.missingChild);
43
48
  }
44
49
  return description;
45
- }),
46
- });
50
+ })),
51
+ }));
47
52
  }
48
53
  const root = frames.get(tree.root);
49
54
  if (root === undefined) {
@@ -52,5 +57,14 @@ export function describeEntryTree(tree) {
52
57
  return root;
53
58
  }
54
59
  export function describeParameterRequirement(requirement) {
55
- return requirement;
60
+ switch (requirement.kind) {
61
+ case PARAMETER_REQUIREMENT_KIND.required:
62
+ case PARAMETER_REQUIREMENT_KIND.optional:
63
+ return ownedRecord({ kind: requirement.kind });
64
+ case PARAMETER_REQUIREMENT_KIND.optionalWithDefault:
65
+ return ownedRecord({
66
+ kind: requirement.kind,
67
+ defaultValue: snapshotJsonValue(requirement.defaultValue),
68
+ });
69
+ }
56
70
  }
package/dist/mcp.d.ts CHANGED
@@ -1,26 +1,27 @@
1
- import type { JsonObject } from "@alpacakit/core";
2
- import type { z } from "zod";
3
- import type { EntryChannelBinding, EntryDefinitionWithOutput, EntryTreeEndpoint, ParameterSet, RawValueMap } from "./model.js";
4
- import { type EntryDecodeResult } from "./runtime.js";
5
- import { CHANNEL, ENTRY_DECODE_RESULT_KIND } from "./values.js";
6
- export type McpBinding<Parameters extends ParameterSet = ParameterSet> = readonly Parameters[number]["name"][];
7
- export declare function bindMcpEntry<const ParameterNames extends readonly string[]>(...parameterNames: ParameterNames): EntryChannelBinding<typeof CHANNEL.mcp, ParameterNames[number]>;
8
- export type McpEntryParseResult<Entry extends EntryTreeEndpoint["entry"]> = EntryDecodeResult<ReturnType<Entry["project"]>>;
9
- export type McpResolvedEntry<Entry extends EntryTreeEndpoint["entry"]> = Extract<McpEntryParseResult<Entry>, {
10
- readonly kind: typeof ENTRY_DECODE_RESULT_KIND.resolved;
11
- }>;
12
- /**
13
- * A compiled MCP endpoint. The binding is validated and compiled once by
14
- * `createMcpEndpoint()`, so definition errors surface eagerly (like the CLI
15
- * router) and `parse()` does no per-invocation recompilation.
16
- */
17
- export interface McpEndpoint<Entry extends EntryTreeEndpoint["entry"]> {
18
- /** Raw Zod object shape for MCP tool registration. */
19
- readonly inputSchema: Record<string, z.ZodType>;
20
- /** Decode and project one MCP invocation through the endpoint. */
21
- parse(raw: RawValueMap): McpEntryParseResult<Entry>;
1
+ import { type ParameterDescription } from "./introspection.js";
2
+ import type { EntryChannelBinding, RawValueMap } from "./model.js";
3
+ import type { InputPlan, Registration } from "./registration.js";
4
+ import { type ContextFor, type EndpointForChannel, type EndpointsOfSource, type EntryTreeSource, type OutputFor } from "./registration.js";
5
+ import { CHANNEL } from "./values.js";
6
+ export type McpBinding<Name extends string = string, Excluded extends readonly string[] = readonly string[]> = {
7
+ readonly name: Name;
8
+ readonly exclude: Excluded;
9
+ };
10
+ export declare function bindMcpEntry<const Name extends string, const Excluded extends readonly string[] = readonly []>(input: {
11
+ readonly name: Name;
12
+ readonly exclude?: Excluded;
13
+ }): EntryChannelBinding<typeof CHANNEL.mcp, McpBinding<Name, Excluded>>;
14
+ export type McpRegistration<Values extends RawValueMap, Input, Context, Output> = Omit<Registration<Values, Input, Context, Output>, "binding"> & {
15
+ readonly binding: EntryChannelBinding<typeof CHANNEL.mcp, McpBinding>;
16
+ readonly name: string;
17
+ readonly parameters: readonly ParameterDescription[];
18
+ readonly input: InputPlan<Values, Input>;
19
+ };
20
+ export interface McpToolCollection<Context, Output> {
21
+ readonly length: number;
22
+ readonly forEach: (visit: <Values extends RawValueMap, Input>(item: McpRegistration<Values, Input, Context, Output>) => void) => void;
22
23
  }
23
- export declare function createMcpEndpoint<const Entry extends EntryTreeEndpoint["entry"]>(endpoint: EntryTreeEndpoint<Entry>): McpEndpoint<Entry>;
24
- export declare function isMcpResolved<Entry extends EntryTreeEndpoint["entry"]>(result: McpEntryParseResult<Entry>): result is McpResolvedEntry<Entry>;
25
- export declare function createMcpOutputSchema<const Name extends string, const Parameters extends ParameterSet, Projection, Output extends JsonObject>(endpoint: EntryTreeEndpoint<EntryDefinitionWithOutput<Name, Parameters, Projection, Output>>): z.ZodType<Output>;
24
+ type McpEndpointFor<Source extends EntryTreeSource> = EndpointForChannel<EndpointsOfSource<Source>, typeof CHANNEL.mcp>;
25
+ export declare function collectMcpTools<const Source extends EntryTreeSource>(source: Source): McpToolCollection<ContextFor<McpEndpointFor<Source>>, OutputFor<McpEndpointFor<Source>>>;
26
+ export {};
26
27
  //# sourceMappingURL=mcp.d.ts.map
package/dist/mcp.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQ7B,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAQxE,OAAO,EACL,OAAO,EACP,wBAAwB,EAEzB,MAAM,aAAa,CAAC;AAOrB,MAAM,MAAM,UAAU,CAAC,UAAU,SAAS,YAAY,GAAG,YAAY,IACnE,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;AAExC,wBAAgB,YAAY,CAAC,KAAK,CAAC,cAAc,SAAS,SAAS,MAAM,EAAE,EACzE,GAAG,cAAc,EAAE,cAAc,GAChC,mBAAmB,CAAC,OAAO,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC,CAGjE;AAED,MAAM,MAAM,mBAAmB,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,IACtE,iBAAiB,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAElD,MAAM,MAAM,gBAAgB,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,IACnE,OAAO,CACL,mBAAmB,CAAC,KAAK,CAAC,EAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,wBAAwB,CAAC,QAAQ,CAAA;CAAE,CAC5D,CAAC;AAEJ;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC;IACnE,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IAChD,kEAAkE;IAClE,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;CACrD;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EAC9C,QAAQ,EAAE,iBAAiB,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAaxD;AAED,wBAAgB,aAAa,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EACpE,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC,GACjC,MAAM,IAAI,gBAAgB,CAAC,KAAK,CAAC,CAEnC;AAmED,wBAAgB,qBAAqB,CACnC,KAAK,CAAC,IAAI,SAAS,MAAM,EACzB,KAAK,CAAC,UAAU,SAAS,YAAY,EACrC,UAAU,EACV,MAAM,SAAS,UAAU,EAEzB,QAAQ,EAAE,iBAAiB,CACzB,yBAAyB,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,CAChE,GACA,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAWnB"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAMA,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,KAAK,EACV,mBAAmB,EAEnB,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAa3B,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,MAAM,UAAU,CACpB,IAAI,SAAS,MAAM,GAAG,MAAM,EAC5B,QAAQ,SAAS,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,IACpD;IACF,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;CAC5B,CAAC;AAEF,wBAAgB,YAAY,CAC1B,KAAK,CAAC,IAAI,SAAS,MAAM,EACzB,KAAK,CAAC,QAAQ,SAAS,SAAS,MAAM,EAAE,GAAG,SAAS,EAAE,EACtD,KAAK,EAAE;IACP,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC;CAC7B,GAAG,mBAAmB,CAAC,OAAO,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAOtE;AAED,MAAM,MAAM,eAAe,CACzB,MAAM,SAAS,WAAW,EAC1B,KAAK,EACL,OAAO,EACP,MAAM,IACJ,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG;IAClE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACrD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CAC1C,CAAC;AAEF,MAAM,WAAW,iBAAiB,CAAC,OAAO,EAAE,MAAM;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,CAChB,KAAK,EAAE,CAAC,MAAM,SAAS,WAAW,EAAE,KAAK,EACvC,IAAI,EAAE,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,KAClD,IAAI,KACN,IAAI,CAAC;CACX;AAED,KAAK,cAAc,CAAC,MAAM,SAAS,eAAe,IAAI,kBAAkB,CACtE,iBAAiB,CAAC,MAAM,CAAC,EACzB,OAAO,OAAO,CAAC,GAAG,CACnB,CAAC;AAOF,wBAAgB,eAAe,CAAC,KAAK,CAAC,MAAM,SAAS,eAAe,EAClE,MAAM,EAAE,MAAM,GACb,iBAAiB,CAClB,UAAU,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,EAClC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAClC,CA0CA"}
package/dist/mcp.js CHANGED
@@ -1,67 +1,99 @@
1
- import { compileParameterBindings, PARAMETER_BINDING_ISSUE_KIND, } from "./binding-compiler.js";
2
- import { requireEntryOutput } from "./contract.js";
3
- import { decodeEntryInput } from "./runtime.js";
4
- import { EntryTreeDefinitionError, findChannelBinding } from "./schema.js";
1
+ import { compileParameterExclusions, PARAMETER_BINDING_ISSUE_KIND, } from "./binding-compiler.js";
2
+ import { compileInputPlan } from "./input-compiler.js";
3
+ import { describeEntry } from "./introspection.js";
4
+ import { ownedArray, ownedRecord } from "./owned.js";
5
+ import { packRegistrationCollection, } from "./registration.js";
6
+ import { EntryTreeDefinitionError, findChannelBinding, validateEntryTreeStructure, visitEntryTreeSource, } from "./schema.js";
5
7
  import { createEntryChannelBinding, isChannelPayload, PAYLOAD_KIND, registerChannelPayload, } from "./tree-internal.js";
6
- import { CHANNEL, ENTRY_DECODE_RESULT_KIND, PARAMETER_REQUIREMENT_KIND, } from "./values.js";
7
- const ZOD_SCHEMA_TYPE = {
8
- object: "object",
9
- readonly: "readonly",
10
- };
11
- export function bindMcpEntry(...parameterNames) {
12
- registerChannelPayload(PAYLOAD_KIND.mcpBinding, parameterNames);
13
- return createEntryChannelBinding(CHANNEL.mcp, parameterNames);
8
+ import { CHANNEL } from "./values.js";
9
+ export function bindMcpEntry(input) {
10
+ const payload = ownedRecord({
11
+ name: input.name,
12
+ exclude: ownedArray(input.exclude ?? []),
13
+ });
14
+ registerChannelPayload(PAYLOAD_KIND.mcpBinding, payload);
15
+ return createEntryChannelBinding(CHANNEL.mcp, payload);
14
16
  }
15
- export function createMcpEndpoint(endpoint) {
16
- const bindings = resolveMcpBindings(endpoint);
17
- const parameterNames = bindings.map((binding) => binding.parameter.name);
18
- const inputSchema = buildMcpInputSchema(bindings);
19
- return {
20
- inputSchema,
21
- parse: (raw) => decodeEntryInput({
22
- entry: endpoint.entry,
23
- parameterNames,
24
- raw,
25
- }),
26
- };
27
- }
28
- export function isMcpResolved(result) {
29
- return result.kind === ENTRY_DECODE_RESULT_KIND.resolved;
30
- }
31
- function buildMcpInputSchema(bindings) {
32
- const shape = {};
33
- for (const binding of bindings) {
34
- const { parameter } = binding;
35
- const describedSchema = parameter.codec.schema.describe(parameter.description);
36
- shape[parameter.name] =
37
- parameter.requirement.kind !== PARAMETER_REQUIREMENT_KIND.required
38
- ? describedSchema.optional()
39
- : describedSchema;
17
+ export function collectMcpTools(source) {
18
+ const structuralIssues = validateEntryTreeStructure(source);
19
+ if (structuralIssues.length > 0) {
20
+ throw new EntryTreeDefinitionError(structuralIssues);
21
+ }
22
+ const issues = [];
23
+ const registrations = [];
24
+ const seenEndpoints = new Set();
25
+ visitEntryTreeSource(source, {
26
+ visitNode() { },
27
+ visitEndpoint(endpoint) {
28
+ if (seenEndpoints.has(endpoint)) {
29
+ return;
30
+ }
31
+ const channelBinding = findChannelBinding(endpoint, CHANNEL.mcp);
32
+ if (channelBinding === null) {
33
+ return;
34
+ }
35
+ seenEndpoints.add(endpoint);
36
+ if (!isMcpChannelBinding(channelBinding)) {
37
+ issues.push(`${endpoint.entry.name}: invalid MCP binding`);
38
+ return;
39
+ }
40
+ const registration = compileMcpRegistration(endpoint, channelBinding, channelBinding.binding, issues);
41
+ registrations.push(registration);
42
+ },
43
+ });
44
+ validateMcpNames(registrations, issues);
45
+ if (issues.length > 0) {
46
+ throw new EntryTreeDefinitionError(issues);
40
47
  }
41
- return shape;
48
+ return packRegistrationCollection(registrations);
42
49
  }
43
- function resolveMcpBindings(endpoint) {
44
- const channelBindings = endpoint.channels.filter((candidate) => candidate.channel === CHANNEL.mcp);
45
- const channelBinding = findChannelBinding(endpoint, CHANNEL.mcp);
46
- if (channelBindings.length !== 1 ||
47
- !channelBinding ||
48
- !isMcpBinding(channelBinding.binding)) {
49
- throw new EntryTreeDefinitionError([
50
- `${endpoint.entry.name}: endpoint must have exactly one valid MCP binding`,
51
- ]);
50
+ function compileMcpRegistration(endpoint, channelBinding, binding, issues) {
51
+ if (binding.name.trim().length === 0) {
52
+ issues.push(`${endpoint.entry.name}: MCP name must not be blank`);
52
53
  }
53
- const compilation = compileParameterBindings(endpoint.entry, channelBinding.binding.map((parameterName) => ({ parameterName })));
54
- if (compilation.issues.length > 0) {
55
- throw new EntryTreeDefinitionError(compilation.issues.map((issue) => formatMcpParameterBindingIssue(endpoint.entry.name, issue)));
54
+ const compilation = compileParameterExclusions(endpoint.entry, binding.exclude);
55
+ issues.push(...compilation.issues.map((issue) => formatMcpParameterBindingIssue(endpoint.entry.name, issue)));
56
+ const input = compileInputPlan({
57
+ entry: endpoint.entry,
58
+ bindings: compilation.bindings,
59
+ });
60
+ const exposed = new Set(input.parameterNames);
61
+ return ownedRecord({
62
+ endpoint,
63
+ entry: endpoint.entry,
64
+ binding: channelBinding,
65
+ name: binding.name,
66
+ parameters: ownedArray(describeEntry(endpoint.entry).parameters.filter((parameter) => exposed.has(parameter.name))),
67
+ input: ownedRecord({
68
+ schema: input.schema,
69
+ parse: input.parse,
70
+ project: input.project,
71
+ }),
72
+ handler: endpoint.handler,
73
+ });
74
+ }
75
+ function validateMcpNames(registrations, issues) {
76
+ const names = new Map();
77
+ for (const registration of registrations) {
78
+ const existing = names.get(registration.name);
79
+ if (existing !== undefined && existing.endpoint !== registration.endpoint) {
80
+ issues.push(`MCP name ${JSON.stringify(registration.name)} is owned by different endpoints`);
81
+ }
82
+ else {
83
+ names.set(registration.name, registration);
84
+ }
56
85
  }
57
- return compilation.bindings;
58
86
  }
59
87
  function formatMcpParameterBindingIssue(entryName, issue) {
60
88
  switch (issue.kind) {
61
89
  case PARAMETER_BINDING_ISSUE_KIND.duplicate:
62
- return `${entryName}: duplicate MCP binding for parameter ${issue.parameterName}`;
90
+ return `${entryName}: duplicate exposed MCP parameter ${issue.parameterName}`;
91
+ case PARAMETER_BINDING_ISSUE_KIND.duplicateExclusion:
92
+ return `${entryName}: duplicate MCP exclusion for parameter ${issue.parameterName}`;
93
+ case PARAMETER_BINDING_ISSUE_KIND.nonexistentExclusion:
94
+ return `${entryName}: MCP excludes nonexistent parameter ${issue.parameterName}`;
63
95
  case PARAMETER_BINDING_ISSUE_KIND.nonexistent:
64
- return `${entryName}: MCP binding names nonexistent parameter ${issue.parameterName}`;
96
+ return `${entryName}: MCP exposes nonexistent parameter ${issue.parameterName}`;
65
97
  case PARAMETER_BINDING_ISSUE_KIND.requiredUnreachable:
66
98
  return `${entryName}: required parameter ${issue.parameterName} is unreachable from MCP`;
67
99
  }
@@ -69,16 +101,6 @@ function formatMcpParameterBindingIssue(entryName, issue) {
69
101
  function isMcpBinding(value) {
70
102
  return isChannelPayload(PAYLOAD_KIND.mcpBinding, value);
71
103
  }
72
- export function createMcpOutputSchema(endpoint) {
73
- resolveMcpBindings(endpoint);
74
- const schema = requireEntryOutput(endpoint.entry).schema;
75
- const normalized = schema.type === ZOD_SCHEMA_TYPE.readonly ? unwrapZodSchema(schema) : schema;
76
- if (normalized.type !== ZOD_SCHEMA_TYPE.object) {
77
- throw new Error(`Entry ${endpoint.entry.name} output contract must use a Zod object schema`);
78
- }
79
- return normalized;
80
- }
81
- function unwrapZodSchema(schema) {
82
- const wrapper = schema;
83
- return wrapper.unwrap();
104
+ function isMcpChannelBinding(binding) {
105
+ return binding.channel === CHANNEL.mcp && isMcpBinding(binding.binding);
84
106
  }