toolcraft 0.0.232 → 0.0.234

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 (180) hide show
  1. package/composition.json +3 -3
  2. package/dist/applied-default.d.ts +3 -0
  3. package/dist/applied-default.js +16 -0
  4. package/dist/cli.js +508 -204
  5. package/dist/composition.json +3 -3
  6. package/dist/discriminator.d.ts +9 -0
  7. package/dist/discriminator.js +32 -0
  8. package/dist/error-report.d.ts +2 -18
  9. package/dist/error-report.js +12 -14
  10. package/dist/http.js +8 -1
  11. package/dist/human-in-loop/gate.js +6 -0
  12. package/dist/index.compile-check.js +22 -5
  13. package/dist/index.d.ts +12 -5
  14. package/dist/index.js +50 -0
  15. package/dist/json-schema-converter.d.ts +14 -6
  16. package/dist/json-schema-converter.js +150 -20
  17. package/dist/mcp-proxy-metadata.d.ts +2 -0
  18. package/dist/mcp-proxy-metadata.js +13 -0
  19. package/dist/mcp-proxy.d.ts +1 -0
  20. package/dist/mcp-proxy.js +65 -47
  21. package/dist/mcp-result.d.ts +3 -0
  22. package/dist/mcp-result.js +10 -0
  23. package/dist/mcp.js +211 -153
  24. package/dist/redaction.js +26 -17
  25. package/dist/renderer.js +40 -29
  26. package/dist/schema-member-names.d.ts +2 -0
  27. package/dist/schema-member-names.js +39 -0
  28. package/dist/schema-scope.d.ts +1 -0
  29. package/dist/schema-scope.js +29 -16
  30. package/dist/schema.d.ts +1 -1
  31. package/dist/sdk.d.ts +2 -1
  32. package/dist/sdk.js +164 -50
  33. package/dist/stream.js +24 -23
  34. package/dist/testing/diagnostic-value.d.ts +1 -0
  35. package/dist/testing/diagnostic-value.js +57 -0
  36. package/dist/testing/fakes.js +2 -1
  37. package/dist/testing/harness.js +1 -0
  38. package/dist/testing/memory-fs.js +42 -1
  39. package/dist/testing/parity.js +50 -6
  40. package/dist/union-validation.d.ts +5 -0
  41. package/dist/union-validation.js +36 -0
  42. package/node_modules/@poe-code/process-runner/dist/types.d.ts +2 -0
  43. package/node_modules/mcp-oauth-server/dist/index.js +47 -6
  44. package/node_modules/tiny-http-mcp-server/dist/cli.js +14 -2
  45. package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
  46. package/node_modules/tiny-http-mcp-server/dist/http-server.d.ts +7 -5
  47. package/node_modules/tiny-http-mcp-server/dist/http-server.js +3 -11
  48. package/node_modules/tiny-http-mcp-server/dist/http-transport.d.ts +6 -1
  49. package/node_modules/tiny-http-mcp-server/dist/http-transport.js +196 -15
  50. package/node_modules/tiny-http-mcp-server/dist/index.d.ts +2 -2
  51. package/node_modules/tiny-http-mcp-server/dist/modern-headers.d.ts +3 -0
  52. package/node_modules/tiny-http-mcp-server/dist/modern-headers.js +35 -0
  53. package/node_modules/tiny-http-mcp-server/dist/parse-body.d.ts +1 -0
  54. package/node_modules/tiny-http-mcp-server/dist/parse-body.js +9 -5
  55. package/node_modules/tiny-http-mcp-server/dist/test-support.js +17 -0
  56. package/node_modules/tiny-http-mcp-server/dist/testing.d.ts +1 -1
  57. package/node_modules/tiny-http-mcp-server/dist/testing.js +3 -2
  58. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +21 -4
  59. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +1 -1
  60. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +14 -6
  61. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.d.ts +1 -0
  62. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.js +8 -0
  63. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.d.ts +1 -0
  64. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.js +50 -0
  65. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -0
  66. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +2 -0
  67. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +21 -3
  68. package/node_modules/tiny-http-mcp-server/package.json +1 -1
  69. package/node_modules/tiny-mcp-client/README.md +1 -0
  70. package/node_modules/tiny-mcp-client/dist/index.d.ts +178 -101
  71. package/node_modules/tiny-mcp-client/dist/index.js +4696 -291
  72. package/node_modules/tiny-mcp-client/package.json +2 -1
  73. package/node_modules/tiny-stdio-mcp-server/dist/base64.d.ts +1 -0
  74. package/node_modules/tiny-stdio-mcp-server/dist/base64.js +5 -0
  75. package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
  76. package/node_modules/tiny-stdio-mcp-server/dist/content/audio.d.ts +3 -0
  77. package/node_modules/tiny-stdio-mcp-server/dist/content/convert.d.ts +5 -1
  78. package/node_modules/tiny-stdio-mcp-server/dist/content/convert.js +41 -6
  79. package/node_modules/tiny-stdio-mcp-server/dist/content/file.d.ts +5 -0
  80. package/node_modules/tiny-stdio-mcp-server/dist/content/image.d.ts +3 -0
  81. package/node_modules/tiny-stdio-mcp-server/dist/headers.d.ts +10 -0
  82. package/node_modules/tiny-stdio-mcp-server/dist/headers.js +180 -0
  83. package/node_modules/tiny-stdio-mcp-server/dist/index.d.ts +5 -5
  84. package/node_modules/tiny-stdio-mcp-server/dist/index.js +1 -1
  85. package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.d.ts +220 -0
  86. package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.js +1995 -0
  87. package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.d.ts +1 -0
  88. package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.js +38 -25
  89. package/node_modules/tiny-stdio-mcp-server/dist/metadata.d.ts +2 -0
  90. package/node_modules/tiny-stdio-mcp-server/dist/metadata.js +37 -0
  91. package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.d.ts +4 -0
  92. package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.js +53 -0
  93. package/node_modules/tiny-stdio-mcp-server/dist/protocol.d.ts +17 -0
  94. package/node_modules/tiny-stdio-mcp-server/dist/protocol.js +228 -0
  95. package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.d.ts +1 -0
  96. package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.js +18 -0
  97. package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.d.ts +18 -0
  98. package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.js +34 -0
  99. package/node_modules/tiny-stdio-mcp-server/dist/schema.d.ts +4 -1
  100. package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +9 -4
  101. package/node_modules/tiny-stdio-mcp-server/dist/server.js +334 -135
  102. package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.d.ts +3 -0
  103. package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.js +50 -28
  104. package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.d.ts +11 -0
  105. package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.js +128 -0
  106. package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +58 -18
  107. package/node_modules/tiny-stdio-mcp-server/dist/types.js +2 -1
  108. package/node_modules/tiny-stdio-mcp-server/dist/uri.d.ts +1 -0
  109. package/node_modules/tiny-stdio-mcp-server/dist/uri.js +41 -0
  110. package/node_modules/tiny-stdio-mcp-server/package.json +8 -0
  111. package/node_modules/toolcraft-design/dist/command-registry.d.ts +11 -0
  112. package/node_modules/toolcraft-design/dist/command-registry.js +24 -0
  113. package/node_modules/toolcraft-design/dist/dashboard/ansi.d.ts +2 -0
  114. package/node_modules/toolcraft-design/dist/dashboard/ansi.js +81 -27
  115. package/node_modules/toolcraft-design/dist/dashboard/buffer.js +39 -25
  116. package/node_modules/toolcraft-design/dist/dashboard/components/border.js +6 -5
  117. package/node_modules/toolcraft-design/dist/dashboard/components/footer.js +26 -21
  118. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.d.ts +1 -1
  119. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.js +12 -6
  120. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.d.ts +2 -1
  121. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.js +51 -10
  122. package/node_modules/toolcraft-design/dist/dashboard/dashboard.d.ts +4 -0
  123. package/node_modules/toolcraft-design/dist/dashboard/dashboard.js +77 -4
  124. package/node_modules/toolcraft-design/dist/dashboard/index.d.ts +2 -0
  125. package/node_modules/toolcraft-design/dist/dashboard/index.js +2 -0
  126. package/node_modules/toolcraft-design/dist/dashboard/keymap.js +26 -6
  127. package/node_modules/toolcraft-design/dist/dashboard/layout.d.ts +1 -0
  128. package/node_modules/toolcraft-design/dist/dashboard/layout.js +19 -3
  129. package/node_modules/toolcraft-design/dist/dashboard/line-buffer.d.ts +9 -0
  130. package/node_modules/toolcraft-design/dist/dashboard/line-buffer.js +63 -0
  131. package/node_modules/toolcraft-design/dist/dashboard/output-preview.d.ts +9 -0
  132. package/node_modules/toolcraft-design/dist/dashboard/output-preview.js +70 -0
  133. package/node_modules/toolcraft-design/dist/dashboard/snapshot.js +3 -1
  134. package/node_modules/toolcraft-design/dist/dashboard/store.js +9 -3
  135. package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.d.ts +6 -0
  136. package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.js +108 -0
  137. package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +3 -1
  138. package/node_modules/toolcraft-design/dist/dashboard/terminal.js +11 -1
  139. package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.d.ts +1 -0
  140. package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.js +126 -0
  141. package/node_modules/toolcraft-design/dist/dashboard/types.d.ts +3 -1
  142. package/node_modules/toolcraft-design/dist/event-groups.d.ts +19 -0
  143. package/node_modules/toolcraft-design/dist/event-groups.js +49 -0
  144. package/node_modules/toolcraft-design/dist/index.d.ts +6 -1
  145. package/node_modules/toolcraft-design/dist/index.js +5 -1
  146. package/node_modules/toolcraft-design/dist/inline-notice.d.ts +14 -0
  147. package/node_modules/toolcraft-design/dist/inline-notice.js +28 -0
  148. package/node_modules/toolcraft-design/dist/metric.d.ts +8 -0
  149. package/node_modules/toolcraft-design/dist/metric.js +21 -0
  150. package/node_modules/toolcraft-design/dist/overlay-manager.d.ts +6 -0
  151. package/node_modules/toolcraft-design/dist/overlay-manager.js +10 -0
  152. package/node_modules/toolcraft-design/dist/primitives.d.ts +12 -0
  153. package/node_modules/toolcraft-design/dist/primitives.js +7 -0
  154. package/node_modules/toolcraft-design/dist/progress-group.d.ts +7 -0
  155. package/node_modules/toolcraft-design/dist/progress-group.js +10 -0
  156. package/node_modules/toolcraft-design/dist/render-performance.d.ts +33 -0
  157. package/node_modules/toolcraft-design/dist/render-performance.js +71 -0
  158. package/node_modules/toolcraft-design/dist/task-tree.d.ts +22 -0
  159. package/node_modules/toolcraft-design/dist/task-tree.js +79 -0
  160. package/node_modules/toolcraft-design/dist/viewport.d.ts +18 -0
  161. package/node_modules/toolcraft-design/dist/viewport.js +56 -0
  162. package/node_modules/toolcraft-schema/dist/clone-default.d.ts +1 -0
  163. package/node_modules/toolcraft-schema/dist/clone-default.js +26 -0
  164. package/node_modules/toolcraft-schema/dist/index.compile-check.js +1 -0
  165. package/node_modules/toolcraft-schema/dist/index.d.ts +18 -9
  166. package/node_modules/toolcraft-schema/dist/index.js +46 -8
  167. package/node_modules/toolcraft-schema/dist/json-schema/compiler.d.ts +1 -0
  168. package/node_modules/toolcraft-schema/dist/json-schema/compiler.js +6 -0
  169. package/node_modules/toolcraft-schema/dist/json-schema/evaluate.js +6 -3
  170. package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.d.ts +1 -0
  171. package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.js +69 -0
  172. package/node_modules/toolcraft-schema/dist/json-schema/types.d.ts +1 -0
  173. package/node_modules/toolcraft-schema/dist/json.d.ts +4 -1
  174. package/node_modules/toolcraft-schema/dist/json.js +57 -1
  175. package/node_modules/toolcraft-schema/dist/native-json-schema.d.ts +10 -0
  176. package/node_modules/toolcraft-schema/dist/native-json-schema.js +11 -0
  177. package/node_modules/toolcraft-schema/dist/validate.d.ts +5 -1
  178. package/node_modules/toolcraft-schema/dist/validate.js +61 -47
  179. package/node_modules/toolcraft-schema/package.json +1 -1
  180. package/package.json +6 -3
