@jini-ai/core 0.1.2 → 0.3.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/README.md +101 -0
- package/dist/daemon.d.ts +2 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +18 -13
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +17 -13
- package/dist/internal.js.map +1 -1
- package/dist/origin-validation.d.ts +17 -1
- package/dist/origin-validation.d.ts.map +1 -1
- package/dist/origin-validation.js +48 -9
- package/dist/origin-validation.js.map +1 -1
- package/dist/pack-lifecycle.d.ts +58 -0
- package/dist/pack-lifecycle.d.ts.map +1 -0
- package/dist/pack-lifecycle.js +61 -0
- package/dist/pack-lifecycle.js.map +1 -0
- package/dist/pack.d.ts +38 -0
- package/dist/pack.d.ts.map +1 -1
- package/dist/pack.js.map +1 -1
- package/dist/tool-registry.d.ts +108 -7
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/tool-registry.js +46 -6
- package/dist/tool-registry.js.map +1 -1
- package/package.json +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# `@jini-ai/core`
|
|
2
|
+
|
|
3
|
+
The Jini kernel: typed DI tokens, pack composition and lifecycle, the tool registry, redaction,
|
|
4
|
+
and origin/API-token auth primitives. It has zero runtime dependencies and is not lifted from
|
|
5
|
+
Open Design — it's a from-scratch implementation of the typed composition contract described in
|
|
6
|
+
the extraction plan (a deliberate rejection of the "structural dependency bag" pattern, e.g. OD's
|
|
7
|
+
`ServerContext`, that decays into dozens of `any` fields). A `Pack` is the one composition unit:
|
|
8
|
+
a named bundle of `services`, plus the optional `tools`/`http`/`cli` transports and `dispose`
|
|
9
|
+
teardown that belong to those services. `createDaemon` composes a set of packs against a bound
|
|
10
|
+
set of tokens, and fails to typecheck if any pack's declared dependency is left unbound.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @jini-ai/core
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
No peer dependencies — this package is pure TypeScript with no runtime dependencies at all.
|
|
19
|
+
|
|
20
|
+
## What you get
|
|
21
|
+
|
|
22
|
+
- **DI tokens** — `token<T>(id)` / `manyToken<T>(id)`, nominal and versioned, plus `bindings()`
|
|
23
|
+
building a typed `Bindings<BoundIds>` set (`.bind`, `.bindMany`) that tracks which ids are bound
|
|
24
|
+
in its own type parameter.
|
|
25
|
+
- **Pack composition** — `definePack({ name, deps, services, tools?, http?, cli?, dispose? })`,
|
|
26
|
+
the `Pack`/`PackContainer` types, and `createDaemon({ packs, bindings, transports? })`, which
|
|
27
|
+
composes packs against bound tokens and resolves each pack's `services` against a container
|
|
28
|
+
scoped to only that pack's declared `deps`.
|
|
29
|
+
- **Pack lifecycle helpers** — `registerPackTools` (walks composed packs' `tools()` into a shared
|
|
30
|
+
`ToolRegistry`) and `disposePacks` (best-effort reverse-order teardown; one pack's `dispose`
|
|
31
|
+
failure never blocks another's, recorded as `PackDisposalFailure`).
|
|
32
|
+
- **Tool registry** — `createToolRegistry()` and the `ToolRegistry`/`ToolDescriptor`/`ToolPolicy`/
|
|
33
|
+
`ToolHandler`/`ToolRegistration`/`ToolAuthorizationContext`/`AuthorizationDecision` types. The
|
|
34
|
+
registry's public surface (`register`/`has`/`list`) exposes descriptors only — handlers and
|
|
35
|
+
policies are never publicly retrievable, so holding a `ToolRegistry` reference lets you enumerate
|
|
36
|
+
what's available but never run a tool yourself; only `@jini-ai/daemon`'s `ToolExecutor` can.
|
|
37
|
+
- **Principal** — the minimal `Principal` interface (`id` + optional `roles`) a tool call or run is
|
|
38
|
+
performed on behalf of.
|
|
39
|
+
- **Security/auth primitives** — `redact` (PII/secret redaction, including Luhn-checked card
|
|
40
|
+
numbers), `api-token-auth`, and `origin-validation` (same-origin decision tree, private-IP/
|
|
41
|
+
loopback classification), each genericized to take env-var names as config rather than hardcoded
|
|
42
|
+
product constants.
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { bindings, definePack, createDaemon, token, createToolRegistry, registerPackTools } from '@jini-ai/core';
|
|
48
|
+
|
|
49
|
+
interface Logger {
|
|
50
|
+
log(msg: string): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const LoggerToken = token<Logger>('app.logger');
|
|
54
|
+
|
|
55
|
+
const loggerPack = definePack({
|
|
56
|
+
name: 'logger',
|
|
57
|
+
deps: [],
|
|
58
|
+
services: () => ({ logger: { log: (m: string) => console.log(m) } }),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const consumerPack = definePack({
|
|
62
|
+
name: 'consumer',
|
|
63
|
+
deps: [LoggerToken],
|
|
64
|
+
services: (c) => ({ logger: c.get(LoggerToken) }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const bound = bindings().bind(LoggerToken, { log: (m: string) => console.log(m) });
|
|
68
|
+
|
|
69
|
+
const daemon = createDaemon({
|
|
70
|
+
packs: [loggerPack, consumerPack] as const,
|
|
71
|
+
bindings: bound,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const registry = createToolRegistry();
|
|
75
|
+
registerPackTools([loggerPack, consumerPack], daemon.services, registry);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Entry points
|
|
79
|
+
|
|
80
|
+
| subpath | what's behind it |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `.` | The public surface above: tokens, packs, daemon composition, tool registry (descriptors only), principal, redact, auth primitives. |
|
|
83
|
+
| `./internal` | **Not part of the public contract.** Exposes `authorizeToolInvocation` (resolves a registry's tool and runs its authorization gate — a security-sensitive value export, but one that only ever hands back a handler alongside an `'allow'` decision) plus type-only `AnyPack`/`RequiredTokenIds`/`MissingTokenIds` re-derivation helpers. Its sole intended consumers are `@jini-ai/daemon`'s `ToolExecutor` and `@jini-ai/server`'s `createLocalNodeDaemon`. Node's resolver will happily let any consumer import it — the boundary is enforced by `scripts/check-engine-boundaries.ts`, not by the exports map — so external adopters should treat it as off-limits regardless. |
|
|
84
|
+
|
|
85
|
+
## What's swappable
|
|
86
|
+
|
|
87
|
+
Everything a pack declares as a `deps` token is a swap point by construction: bind any
|
|
88
|
+
implementation that satisfies the token's type via `bindings().bind(...)`, and `createDaemon`
|
|
89
|
+
resolves it into that pack's `PackContainer` with no other change. `ToolPolicy.authorize` is
|
|
90
|
+
likewise a seam you supply per tool registration. The kernel logic itself (composition ordering,
|
|
91
|
+
the missing-binding compile-time gate, the tool registry's append-only enforcement) is fixed and
|
|
92
|
+
not meant to be replaced.
|
|
93
|
+
|
|
94
|
+
## Runtime
|
|
95
|
+
|
|
96
|
+
Universal — no Node/browser-specific APIs.
|
|
97
|
+
ESM only — ships `"type": "module"` with no CommonJS `require` build.
|
|
98
|
+
|
|
99
|
+
## Provenance
|
|
100
|
+
|
|
101
|
+
See [source-map.md](./source-map.md) for per-file provenance and scope decisions. Apache-2.0.
|
package/dist/daemon.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ import type { Pack } from './pack.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Exported (not just used locally) so `./internal.js` can re-derive `createDaemon`'s exact
|
|
5
5
|
* compile-time "missing binding" gate for a wrapper composition function (today:
|
|
6
|
-
* `@jini-ai/
|
|
6
|
+
* `@jini-ai/server`'s `createLocalNodeDaemon`) without duplicating the type-level logic. Not
|
|
7
7
|
* re-exported from `index.ts` — see `./internal.ts`'s module doc for why this stays off the
|
|
8
|
-
* public surface (same boundary pattern as `tool-registry.ts`'s `
|
|
8
|
+
* public surface (same boundary pattern as `tool-registry.ts`'s `authorizeToolInvocation`).
|
|
9
9
|
*/
|
|
10
10
|
export type AnyPack = Pack<any, any, string>;
|
|
11
11
|
export type RequiredTokenIds<Packs extends readonly AnyPack[]> = Packs[number]['deps'][number]['id'];
|
package/dist/index.d.ts
CHANGED
|
@@ -3,11 +3,13 @@ export * from './pack.js';
|
|
|
3
3
|
export * from './bindings.js';
|
|
4
4
|
export type { Daemon, DaemonConfig } from './daemon.js';
|
|
5
5
|
export { createDaemon } from './daemon.js';
|
|
6
|
+
export type { PackDisposalFailure } from './pack-lifecycle.js';
|
|
7
|
+
export { disposePacks, registerPackTools } from './pack-lifecycle.js';
|
|
6
8
|
export * from './redact.js';
|
|
7
9
|
export * from './api-token-auth.js';
|
|
8
10
|
export * from './origin-validation.js';
|
|
9
11
|
export * from './principal.js';
|
|
10
|
-
export type { AuthorizationDecision, RunRef, ToolAuthorizationContext, ToolDescriptor, ToolExecutionContext, ToolHandler, ToolPolicy, ToolRegistration, ToolRegistry, } from './tool-registry.js';
|
|
11
|
-
export { createToolRegistry } from './tool-registry.js';
|
|
12
|
+
export type { AuthorizationDecision, RunRef, SurfaceEmission, SurfaceEmitter, ToolAuthorizationContext, ToolDescriptor, ToolExecutionContext, ToolHandler, ToolPolicy, ToolRegistration, ToolRegistry, } from './tool-registry.js';
|
|
13
|
+
export { createToolRegistry, ToolInputError } from './tool-registry.js';
|
|
12
14
|
export * from './tool-tokens.js';
|
|
13
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAI9B,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gBAAgB,CAAC;AAC/B,YAAY,EACV,qBAAqB,EACrB,MAAM,EACN,wBAAwB,EACxB,cAAc,EACd,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAI9B,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACtE,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gBAAgB,CAAC;AAC/B,YAAY,EACV,qBAAqB,EACrB,MAAM,EACN,eAAe,EACf,cAAc,EACd,wBAAwB,EACxB,cAAc,EACd,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACxE,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,11 @@ export * from './token.js';
|
|
|
2
2
|
export * from './pack.js';
|
|
3
3
|
export * from './bindings.js';
|
|
4
4
|
export { createDaemon } from './daemon.js';
|
|
5
|
+
export { disposePacks, registerPackTools } from './pack-lifecycle.js';
|
|
5
6
|
export * from './redact.js';
|
|
6
7
|
export * from './api-token-auth.js';
|
|
7
8
|
export * from './origin-validation.js';
|
|
8
9
|
export * from './principal.js';
|
|
9
|
-
export { createToolRegistry } from './tool-registry.js';
|
|
10
|
+
export { createToolRegistry, ToolInputError } from './tool-registry.js';
|
|
10
11
|
export * from './tool-tokens.js';
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAK9B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAK9B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACtE,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gBAAgB,CAAC;AAc/B,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACxE,cAAc,kBAAkB,CAAC"}
|
package/dist/internal.d.ts
CHANGED
|
@@ -5,18 +5,22 @@
|
|
|
5
5
|
* contract. Two unrelated escape hatches live here, each for its own
|
|
6
6
|
* single intended consumer:
|
|
7
7
|
*
|
|
8
|
-
* - {@link
|
|
9
|
-
* resolve a registered tool
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
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
|
|
14
18
|
* `createLocalNodeDaemon` can re-derive `createDaemon`'s exact
|
|
15
19
|
* compile-time "missing binding" gate on its own wrapper config type,
|
|
16
20
|
* instead of either duplicating the type-level logic or losing the
|
|
17
21
|
* compile-time check through the wrapper. These are `import type`-only —
|
|
18
22
|
* erased at build time, carry no runtime capability, and are not a
|
|
19
|
-
* security boundary the way {@link
|
|
23
|
+
* security boundary the way {@link authorizeToolInvocation} is.
|
|
20
24
|
*
|
|
21
25
|
* **Correction (2026-07-19 hardening pass):** a package.json `exports` map
|
|
22
26
|
* subpath is NOT a language-level or per-consumer access modifier — Node's
|
|
@@ -25,12 +29,13 @@
|
|
|
25
29
|
* Earlier revisions of this doc comment claimed this boundary was
|
|
26
30
|
* "enforced" by the exports map; that was inaccurate and left the leak
|
|
27
31
|
* undetected. The actual enforcement is `scripts/check-engine-boundaries.ts`'s
|
|
28
|
-
* rule forbidding any *value* import
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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.
|
|
33
37
|
*/
|
|
34
|
-
export {
|
|
38
|
+
export { authorizeToolInvocation } from './tool-registry.js';
|
|
39
|
+
export type { ToolAuthorizationDelegate, ToolInvocationAuthorization } from './tool-registry.js';
|
|
35
40
|
export type { AnyPack, MissingTokenIds, RequiredTokenIds } from './daemon.js';
|
|
36
41
|
//# sourceMappingURL=internal.d.ts.map
|
package/dist/internal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACjG,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/internal.js
CHANGED
|
@@ -5,18 +5,22 @@
|
|
|
5
5
|
* contract. Two unrelated escape hatches live here, each for its own
|
|
6
6
|
* single intended consumer:
|
|
7
7
|
*
|
|
8
|
-
* - {@link
|
|
9
|
-
* resolve a registered tool
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
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
|
|
14
18
|
* `createLocalNodeDaemon` can re-derive `createDaemon`'s exact
|
|
15
19
|
* compile-time "missing binding" gate on its own wrapper config type,
|
|
16
20
|
* instead of either duplicating the type-level logic or losing the
|
|
17
21
|
* compile-time check through the wrapper. These are `import type`-only —
|
|
18
22
|
* erased at build time, carry no runtime capability, and are not a
|
|
19
|
-
* security boundary the way {@link
|
|
23
|
+
* security boundary the way {@link authorizeToolInvocation} is.
|
|
20
24
|
*
|
|
21
25
|
* **Correction (2026-07-19 hardening pass):** a package.json `exports` map
|
|
22
26
|
* subpath is NOT a language-level or per-consumer access modifier — Node's
|
|
@@ -25,11 +29,11 @@
|
|
|
25
29
|
* Earlier revisions of this doc comment claimed this boundary was
|
|
26
30
|
* "enforced" by the exports map; that was inaccurate and left the leak
|
|
27
31
|
* undetected. The actual enforcement is `scripts/check-engine-boundaries.ts`'s
|
|
28
|
-
* rule forbidding any *value* import
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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.
|
|
33
37
|
*/
|
|
34
|
-
export {
|
|
38
|
+
export { authorizeToolInvocation } from './tool-registry.js';
|
|
35
39
|
//# sourceMappingURL=internal.js.map
|
package/dist/internal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -36,8 +36,24 @@ export interface OriginValidationEnvConfig {
|
|
|
36
36
|
/** Env var carrying the host the daemon itself is bound to (default `'127.0.0.1'`). */
|
|
37
37
|
bindHostEnvVar: string;
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Parses {@link OriginValidationEnvConfig.allowedOriginsEnvVar} into normalized origins.
|
|
41
|
+
*
|
|
42
|
+
* Deliberately never throws — this is on the hot path of {@link isLocalSameOrigin}, called fresh
|
|
43
|
+
* on every same-origin decision a host makes. A malformed entry is dropped and logged instead of
|
|
44
|
+
* aborting the whole parse (see the twin `@jini-ai/http-kit` copy of this module for the fuller
|
|
45
|
+
* incident history this mirrors: a request-time throw here used to turn one config typo into
|
|
46
|
+
* either a 500 on every request, or — on a route with no enclosing catch — an unhandled
|
|
47
|
+
* rejection). Call {@link assertValidAllowedOrigins} once at host startup instead, to fail loudly
|
|
48
|
+
* before any request-serving traffic exists.
|
|
49
|
+
*/
|
|
40
50
|
export declare function configuredAllowedOrigins(config: OriginValidationEnvConfig, env?: NodeJS.ProcessEnv): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Boot-time companion to {@link configuredAllowedOrigins}: throws, naming every malformed entry,
|
|
53
|
+
* instead of silently dropping them. Call once, early in host startup, before the HTTP server
|
|
54
|
+
* accepts connections.
|
|
55
|
+
*/
|
|
56
|
+
export declare function assertValidAllowedOrigins(config: OriginValidationEnvConfig, env?: NodeJS.ProcessEnv): void;
|
|
41
57
|
/** The `host` (hostname:port) component of each configured allowed origin. */
|
|
42
58
|
export declare function configuredAllowedHosts(origins: string[]): string[];
|
|
43
59
|
/** The set of browser-facing ports considered "local": the daemon's own port plus an optional configured web port. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin-validation.d.ts","sourceRoot":"","sources":["../src/origin-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CAAC;CACH;AAED,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACxC,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,6GAA6G;IAC7G,aAAa,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,cAAc,EAAE,MAAM,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"origin-validation.d.ts","sourceRoot":"","sources":["../src/origin-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CAAC;CACH;AAED,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACxC,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,6GAA6G;IAC7G,aAAa,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,cAAc,EAAE,MAAM,CAAC;CACxB;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,yBAAyB,EACjC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,EAAE,CAaV;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,yBAAyB,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAOvH;AAED,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAElE;AAED,sHAAsH;AACtH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,yBAAyB,EACjC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACxC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,EAAE,CAOV;AAUD,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CASvE;AAED,uHAAuH;AACvH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAQxD;AAED,yGAAyG;AACzG,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAQ9D;AAED,6EAA6E;AAC7E,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAWrE;AAED,gGAAgG;AAChG,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,yBAAyB,EACjC,UAAU,EAAE,OAAO,EACnB,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAAE,GAC5B,OAAO,CAaT;AAED,iGAAiG;AACjG,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,yBAAyB,EACjC,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,OAAO,EACnB,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAAE,GAC5B,OAAO,CA4BT;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,yBAAyB,EACjC,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACxC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAmCT"}
|
|
@@ -15,22 +15,61 @@
|
|
|
15
15
|
* it names a specific product route and product feature with no generic
|
|
16
16
|
* equivalent; see `source-map.md`.
|
|
17
17
|
*/
|
|
18
|
-
|
|
19
|
-
export function configuredAllowedOrigins(config, env = process.env) {
|
|
18
|
+
function splitAllowedOriginsEnv(config, env) {
|
|
20
19
|
const raw = env[config.allowedOriginsEnvVar] || '';
|
|
21
20
|
if (!raw.trim())
|
|
22
21
|
return [];
|
|
23
22
|
return raw
|
|
24
23
|
.split(',')
|
|
25
24
|
.map((origin) => origin.trim())
|
|
26
|
-
.filter(Boolean)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
.filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
function parseAllowedOrigin(entry) {
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = new URL(entry);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
36
|
+
return undefined;
|
|
37
|
+
return parsed.origin;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parses {@link OriginValidationEnvConfig.allowedOriginsEnvVar} into normalized origins.
|
|
41
|
+
*
|
|
42
|
+
* Deliberately never throws — this is on the hot path of {@link isLocalSameOrigin}, called fresh
|
|
43
|
+
* on every same-origin decision a host makes. A malformed entry is dropped and logged instead of
|
|
44
|
+
* aborting the whole parse (see the twin `@jini-ai/http-kit` copy of this module for the fuller
|
|
45
|
+
* incident history this mirrors: a request-time throw here used to turn one config typo into
|
|
46
|
+
* either a 500 on every request, or — on a route with no enclosing catch — an unhandled
|
|
47
|
+
* rejection). Call {@link assertValidAllowedOrigins} once at host startup instead, to fail loudly
|
|
48
|
+
* before any request-serving traffic exists.
|
|
49
|
+
*/
|
|
50
|
+
export function configuredAllowedOrigins(config, env = process.env) {
|
|
51
|
+
const origins = [];
|
|
52
|
+
for (const entry of splitAllowedOriginsEnv(config, env)) {
|
|
53
|
+
const origin = parseAllowedOrigin(entry);
|
|
54
|
+
if (origin === undefined) {
|
|
55
|
+
console.warn(`[@jini-ai/core] ignoring malformed ${config.allowedOriginsEnvVar} entry (must be an http:// or https:// origin): ${entry}`);
|
|
56
|
+
continue;
|
|
31
57
|
}
|
|
32
|
-
|
|
33
|
-
}
|
|
58
|
+
origins.push(origin);
|
|
59
|
+
}
|
|
60
|
+
return origins;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Boot-time companion to {@link configuredAllowedOrigins}: throws, naming every malformed entry,
|
|
64
|
+
* instead of silently dropping them. Call once, early in host startup, before the HTTP server
|
|
65
|
+
* accepts connections.
|
|
66
|
+
*/
|
|
67
|
+
export function assertValidAllowedOrigins(config, env = process.env) {
|
|
68
|
+
const invalid = splitAllowedOriginsEnv(config, env).filter((entry) => parseAllowedOrigin(entry) === undefined);
|
|
69
|
+
if (invalid.length === 0)
|
|
70
|
+
return;
|
|
71
|
+
throw new Error(`${config.allowedOriginsEnvVar} has ${invalid.length} invalid entr${invalid.length === 1 ? 'y' : 'ies'} ` +
|
|
72
|
+
`(must be http:// or https:// origins): ${invalid.join(', ')}`);
|
|
34
73
|
}
|
|
35
74
|
/** The `host` (hostname:port) component of each configured allowed origin. */
|
|
36
75
|
export function configuredAllowedHosts(origins) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin-validation.js","sourceRoot":"","sources":["../src/origin-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA0BH,
|
|
1
|
+
{"version":3,"file":"origin-validation.js","sourceRoot":"","sources":["../src/origin-validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module pack-lifecycle
|
|
3
|
+
*
|
|
4
|
+
* The non-HTTP half of pack mounting: `registerPackTools` drains every composed pack's `tools`
|
|
5
|
+
* contribution into one shared `ToolRegistry`, and `disposePacks` runs every composed pack's
|
|
6
|
+
* `dispose` in reverse composition order.
|
|
7
|
+
*
|
|
8
|
+
* These live in `@jini-ai/core` rather than beside `@jini-ai/http-kit`'s `mountPackHttp` because
|
|
9
|
+
* neither has anything to do with a transport — a CLI-only or headless composition needs the tool
|
|
10
|
+
* registry and the teardown just as much as an HTTP one does. `mountPackHttp` stays where it is for
|
|
11
|
+
* the same reason in reverse: it is the only one of the three that touches an `app`.
|
|
12
|
+
*/
|
|
13
|
+
import type { Daemon } from './daemon.js';
|
|
14
|
+
import type { Pack } from './pack.js';
|
|
15
|
+
import type { ToolRegistration, ToolRegistry } from './tool-registry.js';
|
|
16
|
+
type AnyPack = Pack<any, any, string>;
|
|
17
|
+
/**
|
|
18
|
+
* Registers `pack.tools(services)` for every pack in `packs` that declares one, in pack order,
|
|
19
|
+
* into the single shared `registry`.
|
|
20
|
+
*
|
|
21
|
+
* Order matters and is deliberately the caller's to choose: `ToolRegistry.register` throws on a
|
|
22
|
+
* duplicate descriptor id, so whichever pack registers second is the one named in the error. A
|
|
23
|
+
* composition root that wants a host's own tools to win that message registers the host's packs
|
|
24
|
+
* last.
|
|
25
|
+
*
|
|
26
|
+
* @param registry - The shared registry every composed pack contributes into.
|
|
27
|
+
* @param packs - The composed packs, in composition order.
|
|
28
|
+
* @param daemon - The `createDaemon` result holding each pack's already-built services.
|
|
29
|
+
* @returns Every registration that was added, in the order it was added — so a caller can seed a
|
|
30
|
+
* catalog, log an inventory, or assert on the composition without re-deriving it.
|
|
31
|
+
* @throws Whatever `ToolRegistry.register` throws (notably a duplicate descriptor id), at the
|
|
32
|
+
* moment of the offending registration. Composition roots should treat this as fatal: a partially
|
|
33
|
+
* registered tool set is not a state anything downstream can reason about.
|
|
34
|
+
* @complexity O(t) in total contributed tools.
|
|
35
|
+
*/
|
|
36
|
+
export declare function registerPackTools<const Packs extends readonly AnyPack[]>(registry: ToolRegistry, packs: Packs, daemon: Daemon<Packs>): readonly ToolRegistration[];
|
|
37
|
+
/** One pack's teardown failure, kept with the pack's name so a caller can report which one failed. */
|
|
38
|
+
export interface PackDisposalFailure {
|
|
39
|
+
readonly pack: string;
|
|
40
|
+
readonly error: unknown;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Runs `pack.dispose(services)` for every pack that declares one, in **reverse** composition order
|
|
44
|
+
* (a pack composed later may depend on an earlier pack's resources, so it must release first).
|
|
45
|
+
*
|
|
46
|
+
* Best-effort by design: one pack throwing must never prevent the rest from disposing, or a single
|
|
47
|
+
* misbehaving pack would leak every other pack's file handles and listeners. Failures are collected
|
|
48
|
+
* and returned rather than thrown, leaving the composition root free to decide whether a teardown
|
|
49
|
+
* failure is worth surfacing to its own caller.
|
|
50
|
+
*
|
|
51
|
+
* @param packs - The composed packs, in composition order (this function reverses them itself).
|
|
52
|
+
* @param daemon - The `createDaemon` result holding each pack's already-built services.
|
|
53
|
+
* @returns The failures, in the order they occurred. Empty when every pack disposed cleanly.
|
|
54
|
+
* @complexity O(p) in pack count, plus each pack's own teardown cost.
|
|
55
|
+
*/
|
|
56
|
+
export declare function disposePacks<const Packs extends readonly AnyPack[]>(packs: Packs, daemon: Daemon<Packs>): Promise<readonly PackDisposalFailure[]>;
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=pack-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack-lifecycle.d.ts","sourceRoot":"","sources":["../src/pack-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEzE,KAAK,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;AAEtC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,KAAK,SAAS,SAAS,OAAO,EAAE,EACtE,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,GACpB,SAAS,gBAAgB,EAAE,CAU7B;AAED,sGAAsG;AACtG,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAAC,KAAK,CAAC,KAAK,SAAS,SAAS,OAAO,EAAE,EACvE,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,GACpB,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAazC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers `pack.tools(services)` for every pack in `packs` that declares one, in pack order,
|
|
3
|
+
* into the single shared `registry`.
|
|
4
|
+
*
|
|
5
|
+
* Order matters and is deliberately the caller's to choose: `ToolRegistry.register` throws on a
|
|
6
|
+
* duplicate descriptor id, so whichever pack registers second is the one named in the error. A
|
|
7
|
+
* composition root that wants a host's own tools to win that message registers the host's packs
|
|
8
|
+
* last.
|
|
9
|
+
*
|
|
10
|
+
* @param registry - The shared registry every composed pack contributes into.
|
|
11
|
+
* @param packs - The composed packs, in composition order.
|
|
12
|
+
* @param daemon - The `createDaemon` result holding each pack's already-built services.
|
|
13
|
+
* @returns Every registration that was added, in the order it was added — so a caller can seed a
|
|
14
|
+
* catalog, log an inventory, or assert on the composition without re-deriving it.
|
|
15
|
+
* @throws Whatever `ToolRegistry.register` throws (notably a duplicate descriptor id), at the
|
|
16
|
+
* moment of the offending registration. Composition roots should treat this as fatal: a partially
|
|
17
|
+
* registered tool set is not a state anything downstream can reason about.
|
|
18
|
+
* @complexity O(t) in total contributed tools.
|
|
19
|
+
*/
|
|
20
|
+
export function registerPackTools(registry, packs, daemon) {
|
|
21
|
+
const services = daemon.services;
|
|
22
|
+
const registered = [];
|
|
23
|
+
for (const pack of packs) {
|
|
24
|
+
for (const registration of pack.tools?.(services[pack.name]) ?? []) {
|
|
25
|
+
registry.register(registration);
|
|
26
|
+
registered.push(registration);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return registered;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Runs `pack.dispose(services)` for every pack that declares one, in **reverse** composition order
|
|
33
|
+
* (a pack composed later may depend on an earlier pack's resources, so it must release first).
|
|
34
|
+
*
|
|
35
|
+
* Best-effort by design: one pack throwing must never prevent the rest from disposing, or a single
|
|
36
|
+
* misbehaving pack would leak every other pack's file handles and listeners. Failures are collected
|
|
37
|
+
* and returned rather than thrown, leaving the composition root free to decide whether a teardown
|
|
38
|
+
* failure is worth surfacing to its own caller.
|
|
39
|
+
*
|
|
40
|
+
* @param packs - The composed packs, in composition order (this function reverses them itself).
|
|
41
|
+
* @param daemon - The `createDaemon` result holding each pack's already-built services.
|
|
42
|
+
* @returns The failures, in the order they occurred. Empty when every pack disposed cleanly.
|
|
43
|
+
* @complexity O(p) in pack count, plus each pack's own teardown cost.
|
|
44
|
+
*/
|
|
45
|
+
export async function disposePacks(packs, daemon) {
|
|
46
|
+
const services = daemon.services;
|
|
47
|
+
const failures = [];
|
|
48
|
+
for (let index = packs.length - 1; index >= 0; index -= 1) {
|
|
49
|
+
const pack = packs[index];
|
|
50
|
+
if (!pack.dispose)
|
|
51
|
+
continue;
|
|
52
|
+
try {
|
|
53
|
+
await pack.dispose(services[pack.name]);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
failures.push({ pack: pack.name, error });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return failures;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=pack-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack-lifecycle.js","sourceRoot":"","sources":["../src/pack-lifecycle.ts"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAsB,EACtB,KAAY,EACZ,MAAqB;IAErB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAmC,CAAC;IAC5D,MAAM,UAAU,GAAuB,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,YAAY,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACnE,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAChC,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAQD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAY,EACZ,MAAqB;IAErB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAmC,CAAC;IAC5D,MAAM,QAAQ,GAA0B,EAAE,CAAC;IAC3C,KAAK,IAAI,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAE,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,SAAS;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/dist/pack.d.ts
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module pack
|
|
3
|
+
*
|
|
4
|
+
* A `Pack` is this kernel's one composition unit: a named bundle of services, plus the optional
|
|
5
|
+
* transports (`http`/`cli`), **tool registrations** (`tools`), and teardown (`dispose`) that belong
|
|
6
|
+
* to those services.
|
|
7
|
+
*
|
|
8
|
+
* `tools` and `dispose` were added 2026-07-29 to close the **Route-vs-Tool Gap**. Before them a
|
|
9
|
+
* pack could contribute an HTTP surface but had no way to contribute a `ToolRegistration`, so every
|
|
10
|
+
* real composition (notably `@jini-ai/server`'s `createLocalNodeDaemon`) registered a feature's
|
|
11
|
+
* tools in one place and mounted that feature's routes in another. Those two steps could then be
|
|
12
|
+
* gated independently — turning a route family "off" while leaving its tool registered and
|
|
13
|
+
* reachable through the always-mounted delegated-tool-call route. Putting both contributions on one
|
|
14
|
+
* object makes that failure unrepresentable: a pack that is not composed contributes neither, and
|
|
15
|
+
* there is no separate mounting step for a caller to forget.
|
|
16
|
+
*
|
|
17
|
+
* The rule for pack authors is therefore: **a capability's tools and its routes belong to the same
|
|
18
|
+
* pack.** Splitting them across two packs re-opens the gap by hand.
|
|
19
|
+
*/
|
|
20
|
+
import type { ToolRegistration } from './tool-registry.js';
|
|
1
21
|
import type { AnyToken, ManyToken, Token } from './token.js';
|
|
2
22
|
/**
|
|
3
23
|
* The only resolver a pack's `services` factory ever sees. Scoped to that
|
|
@@ -13,14 +33,32 @@ export interface Pack<Deps extends readonly AnyToken<unknown, string>[] = readon
|
|
|
13
33
|
readonly name: Name;
|
|
14
34
|
readonly deps: Deps;
|
|
15
35
|
readonly services: (c: PackContainer) => Services;
|
|
36
|
+
/**
|
|
37
|
+
* `{descriptor, handler, policy}` triples this pack contributes to the composition's shared
|
|
38
|
+
* `ToolRegistry`. Called exactly once per composed pack, before any transport is mounted, so a
|
|
39
|
+
* pack's routes can assume its own tools are already registered.
|
|
40
|
+
*
|
|
41
|
+
* Atomic with `http`/`cli` by construction: a pack that is not composed contributes neither its
|
|
42
|
+
* tools nor its routes. See this module's own doc for the failure mode that makes this
|
|
43
|
+
* load-bearing rather than a convenience.
|
|
44
|
+
*/
|
|
45
|
+
readonly tools?: (services: Services) => readonly ToolRegistration[];
|
|
16
46
|
readonly http?: (app: unknown, services: Services) => void;
|
|
17
47
|
readonly cli?: (reg: unknown, services: Services) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Releases whatever this pack's `services` acquired (a pty manager, a database handle, an OAuth
|
|
50
|
+
* callback listener). Composition roots call these in reverse composition order, best-effort:
|
|
51
|
+
* one pack's failure to dispose must never prevent another's from running.
|
|
52
|
+
*/
|
|
53
|
+
readonly dispose?: (services: Services) => Promise<void> | void;
|
|
18
54
|
}
|
|
19
55
|
export declare function definePack<const Name extends string, const Deps extends readonly AnyToken<unknown, string>[], Services>(def: {
|
|
20
56
|
name: Name;
|
|
21
57
|
deps: Deps;
|
|
22
58
|
services: (c: PackContainer) => Services;
|
|
59
|
+
tools?: (services: Services) => readonly ToolRegistration[];
|
|
23
60
|
http?: (app: unknown, services: Services) => void;
|
|
24
61
|
cli?: (reg: unknown, services: Services) => void;
|
|
62
|
+
dispose?: (services: Services) => Promise<void> | void;
|
|
25
63
|
}): Pack<Deps, Services, Name>;
|
|
26
64
|
//# sourceMappingURL=pack.d.ts.map
|
package/dist/pack.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,IAAI,CACnB,IAAI,SAAS,SAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,EACxF,QAAQ,GAAG,OAAO,EAClB,IAAI,SAAS,MAAM,GAAG,MAAM;IAE5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,QAAQ,CAAC;IAClD,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,IAAI,CACnB,IAAI,SAAS,SAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,EACxF,QAAQ,GAAG,OAAO,EAClB,IAAI,SAAS,MAAM,GAAG,MAAM;IAE5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,QAAQ,CAAC;IAClD;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,SAAS,gBAAgB,EAAE,CAAC;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACjE;AAED,wBAAgB,UAAU,CACxB,KAAK,CAAC,IAAI,SAAS,MAAM,EACzB,KAAK,CAAC,IAAI,SAAS,SAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,EACvD,QAAQ,EACR,GAAG,EAAE;IACL,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,QAAQ,CAAC;IACzC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,SAAS,gBAAgB,EAAE,CAAC;IAC5D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IAClD,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD,GAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,CAE7B"}
|
package/dist/pack.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pack.js","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pack.js","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":"AA6DA,MAAM,UAAU,UAAU,CAIxB,GAQD;IACC,OAAO,GAA4C,CAAC;AACtD,CAAC"}
|
package/dist/tool-registry.d.ts
CHANGED
|
@@ -12,11 +12,18 @@
|
|
|
12
12
|
* or an agent holding a `ToolRegistry` reference can enumerate what's
|
|
13
13
|
* available and check membership, but the only way to actually *run* a
|
|
14
14
|
* tool is through `@jini-ai/daemon`'s `ToolExecutor`, which is the sole
|
|
15
|
-
* consumer of {@link
|
|
15
|
+
* consumer of {@link authorizeToolInvocation} — a function deliberately
|
|
16
16
|
* exported from `./internal.js` (a package-internal entry point, see this
|
|
17
17
|
* package's `package.json` `exports` map and `source-map.md`) rather than
|
|
18
18
|
* from the public `index.ts` barrel. Every other caller of
|
|
19
19
|
* `@jini-ai/core`'s default entry point gets descriptors only.
|
|
20
|
+
* `authorizeToolInvocation` itself runs the tool's `ToolPolicy` (and an
|
|
21
|
+
* optional transport veto) before ever handing back a handler, so a
|
|
22
|
+
* runtime handler closure is unobtainable without already having passed
|
|
23
|
+
* authorization — the earlier design (a separate `getToolRegistration`
|
|
24
|
+
* that returned the handler unconditionally) let any caller of this
|
|
25
|
+
* package-internal entry point skip the gate entirely; see this package's
|
|
26
|
+
* `source-map.md` for the 2026-07-29 hardening note.
|
|
20
27
|
*/
|
|
21
28
|
import type { Principal } from './principal.js';
|
|
22
29
|
/** A structural run reference — anything with a stable `id` satisfies this (e.g. `@jini-ai/protocol`'s `RunStatus`). No import needed to satisfy it; see the module doc. */
|
|
@@ -25,7 +32,7 @@ export interface RunRef {
|
|
|
25
32
|
}
|
|
26
33
|
/**
|
|
27
34
|
* The public, non-secret shape of a registered tool. Never carries the
|
|
28
|
-
* handler or policy — those are only reachable via {@link
|
|
35
|
+
* handler or policy — those are only reachable via {@link authorizeToolInvocation}.
|
|
29
36
|
*/
|
|
30
37
|
export interface ToolDescriptor {
|
|
31
38
|
readonly id: string;
|
|
@@ -48,6 +55,49 @@ export interface ToolDescriptor {
|
|
|
48
55
|
/** Byte/character ceiling `ToolExecutor` truncates string output to. Omit for no truncation. */
|
|
49
56
|
readonly maxOutputBytes?: number;
|
|
50
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* One human-facing message a handler pushes out mid-call.
|
|
60
|
+
*
|
|
61
|
+
* `channel` becomes the run event payload's `type`, and `payload` is merged in beside it. It is
|
|
62
|
+
* deliberately NOT a union of the known channels: `@jini-ai/protocol` sits below every package in
|
|
63
|
+
* the graph and already types each channel's own body as `unknown` for exactly this reason —
|
|
64
|
+
* structural validation belongs where the concrete type is known (`@jini-ai/ui`'s `parseUIResource`
|
|
65
|
+
* for mcp-ui, `@jini-ai/agentic/a2ui`'s `parseAgentToRendererMessage` for a2ui). Naming the channel
|
|
66
|
+
* as a string is what lets a handler drive `mcp-ui`, `a2ui`, or `surface_request` — or a channel
|
|
67
|
+
* that does not exist yet — through one seam.
|
|
68
|
+
*
|
|
69
|
+
* @example mcp-ui — a sandboxed HTML surface; the transport supplies `toolUseId`
|
|
70
|
+
* `{ channel: 'mcp-ui', payload: { resource } }`
|
|
71
|
+
* @example a2ui — a component tree that can be updated repeatedly on one held-open call
|
|
72
|
+
* `{ channel: 'a2ui', payload: { message: createSurface(exchangeId, ...) } }`
|
|
73
|
+
* @example the protocol-neutral human-in-the-loop pair
|
|
74
|
+
* `{ channel: 'surface_request', payload: { surfaceId, surfaceKind: 'form', payload: {...} } }`
|
|
75
|
+
*/
|
|
76
|
+
export interface SurfaceEmission {
|
|
77
|
+
/** The run event payload `type` this belongs on. */
|
|
78
|
+
readonly channel: string;
|
|
79
|
+
/** The channel's own fields, merged into the emitted event beside `type`. */
|
|
80
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Pushes a human-facing message into the run's event stream *while the call is still in flight*.
|
|
84
|
+
*
|
|
85
|
+
* A handler's return value is the only other way to reach a human, and that route is useless to a
|
|
86
|
+
* handler that must WAIT for one: the daemon reads surfaces out of the completed result, so a
|
|
87
|
+
* handler that parks first never emits the thing being answered, and the park can never resolve.
|
|
88
|
+
* That is a deadlock by construction, not a race. This is the seam that breaks it — emit, then await.
|
|
89
|
+
*
|
|
90
|
+
* Callable any number of times before the call settles, which is what makes a multi-turn exchange
|
|
91
|
+
* (A2UI's `createSurface` → action → `updateComponents` → action) expressible on a single tool call
|
|
92
|
+
* rather than only a one-shot ask.
|
|
93
|
+
*
|
|
94
|
+
* Human-only in both directions: emitting never puts anything into model context.
|
|
95
|
+
*
|
|
96
|
+
* Optional because it is transport-supplied. A caller with no run event stream to emit into (a
|
|
97
|
+
* headless or synthetic execution) omits it, and a handler that finds it absent must fall back to
|
|
98
|
+
* returning its surface rather than parking — see the `@jini-ai/daemon` bridge for the supplying end.
|
|
99
|
+
*/
|
|
100
|
+
export type SurfaceEmitter = (emission: SurfaceEmission) => Promise<void>;
|
|
51
101
|
/** What a `ToolHandler` receives — everything it needs and nothing it could use to bypass the gate (no registry access, no other tools' state). */
|
|
52
102
|
export interface ToolExecutionContext {
|
|
53
103
|
/** The id `ToolExecutor` assigned this call — for the handler's own logging/correlation; carries no authority (the gate already ran before the handler is invoked). */
|
|
@@ -56,9 +106,27 @@ export interface ToolExecutionContext {
|
|
|
56
106
|
readonly run: RunRef;
|
|
57
107
|
readonly input: unknown;
|
|
58
108
|
readonly signal: AbortSignal;
|
|
109
|
+
/** Present only when the invoking transport owns a run event stream — see {@link SurfaceEmitter}. */
|
|
110
|
+
readonly emitSurface?: SurfaceEmitter;
|
|
59
111
|
}
|
|
60
112
|
/** Runs the tool's actual side effect. Only ever invoked by `ToolExecutor`, never called directly by a route/agent holding the registry. */
|
|
61
113
|
export type ToolHandler = (ctx: ToolExecutionContext) => Promise<unknown>;
|
|
114
|
+
/**
|
|
115
|
+
* Thrown by a `ToolHandler` (or a validator it calls, e.g. `@jini-ai/cms`'s `registration-kit.ts`
|
|
116
|
+
* `requireString`/`requireInputRecord`/etc.) to mean "the caller's input was malformed or missing a
|
|
117
|
+
* required field" — a fact about the CALL, not the server. `@jini-ai/daemon`'s `ToolExecutor.execute`
|
|
118
|
+
* tags a handler rejection's `ToolExecutionResult.errorKind` as `'validation'` when the thrown error
|
|
119
|
+
* is `instanceof ToolInputError`, and every HTTP-facing mapping of a `'failed'` execution (today:
|
|
120
|
+
* `@jini-ai/http-kit`'s `delegated-tools.ts`) uses that tag to answer with a 4xx instead of folding
|
|
121
|
+
* it into the same redacted 500 an actual internal/infrastructure failure gets.
|
|
122
|
+
*
|
|
123
|
+
* Deliberately a plain marker with no extra fields: the message itself is already caller-actionable
|
|
124
|
+
* (`withSchemaOnRejection` appends the tool's own input schema to it), so nothing beyond "this IS one
|
|
125
|
+
* of these" needs to survive the `instanceof` check.
|
|
126
|
+
*/
|
|
127
|
+
export declare class ToolInputError extends Error {
|
|
128
|
+
constructor(message: string);
|
|
129
|
+
}
|
|
62
130
|
export type AuthorizationDecision = 'allow' | 'deny';
|
|
63
131
|
export interface ToolAuthorizationContext {
|
|
64
132
|
readonly principal: Principal;
|
|
@@ -95,20 +163,53 @@ export interface ToolRegistry {
|
|
|
95
163
|
*
|
|
96
164
|
* @returns A `ToolRegistry` whose backing `{descriptor, handler, policy}`
|
|
97
165
|
* map is held in a module-private `WeakMap` keyed by the returned instance
|
|
98
|
-
* — reachable only via {@link
|
|
166
|
+
* — reachable only via {@link authorizeToolInvocation}, not through any method
|
|
99
167
|
* on the returned object itself.
|
|
100
168
|
* @complexity `register`/`has` O(1); `list` O(n) in registered tool count.
|
|
101
169
|
* @overallScore 100/100
|
|
102
170
|
*/
|
|
103
171
|
export declare function createToolRegistry(): ToolRegistry;
|
|
172
|
+
/** Transport-level authorization veto, structurally matching `@jini-ai/daemon`'s `ExecutionDelegate.onAuthorize` without either package importing the other's type. */
|
|
173
|
+
export interface ToolAuthorizationDelegate {
|
|
174
|
+
onAuthorize?(ctx: ToolAuthorizationContext): AuthorizationDecision | Promise<AuthorizationDecision>;
|
|
175
|
+
}
|
|
104
176
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
177
|
+
* Outcome of {@link authorizeToolInvocation} — a union discriminated on
|
|
178
|
+
* `decision`, deliberately rather than one shape with an optional `handler`.
|
|
179
|
+
* "A handler exists if and only if the call was allowed" is the whole point
|
|
180
|
+
* of this function, so it is expressed as something the compiler enforces:
|
|
181
|
+
* the `'deny'` arm has no `handler` property to read at all, and the
|
|
182
|
+
* `'allow'` arm's is non-optional, so the caller needs no non-null assertion
|
|
183
|
+
* to use it after checking the decision.
|
|
184
|
+
*/
|
|
185
|
+
export type ToolInvocationAuthorization = {
|
|
186
|
+
readonly descriptor: ToolDescriptor;
|
|
187
|
+
readonly decision: 'allow';
|
|
188
|
+
readonly handler: ToolHandler;
|
|
189
|
+
} | {
|
|
190
|
+
readonly descriptor: ToolDescriptor;
|
|
191
|
+
readonly decision: 'deny';
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Package-internal escape hatch: resolves `toolId` in `registry` and runs
|
|
195
|
+
* its authorization gate — the tool's own {@link ToolPolicy}, then an
|
|
196
|
+
* optional transport-level `delegate.onAuthorize` veto — before ever
|
|
197
|
+
* exposing the handler. Exported only from `./internal.js` (see this
|
|
198
|
+
* package's `package.json` `exports` map) — `@jini-ai/daemon`'s
|
|
108
199
|
* `ToolExecutor` is the one and only intended caller. Not re-exported from
|
|
109
200
|
* `index.ts`, so it never reaches `@jini-ai/core`'s public consumers.
|
|
110
201
|
*
|
|
202
|
+
* Replaces an earlier `getToolRegistration` that returned the full
|
|
203
|
+
* `{descriptor, handler, policy}` triple unconditionally — since a
|
|
204
|
+
* package.json `exports` subpath is not an access modifier (see this
|
|
205
|
+
* package's `source-map.md`), that let any consumer able to `import` this
|
|
206
|
+
* package-internal entry point retrieve a live handler closure without
|
|
207
|
+
* ever calling a policy. Bundling resolution with authorization here means
|
|
208
|
+
* the handler itself is now unobtainable without already having passed the
|
|
209
|
+
* gate — the leak closes structurally rather than by convention.
|
|
210
|
+
*
|
|
211
|
+
* @returns `undefined` if `toolId` isn't registered on `registry`.
|
|
111
212
|
* @internal
|
|
112
213
|
*/
|
|
113
|
-
export declare function
|
|
214
|
+
export declare function authorizeToolInvocation(registry: ToolRegistry, toolId: string, principal: Principal, run: RunRef, input: unknown, delegate?: ToolAuthorizationDelegate): Promise<ToolInvocationAuthorization | undefined>;
|
|
114
215
|
//# sourceMappingURL=tool-registry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"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"}
|
package/dist/tool-registry.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by a `ToolHandler` (or a validator it calls, e.g. `@jini-ai/cms`'s `registration-kit.ts`
|
|
3
|
+
* `requireString`/`requireInputRecord`/etc.) to mean "the caller's input was malformed or missing a
|
|
4
|
+
* required field" — a fact about the CALL, not the server. `@jini-ai/daemon`'s `ToolExecutor.execute`
|
|
5
|
+
* tags a handler rejection's `ToolExecutionResult.errorKind` as `'validation'` when the thrown error
|
|
6
|
+
* is `instanceof ToolInputError`, and every HTTP-facing mapping of a `'failed'` execution (today:
|
|
7
|
+
* `@jini-ai/http-kit`'s `delegated-tools.ts`) uses that tag to answer with a 4xx instead of folding
|
|
8
|
+
* it into the same redacted 500 an actual internal/infrastructure failure gets.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately a plain marker with no extra fields: the message itself is already caller-actionable
|
|
11
|
+
* (`withSchemaOnRejection` appends the tool's own input schema to it), so nothing beyond "this IS one
|
|
12
|
+
* of these" needs to survive the `instanceof` check.
|
|
13
|
+
*/
|
|
14
|
+
export class ToolInputError extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'ToolInputError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
1
20
|
const registrationsByRegistry = new WeakMap();
|
|
2
21
|
/**
|
|
3
22
|
* Creates an empty, append-only `ToolRegistry`.
|
|
4
23
|
*
|
|
5
24
|
* @returns A `ToolRegistry` whose backing `{descriptor, handler, policy}`
|
|
6
25
|
* map is held in a module-private `WeakMap` keyed by the returned instance
|
|
7
|
-
* — reachable only via {@link
|
|
26
|
+
* — reachable only via {@link authorizeToolInvocation}, not through any method
|
|
8
27
|
* on the returned object itself.
|
|
9
28
|
* @complexity `register`/`has` O(1); `list` O(n) in registered tool count.
|
|
10
29
|
* @overallScore 100/100
|
|
@@ -29,15 +48,36 @@ export function createToolRegistry() {
|
|
|
29
48
|
return registry;
|
|
30
49
|
}
|
|
31
50
|
/**
|
|
32
|
-
* Package-internal escape hatch: resolves
|
|
33
|
-
*
|
|
34
|
-
*
|
|
51
|
+
* Package-internal escape hatch: resolves `toolId` in `registry` and runs
|
|
52
|
+
* its authorization gate — the tool's own {@link ToolPolicy}, then an
|
|
53
|
+
* optional transport-level `delegate.onAuthorize` veto — before ever
|
|
54
|
+
* exposing the handler. Exported only from `./internal.js` (see this
|
|
55
|
+
* package's `package.json` `exports` map) — `@jini-ai/daemon`'s
|
|
35
56
|
* `ToolExecutor` is the one and only intended caller. Not re-exported from
|
|
36
57
|
* `index.ts`, so it never reaches `@jini-ai/core`'s public consumers.
|
|
37
58
|
*
|
|
59
|
+
* Replaces an earlier `getToolRegistration` that returned the full
|
|
60
|
+
* `{descriptor, handler, policy}` triple unconditionally — since a
|
|
61
|
+
* package.json `exports` subpath is not an access modifier (see this
|
|
62
|
+
* package's `source-map.md`), that let any consumer able to `import` this
|
|
63
|
+
* package-internal entry point retrieve a live handler closure without
|
|
64
|
+
* ever calling a policy. Bundling resolution with authorization here means
|
|
65
|
+
* the handler itself is now unobtainable without already having passed the
|
|
66
|
+
* gate — the leak closes structurally rather than by convention.
|
|
67
|
+
*
|
|
68
|
+
* @returns `undefined` if `toolId` isn't registered on `registry`.
|
|
38
69
|
* @internal
|
|
39
70
|
*/
|
|
40
|
-
export function
|
|
41
|
-
|
|
71
|
+
export async function authorizeToolInvocation(registry, toolId, principal, run, input, delegate) {
|
|
72
|
+
const registration = registrationsByRegistry.get(registry)?.get(toolId);
|
|
73
|
+
if (!registration) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const { descriptor, handler, policy } = registration;
|
|
77
|
+
let decision = await policy.authorize({ principal, run, tool: descriptor, input });
|
|
78
|
+
if (decision === 'allow' && delegate?.onAuthorize) {
|
|
79
|
+
decision = await delegate.onAuthorize({ principal, run, tool: descriptor, input });
|
|
80
|
+
}
|
|
81
|
+
return decision === 'allow' ? { descriptor, decision, handler } : { descriptor, decision: 'deny' };
|
|
42
82
|
}
|
|
43
83
|
//# sourceMappingURL=tool-registry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"AAwHA;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAsCD,MAAM,uBAAuB,GAAG,IAAI,OAAO,EAA+C,CAAC;AAE3F;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,aAAa,GAAG,IAAI,GAAG,EAA4B,CAAC;IAE1D,MAAM,QAAQ,GAAiB;QAC7B,QAAQ,CAAC,YAA8B;YACrC,IAAI,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;gBAClD,MAAM,IAAI,KAAK,CAAC,uBAAuB,YAAY,CAAC,UAAU,CAAC,EAAE,yBAAyB,CAAC,CAAC;YAC9F,CAAC;YACD,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QAC9D,CAAC;QACD,GAAG,CAAC,MAAc;YAChB,OAAO,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,IAAI;YACF,OAAO,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACjE,CAAC;KACF,CAAC;IAEF,uBAAuB,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IACrD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAoBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,QAAsB,EACtB,MAAc,EACd,SAAoB,EACpB,GAAW,EACX,KAAc,EACd,QAAoC;IAEpC,MAAM,YAAY,GAAG,uBAAuB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC;IACrD,IAAI,QAAQ,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IACnF,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,EAAE,WAAW,EAAE,CAAC;QAClD,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AACrG,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "The Jini kernel: typed DI tokens, pack composition and lifecycle, the tool registry, redaction, and origin/API-token auth primitives. Zero dependencies.",
|
|
4
5
|
"license": "Apache-2.0",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
@@ -10,10 +11,10 @@
|
|
|
10
11
|
"jini": {
|
|
11
12
|
"domain": "engine",
|
|
12
13
|
"kind": "kernel",
|
|
13
|
-
"runtime": "universal"
|
|
14
|
-
"admission": "locked"
|
|
14
|
+
"runtime": "universal"
|
|
15
15
|
},
|
|
16
16
|
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
17
18
|
"main": "./dist/index.js",
|
|
18
19
|
"types": "./dist/index.d.ts",
|
|
19
20
|
"exports": {
|