@jini-ai/core 0.6.15-rc.2 → 0.6.16
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 +28 -0
- package/README.md +3 -2
- package/dist/approval.d.ts +19 -4
- package/dist/approval.js +54 -14
- package/dist/batch-tool-inputs.d.ts +8 -0
- package/dist/batch-tool-inputs.js +63 -8
- package/dist/contribution-registry.d.ts +23 -0
- package/dist/fetch/cache.d.ts +1 -0
- package/dist/fetch/cache.js +15 -7
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/optional-peer.d.ts +17 -0
- package/dist/optional-peer.js +40 -0
- package/dist/origin-validation.js +9 -8
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/paths.js +2 -1
- package/dist/primitives/sse.js +4 -3
- package/dist/primitives/trim-chars.d.ts +25 -0
- package/dist/primitives/trim-chars.js +28 -0
- package/dist/redact.d.ts +0 -1
- package/dist/redact.js +29 -7
- package/dist/registration-kit.js +1 -3
- package/dist/settings/agent-tools.js +7 -5
- package/dist/settings/agent-value-write-tools.js +7 -3
- package/dist/settings/ensure-definitions.js +4 -2
- package/dist/settings/express/http-request.js +2 -1
- package/dist/settings/express/http.js +12 -4
- package/dist/settings/repo.memory.js +5 -2
- package/dist/settings/settings.js +5 -1
- package/dist/settings/tool-registrations.js +9 -3
- package/dist/settings/write-service.js +11 -9
- package/dist/target-scope.js +2 -1
- package/dist/text/index.d.ts +2 -0
- package/dist/text/index.js +2 -0
- package/dist/text/line-tail.d.ts +17 -0
- package/dist/text/line-tail.js +25 -0
- package/dist/text/sanitize-untrusted-text.js +33 -4
- package/dist/tool-exposure.js +20 -16
- package/dist/tool-input-normalization.d.ts +11 -3
- package/dist/tool-input-normalization.js +14 -8
- package/dist/tool-metadata-types.d.ts +5 -0
- package/dist/tool-registry.d.ts +9 -0
- package/package.json +8 -10
package/dist/tool-exposure.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const SAFE_NAME = /^[A-Za-z_]
|
|
1
|
+
const SAFE_NAME = /^[A-Za-z_]\w{0,63}$/;
|
|
2
2
|
const MAX_NAME_LENGTH = 64;
|
|
3
3
|
/** A stable, non-security hash keeps transformed names independent of discovery order.
|
|
4
4
|
* Collisions are still checked against the entire namespace, never trusted as unique. */
|
|
@@ -11,7 +11,7 @@ function nameHash(id) {
|
|
|
11
11
|
function nameBase(id) {
|
|
12
12
|
if (SAFE_NAME.test(id))
|
|
13
13
|
return id;
|
|
14
|
-
const readable = id.replace(/
|
|
14
|
+
const readable = id.replace(/\W/g, '_');
|
|
15
15
|
return `tool_${readable.slice(0, 49)}_${nameHash(id)}`;
|
|
16
16
|
}
|
|
17
17
|
/** O(c) collision probes for c occupied alternatives; suffix space stays inside the wire limit. */
|
|
@@ -22,7 +22,8 @@ function assignName(index, id) {
|
|
|
22
22
|
let candidate = base;
|
|
23
23
|
let counter = 1;
|
|
24
24
|
while (index.occupied.has(candidate)) {
|
|
25
|
-
const suffix = `_${counter
|
|
25
|
+
const suffix = `_${counter}`;
|
|
26
|
+
counter += 1;
|
|
26
27
|
candidate = `${base.slice(0, MAX_NAME_LENGTH - suffix.length)}${suffix}`;
|
|
27
28
|
}
|
|
28
29
|
index.forward.set(id, candidate);
|
|
@@ -89,6 +90,14 @@ function pinDescriptor(descriptor) {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
/** Count and byte caps reject new entries without evicting any previously callable definition. */
|
|
93
|
+
/** A tool that can join the snapshot (pinned), or why it is skipped. */
|
|
94
|
+
function promotionCandidate(descriptor, snapshot, limits) {
|
|
95
|
+
if (!descriptor)
|
|
96
|
+
return { reason: 'not-found' };
|
|
97
|
+
const pinned = pinDescriptor(descriptor);
|
|
98
|
+
const reason = limitReason(snapshot, pinned.bytes, limits);
|
|
99
|
+
return reason ? { reason } : { pinned };
|
|
100
|
+
}
|
|
92
101
|
function limitReason(snapshot, bytes, limits) {
|
|
93
102
|
if (snapshot.tools.length >= limits.maxTools)
|
|
94
103
|
return 'tool-limit';
|
|
@@ -107,21 +116,16 @@ function promoteDescriptors(snapshot, toolIds, descriptors, names, limits) {
|
|
|
107
116
|
for (const toolId of new Set(toolIds)) {
|
|
108
117
|
if (published.has(toolId))
|
|
109
118
|
continue;
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
skipped.push({ toolId, reason:
|
|
113
|
-
continue;
|
|
119
|
+
const candidate = promotionCandidate(available.get(toolId), { tools, schemaBytes, revision: snapshot.revision }, limits);
|
|
120
|
+
if ('reason' in candidate) {
|
|
121
|
+
skipped.push({ toolId, reason: candidate.reason });
|
|
114
122
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
else {
|
|
124
|
+
assignName(names, toolId);
|
|
125
|
+
tools.push({ wireName: names.forward.get(toolId), descriptor: candidate.pinned.descriptor });
|
|
126
|
+
schemaBytes += candidate.pinned.bytes;
|
|
127
|
+
added.push(toolId);
|
|
120
128
|
}
|
|
121
|
-
assignName(names, toolId);
|
|
122
|
-
tools.push({ wireName: names.forward.get(toolId), descriptor: pinned.descriptor });
|
|
123
|
-
schemaBytes += pinned.bytes;
|
|
124
|
-
added.push(toolId);
|
|
125
129
|
}
|
|
126
130
|
let next = snapshot;
|
|
127
131
|
if (added.length > 0)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ToolRegistration } from './tool-registry.js';
|
|
2
1
|
export { ToolInputNormalizationError } from './tool-input-errors.js';
|
|
3
2
|
/** A value-free account of canonicalization; domain hooks use the same contract as the gate. */
|
|
4
3
|
export interface NormalizedToolInput {
|
|
@@ -8,8 +7,17 @@ export interface NormalizedToolInput {
|
|
|
8
7
|
export type ToolInputNormalizer = (required: {
|
|
9
8
|
input: Readonly<Record<string, unknown>>;
|
|
10
9
|
}, optional?: Record<string, never>) => NormalizedToolInput | Promise<NormalizedToolInput>;
|
|
10
|
+
/** Only the registration facts consumed by normalization; carries no execution authority. */
|
|
11
|
+
interface NormalizationRegistration {
|
|
12
|
+
readonly descriptor: {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly inputSchema?: unknown;
|
|
15
|
+
};
|
|
16
|
+
readonly invocationSchema?: unknown;
|
|
17
|
+
readonly normalizeInput?: ToolInputNormalizer;
|
|
18
|
+
}
|
|
11
19
|
/** Canonicalize one batch layer (including declared aliases) before merging; leave value conversion
|
|
12
|
-
* and domain hooks for final normalization. Throws ToolInputError for
|
|
20
|
+
* and domain hooks for final normalization. Throws ToolInputError for differing colliding values within this layer, never across
|
|
13
21
|
* layers. Uses the same bounded schema walk as normalizeToolInput.
|
|
14
22
|
* @example canonicalizeToolInputKeys({ input: { max_chars: '1000' }, schema });
|
|
15
23
|
*/
|
|
@@ -28,7 +36,7 @@ export declare function canonicalizeToolInputKeys({ input, schema }: {
|
|
|
28
36
|
*/
|
|
29
37
|
export declare function normalizeToolInput({ input, registration }: {
|
|
30
38
|
input: unknown;
|
|
31
|
-
registration:
|
|
39
|
+
registration: NormalizationRegistration;
|
|
32
40
|
}, _options?: Record<string, never>): Promise<NormalizedToolInput>;
|
|
33
41
|
/** Keep result shapes intact and make rewrite notes visible in plain and MCP tool results. */
|
|
34
42
|
export declare function withNormalizationNotes({ output, notes }: {
|
|
@@ -117,10 +117,14 @@ function unknownKeyError({ key, location, shape }, _options = {}) {
|
|
|
117
117
|
const names = [...shape.names.filter(name => required.has(name)), ...shape.names.filter(name => !required.has(name))];
|
|
118
118
|
const declared = names.map(name => `${name} (${required.has(name) ? 'required' : 'optional'})`).join(', ') || 'none';
|
|
119
119
|
const hints = names.filter(name => nearMiss({ left: keyShape(key), right: keyShape(name) }));
|
|
120
|
-
const
|
|
120
|
+
const quotedHints = hints.map(name => `'${name}'`).join(', ');
|
|
121
|
+
const suggestion = hints.length ? ` Did you mean ${quotedHints}?` : '';
|
|
121
122
|
return new ToolInputError({ message: `Unknown input '${location}'. Declared inputs: ${declared}.${suggestion} Resend with the declared name.` });
|
|
122
123
|
}
|
|
123
|
-
/** One key of an object: canonical rename + recursion, closed-schema refusal, or open pass-through.
|
|
124
|
+
/** One key of an object: canonical rename + recursion, closed-schema refusal, or open pass-through.
|
|
125
|
+
* Agreeing scalar aliases coalesce after coercion; differing values still fail without disclosure.
|
|
126
|
+
* @complexity The bounded child walk plus O(1) source/value checks.
|
|
127
|
+
*/
|
|
124
128
|
function normalizeEntry({ key, raw, shape, sources, step }) {
|
|
125
129
|
const { path, depth, walk } = step;
|
|
126
130
|
const location = path ? `${path}.${key}` : key;
|
|
@@ -131,13 +135,15 @@ function normalizeEntry({ key, raw, shape, sources, step }) {
|
|
|
131
135
|
const childSchema = object(shape.schema.additionalProperties) ? shape.schema.additionalProperties : undefined;
|
|
132
136
|
return [key, normalizeValue({ value: raw, schema: childSchema, step: { path: location, depth: depth + 1, walk } })];
|
|
133
137
|
}
|
|
134
|
-
const previous = sources.get(target);
|
|
135
|
-
if (previous !== undefined)
|
|
136
|
-
throw new ToolInputError({ message: `Conflicting inputs '${previous}' and '${key}' both name '${target}'. Send only '${target}'.` });
|
|
137
|
-
sources.set(target, key);
|
|
138
138
|
if (target !== key)
|
|
139
139
|
walk.notes.push(`${location}→${target}`);
|
|
140
|
-
|
|
140
|
+
const parentPrefix = path ? `${path}.` : '';
|
|
141
|
+
const value = normalizeValue({ value: raw, schema: shape.properties[target], step: { path: `${parentPrefix}${target}`, depth: depth + 1, walk } });
|
|
142
|
+
const previous = sources.get(target);
|
|
143
|
+
if (previous !== undefined && !Object.is(previous.value, value))
|
|
144
|
+
throw new ToolInputError({ message: `Conflicting inputs '${previous.key}' and '${key}' both name '${target}'. Send only '${target}'.` });
|
|
145
|
+
sources.set(target, { key, value });
|
|
146
|
+
return [target, value];
|
|
141
147
|
}
|
|
142
148
|
function normalizeObject({ value, schema, step }) {
|
|
143
149
|
const properties = object(schema.properties) ? schema.properties : {};
|
|
@@ -170,7 +176,7 @@ function walkRoot({ input, schema, notes, validateClosed, keysOnly }) {
|
|
|
170
176
|
return normalizeValue({ value: input, schema, step: { path: '', depth: 0, walk: { notes, validateClosed, keysOnly } } });
|
|
171
177
|
}
|
|
172
178
|
/** Canonicalize one batch layer (including declared aliases) before merging; leave value conversion
|
|
173
|
-
* and domain hooks for final normalization. Throws ToolInputError for
|
|
179
|
+
* and domain hooks for final normalization. Throws ToolInputError for differing colliding values within this layer, never across
|
|
174
180
|
* layers. Uses the same bounded schema walk as normalizeToolInput.
|
|
175
181
|
* @example canonicalizeToolInputKeys({ input: { max_chars: '1000' }, schema });
|
|
176
182
|
*/
|
|
@@ -7,6 +7,11 @@ export interface ToolApprovalMetadata {
|
|
|
7
7
|
readonly rule?: string;
|
|
8
8
|
}
|
|
9
9
|
export interface ToolMetadata<A extends ToolApprovalMetadata = ToolApprovalMetadata> {
|
|
10
|
+
/** Human-facing action name and optional button verb; presentation only, never authority. */
|
|
11
|
+
readonly title?: string;
|
|
12
|
+
/** Plain-language consequence shown before approval; never used to classify or authorize. */
|
|
13
|
+
readonly summary?: string;
|
|
14
|
+
readonly actionVerb?: string;
|
|
10
15
|
/** Installed guidance capabilities needed by a writer; independent of permission and risk. */
|
|
11
16
|
readonly guidanceCapabilities?: readonly string[];
|
|
12
17
|
readonly search?: {
|
package/dist/tool-registry.d.ts
CHANGED
|
@@ -160,9 +160,18 @@ export interface ToolExecutionContext {
|
|
|
160
160
|
readonly input: unknown;
|
|
161
161
|
readonly signal: AbortSignal;
|
|
162
162
|
}
|
|
163
|
+
/** Value-free human-dialog outcomes recorded by the executor; never authorization grants. */
|
|
164
|
+
export type ToolApprovalDecision = 'approved' | 'declined' | 'expired' | 'abandoned';
|
|
163
165
|
/** Optional handler ports supplied by the invoking transport. */
|
|
164
166
|
export interface ToolExecutionOptions {
|
|
165
167
|
readonly emitSurface?: SurfaceEmitter;
|
|
168
|
+
/** Reports the human wait to the executor so concurrent writes can be refused. This is
|
|
169
|
+
* notification only; it never grants authority or substitutes for the human dialog. */
|
|
170
|
+
readonly onApprovalStateChange?: (required: {
|
|
171
|
+
pending: boolean;
|
|
172
|
+
/** Bound frontend's human-dialog outcome; observation only, never an authorization grant. */
|
|
173
|
+
decision?: ToolApprovalDecision;
|
|
174
|
+
}, optional?: Record<string, never>) => void;
|
|
166
175
|
/** Invoke through the same executor, retaining this call's principal, run, cancellation and
|
|
167
176
|
* transport gates. Rejects on denial, failed confirmation, validation or execution failure.
|
|
168
177
|
* A composition-owned registry may retain a source registration behind a derived catalog card.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.16",
|
|
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": {
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"./settings": "universal",
|
|
29
29
|
"./settings/express": "node",
|
|
30
30
|
"./plugin-directory": "universal",
|
|
31
|
-
"./filesystem": "universal"
|
|
31
|
+
"./filesystem": "universal",
|
|
32
|
+
"./optional-peer": "universal"
|
|
32
33
|
},
|
|
33
34
|
"isolation": {
|
|
34
35
|
"entries": {
|
|
@@ -161,6 +162,11 @@
|
|
|
161
162
|
"types": "./dist/filesystem.d.ts",
|
|
162
163
|
"import": "./dist/filesystem.js",
|
|
163
164
|
"default": "./dist/filesystem.js"
|
|
165
|
+
},
|
|
166
|
+
"./optional-peer": {
|
|
167
|
+
"types": "./dist/optional-peer.d.ts",
|
|
168
|
+
"import": "./dist/optional-peer.js",
|
|
169
|
+
"default": "./dist/optional-peer.js"
|
|
164
170
|
}
|
|
165
171
|
},
|
|
166
172
|
"files": [
|
|
@@ -187,14 +193,6 @@
|
|
|
187
193
|
"express": "^4.21.0",
|
|
188
194
|
"@types/express": "^4.17.21"
|
|
189
195
|
},
|
|
190
|
-
"peerDependencies": {
|
|
191
|
-
"express": "^4.21.0 || ^5.0.0"
|
|
192
|
-
},
|
|
193
|
-
"peerDependenciesMeta": {
|
|
194
|
-
"express": {
|
|
195
|
-
"optional": true
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
196
|
"scripts": {
|
|
199
197
|
"build": "tsc -p tsconfig.json",
|
|
200
198
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|