package/dist/sdk.js CHANGED
@@ -1,16 +1,23 @@
1
1
  import "./node-require-shim.js";
2
+ import { cloneDefaultValue, formatIssues, isJsonValue, isPlainRecord, nativeJsonSchema, unicodeLength, validate as validateSchema } from "toolcraft-schema";
2
3
  import { ToolcraftBugError, UserError, assertCommandRequirements, resolveCommandSecrets } from "./index.js";
3
4
  import { writeErrorReport } from "./error-report.js";
4
5
  import { assertHumanInLoopWired, mergeApprovalsRoot } from "./human-in-loop/wiring.js";
5
6
  import { hasMcpProxyGroups, resolveMcpProxies } from "./mcp-proxy.js";
7
+ import { isMCPResult } from "./mcp-result.js";
6
8
  import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
9
+ import { validateAppliedDefault } from "./applied-default.js";
7
10
  import { filterSchemaForScope } from "./schema-scope.js";
11
+ import { validateCasedSchemaMembers } from "./schema-member-names.js";
8
12
  import { enableSourceMaps } from "./stack-trim.js";
9
13
  import { suggest } from "./suggest.js";
10
14
  import { throwValidationErrors } from "./validation-errors.js";
15
+ import { resolveDiscriminatedBranch } from "./discriminator.js";
16
+ import { validateUnionSchema } from "./union-validation.js";
11
17
  import { createRuntimeLogger } from "./runtime-logging.js";
