@feniix/bridgekit 0.16.0 → 0.17.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/CHANGELOG.md +23 -0
- package/dist/src/core/execute-tool.js +52 -19
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,29 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.17.0] - 2026-10-06
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- `@types/node` moves from 25.x to 22.x to match the `>=22.19.0` engines floor,
|
|
14
|
+
which also matches pi (`@earendil-works/pi-*`); APIs added after Node 22 now
|
|
15
|
+
fail to typecheck. The Node floor itself is unchanged.
|
|
16
|
+
- Dependabot version updates are disabled (`.github/dependabot.yml` removed);
|
|
17
|
+
dependency bumps are now made manually. CI's `npm audit` gate is unchanged.
|
|
18
|
+
|
|
19
|
+
## [0.16.1] - 2026-10-06
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Validation errors report correct field names under TypeBox 1.3+, which the
|
|
24
|
+
`typebox ^1.1.31` range already resolves to on fresh installs. TypeBox 1.3
|
|
25
|
+
escapes JSON Pointer paths per RFC 6901, so a property named `a/b` surfaced as
|
|
26
|
+
`a~1b` (and `a~b` as `a~0b`); fields are now decoded back to the literal key.
|
|
27
|
+
- `additionalProperties` violations are reported once per key again. TypeBox 1.3
|
|
28
|
+
adds a per-key sub-schema error beside the summary, which duplicated fields and
|
|
29
|
+
let a losing union branch's closed-object errors slip past phantom suppression.
|
|
30
|
+
Both TypeBox 1.1 and 1.3 now produce identical validation errors.
|
|
31
|
+
|
|
9
32
|
## [0.16.0] - 2026-10-06
|
|
10
33
|
|
|
11
34
|
### Added
|
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
import { Check, Errors, Pointer } from "typebox/value";
|
|
2
2
|
import { assertPortableOutputSchema } from "./output-schema.js";
|
|
3
3
|
const ROOT_FIELD = "(root)";
|
|
4
|
+
/**
|
|
5
|
+
* Decode one RFC 6901 JSON Pointer segment (`~1` → `/`, `~0` → `~`).
|
|
6
|
+
*
|
|
7
|
+
* TypeBox 1.3+ escapes property names in `instancePath` and `schemaPath`
|
|
8
|
+
* (`"a/b"` → `/a~1b`); TypeBox 1.1 did not. The package range spans both, so
|
|
9
|
+
* every lookup tries the raw segment first and the decoded form second.
|
|
10
|
+
*/
|
|
11
|
+
function decodePointerSegment(segment) {
|
|
12
|
+
return segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
13
|
+
}
|
|
4
14
|
function fieldFromPath(instancePath) {
|
|
5
|
-
|
|
15
|
+
const last = instancePath.split("/").filter(Boolean).at(-1);
|
|
16
|
+
return last === undefined ? ROOT_FIELD : decodePointerSegment(last);
|
|
6
17
|
}
|
|
7
18
|
/**
|
|
8
19
|
* Resolve the leaf property name from a TypeBox error by walking
|
|
9
20
|
* `error.schemaPath` rather than `error.instancePath`.
|
|
10
21
|
*
|
|
11
|
-
* `instancePath`
|
|
12
|
-
*
|
|
13
|
-
* slash-named property `"a/b"` AND a nested path `a.b`
|
|
22
|
+
* Before 1.3, TypeBox's `instancePath` did not escape `/` inside property
|
|
23
|
+
* names (no JSON Pointer RFC 6901 `~1` encoding), so a schema with BOTH a
|
|
24
|
+
* slash-named property `"a/b"` AND a nested path `a.b` produced the same
|
|
14
25
|
* `instancePath: "/a/b"` for either failure — the two cases are
|
|
15
|
-
* indistinguishable from the data path alone.
|
|
26
|
+
* indistinguishable from the data path alone. TypeBox 1.3+ escapes both paths
|
|
27
|
+
* (`/a~1b`, `#/properties/a~1b`); the walker accepts either form. `schemaPath` carries explicit
|
|
16
28
|
* `/properties/` markers per nesting level, so the two cases become
|
|
17
29
|
* structurally distinct:
|
|
18
30
|
*
|
|
@@ -87,13 +99,17 @@ function walkSegments(node, segments, i, lastField) {
|
|
|
87
99
|
return walkSegments(obj.items, segments, i + 1, lastField);
|
|
88
100
|
}
|
|
89
101
|
// `properties` command: subsequent segments form the property name (joined
|
|
90
|
-
// by `/` to recover slash-named keys). Consume
|
|
91
|
-
// match against the parent's actual `properties`
|
|
102
|
+
// by `/` to recover unescaped slash-named keys from TypeBox < 1.3). Consume
|
|
103
|
+
// by greedy longest-prefix match against the parent's actual `properties`
|
|
104
|
+
// keys, then descend. The raw candidate wins; the RFC 6901-decoded form
|
|
105
|
+
// covers TypeBox 1.3+, which emits `a~1b` as a single segment.
|
|
92
106
|
if (head === "properties" && obj.properties && typeof obj.properties === "object") {
|
|
93
107
|
const props = obj.properties;
|
|
94
108
|
for (let j = segments.length; j > i + 1; j--) {
|
|
95
|
-
const
|
|
96
|
-
|
|
109
|
+
const raw = segments.slice(i + 1, j).join("/");
|
|
110
|
+
for (const candidate of new Set([raw, decodePointerSegment(raw)])) {
|
|
111
|
+
if (!Object.hasOwn(props, candidate))
|
|
112
|
+
continue;
|
|
97
113
|
const child = walkSegments(props[candidate], segments, j, candidate);
|
|
98
114
|
if (child !== undefined)
|
|
99
115
|
return child;
|
|
@@ -151,12 +167,15 @@ function resolveSchemaAtPath(schema, instancePath) {
|
|
|
151
167
|
if (!current || typeof current !== "object")
|
|
152
168
|
return undefined;
|
|
153
169
|
const obj = current;
|
|
154
|
-
if (obj.properties && typeof obj.properties === "object"
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
170
|
+
if (obj.properties && typeof obj.properties === "object") {
|
|
171
|
+
const key = segment in obj.properties ? segment : decodePointerSegment(segment);
|
|
172
|
+
if (key in obj.properties) {
|
|
173
|
+
const next = obj.properties[key];
|
|
174
|
+
if (next === undefined)
|
|
175
|
+
return undefined;
|
|
176
|
+
current = next;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
160
179
|
}
|
|
161
180
|
if (obj.items !== undefined && /^\d+$/.test(segment)) {
|
|
162
181
|
if (Array.isArray(obj.items)) {
|
|
@@ -357,7 +376,8 @@ function suppressSiblingErrorsUnderUnion(schema, value, errors) {
|
|
|
357
376
|
const remainder = error.instancePath.slice(prefix.length);
|
|
358
377
|
if (remainder.includes("/"))
|
|
359
378
|
continue; // only direct discriminator props
|
|
360
|
-
const losingValues = resolution.losingDiscriminators.get(remainder)
|
|
379
|
+
const losingValues = resolution.losingDiscriminators.get(remainder) ??
|
|
380
|
+
resolution.losingDiscriminators.get(decodePointerSegment(remainder));
|
|
361
381
|
if (!losingValues)
|
|
362
382
|
continue;
|
|
363
383
|
if (error.keyword === "const" && losingValues.has(error.params.allowedValue))
|
|
@@ -370,6 +390,21 @@ function suppressSiblingErrorsUnderUnion(schema, value, errors) {
|
|
|
370
390
|
return true;
|
|
371
391
|
});
|
|
372
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* TypeBox 1.3+ reports each additional property twice: once as the
|
|
395
|
+
* `additionalProperties` summary at the object (which `expandTypeBoxError`
|
|
396
|
+
* expands per key) and once per key as the failure of the `additionalProperties`
|
|
397
|
+
* sub-schema itself (`keyword: "boolean"` for `false`, or e.g. `"type"` for a
|
|
398
|
+
* schema value), with `schemaPath` ending at that keyword. TypeBox 1.1 emitted
|
|
399
|
+
* only the summary. Dropping the per-key duplicates keeps one error per key and
|
|
400
|
+
* lets union phantom suppression, which reasons about the summary, stay
|
|
401
|
+
* authoritative. A trailing segment after a `properties` command is a property
|
|
402
|
+
* literally named `additionalProperties`, not the keyword.
|
|
403
|
+
*/
|
|
404
|
+
function isAdditionalPropertiesSubschemaError(error) {
|
|
405
|
+
const segments = error.schemaPath.split("/");
|
|
406
|
+
return segments.at(-1) === "additionalProperties" && segments.at(-2) !== "properties";
|
|
407
|
+
}
|
|
373
408
|
function expandTypeBoxError(schema, error) {
|
|
374
409
|
// Read offending property names from TypeBox's structured `params` rather
|
|
375
410
|
// than parsing the message string. Two payoffs: locale independence (the
|
|
@@ -428,9 +463,7 @@ export function validatePortableToolArgs(tool, args) {
|
|
|
428
463
|
// satisfy ONE branch, not all of them). Then dedupe the survivors by
|
|
429
464
|
// (field, message) using JSON.stringify so the key can't collide regardless
|
|
430
465
|
// of what characters field or message contain.
|
|
431
|
-
const rawErrors = suppressSiblingErrorsUnderUnion(tool.parameters, args, [
|
|
432
|
-
...Errors(tool.parameters, args),
|
|
433
|
-
]);
|
|
466
|
+
const rawErrors = suppressSiblingErrorsUnderUnion(tool.parameters, args, [...Errors(tool.parameters, args)].filter((error) => !isAdditionalPropertiesSubschemaError(error)));
|
|
434
467
|
const seen = new Set();
|
|
435
468
|
const errors = rawErrors
|
|
436
469
|
.flatMap((error) => expandTypeBoxError(tool.parameters, error))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feniix/bridgekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "BridgeKit defines TypeBox-backed tools once and adapts them to pi, MCP, and other hosts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -73,11 +73,11 @@
|
|
|
73
73
|
"typebox": "^1.1.31"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
+
"@biomejs/biome": "2.5.15",
|
|
76
77
|
"@modelcontextprotocol/client": "^2.3.1",
|
|
77
78
|
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
78
|
-
"@biomejs/biome": "2.5.4",
|
|
79
79
|
"@total-typescript/shoehorn": "^0.1.2",
|
|
80
|
-
"@types/node": "^
|
|
80
|
+
"@types/node": "^22.20.5",
|
|
81
81
|
"typescript": "^6.0.3"
|
|
82
82
|
},
|
|
83
83
|
"license": "MIT"
|