@jini-ai/core 0.4.0 → 0.4.1
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 +5 -0
- package/dist/registration-kit.d.ts +34 -0
- package/dist/registration-kit.js +43 -0
- package/package.json +3 -2
- package/dist/internal.d.ts +0 -41
- package/dist/internal.js +0 -39
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# @jini-ai/core
|
|
2
2
|
|
|
3
|
+
## 0.4.1 — 2026-10-05
|
|
4
|
+
|
|
5
|
+
- New `readToolLimit({ input, max, fallback }, { key })`: an absent limit is `fallback`, one above `max` is capped at `max`, and a non-integer or one below 1 throws `ToolInputError` ("'limit' must be an integer between 1 and <max>").
|
|
6
|
+
- New `optionalOneOf({ input, key, values })`: a value outside `values` throws `ToolInputError` ("'<key>' must be one of: ..."). Both are additive.
|
|
7
|
+
|
|
3
8
|
## 0.4.0 — 2026-10-02
|
|
4
9
|
|
|
5
10
|
### BREAKING
|
|
@@ -167,6 +167,40 @@ export declare function optionalBoolean(requiredArgs: {
|
|
|
167
167
|
input: Record<string, unknown>;
|
|
168
168
|
key: string;
|
|
169
169
|
}, optionalArgs?: Record<string, never>): boolean | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Reads a list tool's page-size field. A list schema's `minimum: 1, maximum: <max>` is advice to the
|
|
172
|
+
* model, not enforcement: without this a `limit` of 0, -5 or 2.5 reached the query and came back as an
|
|
173
|
+
* empty or nonsensical page the model could not tell apart from "no rows".
|
|
174
|
+
*
|
|
175
|
+
* @param required.max The schema's documented cap; a larger request is capped to it, not refused,
|
|
176
|
+
* because the schemas promise "server-capped at <max>".
|
|
177
|
+
* @param required.fallback Returned when the key is absent.
|
|
178
|
+
* @param _optional.key The field name, `limit` by default; it is also the name the error message uses.
|
|
179
|
+
* @returns An integer in `[1, max]`.
|
|
180
|
+
* @throws {ToolInputError} when the value is present but not an integer >= 1.
|
|
181
|
+
* @complexity O(1).
|
|
182
|
+
*/
|
|
183
|
+
export declare function readToolLimit(required: {
|
|
184
|
+
input: Record<string, unknown>;
|
|
185
|
+
max: number;
|
|
186
|
+
fallback: number;
|
|
187
|
+
}, _optional?: {
|
|
188
|
+
key?: string;
|
|
189
|
+
}): number;
|
|
190
|
+
/**
|
|
191
|
+
* Reads an optional enum field (a list filter's `status`, say). An off-enum value is refused rather
|
|
192
|
+
* than passed through, because as a filter it silently matches nothing and reads as "no rows".
|
|
193
|
+
*
|
|
194
|
+
* @param required.values The schema's `enum`, in the order the error message lists them.
|
|
195
|
+
* @returns The value, or `undefined` when the key is absent.
|
|
196
|
+
* @throws {ToolInputError} naming every allowed value when the value is present but not listed.
|
|
197
|
+
* @complexity O(values).
|
|
198
|
+
*/
|
|
199
|
+
export declare function optionalOneOf<T extends string>(required: {
|
|
200
|
+
input: Record<string, unknown>;
|
|
201
|
+
key: string;
|
|
202
|
+
values: readonly T[];
|
|
203
|
+
}, _optional?: Record<string, never>): T | undefined;
|
|
170
204
|
/**
|
|
171
205
|
* The reader for a parameterless tool: `ctx.input` may be omitted entirely or passed as `{}`,
|
|
172
206
|
* nothing else. Refusing a populated object is deliberate — silently ignoring keys would teach a
|
package/dist/registration-kit.js
CHANGED
|
@@ -163,6 +163,49 @@ export function optionalBoolean(requiredArgs, optionalArgs = {}) {
|
|
|
163
163
|
throw new ToolInputError({ message: `'${key}' must be a boolean` });
|
|
164
164
|
return value;
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Reads a list tool's page-size field. A list schema's `minimum: 1, maximum: <max>` is advice to the
|
|
168
|
+
* model, not enforcement: without this a `limit` of 0, -5 or 2.5 reached the query and came back as an
|
|
169
|
+
* empty or nonsensical page the model could not tell apart from "no rows".
|
|
170
|
+
*
|
|
171
|
+
* @param required.max The schema's documented cap; a larger request is capped to it, not refused,
|
|
172
|
+
* because the schemas promise "server-capped at <max>".
|
|
173
|
+
* @param required.fallback Returned when the key is absent.
|
|
174
|
+
* @param _optional.key The field name, `limit` by default; it is also the name the error message uses.
|
|
175
|
+
* @returns An integer in `[1, max]`.
|
|
176
|
+
* @throws {ToolInputError} when the value is present but not an integer >= 1.
|
|
177
|
+
* @complexity O(1).
|
|
178
|
+
*/
|
|
179
|
+
export function readToolLimit(required, _optional = {}) {
|
|
180
|
+
const { input, max, fallback } = required;
|
|
181
|
+
const key = _optional.key ?? "limit";
|
|
182
|
+
const value = input[key];
|
|
183
|
+
if (value === undefined)
|
|
184
|
+
return fallback;
|
|
185
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
186
|
+
throw new ToolInputError({ message: `'${key}' must be an integer between 1 and ${max}` });
|
|
187
|
+
}
|
|
188
|
+
return Math.min(value, max);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Reads an optional enum field (a list filter's `status`, say). An off-enum value is refused rather
|
|
192
|
+
* than passed through, because as a filter it silently matches nothing and reads as "no rows".
|
|
193
|
+
*
|
|
194
|
+
* @param required.values The schema's `enum`, in the order the error message lists them.
|
|
195
|
+
* @returns The value, or `undefined` when the key is absent.
|
|
196
|
+
* @throws {ToolInputError} naming every allowed value when the value is present but not listed.
|
|
197
|
+
* @complexity O(values).
|
|
198
|
+
*/
|
|
199
|
+
export function optionalOneOf(required, _optional = {}) {
|
|
200
|
+
const { input, key, values } = required;
|
|
201
|
+
const value = input[key];
|
|
202
|
+
if (value === undefined)
|
|
203
|
+
return undefined;
|
|
204
|
+
if (typeof value !== "string" || !values.includes(value)) {
|
|
205
|
+
throw new ToolInputError({ message: `'${key}' must be one of: ${values.join(", ")}` });
|
|
206
|
+
}
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
166
209
|
/**
|
|
167
210
|
* The reader for a parameterless tool: `ctx.input` may be omitted entirely or passed as `{}`,
|
|
168
211
|
* nothing else. Refusing a populated object is deliberate — silently ignoring keys would teach a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "The Jini kernel: typed DI tokens, pack composition and lifecycle, the tool registry, redaction, and origin/API-token auth primitives. Zero dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -79,7 +79,8 @@
|
|
|
79
79
|
"!dist/**/*.test.js.map",
|
|
80
80
|
"!dist/**/*.test.d.ts.map",
|
|
81
81
|
"!dist/**/__tests__/**",
|
|
82
|
-
"!dist/**/*.typecheck.*"
|
|
82
|
+
"!dist/**/*.typecheck.*",
|
|
83
|
+
"!dist/**/*.map"
|
|
83
84
|
],
|
|
84
85
|
"publishConfig": {
|
|
85
86
|
"access": "public",
|
package/dist/internal.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module @jini-ai/core/internal
|
|
3
|
-
*
|
|
4
|
-
* Package-internal entry point — NOT part of `@jini-ai/core`'s public
|
|
5
|
-
* contract. Two unrelated escape hatches live here, each for its own
|
|
6
|
-
* single intended consumer:
|
|
7
|
-
*
|
|
8
|
-
* - {@link authorizeToolInvocation} so `@jini-ai/daemon`'s `ToolExecutor` can
|
|
9
|
-
* resolve a registered tool and run its authorization gate — the tool's
|
|
10
|
-
* own `ToolPolicy`, then an optional transport-level veto — before ever
|
|
11
|
-
* receiving the handler. This is the security-sensitive one: it is a
|
|
12
|
-
* runtime *value* export, but unlike the `getToolRegistration` it
|
|
13
|
-
* replaced (2026-07-29 hardening pass), it only ever returns a handler
|
|
14
|
-
* closure alongside a `decision: 'allow'` — a denied or unauthorized call
|
|
15
|
-
* gets no handler at all, so the authorization gate cannot be bypassed by
|
|
16
|
-
* calling this export directly instead of going through `ToolExecutor`.
|
|
17
|
-
* - `AnyPack`/`RequiredTokenIds`/`MissingTokenIds` so `@jini-ai/server`'s
|
|
18
|
-
* `createLocalNodeDaemon` can re-derive `createDaemon`'s exact
|
|
19
|
-
* compile-time "missing binding" gate on its own wrapper config type,
|
|
20
|
-
* instead of either duplicating the type-level logic or losing the
|
|
21
|
-
* compile-time check through the wrapper. These are `import type`-only —
|
|
22
|
-
* erased at build time, carry no runtime capability, and are not a
|
|
23
|
-
* security boundary the way {@link authorizeToolInvocation} is.
|
|
24
|
-
*
|
|
25
|
-
* **Correction (2026-07-19 hardening pass):** a package.json `exports` map
|
|
26
|
-
* subpath is NOT a language-level or per-consumer access modifier — Node's
|
|
27
|
-
* module resolution will happily resolve `@jini-ai/core/internal` for *any*
|
|
28
|
-
* package that depends on `@jini-ai/core`, not just the intended consumer.
|
|
29
|
-
* Earlier revisions of this doc comment claimed this boundary was
|
|
30
|
-
* "enforced" by the exports map; that was inaccurate and left the leak
|
|
31
|
-
* undetected. The actual enforcement is `scripts/check-engine-boundaries.ts`'s
|
|
32
|
-
* rule forbidding any *value* import from `@jini-ai/core/internal` outside
|
|
33
|
-
* `packages/daemon/**` (type-only imports of the DI-token-derivation types
|
|
34
|
-
* remain unrestricted, since they carry no runtime capability) — that guard
|
|
35
|
-
* is wired in, but is a second, independent layer on top of the 2026-07-29
|
|
36
|
-
* structural fix above, not a substitute for it.
|
|
37
|
-
*/
|
|
38
|
-
export { authorizeToolInvocation } from './tool-registry.js';
|
|
39
|
-
export type { ToolAuthorizationDelegate, ToolInvocationAuthorization } from './tool-registry.js';
|
|
40
|
-
export type { AnyPack, MissingTokenIds, RequiredTokenIds } from './daemon.js';
|
|
41
|
-
//# sourceMappingURL=internal.d.ts.map
|
package/dist/internal.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module @jini-ai/core/internal
|
|
3
|
-
*
|
|
4
|
-
* Package-internal entry point — NOT part of `@jini-ai/core`'s public
|
|
5
|
-
* contract. Two unrelated escape hatches live here, each for its own
|
|
6
|
-
* single intended consumer:
|
|
7
|
-
*
|
|
8
|
-
* - {@link authorizeToolInvocation} so `@jini-ai/daemon`'s `ToolExecutor` can
|
|
9
|
-
* resolve a registered tool and run its authorization gate — the tool's
|
|
10
|
-
* own `ToolPolicy`, then an optional transport-level veto — before ever
|
|
11
|
-
* receiving the handler. This is the security-sensitive one: it is a
|
|
12
|
-
* runtime *value* export, but unlike the `getToolRegistration` it
|
|
13
|
-
* replaced (2026-07-29 hardening pass), it only ever returns a handler
|
|
14
|
-
* closure alongside a `decision: 'allow'` — a denied or unauthorized call
|
|
15
|
-
* gets no handler at all, so the authorization gate cannot be bypassed by
|
|
16
|
-
* calling this export directly instead of going through `ToolExecutor`.
|
|
17
|
-
* - `AnyPack`/`RequiredTokenIds`/`MissingTokenIds` so `@jini-ai/server`'s
|
|
18
|
-
* `createLocalNodeDaemon` can re-derive `createDaemon`'s exact
|
|
19
|
-
* compile-time "missing binding" gate on its own wrapper config type,
|
|
20
|
-
* instead of either duplicating the type-level logic or losing the
|
|
21
|
-
* compile-time check through the wrapper. These are `import type`-only —
|
|
22
|
-
* erased at build time, carry no runtime capability, and are not a
|
|
23
|
-
* security boundary the way {@link authorizeToolInvocation} is.
|
|
24
|
-
*
|
|
25
|
-
* **Correction (2026-07-19 hardening pass):** a package.json `exports` map
|
|
26
|
-
* subpath is NOT a language-level or per-consumer access modifier — Node's
|
|
27
|
-
* module resolution will happily resolve `@jini-ai/core/internal` for *any*
|
|
28
|
-
* package that depends on `@jini-ai/core`, not just the intended consumer.
|
|
29
|
-
* Earlier revisions of this doc comment claimed this boundary was
|
|
30
|
-
* "enforced" by the exports map; that was inaccurate and left the leak
|
|
31
|
-
* undetected. The actual enforcement is `scripts/check-engine-boundaries.ts`'s
|
|
32
|
-
* rule forbidding any *value* import from `@jini-ai/core/internal` outside
|
|
33
|
-
* `packages/daemon/**` (type-only imports of the DI-token-derivation types
|
|
34
|
-
* remain unrestricted, since they carry no runtime capability) — that guard
|
|
35
|
-
* is wired in, but is a second, independent layer on top of the 2026-07-29
|
|
36
|
-
* structural fix above, not a substitute for it.
|
|
37
|
-
*/
|
|
38
|
-
export { authorizeToolInvocation } from './tool-registry.js';
|
|
39
|
-
//# sourceMappingURL=internal.js.map
|