12
18
  import { createEnv, createFs, validateServices } from "./runtime/io.js";
13
19
  import { createManagedStream } from "./stream.js";
20
+ export { mergeApprovalsRoot };
14
21
  function splitWords(value) {
15
22
  const words = [];
16
23
  let current = "";
@@ -59,9 +66,6 @@ function unwrapOptional(schema) {
59
66
  function isOptional(schema) {
60
67
  return schema.kind === "optional";
61
68
  }
62
- function isPlainObject(value) {
63
- return typeof value === "object" && value !== null && !Array.isArray(value);
64
- }
65
69
  function formatAvailableList(values) {
66
70
  return `Available: ${[...values].sort().join(", ")}.`;
67
71
  }
@@ -83,15 +87,121 @@ function describeReceived(value) {
83
87
  if (Array.isArray(value))
84
88
  return `array(${value.length})`;
85
89
  if (typeof value === "object")
86
- return "object";
90
+ return isPlainRecord(value) ? "object" : "non-plain object";
87
91
  if (typeof value === "string") {
88
92
  const s = value.length > 40 ? `${value.slice(0, 40)}…` : value;
89
93
  return `${JSON.stringify(s)}`;
90
94
  }
91
- return JSON.stringify(value);
95
+ if (typeof value === "bigint" || typeof value === "symbol" || typeof value === "function") {
96
+ return typeof value;
97
+ }
98
+ return String(value);
99
+ }
100
+ function nativeSDKFields(schema) {
101
+ const shapes = schema.kind === "object" ? [schema.shape]
102
+ : schema.kind === "union" ? schema.branches.map((branch) => branch.shape)
103
+ : schema.kind === "oneOf" ? Object.values(schema.branches).map((branch) => branch.shape) : [];
104
+ return new Map(shapes.flatMap((shape) => Object.entries(shape).map(([key, child]) => [formatSegment(key), [key, child]])));
105
+ }
106
+ function omitNativeOptionalUndefined(schema, value, depth = 0, budget = { nodes: 10_000 }) {
107
+ if (depth > 64 || --budget.nodes < 0)
108
+ return value;
109
+ const canonical = unwrapOptional(schema);
110
+ if (canonical.kind === "array" && Array.isArray(value)) {
111
+ if (value.length > 10_000)
112
+ return value;
113
+ const descriptors = Object.getOwnPropertyDescriptors(value);
114
+ for (let index = 0; index < value.length; index++) {
115
+ const property = descriptors[String(index)];
116
+ if (property !== undefined && "value" in property) {
117
+ property.value = omitNativeOptionalUndefined(canonical.item, property.value, depth + 1, budget);
118
+ }
119
+ }
120
+ const output = Object.defineProperties([], descriptors);
121
+ Object.setPrototypeOf(output, Object.getPrototypeOf(value));
122
+ return output;
123
+ }
124
+ if (canonical.kind === "record" && isPlainRecord(value)) {
125
+ const descriptors = Object.getOwnPropertyDescriptors(value);
126
+ for (const property of Object.values(descriptors))
127
+ if ("value" in property) {
128
+ property.value = omitNativeOptionalUndefined(canonical.value, property.value, depth + 1, budget);
129
+ }
130
+ return Object.create(Object.getPrototypeOf(value), descriptors);
131
+ }
132
+ if (!isPlainRecord(value) || (canonical.kind !== "object" && canonical.kind !== "union" && canonical.kind !== "oneOf"))
133
+ return value;
134
+ const descriptors = Object.getOwnPropertyDescriptors(value);
135
+ for (const [inputKey, [, field]] of nativeSDKFields(canonical)) {
136
+ const property = descriptors[inputKey];
137
+ if (property === undefined || !("value" in property))
138
+ continue;
139
+ if (property.value === undefined && isOptional(field))
140
+ delete descriptors[inputKey];
141
+ else
142
+ property.value = omitNativeOptionalUndefined(field, property.value, depth + 1, budget);
143
+ }
144
+ return Object.create(Object.getPrototypeOf(value), descriptors);
145
+ }
146
+ function normalizeNativeSDKKeys(schema, value) {
147
+ const canonical = unwrapOptional(schema);
148
+ if (canonical.kind === "array" && Array.isArray(value)) {
149
+ return value.map((item) => normalizeNativeSDKKeys(canonical.item, item));
150
+ }
151
+ if (canonical.kind === "record" && isPlainRecord(value)) {
152
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, normalizeNativeSDKKeys(canonical.value, item)]));
153
+ }
154
+ if ((canonical.kind === "object" || canonical.kind === "union" || canonical.kind === "oneOf") && isPlainRecord(value)) {
155
+ const fields = nativeSDKFields(canonical);
156
+ const result = {};
157
+ for (const [key, item] of Object.entries(value)) {
158
+ const field = fields.get(key);
159
+ const outputKey = field?.[0] ?? key;
160
+ if (Object.hasOwn(result, outputKey))
161
+ throw new UserError(`Multiple parameters map to "${outputKey}".`);
162
+ Object.defineProperty(result, outputKey, {
163
+ value: field === undefined ? item : normalizeNativeSDKKeys(field[1], item),
164
+ enumerable: true, configurable: true, writable: true
165
+ });
166
+ }
167
+ if (canonical.kind === "object") {
168
+ for (const [key, child] of Object.entries(canonical.shape)) {
169
+ const field = unwrapOptional(child);
170
+ if (!Object.hasOwn(result, key) && field.default !== undefined)
171
+ Object.defineProperty(result, key, {
172
+ value: normalizeNativeSDKKeys(field, cloneDefaultValue(field.default)),
173
+ enumerable: true, configurable: true, writable: true
174
+ });
175
+ }
176
+ }
177
+ return result;
178
+ }
179
+ return value;
180
+ }
181
+ function validateNativeSDKValue(schema, value, label, errors) {
182
+ value = omitNativeOptionalUndefined(schema, value);
183
+ if (!isJsonValue(value)) {
184
+ errors.push({ path: label, message: `Invalid value for "${label}". Expected a JSON value.` });
185
+ return value;
186
+ }
187
+ const normalized = normalizeNativeSDKKeys(schema, value);
188
+ const validation = validateSchema(schema, normalized);
189
+ if (!validation.ok) {
190
+ errors.push(...validation.issues.map((issue) => ({
191
+ path: [label, ...issue.path].filter((part) => part !== "").join("."),
192
+ message: formatIssues([issue])
193
+ })));
194
+ }
195
+ return normalized;
92
196
  }
