@jini-ai/core 0.4.1 → 0.4.2
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 +6 -0
- package/README.md +18 -1
- package/dist/agent-tools.d.ts +1 -0
- package/dist/approval.d.ts +134 -0
- package/dist/approval.js +141 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/model-facing-tool-errors.d.ts +152 -0
- package/dist/model-facing-tool-errors.js +124 -0
- package/dist/native-regexp.d.ts +10 -0
- package/dist/native-regexp.js +2 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/sse.d.ts +60 -0
- package/dist/primitives/sse.js +122 -0
- package/dist/redact.js +4 -3
- package/dist/registration-kit.d.ts +13 -0
- package/dist/registration-kit.js +12 -0
- package/dist/settings/agent-tools.d.ts +10 -0
- package/dist/settings/agent-tools.js +204 -0
- package/dist/settings/agent-value-write-tools.d.ts +22 -0
- package/dist/settings/agent-value-write-tools.js +157 -0
- package/dist/settings/agent-writable-preferences.d.ts +67 -0
- package/dist/settings/agent-writable-preferences.js +132 -0
- package/dist/settings/agent-write-denylist.d.ts +19 -0
- package/dist/settings/agent-write-denylist.js +15 -0
- package/dist/settings/change-feed.d.ts +83 -0
- package/dist/settings/change-feed.js +61 -0
- package/dist/settings/definitions-dispatch.d.ts +56 -0
- package/dist/settings/definitions-dispatch.js +86 -0
- package/dist/settings/dictionaries/index.d.ts +48 -0
- package/dist/settings/dictionaries/index.js +69 -0
- package/dist/settings/dictionaries/settings-dialog.ar.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ar.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.bn.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.bn.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.de.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.de.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.es.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.es.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.fa.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fa.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.fr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fr.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.hi.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hi.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.hu.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hu.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.id.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.id.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.it.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.it.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ja.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ja.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ko.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ko.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.pl.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pl.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ru.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ru.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.th.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.th.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.tr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.tr.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.uk.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.uk.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ur.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ur.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +27 -0
- package/dist/settings/ensure-definitions.d.ts +101 -0
- package/dist/settings/ensure-definitions.js +101 -0
- package/dist/settings/errors.d.ts +42 -0
- package/dist/settings/errors.js +46 -0
- package/dist/settings/express/cms-adapter.d.ts +20 -0
- package/dist/settings/express/cms-adapter.js +124 -0
- package/dist/settings/express/contracts.d.ts +135 -0
- package/dist/settings/express/contracts.js +2 -0
- package/dist/settings/express/http.d.ts +10 -0
- package/dist/settings/express/http.js +211 -0
- package/dist/settings/express/index.d.ts +5 -0
- package/dist/settings/express/index.js +4 -0
- package/dist/settings/express/shared.d.ts +68 -0
- package/dist/settings/express/shared.js +54 -0
- package/dist/settings/express/stream.d.ts +24 -0
- package/dist/settings/express/stream.js +147 -0
- package/dist/settings/index.d.ts +40 -0
- package/dist/settings/index.js +37 -0
- package/dist/settings/ports.d.ts +118 -0
- package/dist/settings/ports.js +2 -0
- package/dist/settings/principal-lookup.d.ts +9 -0
- package/dist/settings/principal-lookup.js +2 -0
- package/dist/settings/purge-service.d.ts +54 -0
- package/dist/settings/purge-service.js +106 -0
- package/dist/settings/repo.memory.d.ts +119 -0
- package/dist/settings/repo.memory.js +199 -0
- package/dist/settings/settings.d.ts +139 -0
- package/dist/settings/settings.js +259 -0
- package/dist/settings/tool-registrations.d.ts +54 -0
- package/dist/settings/tool-registrations.js +250 -0
- package/dist/settings/types.d.ts +125 -0
- package/dist/settings/types.js +3 -0
- package/dist/settings/ui-tab-definitions.d.ts +41 -0
- package/dist/settings/ui-tab-definitions.js +148 -0
- package/dist/settings/write-service.d.ts +320 -0
- package/dist/settings/write-service.js +683 -0
- package/dist/surface-exchanges.d.ts +209 -0
- package/dist/surface-exchanges.js +55 -0
- package/dist/tool-metadata.d.ts +71 -0
- package/dist/tool-metadata.js +56 -0
- package/dist/tool-permissions.d.ts +76 -0
- package/dist/tool-permissions.js +57 -0
- package/dist/tool-registry.d.ts +3 -0
- package/package.json +60 -3
|
@@ -7,12 +7,57 @@
|
|
|
7
7
|
// Domains explicitly own which classes/messages are safe to expose, including anonymous callers:
|
|
8
8
|
// stacks, internal paths, SQL, credentials, and another tenant's data must remain unlisted/redacted.
|
|
9
9
|
import { ToolInputError } from "./tool-registry.js";
|
|
10
|
+
/**
|
|
11
|
+
* The code every domain publishes an authorization refusal under, parameterized by domain prefix.
|
|
12
|
+
*
|
|
13
|
+
* Exists because `ForbiddenError` is the ONE class in this ladder that no domain declares: the kit's
|
|
14
|
+
* `requireToolPermission` throws it on every domain's behalf. Deriving the rule here rather than
|
|
15
|
+
* retyping `{ error: ForbiddenError, code: "X_FORBIDDEN" }` in six files means a later decision to
|
|
16
|
+
* reconcile this code with the HTTP arm's own `FORBIDDEN` is a one-line change here, not a sweep.
|
|
17
|
+
*
|
|
18
|
+
* NOTE (2026-09-16): the model-facing and HTTP arms disagree on this code today — the HTTP mappers
|
|
19
|
+
* return `FORBIDDEN` while the model-facing arm returns `<DOMAIN>_FORBIDDEN`. That drift predates
|
|
20
|
+
* this file (see `features/widgets/tool-registrations.ts`'s `toModelFacingWidgetsError` doc, which
|
|
21
|
+
* documented it for Widgets) and is owned elsewhere. This helper deliberately matches the SHIPPED
|
|
22
|
+
* model-facing convention rather than inventing a third spelling.
|
|
23
|
+
*
|
|
24
|
+
* The refusal message `requireToolPermission` builds names the principal, the permission, and the
|
|
25
|
+
* `authorize()` reason — no data, no internals. Surfacing it tells a model whether to stop asking
|
|
26
|
+
* or to ask a human for access; redacting it tells it only that something broke.
|
|
27
|
+
*
|
|
28
|
+
* @param required.domainPrefix - The domain's SCREAMING_SNAKE prefix, e.g. `MEMBERS`.
|
|
29
|
+
* @param required.error - The consumer's permission-error class; core has no CMS dependency.
|
|
30
|
+
* @returns The rule to place in that domain's allowlist.
|
|
31
|
+
* @complexity O(1).
|
|
32
|
+
*/
|
|
10
33
|
export function forbiddenRule(required) {
|
|
11
34
|
return { error: required.error, code: `${required.domainPrefix}_FORBIDDEN` };
|
|
12
35
|
}
|
|
13
36
|
// First match wins: list subclasses before superclasses. Unmatched values must retain identity so
|
|
14
37
|
// the transport's redaction remains the default; already-classified ToolInputError also stays intact
|
|
15
38
|
// to avoid doubling a prefix attached by schema/recovery wrapping.
|
|
39
|
+
/**
|
|
40
|
+
* Re-classifies one rejection against a domain's allowlist, so a listed domain error reaches the
|
|
41
|
+
* model as a `ToolInputError` carrying its real message instead of a redacted `INTERNAL_ERROR`.
|
|
42
|
+
*
|
|
43
|
+
* Rules are evaluated IN ORDER and the first `instanceof` match wins, so a domain listing both a
|
|
44
|
+
* subclass and its superclass must place the subclass first — the same ordering discipline any
|
|
45
|
+
* hand-written `instanceof` ladder carries, made explicit here because an array hides it less than
|
|
46
|
+
* a chain of `if`s does.
|
|
47
|
+
*
|
|
48
|
+
* A matched rule publishes the error's own message, or {@link ModelFacingErrorRule.message} instead
|
|
49
|
+
* when the rule sets one — see that field for which classes need it and why.
|
|
50
|
+
*
|
|
51
|
+
* An already-`ToolInputError` rejection is returned untouched: it is already correctly classified,
|
|
52
|
+
* and re-wrapping it would double a code prefix that a `withSchemaOnRejection` wrap may have
|
|
53
|
+
* already attached.
|
|
54
|
+
*
|
|
55
|
+
* @param required.err - The rejection, of unknown type — anything a handler can throw.
|
|
56
|
+
* @param required.rules - The domain's allowlist. Anything unmatched is returned UNCHANGED and stays
|
|
57
|
+
* redacted; see this file's header for why that default is the security-relevant half.
|
|
58
|
+
* @returns The value to re-throw: a `ToolInputError` for a matched rule, otherwise `err` itself.
|
|
59
|
+
* @complexity O(r) in the rule count, on the failure path only.
|
|
60
|
+
*/
|
|
16
61
|
export function reclassifyToolError(required) {
|
|
17
62
|
const { err, rules } = required;
|
|
18
63
|
if (err instanceof ToolInputError)
|
|
@@ -28,6 +73,24 @@ export function reclassifyToolError(required) {
|
|
|
28
73
|
// Wrap the whole map once: a correct per-handler converter previously reached only one handler
|
|
29
74
|
// while sibling operations still leaked into the internal-error redactor. Central wrapping has no
|
|
30
75
|
// omitted call-site arm, and successful results pass through untouched.
|
|
76
|
+
/**
|
|
77
|
+
* Wraps EVERY handler in a domain's map with {@link reclassifyToolError}, returning a new map.
|
|
78
|
+
*
|
|
79
|
+
* Wrapping the whole map once — rather than reclassifying at each call site — is the point. This
|
|
80
|
+
* codebase's dominant defect is a correct primitive with an unwired call site, and a per-call-site
|
|
81
|
+
* reshape is exactly that defect waiting to happen: `features/post/tool-registrations.ts` had a
|
|
82
|
+
* correct `toModelFacingUpdateError` wired into ONE of its handlers while its siblings kept
|
|
83
|
+
* throwing the same class straight through to the redactor. A map-level wrap cannot have a missed
|
|
84
|
+
* arm, because it has no arms.
|
|
85
|
+
*
|
|
86
|
+
* The wrap is transparent on the success path: the handler's own return value is passed through
|
|
87
|
+
* untouched, and only the `catch` does work.
|
|
88
|
+
*
|
|
89
|
+
* @param required.handlers - The domain's handler map, keyed by tool id.
|
|
90
|
+
* @param rules - The domain's allowlist, applied identically to every handler.
|
|
91
|
+
* @returns A new map with the same keys; the input map is not mutated.
|
|
92
|
+
* @complexity O(h) handlers wrapped once at build time; O(r) per failed call, none per successful one.
|
|
93
|
+
*/
|
|
31
94
|
export function withModelFacingErrors(required) {
|
|
32
95
|
const { handlers, rules } = required;
|
|
33
96
|
return Object.fromEntries(Object.entries(handlers).map(([toolId, handler]) => [
|
|
@@ -44,6 +107,24 @@ export function withModelFacingErrors(required) {
|
|
|
44
107
|
}
|
|
45
108
|
// Registration-array builders need the same blanket coverage as handler maps. Preserve descriptor,
|
|
46
109
|
// policy, ordering, and successful results; only the rejection path is reclassified.
|
|
110
|
+
/**
|
|
111
|
+
* Wraps EVERY registration's handler in a domain's `ToolRegistration[]` with
|
|
112
|
+
* {@link reclassifyToolError}, returning a new array. The `ToolRegistration[]`-shaped sibling of
|
|
113
|
+
* {@link withModelFacingErrors} — for the registry-converted domains whose `build*Registrations`
|
|
114
|
+
* returns an array of `{descriptor, handler, policy}` (see `assistant/tool-contribution-registry.ts`'s
|
|
115
|
+
* `ToolContributor.build`) rather than a `Record<toolId, handler>`. `features/entries`,
|
|
116
|
+
* `features/content-types`, `features/navigation` and `features/workspace` are re-exported Jini
|
|
117
|
+
* builders of exactly this shape — see each one's `contribute*Tools`.
|
|
118
|
+
*
|
|
119
|
+
* Same transparency guarantee as {@link withModelFacingErrors}: only the `catch` arm does work, and
|
|
120
|
+
* every other field of each registration (`descriptor`, `policy`) passes through unchanged.
|
|
121
|
+
*
|
|
122
|
+
* @param required.registrations - The domain's registrations, in whatever order `build*Registrations`
|
|
123
|
+
* returned them.
|
|
124
|
+
* @param rules - The domain's allowlist, applied identically to every registration.
|
|
125
|
+
* @returns A new array with the same length and order; the input array is not mutated.
|
|
126
|
+
* @complexity O(n) registrations wrapped once at build time; O(r) per failed call, none per successful one.
|
|
127
|
+
*/
|
|
47
128
|
export function withModelFacingRegistrationErrors(required) {
|
|
48
129
|
const { registrations, rules } = required;
|
|
49
130
|
return registrations.map((registration) => ({
|
|
@@ -60,6 +141,20 @@ export function withModelFacingRegistrationErrors(required) {
|
|
|
60
141
|
}
|
|
61
142
|
// Unknown classes, raw driver errors, and non-Error throws get fixed fallback text, never raw error
|
|
62
143
|
// content. The fallback itself must not be derived from err; first matching rule still wins.
|
|
144
|
+
/**
|
|
145
|
+
* The message a RETURNED failure result may carry for `err`: a listed class's message (or its fixed
|
|
146
|
+
* replacement), otherwise `fallback`.
|
|
147
|
+
*
|
|
148
|
+
* Unknown means redacted. An error class added tomorrow, a raw driver error, or a non-`Error` throw
|
|
149
|
+
* all get `fallback`, never their own text. Rules are evaluated in order; the first `instanceof`
|
|
150
|
+
* match wins.
|
|
151
|
+
*
|
|
152
|
+
* @param required.err - The caught value, of unknown type.
|
|
153
|
+
* @param required.rules - The allowlist.
|
|
154
|
+
* @param required.fallback - Fixed text for everything unlisted. Must not be built from `err`.
|
|
155
|
+
* @returns The message to publish.
|
|
156
|
+
* @complexity O(r) in the rule count.
|
|
157
|
+
*/
|
|
63
158
|
export function callerSafeErrorMessage(required) {
|
|
64
159
|
const { err, rules, fallback } = required;
|
|
65
160
|
for (const rule of rules) {
|
|
@@ -68,4 +163,33 @@ export function callerSafeErrorMessage(required) {
|
|
|
68
163
|
}
|
|
69
164
|
return fallback;
|
|
70
165
|
}
|
|
166
|
+
/** A driver/system error code worth logging (`SQLITE_READONLY`, `ECONNREFUSED`, `ERR_INVALID_CHAR`) —
|
|
167
|
+
* a short constant token, never free text that could carry a value. */
|
|
168
|
+
const LOGGABLE_ERROR_CODE = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
169
|
+
/** `ClassName` or `ClassName(CODE)` for one thrown value; `typeof` for a non-`Error` throw. */
|
|
170
|
+
function describeOneErrorForLog(value) {
|
|
171
|
+
if (!(value instanceof Error))
|
|
172
|
+
return typeof value;
|
|
173
|
+
const className = value.constructor.name || "Error";
|
|
174
|
+
const code = value.code;
|
|
175
|
+
return typeof code === "string" && LOGGABLE_ERROR_CODE.test(code) ? `${className}(${code})` : className;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* What a server-side log line may say about an error whose message is NOT known to be safe: its class
|
|
179
|
+
* name, a constant-token `code`, and the same for its direct `cause`. Never the message, which can
|
|
180
|
+
* quote a secret — `JSON.parse` quotes its input, and a sealer is handed plaintext.
|
|
181
|
+
*
|
|
182
|
+
* Use it wherever the raw message could carry a credential. Where a class's message is designed for
|
|
183
|
+
* logs (`EgressRefusedError.message`), log that instead.
|
|
184
|
+
*
|
|
185
|
+
* @param required.err - The caught value.
|
|
186
|
+
* @returns e.g. `SqliteError(SQLITE_BUSY)`, or `TypeError cause=Error(ECONNREFUSED)`.
|
|
187
|
+
* @complexity O(1).
|
|
188
|
+
*/
|
|
189
|
+
export function describeErrorForLog(required, _optional = {}) {
|
|
190
|
+
const { err } = required;
|
|
191
|
+
const described = describeOneErrorForLog(err);
|
|
192
|
+
const cause = err instanceof Error ? err.cause : undefined;
|
|
193
|
+
return cause === undefined ? described : `${described} cause=${describeOneErrorForLog(cause)}`;
|
|
194
|
+
}
|
|
71
195
|
//# sourceMappingURL=model-facing-tool-errors.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Native RegExp.escape is available in Node 24 and modern browsers, but the supported
|
|
2
|
+
* TypeScript 5.8 libraries do not declare it yet. Declare the native contract without a
|
|
3
|
+
* polyfill or a second escaping implementation; hosts must supply the native capability. */
|
|
4
|
+
declare global {
|
|
5
|
+
interface RegExpConstructor {
|
|
6
|
+
escape(value: string): string;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=native-regexp.d.ts.map
|
|
@@ -2,6 +2,8 @@ export * from './time.js';
|
|
|
2
2
|
export * from './ids.js';
|
|
3
3
|
export * from './json.js';
|
|
4
4
|
export * from './http.js';
|
|
5
|
+
export { decodeSseFrames, decodeSseStream, parseSseRecord } from './sse.js';
|
|
6
|
+
export type { DecodedSseEvent, DecodedSseFrame, SseFramingOptions } from './sse.js';
|
|
5
7
|
export * from './logger.js';
|
|
6
8
|
export * from './result.js';
|
|
7
9
|
export * from './paths.js';
|
package/dist/primitives/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from './time.js';
|
|
|
2
2
|
export * from './ids.js';
|
|
3
3
|
export * from './json.js';
|
|
4
4
|
export * from './http.js';
|
|
5
|
+
export { decodeSseFrames, decodeSseStream, parseSseRecord } from './sse.js';
|
|
5
6
|
export * from './logger.js';
|
|
6
7
|
export * from './result.js';
|
|
7
8
|
export * from './paths.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module primitives/sse
|
|
3
|
+
*
|
|
4
|
+
* Minimal, tolerant Server-Sent-Events *frame decoder* for an INBOUND
|
|
5
|
+
* provider stream — the response body a call to the Anthropic Messages API
|
|
6
|
+
* or the OpenAI Chat Completions API sends back when `stream: true` is
|
|
7
|
+
* requested. This is deliberately not `@jini-ai/http-kit`'s `sse.ts`: that module
|
|
8
|
+
* is the OUTBOUND channel a route uses to push events to a browser client
|
|
9
|
+
* (bounded queue, backpressure, `Last-Event-ID` replay). This module has
|
|
10
|
+
* none of that — it only turns a raw byte/text stream into `{event, data}`
|
|
11
|
+
* frames per the SSE wire format (`text/event-stream`, RFC-ish: fields
|
|
12
|
+
* separated by `\n`, records separated by a blank line), so a provider-
|
|
13
|
+
* specific turn-runner (`anthropic-messages.ts`, `openai-chat.ts`) can
|
|
14
|
+
* `JSON.parse` each frame's `data` field without re-deriving line/record
|
|
15
|
+
* framing itself.
|
|
16
|
+
*
|
|
17
|
+
* Field parsing and framing are shared by providers and durable run consumers.
|
|
18
|
+
* The public leaf has no Node imports; callers keep their own JSON, EOF and
|
|
19
|
+
* reader-ownership policies rather than re-deriving line/record framing.
|
|
20
|
+
*/
|
|
21
|
+
/** Provider-facing record. Metadata remains absent from this compatibility contract. */
|
|
22
|
+
export interface DecodedSseEvent {
|
|
23
|
+
/** Null without event: (OpenAI Chat Completions implicitly sends plain messages). */
|
|
24
|
+
readonly event: string | null;
|
|
25
|
+
/** Multiple data: values joined with newlines, per the SSE spec. */
|
|
26
|
+
readonly data: string;
|
|
27
|
+
}
|
|
28
|
+
/** Raw field values, including explicit (possibly empty) IDs; IDs do not carry across records. */
|
|
29
|
+
export interface DecodedSseFrame extends DecodedSseEvent {
|
|
30
|
+
readonly id?: string;
|
|
31
|
+
readonly dataLines: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
export interface SseFramingOptions {
|
|
34
|
+
/** Providers flush EOF tails; durable run readers require a terminating blank line. */
|
|
35
|
+
readonly flushFinalFrame?: boolean;
|
|
36
|
+
/** Bounds all pending record text, including comments and ignored fields, in UTF-16 units. */
|
|
37
|
+
readonly maxBufferChars?: number;
|
|
38
|
+
}
|
|
39
|
+
/** Parse one delimiter-free record without JSON interpretation. O(text) time and space. */
|
|
40
|
+
export declare function parseSseRecord({ rawFrame }: {
|
|
41
|
+
rawFrame: string;
|
|
42
|
+
}, _options?: Record<string, never>): DecodedSseFrame;
|
|
43
|
+
/**
|
|
44
|
+
* Browser-safe inbound framing for byte/text iterables; accepts LF and CRLF, including split
|
|
45
|
+
* UTF-8 and CRLF chunks. Reader ownership stays with the source iterator. Optional bounds throw
|
|
46
|
+
* RangeError before buffered text exceeds the limit. Early return closes the source iterator.
|
|
47
|
+
* @complexity O(text + repeated copying of an unfinished line); O(pending record + chunk) space.
|
|
48
|
+
*/
|
|
49
|
+
export declare function decodeSseFrames({ source }: {
|
|
50
|
+
source: AsyncIterable<Uint8Array | string>;
|
|
51
|
+
}, options?: SseFramingOptions): AsyncGenerator<DecodedSseFrame>;
|
|
52
|
+
/**
|
|
53
|
+
* Provider compatibility entry: drops reconnect metadata (meaningless for a single proxied
|
|
54
|
+
* request/response pair) and flushes unterminated EOF records. Keeps the published frame shape.
|
|
55
|
+
* @complexity Inherits decodeSseFrames' streaming cost and buffer size.
|
|
56
|
+
*/
|
|
57
|
+
export declare function decodeSseStream({ source }: {
|
|
58
|
+
source: AsyncIterable<Uint8Array | string>;
|
|
59
|
+
}, _options?: Record<string, never>): AsyncGenerator<DecodedSseEvent>;
|
|
60
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module primitives/sse
|
|
3
|
+
*
|
|
4
|
+
* Minimal, tolerant Server-Sent-Events *frame decoder* for an INBOUND
|
|
5
|
+
* provider stream — the response body a call to the Anthropic Messages API
|
|
6
|
+
* or the OpenAI Chat Completions API sends back when `stream: true` is
|
|
7
|
+
* requested. This is deliberately not `@jini-ai/http-kit`'s `sse.ts`: that module
|
|
8
|
+
* is the OUTBOUND channel a route uses to push events to a browser client
|
|
9
|
+
* (bounded queue, backpressure, `Last-Event-ID` replay). This module has
|
|
10
|
+
* none of that — it only turns a raw byte/text stream into `{event, data}`
|
|
11
|
+
* frames per the SSE wire format (`text/event-stream`, RFC-ish: fields
|
|
12
|
+
* separated by `\n`, records separated by a blank line), so a provider-
|
|
13
|
+
* specific turn-runner (`anthropic-messages.ts`, `openai-chat.ts`) can
|
|
14
|
+
* `JSON.parse` each frame's `data` field without re-deriving line/record
|
|
15
|
+
* framing itself.
|
|
16
|
+
*
|
|
17
|
+
* Field parsing and framing are shared by providers and durable run consumers.
|
|
18
|
+
* The public leaf has no Node imports; callers keep their own JSON, EOF and
|
|
19
|
+
* reader-ownership policies rather than re-deriving line/record framing.
|
|
20
|
+
*/
|
|
21
|
+
/** Owns field parsing for both raw records and incremental streams; callers own interpretation. */
|
|
22
|
+
function createRecordDecoder() {
|
|
23
|
+
let event = null;
|
|
24
|
+
let id;
|
|
25
|
+
let dataLines = [];
|
|
26
|
+
return {
|
|
27
|
+
line(raw) {
|
|
28
|
+
const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw;
|
|
29
|
+
if (line.startsWith(':'))
|
|
30
|
+
return; // comment line, per spec
|
|
31
|
+
const colon = line.indexOf(':');
|
|
32
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
33
|
+
let value = colon === -1 ? '' : line.slice(colon + 1);
|
|
34
|
+
if (value.startsWith(' '))
|
|
35
|
+
value = value.slice(1);
|
|
36
|
+
if (field === 'event')
|
|
37
|
+
event = value;
|
|
38
|
+
else if (field === 'data')
|
|
39
|
+
dataLines.push(value);
|
|
40
|
+
else if (field === 'id')
|
|
41
|
+
id = value;
|
|
42
|
+
// retry/unknown fields do not belong to per-record interpretation.
|
|
43
|
+
},
|
|
44
|
+
take() {
|
|
45
|
+
const frame = { event, data: dataLines.join('\n'), dataLines, ...(id !== undefined ? { id } : {}) };
|
|
46
|
+
event = null;
|
|
47
|
+
id = undefined;
|
|
48
|
+
dataLines = [];
|
|
49
|
+
return frame;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Parse one delimiter-free record without JSON interpretation. O(text) time and space. */
|
|
54
|
+
export function parseSseRecord({ rawFrame }, _options = {}) {
|
|
55
|
+
const record = createRecordDecoder();
|
|
56
|
+
for (const line of rawFrame.split('\n'))
|
|
57
|
+
record.line(line);
|
|
58
|
+
return record.take();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Browser-safe inbound framing for byte/text iterables; accepts LF and CRLF, including split
|
|
62
|
+
* UTF-8 and CRLF chunks. Reader ownership stays with the source iterator. Optional bounds throw
|
|
63
|
+
* RangeError before buffered text exceeds the limit. Early return closes the source iterator.
|
|
64
|
+
* @complexity O(text + repeated copying of an unfinished line); O(pending record + chunk) space.
|
|
65
|
+
*/
|
|
66
|
+
export async function* decodeSseFrames({ source }, options = {}) {
|
|
67
|
+
const decoder = new TextDecoder();
|
|
68
|
+
const record = createRecordDecoder();
|
|
69
|
+
let buffer = '';
|
|
70
|
+
let scanFrom = 0;
|
|
71
|
+
let recordChars = 0;
|
|
72
|
+
function* drainCompleteLines() {
|
|
73
|
+
if (options.maxBufferChars !== undefined && recordChars + buffer.length > options.maxBufferChars) {
|
|
74
|
+
throw new RangeError(`SSE stream exceeded the ${options.maxBufferChars}-character unterminated-frame buffer limit`);
|
|
75
|
+
}
|
|
76
|
+
let newlineIndex;
|
|
77
|
+
while ((newlineIndex = buffer.indexOf('\n', scanFrom)) !== -1) {
|
|
78
|
+
const line = buffer.slice(0, newlineIndex);
|
|
79
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
80
|
+
scanFrom = 0;
|
|
81
|
+
if (line === '' || line === '\r') {
|
|
82
|
+
const completed = record.take();
|
|
83
|
+
recordChars = 0;
|
|
84
|
+
if (completed.event !== null || completed.dataLines.length > 0 || completed.id !== undefined)
|
|
85
|
+
yield completed;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
recordChars += newlineIndex + 1;
|
|
89
|
+
record.line(line);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
scanFrom = buffer.length;
|
|
93
|
+
}
|
|
94
|
+
for await (const chunk of source) {
|
|
95
|
+
buffer += typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
|
|
96
|
+
yield* drainCompleteLines();
|
|
97
|
+
}
|
|
98
|
+
buffer += decoder.decode();
|
|
99
|
+
yield* drainCompleteLines();
|
|
100
|
+
if (options.flushFinalFrame === false)
|
|
101
|
+
return;
|
|
102
|
+
// Flush whatever is left: either a trailing unterminated line (no final
|
|
103
|
+
// `\n` at all), or a fully-parsed-but-not-blank-line-terminated record.
|
|
104
|
+
// A provider closing immediately after its last byte must not silently lose it.
|
|
105
|
+
if (buffer.length > 0)
|
|
106
|
+
record.line(buffer);
|
|
107
|
+
const completed = record.take();
|
|
108
|
+
if (completed.event !== null || completed.dataLines.length > 0 || completed.id !== undefined)
|
|
109
|
+
yield completed;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Provider compatibility entry: drops reconnect metadata (meaningless for a single proxied
|
|
113
|
+
* request/response pair) and flushes unterminated EOF records. Keeps the published frame shape.
|
|
114
|
+
* @complexity Inherits decodeSseFrames' streaming cost and buffer size.
|
|
115
|
+
*/
|
|
116
|
+
export async function* decodeSseStream({ source }, _options = {}) {
|
|
117
|
+
for await (const frame of decodeSseFrames({ source })) {
|
|
118
|
+
if (frame.event !== null || frame.dataLines.length > 0)
|
|
119
|
+
yield { event: frame.event, data: frame.data };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=sse.js.map
|
package/dist/redact.js
CHANGED
|
@@ -102,11 +102,12 @@ const PATTERNS = [
|
|
|
102
102
|
// Phone numbers. Tight US-leaning shape; a global PII detector would
|
|
103
103
|
// need a real lib. We keep this so US-based test prompts ('call me at
|
|
104
104
|
// (415) 555-...') don't ship. Note: no leading \b — '(' isn't a word
|
|
105
|
-
// char, so a starting boundary would refuse to match `(415)`.
|
|
106
|
-
//
|
|
105
|
+
// char, so a starting boundary would refuse to match `(415)`. Reject
|
|
106
|
+
// adjacent word characters or hyphens so digit segments of UUIDs,
|
|
107
|
+
// hashes and identifiers stay readable; separators within phones still match.
|
|
107
108
|
{
|
|
108
109
|
name: 'phone',
|
|
109
|
-
regex: /(
|
|
110
|
+
regex: /(?<![\w-])(?:\+?\d{1,3}[\s.-]?)?(?:\(\d{3}\)|\d{3})[\s.-]?\d{3}[\s.-]?\d{4}(?![\w-])/g,
|
|
110
111
|
},
|
|
111
112
|
];
|
|
112
113
|
// Credit card sweep is special: a naive 13-19 digit run matches a lot of
|
|
@@ -139,6 +139,17 @@ export declare function isRecord(required: {
|
|
|
139
139
|
}, _optional?: Record<string, never>): required is {
|
|
140
140
|
value: Record<string, unknown>;
|
|
141
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* Accepts non-null, non-array objects, including class instances and null-prototype records.
|
|
144
|
+
* This is a shape check, not plain-object validation or sanitization. Keep `isRecord`'s
|
|
145
|
+
* broader contract: existing tool readers deliberately continue to accept arrays.
|
|
146
|
+
* @param required.value - The untrusted value whose object shape is checked.
|
|
147
|
+
*/
|
|
148
|
+
export declare function isNonArrayRecord(required: {
|
|
149
|
+
value: unknown;
|
|
150
|
+
}, _optional?: Record<string, never>): required is {
|
|
151
|
+
value: Record<string, unknown>;
|
|
152
|
+
};
|
|
142
153
|
/** Narrows `ctx.input` to a record, refusing anything else. The first line of most handlers. */
|
|
143
154
|
export declare function requireInputRecord(required: {
|
|
144
155
|
input: unknown;
|
|
@@ -327,6 +338,8 @@ export declare function buildDomainRegistrations(spec: {
|
|
|
327
338
|
catalog: ReadonlyMap<string, AgentToolDefinition>;
|
|
328
339
|
handlers: Record<string, ToolHandler>;
|
|
329
340
|
derivedRisk: DerivedRiskByToolId;
|
|
341
|
+
/** Host vocabulary and reviewed action policy; risk remains independently checked above. */
|
|
342
|
+
metadata?: import('./tool-metadata.js').ToolMetadataById;
|
|
330
343
|
}, optional?: {
|
|
331
344
|
unwiredToolIds?: ReadonlySet<string>;
|
|
332
345
|
}): ToolRegistration[];
|
package/dist/registration-kit.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* describes, where a reviewer reading the handler can see it.
|
|
26
26
|
*/
|
|
27
27
|
import { ToolInputError } from "./tool-registry.js";
|
|
28
|
+
import { toolMetadataFor } from "./tool-metadata.js";
|
|
28
29
|
/**
|
|
29
30
|
* Audit provenance stamped onto every revision row a tool handler writes.
|
|
30
31
|
*
|
|
@@ -103,6 +104,15 @@ export function isRecord(required, _optional = {}) {
|
|
|
103
104
|
const { value } = required;
|
|
104
105
|
return typeof value === "object" && value !== null;
|
|
105
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Accepts non-null, non-array objects, including class instances and null-prototype records.
|
|
109
|
+
* This is a shape check, not plain-object validation or sanitization. Keep `isRecord`'s
|
|
110
|
+
* broader contract: existing tool readers deliberately continue to accept arrays.
|
|
111
|
+
* @param required.value - The untrusted value whose object shape is checked.
|
|
112
|
+
*/
|
|
113
|
+
export function isNonArrayRecord(required, _optional = {}) {
|
|
114
|
+
return isRecord(required) && !Array.isArray(required.value);
|
|
115
|
+
}
|
|
106
116
|
/** Narrows `ctx.input` to a record, refusing anything else. The first line of most handlers. */
|
|
107
117
|
export function requireInputRecord(required, _optional = {}) {
|
|
108
118
|
const { input } = required;
|
|
@@ -371,11 +381,13 @@ export function buildDomainRegistrations(spec, optional = {}) {
|
|
|
371
381
|
if (!catalogEntry.inputSchema) {
|
|
372
382
|
throw new Error(`tool-registrations: wired tool '${id}' publishes no inputSchema — add one to its entry in ${spec.catalogModule} so the model gets a contract, or leave the tool unwired`);
|
|
373
383
|
}
|
|
384
|
+
const metadata = catalogEntry.metadata ?? toolMetadataFor({ toolId: id, metadata: spec.metadata });
|
|
374
385
|
registrations.push({
|
|
375
386
|
descriptor: {
|
|
376
387
|
id,
|
|
377
388
|
description: catalogEntry.description,
|
|
378
389
|
inputSchema: catalogEntry.inputSchema,
|
|
390
|
+
...(metadata ? { metadata } : {}),
|
|
379
391
|
// The ONE place a domain's declared risk becomes the descriptor flag every read-only gate
|
|
380
392
|
// reads (`@jini-ai/core`'s `isReadOnlyTool`). Placed here rather than in each domain's
|
|
381
393
|
// `tool-registrations.ts` for the same reason the drift tripwire is: twelve copies of this
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AgentToolDefinition } from "../agent-tools.js";
|
|
2
|
+
/**
|
|
3
|
+
* The Settings domain's fixed agent-tool catalog — three reads, three bounded writes, and two bulk/schema writes documented but never
|
|
4
|
+
* wired. See file header.
|
|
5
|
+
*
|
|
6
|
+
* @complexity O(1) — a fixed, statically-defined list.
|
|
7
|
+
* @overallScore 100
|
|
8
|
+
*/
|
|
9
|
+
export declare function getSettingsAgentToolCatalog(): AgentToolDefinition[];
|
|
10
|
+
//# sourceMappingURL=agent-tools.d.ts.map
|