@feniix/bridgekit 0.8.2 → 0.8.3
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 +15 -1
- package/dist/src/core/execute-tool.js +81 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,21 @@ All notable changes to `@feniix/bridgekit` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [0.8.
|
|
7
|
+
## [0.8.3] - Unreleased
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `PortableValidationError.field` now preserves the full property name when
|
|
12
|
+
it contains `/`. TypeBox does not implement JSON Pointer RFC 6901's `~1`
|
|
13
|
+
escape, so a property named `a/b` previously produced `field: "b"` for
|
|
14
|
+
wrong-type / `const` / `enum` errors (the string-split fallback dropped
|
|
15
|
+
the prefix). The fix walks the actual schema with a greedy longest-prefix
|
|
16
|
+
match against `properties` keys and falls back to the prior behavior for
|
|
17
|
+
paths the schema doesn't model. The `required` and `additionalProperties`
|
|
18
|
+
paths were already correct via structured `params` and are unchanged.
|
|
19
|
+
Resolves [#36](https://github.com/feniix/bridgekit/issues/36).
|
|
20
|
+
|
|
21
|
+
## [0.8.2] - 2026-05-26
|
|
8
22
|
|
|
9
23
|
### Fixed
|
|
10
24
|
|
|
@@ -3,6 +3,82 @@ const ROOT_FIELD = "(root)";
|
|
|
3
3
|
function fieldFromPath(instancePath) {
|
|
4
4
|
return instancePath.split("/").filter(Boolean).at(-1) ?? ROOT_FIELD;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the leaf field name of an instancePath against the actual schema.
|
|
8
|
+
*
|
|
9
|
+
* TypeBox does not escape `/` inside property names when building
|
|
10
|
+
* `instancePath` (it does not follow JSON Pointer RFC 6901's `~1` encoding),
|
|
11
|
+
* so a property literally named `a/b` produces `instancePath: "/a/b"`. The
|
|
12
|
+
* string-split fallback (`fieldFromPath`) would then surface `"b"` and lose
|
|
13
|
+
* the prefix. This helper walks the schema greedily — at each object node it
|
|
14
|
+
* matches the longest prefix of remaining path segments that names a real
|
|
15
|
+
* property key, descending into the matched sub-schema — and returns the
|
|
16
|
+
* actual leaf key. Array nodes consume a single numeric segment via `items`.
|
|
17
|
+
*
|
|
18
|
+
* Returns `undefined` if the walk gets stuck (the schema does not model the
|
|
19
|
+
* data path — e.g. `additionalProperties` content). The caller then falls
|
|
20
|
+
* back to the string-split `fieldFromPath`, preserving prior behavior.
|
|
21
|
+
*
|
|
22
|
+
* The `required` and `additionalProperties` callers already read structured
|
|
23
|
+
* `params` and are unaffected; this helper covers the `const` / `enum` /
|
|
24
|
+
* default keyword branches.
|
|
25
|
+
*/
|
|
26
|
+
function fieldFromSchemaWalk(schema, instancePath) {
|
|
27
|
+
if (instancePath === "")
|
|
28
|
+
return undefined;
|
|
29
|
+
const segments = instancePath.split("/").filter(Boolean);
|
|
30
|
+
if (segments.length === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
let current = schema;
|
|
33
|
+
let lastKey;
|
|
34
|
+
let i = 0;
|
|
35
|
+
while (i < segments.length) {
|
|
36
|
+
if (!current || typeof current !== "object")
|
|
37
|
+
return undefined;
|
|
38
|
+
const node = current;
|
|
39
|
+
// Array descent: numeric segment against an `items` schema. Tuple-form
|
|
40
|
+
// `items` (array) indexes positionally; schema-form descends uniformly.
|
|
41
|
+
if (node.items !== undefined && /^\d+$/.test(segments[i])) {
|
|
42
|
+
const idx = Number(segments[i]);
|
|
43
|
+
if (Array.isArray(node.items)) {
|
|
44
|
+
if (idx >= node.items.length)
|
|
45
|
+
return undefined;
|
|
46
|
+
current = node.items[idx];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
current = node.items;
|
|
50
|
+
}
|
|
51
|
+
lastKey = segments[i];
|
|
52
|
+
i++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Object descent: greedy longest-prefix match against `properties` keys.
|
|
56
|
+
// This is what lets `a/b` resolve to a single key when the schema
|
|
57
|
+
// declares it, even though the instancePath segments it as ["a", "b"].
|
|
58
|
+
if (node.properties && typeof node.properties === "object") {
|
|
59
|
+
const props = node.properties;
|
|
60
|
+
let matched = -1;
|
|
61
|
+
for (let j = segments.length; j > i; j--) {
|
|
62
|
+
const candidate = segments.slice(i, j).join("/");
|
|
63
|
+
if (Object.hasOwn(props, candidate)) {
|
|
64
|
+
matched = j;
|
|
65
|
+
lastKey = candidate;
|
|
66
|
+
current = props[candidate];
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (matched === -1)
|
|
71
|
+
return undefined;
|
|
72
|
+
i = matched;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return lastKey;
|
|
78
|
+
}
|
|
79
|
+
function fieldFromError(schema, error) {
|
|
80
|
+
return fieldFromSchemaWalk(schema, error.instancePath) ?? fieldFromPath(error.instancePath);
|
|
81
|
+
}
|
|
6
82
|
function readDiscriminatorValues(propSchema) {
|
|
7
83
|
// Extract the allowed values from a discriminator-eligible prop schema.
|
|
8
84
|
// Recognizes three shapes:
|
|
@@ -253,7 +329,7 @@ function suppressSiblingErrorsUnderUnion(schema, value, errors) {
|
|
|
253
329
|
return true;
|
|
254
330
|
});
|
|
255
331
|
}
|
|
256
|
-
function expandTypeBoxError(error) {
|
|
332
|
+
function expandTypeBoxError(schema, error) {
|
|
257
333
|
// Read offending property names from TypeBox's structured `params` rather
|
|
258
334
|
// than parsing the message string. Two payoffs: locale independence (the
|
|
259
335
|
// helper works the same after `Locale.Set("de_DE")`), and faithful
|
|
@@ -284,7 +360,7 @@ function expandTypeBoxError(error) {
|
|
|
284
360
|
if (error.keyword === "const") {
|
|
285
361
|
return [
|
|
286
362
|
{
|
|
287
|
-
field:
|
|
363
|
+
field: fieldFromError(schema, error),
|
|
288
364
|
message: `must equal ${JSON.stringify(error.params.allowedValue)}`,
|
|
289
365
|
},
|
|
290
366
|
];
|
|
@@ -293,12 +369,12 @@ function expandTypeBoxError(error) {
|
|
|
293
369
|
const allowed = error.params.allowedValues ?? [];
|
|
294
370
|
return [
|
|
295
371
|
{
|
|
296
|
-
field:
|
|
372
|
+
field: fieldFromError(schema, error),
|
|
297
373
|
message: allowed.length > 0 ? `must equal one of ${allowed.map((v) => JSON.stringify(v)).join(", ")}` : error.message,
|
|
298
374
|
},
|
|
299
375
|
];
|
|
300
376
|
}
|
|
301
|
-
return [{ field:
|
|
377
|
+
return [{ field: fieldFromError(schema, error), message: error.message }];
|
|
302
378
|
}
|
|
303
379
|
export function validatePortableToolArgs(tool, args) {
|
|
304
380
|
if (Check(tool.parameters, args)) {
|
|
@@ -316,7 +392,7 @@ export function validatePortableToolArgs(tool, args) {
|
|
|
316
392
|
]);
|
|
317
393
|
const seen = new Set();
|
|
318
394
|
const errors = rawErrors
|
|
319
|
-
.flatMap((error) => expandTypeBoxError(error))
|
|
395
|
+
.flatMap((error) => expandTypeBoxError(tool.parameters, error))
|
|
320
396
|
.filter(({ field, message }) => {
|
|
321
397
|
const key = JSON.stringify([field, message]);
|
|
322
398
|
if (seen.has(key))
|