93
197
  function validateSchemaValue(schema, value, label, errors) {
94
198
  const unwrappedSchema = unwrapOptional(schema);
199
+ if (isOptional(schema) && value === undefined) {
200
+ return validateAppliedDefault(unwrappedSchema, label, errors);
201
+ }
202
+ if (unwrappedSchema[nativeJsonSchema] !== undefined) {
203
+ return validateNativeSDKValue(unwrappedSchema, value, label, errors);
204
+ }
95
205
  if (value === null && unwrappedSchema.nullable === true) {
96
206
  return null;
97
207
  }
@@ -137,13 +247,23 @@ function validateSchemaValue(schema, value, label, errors) {
137
247
  return value;
138
248
  }
139
249
  validateArrayConstraints(unwrappedSchema, value, label, errors);
140
- return value.map((item, index) => validateSchemaValue(unwrappedSchema.item, item, `${label}[${index}]`, errors));
250
+ return Array.from({ length: value.length }, (_item, index) => validateSchemaValue(unwrappedSchema.item, value[index], `${label}[${index}]`, errors));
141
251
  case "object":
142
252
  return validateObjectSchema(unwrappedSchema, value, label, errors);
143
- case "json":
253
+ case "json": {
254
+ const validation = validateSchema(unwrappedSchema, value);
255
+ if (!validation.ok) {
256
+ for (const issue of validation.issues) {
257
+ errors.push({
258
+ path: label,
259
+ message: `Invalid value for "${label}". Expected a JSON value, got ${issue.received}.`
260
+ });
261
+ }
262
+ }
144
263
  return value;
264
+ }
145
265
  case "record": {
146
- if (!isPlainObject(value)) {
266
+ if (!isPlainRecord(value)) {
147
267
  errors.push({
148
268
  path: label,
149
269
  message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
@@ -156,42 +276,32 @@ function validateSchemaValue(schema, value, label, errors) {
156
276
  ]));
157
277
  }
158
278
  case "oneOf": {
159
- if (!isPlainObject(value)) {
279
+ const resolved = resolveDiscriminatedBranch(unwrappedSchema, value, formatSegment(unwrappedSchema.discriminator), label, errors);
280
+ if (resolved === undefined) {
160
281
  return value;
161
282
  }
162
- const discriminator = value[unwrappedSchema.discriminator];
163
- const branch = typeof discriminator === "string" ? unwrappedSchema.branches[discriminator] : undefined;
164
- if (branch === undefined) {
165
- return value;
166
- }
167
- const { [unwrappedSchema.discriminator]: ignoredDiscriminator, ...branchValue } = value;
168
- void ignoredDiscriminator;
169
283
  return {
170
- [unwrappedSchema.discriminator]: discriminator,
171
- ...validateObjectSchema(branch, branchValue, label, errors)
284
+ ...validateObjectSchema(resolved.branch, resolved.value, label, errors),
285
+ [unwrappedSchema.discriminator]: resolved.discriminator
172
286
  };
173
287
  }
174
288
  case "union": {
175
- if (!isPlainObject(value)) {
176
- return value;
177
- }
178
- const branch = unwrappedSchema.branches.find((candidate) => Object.keys(candidate.shape).every((key) => candidate.shape[key]?.kind === "optional" ||
179
- Object.prototype.hasOwnProperty.call(value, formatSegment(key))));
180
- return branch === undefined ? value : validateObjectSchema(branch, value, label, errors);
289
+ return validateUnionSchema(unwrappedSchema, value, label, errors, (branch, branchErrors) => validateObjectSchema(branch, value, label, branchErrors));
181
290
  }
182
291
  }
183
292
  }
184
293
  function validateStringConstraints(schema, value, label, errors) {
185
- if (schema.minLength !== undefined && value.length < schema.minLength) {
294
+ const length = unicodeLength(value);
295
+ if (schema.minLength !== undefined && length < schema.minLength) {
186
296
  errors.push({
187
297
  path: label,
188
- message: `Invalid value for "${label}". Expected a string with length at least ${schema.minLength}, got string with length ${value.length}.`
298
+ message: `Invalid value for "${label}". Expected a string with length at least ${schema.minLength}, got string with length ${length}.`
189
299
  });
190
300
  }
191
- if (schema.maxLength !== undefined && value.length > schema.maxLength) {
301
+ if (schema.maxLength !== undefined && length > schema.maxLength) {
192
302
  errors.push({
193
303
  path: label,
194
- message: `Invalid value for "${label}". Expected a string with length at most ${schema.maxLength}, got string with length ${value.length}.`
304
+ message: `Invalid value for "${label}". Expected a string with length at most ${schema.maxLength}, got string with length ${length}.`
195
305
  });
196
306
  }
197
307
  if (schema.pattern !== undefined && !new RegExp(schema.pattern).test(value)) {
@@ -216,7 +326,10 @@ function validateArrayConstraints(schema, value, label, errors) {
216
326
  }
217
327
  }
218
328
  export function validateObjectSchema(schema, value, label, errors) {
219
- if (!isPlainObject(value)) {
329
+ if (schema[nativeJsonSchema] !== undefined) {
330
+ return validateNativeSDKValue(schema, value, label, errors);
331
+ }
332
+ if (!isPlainRecord(value)) {
220
333
  errors.push({
221
334
  path: label,
222
335
  message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
@@ -250,10 +363,10 @@ export function validateObjectSchema(schema, value, label, errors) {
250
363
  const childSchema = unwrapOptional(rawChildSchema);
251
364
  const hasValue = Object.prototype.hasOwnProperty.call(value, inputKey);
252
365
  const fieldLabel = label.length === 0 ? inputKey : `${label}.${inputKey}`;
253
- if (!hasValue) {
366
+ if (!hasValue || (isOptional(rawChildSchema) && value[inputKey] === undefined)) {
254
367
  if (childSchema.default !== undefined) {
255
368
  Object.defineProperty(result, outputKey, {
256
- value: childSchema.default,
369
+ value: validateAppliedDefault(childSchema, fieldLabel, errors),
257
370
  enumerable: true,
258
371
  configurable: true,
259
372
  writable: true
@@ -261,6 +374,13 @@ export function validateObjectSchema(schema, value, label, errors) {
261
374
  continue;
262
375
  }
263
376
  if (isOptional(rawChildSchema)) {
377
+ if (inputKey !== outputKey && Object.hasOwn(result, outputKey)) {
378
+ const aliasLabel = label.length === 0 ? outputKey : `${label}.${outputKey}`;
379
+ errors.push({
380
+ path: aliasLabel,
381
+ message: `Unexpected parameter "${aliasLabel}". Use "${fieldLabel}" for this declared parameter.`
382
+ });
383
+ }
264
384
  continue;
265
385
  }
266
386
  errors.push({ path: fieldLabel, message: `Missing required parameter "${fieldLabel}".` });
@@ -275,21 +395,6 @@ export function validateObjectSchema(schema, value, label, errors) {
275
395
  }
276
396
  return result;
277
397
  }
278
- function validateUniqueSDKParameterMembers(schema) {
279
- const sourceKeysByMember = new Map();
280
- for (const [key, rawChildSchema] of Object.entries(schema.shape)) {
281
- const member = formatSegment(key);
282
- const existingKey = sourceKeysByMember.get(member);
283
- if (existingKey !== undefined) {
284
- throw new UserError(`Parameters "${existingKey}" and "${key}" use conflicting SDK member "${member}".`);
285
- }
286
- sourceKeysByMember.set(member, key);
287
- const childSchema = unwrapOptional(rawChildSchema);
288
- if (childSchema.kind === "object") {
289
- validateUniqueSDKParameterMembers(childSchema);
290
- }
291
- }
292
- }
293
398
  function validateSDKArguments(schema, argumentsValue) {
294
399
  const errors = [];
295
400
  const result = validateObjectSchema(schema, argumentsValue ?? {}, "", errors);
@@ -330,7 +435,7 @@ function createResolvedSDK(root, options = {}) {
330
435
  if (node.kind === "command") {
331
436
  const sdkParamsSchema = filterSchemaForScope(node.params, "sdk");
332
437
  if (sdkParamsSchema?.kind === "object") {
333
- validateUniqueSDKParameterMembers(sdkParamsSchema);
438
+ validateCasedSchemaMembers(sdkParamsSchema, formatSegment, "SDK member");
334
439
  }
335
440
  if (node.stream !== undefined) {
336
441
  return (params, streamOptions = {}) => createManagedStream({
@@ -419,9 +524,15 @@ function createResolvedSDK(root, options = {}) {
419
524
  ...baseContext,
420
525
  params: validatedParams
421
526
  };
422
- return humanInLoop === undefined
527
+ const result = humanInLoop === undefined
423
528
  ? await node.handler(handlerContext)
424
529
  : await humanInLoop.invoke(node, handlerContext, commandPath);
530
+ if (node.result !== undefined && isMCPResult(result) && result.isError === true) {
531
+ const text = result.content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
532
+ const message = text || (result.structuredContent === undefined ? "Upstream tool failed." : JSON.stringify(result.structuredContent));
533
+ throw new UserError(message, { cause: result });
534
+ }
535
+ return result;
425
536
  }
426
537
  catch (error) {
427
538
  await writeErrorReport({
@@ -451,7 +562,7 @@ function createResolvedSDK(root, options = {}) {
451
562
  }
452
563
  else {
453
564
  childValue = build(child, nextPath);
454
- if (!isPlainObject(childValue) || Object.keys(childValue).length === 0) {
565
+ if (!isPlainRecord(childValue) || Object.keys(childValue).length === 0) {
455
566
  continue;
456
567
  }
457
568
  }
@@ -476,7 +587,10 @@ function createDeferredSDK(root, options) {
476
587
  sdkPromise ??= (async () => {
477
588
  await resolveMcpProxies(root, { projectRoot: options.projectRoot });
478
589
  return createResolvedSDK(root, options);
479
- })();
590
+ })().catch((error) => {
591
+ sdkPromise = undefined;
592
+ throw error;
593
+ });
480
594
  return sdkPromise;
481
595
  };
482
596
  const resolvePath = async (path) => {
package/dist/stream.js CHANGED
@@ -6,39 +6,32 @@ function validationMessage(schema, value) {
6
6
  }
7
7
  export function createManagedStream(options) {
8
8
  const controller = new AbortController();
9
- let iterator;
10
9
  let iteratorPromise;
11
10
  let closePromise;
12
11
  let done = false;
13
- const getIterator = async () => {
14
- if (iterator !== undefined) {
15
- return iterator;
16
- }
17
- iteratorPromise ??= options
18
- .create(controller.signal, (event) => options.onStatus?.(event))
12
+ const getIterator = () => {
13
+ iteratorPromise ??= Promise.resolve()
14
+ .then(() => options.create(controller.signal, (event) => options.onStatus?.(event)))
19
15
  .then((iterable) => iterable[Symbol.asyncIterator]());
20
- iterator = await iteratorPromise;
21
- return iterator;
16
+ return iteratorPromise;
22
17
  };
23
- const close = async (reason) => {
18
+ const close = (reason) => {
24
19
  if (closePromise !== undefined) {
25
20
  return closePromise;
26
21
  }
27
- closePromise = (async () => {
28
- controller.abort(reason);
29
- options.signal?.removeEventListener("abort", abortFromConsumer);
30
- if (done || (iterator === undefined && iteratorPromise === undefined)) {
31
- done = true;
32
- return;
33
- }
34
- const activeIterator = await getIterator();
35
- done = true;
36
- await activeIterator.return?.();
37
- })();
22
+ const closingIterator = done ? undefined : iteratorPromise;
23
+ done = true;
24
+ options.signal?.removeEventListener("abort", abortFromConsumer);
25
+ closePromise = closingIterator === undefined
26
+ ? Promise.resolve()
27
+ : closingIterator.then(async (activeIterator) => {
28
+ await activeIterator.return?.();
29
+ }, () => undefined);
30
+ controller.abort(reason);
38
31
  return closePromise;
39
32
  };
40
33
  const abortFromConsumer = () => {
41
- void close(options.signal?.reason);
34
+ void close(options.signal?.reason).catch(() => undefined);
42
35
  };
43
36
  if (options.signal?.aborted === true) {
44
37
  abortFromConsumer();
@@ -56,15 +49,23 @@ export function createManagedStream(options) {
56
49
  await close(controller.signal.reason);
57
50
  return { done: true, value: undefined };
58
51
  }
59
- const activeIterator = await getIterator();
60
52
  let result;
61
53
  try {
54
+ const activeIterator = await getIterator();
55
+ if (done || controller.signal.aborted) {
56
+ await close(controller.signal.reason);
57
+ return { done: true, value: undefined };
58
+ }
62
59
  result = await activeIterator.next();
63
60
  }
64
61
  catch (error) {
65
62
  await close(error);
66
63
  throw error;
67
64
  }
65
+ if (controller.signal.aborted) {
66
+ await close(controller.signal.reason);
67
+ return { done: true, value: undefined };
68
+ }
68
69
  if (result.done === true) {
69
70
  done = true;
70
71
  options.signal?.removeEventListener("abort", abortFromConsumer);
@@ -0,0 +1 @@
1
+ export declare function formatDiagnosticValue(value: unknown): string;
@@ -0,0 +1,57 @@
1
+ import { types } from "node:util";
2
+ export function formatDiagnosticValue(value) {
3
+ const ancestors = new WeakSet();
4
+ let remaining = 100;
5
+ function render(current, depth) {
6
+ if (remaining <= 0)
7
+ return "…";
8
+ remaining -= 1;
9
+ if (current === null)
10
+ return "null";
11
+ if (current === undefined)
12
+ return "undefined";
13
+ if (typeof current === "string") {
14
+ return JSON.stringify(current.length > 1000 ? `${current.slice(0, 1000)}…` : current);
15
+ }
16
+ if (typeof current === "function")
17
+ return "[Function]";
18
+ if (typeof current === "bigint")
19
+ return `${current}n`;
20
+ if (typeof current !== "object")
21
+ return Object.is(current, -0) ? "-0" : String(current);
22
+ if (types.isProxy(current))
23
+ return "[Proxy]";
24
+ if (ancestors.has(current))
25
+ return "[Circular]";
26
+ const array = Array.isArray(current);
27
+ if (depth >= 4)
28
+ return array ? "[Array]" : "[Object]";
29
+ const keys = array
30
+ ? Array.from({ length: Math.min(current.length, 25) }, (_value, index) => String(index))
31
+ : Object.keys(current);
32
+ const count = array ? current.length : keys.length;
33
+ ancestors.add(current);
34
+ try {
35
+ const entries = keys.slice(0, 25).map((key) => {
36
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
37
+ const rendered = descriptor === undefined
38
+ ? "[Empty]"
39
+ : "value" in descriptor ? render(descriptor.value, depth + 1) : "[Accessor]";
40
+ return array ? rendered : `${JSON.stringify(key)}:${rendered}`;
41
+ });
42
+ if (count > entries.length)
43
+ entries.push("…");
44
+ return array ? `[${entries.join(",")}]` : `{${entries.join(",")}}`;
45
+ }
46
+ finally {
47
+ ancestors.delete(current);
48
+ }
49
+ }
50
+ try {
51
+ const rendered = render(value, 0);
52
+ return rendered.length > 4000 ? `${rendered.slice(0, 4000)}…` : rendered;
53
+ }
54
+ catch {
55
+ return "[Uninspectable]";
56
+ }
57
+ }
@@ -11,7 +11,7 @@ export function fakeService(stubs = {}) {
11
11
  }
12
12
  const stub = Reflect.get(target, property, receiver);
13
13
  if (typeof stub !== "function") {
14
- if (stub !== undefined) {
14
+ if (stub !== undefined || property === "then") {
15
15
  return stub;
16
16
  }
17
17
  return (...args) => {
@@ -63,6 +63,7 @@ export function fakeFetch(routes) {
63
63
  const fetch = async (input, init) => {
64
64
  const request = new Request(input, init);
65
65
  calls.push(request);
66
+ request.signal.throwIfAborted();
66
67
  const route = routes.find((candidate) => routeMatches(candidate, request));
67
68
  if (route === undefined) {
68
69
  const configuredRoutes = routes.map(routeDescription).join(", ") || "none";
@@ -199,6 +199,7 @@ export function createCommandTestHarness(root, options = {}) {
199
199
  env: Object.fromEntries(Object.entries(options.env ?? {}).flatMap(([key, value]) => value === undefined ? [] : [[key, value]])),
200
200
  fs: memoryFs,
201
201
  fetch: runtimeFetch,
202
+ apiVersion: options.apiVersion,
202
203
  logLevel: options.logLevel,
203
204
  outputEmitter(entry) {
204
205
  stdout += entry;
@@ -1,3 +1,9 @@
1
+ const writeModes = new Map([
2
+ ...["r", "rs", "sr"].map((flag) => [flag, "read"]),
3
+ ...["r+", "rs+", "sr+"].map((flag) => [flag, "update"]),
4
+ ...["w", "w+", "wx", "xw", "wx+", "xw+"].map((flag) => [flag, "replace"]),
5
+ ...["a", "a+", "ax", "xa", "ax+", "xa+", "as", "sa", "as+", "sa+"].map((flag) => [flag, "append"])
6
+ ]);
1
7
  function missingFileError(syscall, path, dest) {
2
8
  const target = dest === undefined ? `'${path}'` : `'${path}' -> '${dest}'`;
3
9
  const error = new Error(`ENOENT: no such file or directory, ${syscall} ${target}`);
@@ -22,7 +28,42 @@ export function createMemoryFs(files = {}) {
22
28
  return value.toString(encoding);
23
29
  },
24
30
  async writeFile(path, value, options) {
25
- contents.set(path, Buffer.from(value, options?.encoding ?? "utf8"));
31
+ const bytes = Buffer.from(value, options?.encoding ?? "utf8");
32
+ const flag = options?.flag || "w";
33
+ const mode = writeModes.get(flag);
34
+ if (mode === undefined) {
35
+ throw Object.assign(new TypeError(`The argument 'flags' is invalid. Received '${flag}'`), {
36
+ code: "ERR_INVALID_ARG_VALUE"
37
+ });
38
+ }
39
+ const previous = contents.get(path);
40
+ if (flag.includes("x") && previous !== undefined) {
41
+ throw Object.assign(new Error(`EEXIST: file already exists, open '${path}'`), {
42
+ code: "EEXIST",
43
+ errno: -17,
44
+ syscall: "open",
45
+ path
46
+ });
47
+ }
48
+ if ((mode === "read" || mode === "update") && previous === undefined) {
49
+ throw missingFileError("open", path);
50
+ }
51
+ if (mode === "read" && bytes.length > 0) {
52
+ throw Object.assign(new Error("EBADF: bad file descriptor, write"), {
53
+ code: "EBADF",
54
+ errno: -9,
55
+ syscall: "write"
56
+ });
57
+ }
58
+ if (mode === "append" && previous !== undefined) {
59
+ contents.set(path, Buffer.concat([previous, bytes]));
60
+ }
61
+ else if (mode === "update" && previous !== undefined) {
62
+ contents.set(path, Buffer.concat([bytes, previous.subarray(bytes.length)]));
63
+ }
64
+ else if (mode !== "read") {
65
+ contents.set(path, bytes);
66
+ }
26
67
  changeLog.push({ op: "writeFile", path });
27
68
  },
28
69
  async exists(path) {
@@ -1,10 +1,11 @@
1
- import { isDeepStrictEqual } from "node:util";
1
+ import { isDeepStrictEqual, types } from "node:util";
2
2
  import { McpClient, McpError, ERROR_INVALID_PARAMS, createSdkTestPair } from "tiny-mcp-client";
3
3
  import { runCLI } from "../cli.js";
4
4
  import { UserError } from "../index.js";
5
5
  import { createMCPServer } from "../mcp.js";
6
6
  import { filterSchemaForScope } from "../schema-scope.js";
7
7
  import { createSDK } from "../sdk.js";
8
+ import { formatDiagnosticValue } from "./diagnostic-value.js";
8
9
  class SurfaceScopeError extends Error {
9
10
  constructor(surface, path) {
10
11
  super(`Command "${path.join(" ")}" is filtered out of the ${surface} surface.`);
@@ -338,11 +339,53 @@ async function runCLISurface(root, resolved, params, options, reference) {
338
339
  process.exitCode = previousExitCode;
339
340
  }
340
341
  }
342
+ const domExceptionMessageGetter = typeof DOMException === "undefined"
343
+ ? undefined
344
+ : Object.getOwnPropertyDescriptor(DOMException.prototype, "message")?.get;
345
+ function nativeDOMExceptionMessage(error) {
346
+ if (typeof error !== "object" || error === null || types.isProxy(error))
347
+ return undefined;
348
+ try {
349
+ return domExceptionMessageGetter?.call(error);
350
+ }
351
+ catch {
352
+ return undefined;
353
+ }
354
+ }
355
+ function propertyDescriptor(value, name) {
356
+ let current = value;
357
+ while (current !== null &&
358
+ (typeof current === "object" || typeof current === "function") &&
359
+ !types.isProxy(current)) {
360
+ const descriptor = Object.getOwnPropertyDescriptor(current, name);
361
+ if (descriptor !== undefined)
362
+ return descriptor;
363
+ current = Object.getPrototypeOf(current);
364
+ }
365
+ return undefined;
366
+ }
341
367
  function errorIdentity(error) {
342
- if (error instanceof Error) {
343
- return { name: error.constructor.name, message: error.message };
368
+ const domMessage = nativeDOMExceptionMessage(error);
369
+ if (types.isNativeError(error) || domMessage !== undefined) {
370
+ const prototype = Object.getPrototypeOf(error);
371
+ const constructor = propertyDescriptor(prototype, "constructor")?.value;
372
+ const name = typeof constructor === "function" && !types.isProxy(constructor)
373
+ ? Object.getOwnPropertyDescriptor(constructor, "name")?.value
374
+ : undefined;
375
+ const descriptor = propertyDescriptor(error, "message");
376
+ const message = descriptor === undefined
377
+ ? ""
378
+ : "value" in descriptor
379
+ ? typeof descriptor.value === "string" ? descriptor.value : formatDiagnosticValue(descriptor.value)
380
+ : descriptor.get === domExceptionMessageGetter && domMessage !== undefined
381
+ ? domMessage
382
+ : "[Accessor]";
383
+ return { name: typeof name === "string" ? name : "Error", message };
344
384
  }
345
- return { name: typeof error, message: String(error) };
385
+ return {
386
+ name: typeof error,
387
+ message: typeof error === "string" ? error : formatDiagnosticValue(error)
388
+ };
346
389
  }
347
390
  function outcomesAgree(left, right) {
348
391
  if (left.ok !== right.ok) {
@@ -355,10 +398,11 @@ function outcomesAgree(left, right) {
355
398
  }
356
399
  function describeOutcome(surface, outcome) {
357
400
  if (outcome.ok) {
358
- return `${surface}: ok ${JSON.stringify(outcome.value)}`;
401
+ return `${surface}: ok ${formatDiagnosticValue(outcome.value)}`;
359
402
  }
360
403
  const error = errorIdentity(outcome.error);
361
- return `${surface}: error ${error.name}: ${error.message}`;
404
+ const description = `${surface}: error ${error.name}: ${error.message}`;
405
+ return description.length > 4000 ? `${description.slice(0, 4000)}…` : description;
362
406
  }
363
407
  export async function runParity(root, resolved, params, options) {
364
408
  const sdk = await runSDK(root, resolved, params, options);
@@ -0,0 +1,5 @@
1
+ import { type AnySchema, type ObjectSchema } from "toolcraft-schema";
2
+ import type { ValidationError } from "./validation-errors.js";
3
+ export declare function validateUnionSchema(schema: Extract<AnySchema, {
4
+ kind: "union";
5
+ }>, value: unknown, label: string, errors: ValidationError[], validateBranch: (branch: ObjectSchema<any>, branchErrors: ValidationError[]) => Record<string, unknown>): unknown;