@jini-ai/core 0.4.8 → 0.6.10
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 +23 -2
- package/README.md +5 -0
- package/dist/agent-tools.d.ts +1 -1
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +87 -22
- package/dist/batch-tool-inputs.d.ts +147 -0
- package/dist/batch-tool-inputs.js +257 -0
- package/dist/feature-flags.d.ts +21 -0
- package/dist/feature-flags.js +17 -0
- package/dist/fetch/index.d.ts +2 -1
- package/dist/fetch/index.js +1 -0
- package/dist/fetch/loader-read.d.ts +16 -0
- package/dist/fetch/loader-read.js +22 -0
- package/dist/fetch/types.d.ts +12 -2
- package/dist/gated-mutations/gateway.js +32 -27
- package/dist/index.d.ts +8 -1
- package/dist/index.js +7 -1
- package/dist/object-store.d.ts +115 -0
- package/dist/object-store.js +70 -0
- package/dist/origin-validation.js +64 -57
- package/dist/primitives/http.d.ts +2 -2
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/paths.js +17 -10
- package/dist/primitives/tasks.d.ts +10 -0
- package/dist/primitives/tasks.js +8 -0
- package/dist/provider-catalog.d.ts +76 -0
- package/dist/provider-catalog.js +6 -0
- package/dist/provider-registry.d.ts +144 -0
- package/dist/provider-registry.js +138 -0
- package/dist/records.d.ts +24 -0
- package/dist/records.js +25 -0
- package/dist/redact.d.ts +32 -0
- package/dist/redact.js +91 -34
- package/dist/registration-kit.d.ts +7 -4
- package/dist/registration-kit.js +81 -62
- package/dist/settings/agent-value-write-tools.d.ts +19 -0
- package/dist/settings/agent-value-write-tools.js +131 -70
- package/dist/settings/ensure-definitions.d.ts +4 -2
- package/dist/settings/ensure-definitions.js +79 -63
- package/dist/settings/errors.d.ts +13 -0
- package/dist/settings/errors.js +15 -0
- package/dist/settings/express/cms-adapter.d.ts +2 -2
- package/dist/settings/express/cms-adapter.js +34 -22
- package/dist/settings/express/http-request.d.ts +33 -0
- package/dist/settings/express/http-request.js +49 -0
- package/dist/settings/express/http.d.ts +2 -2
- package/dist/settings/express/http.js +16 -39
- package/dist/settings/express/stream.d.ts +2 -2
- package/dist/settings/express/stream.js +49 -27
- package/dist/settings/index.d.ts +2 -2
- package/dist/settings/index.js +1 -1
- package/dist/settings/ports.d.ts +7 -1
- package/dist/settings/repo.memory.js +7 -4
- package/dist/settings/settings.js +54 -60
- package/dist/settings/tool-registrations.d.ts +2 -3
- package/dist/settings/tool-registrations.js +64 -39
- package/dist/settings/write-service.d.ts +42 -4
- package/dist/settings/write-service.js +72 -5
- package/dist/target-scope.d.ts +2 -0
- package/dist/target-scope.js +18 -2
- package/dist/text/escape.js +2 -2
- package/dist/text/sanitize-untrusted-text.d.ts +5 -0
- package/dist/text/sanitize-untrusted-text.js +55 -35
- package/dist/tool-exposure.d.ts +79 -0
- package/dist/tool-exposure.js +163 -0
- package/dist/tool-input-errors.d.ts +23 -0
- package/dist/tool-input-errors.js +24 -0
- package/dist/tool-input-normalization.d.ts +37 -0
- package/dist/tool-input-normalization.js +188 -0
- package/dist/tool-metadata-types.d.ts +26 -0
- package/dist/tool-metadata-types.js +2 -0
- package/dist/tool-metadata.d.ts +2 -23
- package/dist/tool-registry.d.ts +38 -22
- package/dist/tool-registry.js +35 -20
- package/package.json +20 -2
- package/plugin-directory.js +72 -33
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { adaptLegacyAuthorize } from "../tool-permissions.js";
|
|
2
|
-
import { buildSettingsValueWriteHandler } from "./agent-value-write-tools.js";
|
|
3
|
-
import {
|
|
2
|
+
import { agentWriteRules, buildSettingsValueWriteHandler, toWriteDeps } from "./agent-value-write-tools.js";
|
|
3
|
+
import { findAgentWriteRule } from "./agent-write-denylist.js";
|
|
4
4
|
import { buildDomainRegistrations, indexCatalogById, optionalString, requireInputRecord, requireNoInput, requireString, withSchemaOnRejection } from "../registration-kit.js";
|
|
5
|
+
import { ToolInputError } from "../tool-registry.js";
|
|
5
6
|
import { requireToolPermission } from "../tool-permissions.js";
|
|
6
7
|
import { getSettingsAgentToolCatalog } from "./agent-tools.js";
|
|
7
8
|
import { AGENT_PREFERENCE_WRITE_SCOPE, AGENT_WRITABLE_PREFERENCE_IDS, resolveAgentWritablePreference, } from "./agent-writable-preferences.js";
|
|
@@ -42,25 +43,6 @@ async function resolveOwnOrOtherPrincipalRead(routeDeps, required) {
|
|
|
42
43
|
function layerValueOf(record) {
|
|
43
44
|
return record && record.state === "set" ? record.valueJson : null;
|
|
44
45
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Builds `write-service.ts`'s own five-field dep shape from `RouteDeps`.
|
|
47
|
-
*
|
|
48
|
-
* Deliberately duplicates a host's own HTTP admin layer's equivalent field-mapping helper rather
|
|
49
|
-
* than importing it, for the reason this file's header already gives about
|
|
50
|
-
* `resolveOwnOrOtherPrincipalRead`: this is domain-owned wiring, and importing from a host's HTTP
|
|
51
|
-
* admin layer would invert the ports/adapters direction. It is a field mapping, not logic, so the
|
|
52
|
-
* two copies carry no behavioral drift risk — and `write-service.ts` is the single chokepoint
|
|
53
|
-
* either way, so a divergence here could only produce a missing dependency, which fails loudly.
|
|
54
|
-
*/
|
|
55
|
-
function toWriteDeps(routeDeps) {
|
|
56
|
-
return {
|
|
57
|
-
repo: routeDeps.settingsRepo,
|
|
58
|
-
clock: routeDeps.clock,
|
|
59
|
-
ids: routeDeps.idGen,
|
|
60
|
-
authorize: routeDeps.authorize,
|
|
61
|
-
principals: routeDeps.principalRepo,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
46
|
/**
|
|
65
47
|
* Errors a DIFFERENT input would fix, for `withSchemaOnRejection`'s decoration.
|
|
66
48
|
*
|
|
@@ -110,6 +92,49 @@ const UNWIRED_SETTINGS_TOOL_IDS = new Set([
|
|
|
110
92
|
// platform-wide.
|
|
111
93
|
"settings_register_definitions",
|
|
112
94
|
]);
|
|
95
|
+
/**
|
|
96
|
+
* One `settings_list_definitions` row. Only aliases need an extra lookup. Their target's
|
|
97
|
+
* schema/scope and BOTH coordinates' key policies must agree with the write handler;
|
|
98
|
+
* dangling/tombstoned targets fail closed. O(r) in policy rules plus at most one alias resolution.
|
|
99
|
+
*/
|
|
100
|
+
async function describeDefinitionForAgent(routeDeps, rules, def) {
|
|
101
|
+
const resolved = def.status === "alias"
|
|
102
|
+
? await resolveDefinitionRaw({ repo: routeDeps.settingsRepo }, { namespace: def.namespace, key: def.key, workspaceId: routeDeps.workspaceId })
|
|
103
|
+
: def;
|
|
104
|
+
const coordinates = resolved ? [def, resolved] : [def];
|
|
105
|
+
const matchesAny = (list) => coordinates.some((coordinate) => !!findAgentWriteRule(list, coordinate));
|
|
106
|
+
const denied = matchesAny(rules.denied);
|
|
107
|
+
const confirmationRequired = matchesAny(rules.confirmation);
|
|
108
|
+
const scopes = resolved?.scopes ?? def.scopes;
|
|
109
|
+
return { namespace: def.namespace, key: def.key, ownerKind: def.ownerKind, scopes, status: def.status, version: def.version,
|
|
110
|
+
schema: resolved?.schema ?? def.schema,
|
|
111
|
+
agentWritable: isAgentWritable({ resolved, denied, confirmationRequired, canConfirm: !!routeDeps.confirmWrite, scopes }),
|
|
112
|
+
confirmationRequired };
|
|
113
|
+
}
|
|
114
|
+
/** Mirrors the write handler's refusals: live target, not denied, confirmable when protected,
|
|
115
|
+
* and writable at workspace or user scope. O(1). */
|
|
116
|
+
function isAgentWritable(facts) {
|
|
117
|
+
if (!facts.resolved || facts.resolved.status === "tombstone" || facts.denied)
|
|
118
|
+
return false;
|
|
119
|
+
if (facts.confirmationRequired && !facts.canConfirm)
|
|
120
|
+
return false;
|
|
121
|
+
return (facts.scopes & (SCOPE_BIT.workspace | SCOPE_BIT.user)) !== 0;
|
|
122
|
+
}
|
|
123
|
+
/** Every split point of a dotted key that names a live (non-tombstoned) definition.
|
|
124
|
+
* Leading/trailing dots are never split points. O(k) resolver calls for k dots. */
|
|
125
|
+
async function findDottedKeySplits(routeDeps, dottedKey) {
|
|
126
|
+
const matches = [];
|
|
127
|
+
// Reuse the definition resolver, including workspace fencing and aliases; never split on a guess.
|
|
128
|
+
for (let index = 1; index < dottedKey.length - 1; index++) {
|
|
129
|
+
if (dottedKey[index] !== ".")
|
|
130
|
+
continue;
|
|
131
|
+
const candidate = { namespace: dottedKey.slice(0, index), key: dottedKey.slice(index + 1) };
|
|
132
|
+
const definition = await resolveDefinitionRaw({ repo: routeDeps.settingsRepo }, { ...candidate, workspaceId: routeDeps.workspaceId });
|
|
133
|
+
if (definition && definition.status !== "tombstone")
|
|
134
|
+
matches.push(candidate);
|
|
135
|
+
}
|
|
136
|
+
return matches;
|
|
137
|
+
}
|
|
113
138
|
export function buildSettingsRegistrations(routeDeps) {
|
|
114
139
|
const handlers = {
|
|
115
140
|
settings_set_value: buildSettingsValueWriteHandler(routeDeps, "settings_set_value"),
|
|
@@ -122,23 +147,8 @@ export function buildSettingsRegistrations(routeDeps) {
|
|
|
122
147
|
routeDeps.settingsRepo.listActiveDefinitions({ workspaceId: null }),
|
|
123
148
|
routeDeps.settingsRepo.listActiveDefinitions({ workspaceId: routeDeps.workspaceId }),
|
|
124
149
|
]);
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
// Only aliases need an extra lookup. Their target's schema/scope and BOTH coordinates'
|
|
128
|
-
// key policies must agree with the write handler; dangling/tombstoned targets fail closed.
|
|
129
|
-
const data = (await Promise.all([...platformDefs, ...siteDefs].map(async (def) => {
|
|
130
|
-
const resolved = def.status === "alias"
|
|
131
|
-
? await resolveDefinitionRaw({ repo: routeDeps.settingsRepo }, { namespace: def.namespace, key: def.key, workspaceId: routeDeps.workspaceId })
|
|
132
|
-
: def;
|
|
133
|
-
const coordinates = resolved ? [def, resolved] : [def];
|
|
134
|
-
const denied = coordinates.some((coordinate) => !!findAgentWriteRule(deniedRules, coordinate));
|
|
135
|
-
const confirmationRequired = coordinates.some((coordinate) => !!findAgentWriteRule(confirmationRules, coordinate));
|
|
136
|
-
const scopes = resolved?.scopes ?? def.scopes;
|
|
137
|
-
return { namespace: def.namespace, key: def.key, ownerKind: def.ownerKind, scopes, status: def.status, version: def.version,
|
|
138
|
-
schema: resolved?.schema ?? def.schema,
|
|
139
|
-
agentWritable: !!resolved && resolved.status !== "tombstone" && !denied && (!confirmationRequired || !!routeDeps.confirmWrite) && (scopes & (SCOPE_BIT.workspace | SCOPE_BIT.user)) !== 0,
|
|
140
|
-
confirmationRequired };
|
|
141
|
-
})))
|
|
150
|
+
const rules = agentWriteRules(routeDeps);
|
|
151
|
+
const data = (await Promise.all([...platformDefs, ...siteDefs].map((def) => describeDefinitionForAgent(routeDeps, rules, def))))
|
|
142
152
|
.sort((a, b) => a.namespace.localeCompare(b.namespace) || a.key.localeCompare(b.key));
|
|
143
153
|
return { data };
|
|
144
154
|
},
|
|
@@ -245,6 +255,21 @@ export function buildSettingsRegistrations(routeDeps) {
|
|
|
245
255
|
return { setting: settingId, value: stored, scope: AGENT_PREFERENCE_WRITE_SCOPE, revisionSeq };
|
|
246
256
|
},
|
|
247
257
|
};
|
|
248
|
-
return buildDomainRegistrations({ domain: "settings", catalogModule: "settings/agent-tools.ts", catalog: CATALOG_BY_ID, handlers, derivedRisk: settingsDerivedRisk
|
|
258
|
+
return buildDomainRegistrations({ domain: "settings", catalogModule: "settings/agent-tools.ts", catalog: CATALOG_BY_ID, handlers, derivedRisk: settingsDerivedRisk,
|
|
259
|
+
inputNormalizers: { settings_set_value: async ({ input }) => {
|
|
260
|
+
if (input.namespace !== undefined || typeof input.key !== "string" || !input.key.includes("."))
|
|
261
|
+
return { input, notes: [] };
|
|
262
|
+
if (input.key.length > 512)
|
|
263
|
+
throw new ToolInputError({ message: "settings_set_value: dotted key is too long. Send namespace and key separately." });
|
|
264
|
+
await routeDeps.settingsReady;
|
|
265
|
+
await routeDeps.settingsUiTabsReady;
|
|
266
|
+
const matches = await findDottedKeySplits(routeDeps, input.key);
|
|
267
|
+
if (matches.length !== 1)
|
|
268
|
+
throw new ToolInputError({ message: matches.length
|
|
269
|
+
? `settings_set_value: dotted key is ambiguous (${matches.map(match => `namespace=${match.namespace}, key=${match.key}`).join("; ")}). Send namespace and key explicitly.`
|
|
270
|
+
: "settings_set_value: no registered namespace/key pair matches this dotted key. Call settings_list_definitions to see valid keys; send namespace and key explicitly." });
|
|
271
|
+
return { input: { ...input, ...matches[0] }, notes: ["dotted key→namespace + key"] };
|
|
272
|
+
} },
|
|
273
|
+
}, { unwiredToolIds: UNWIRED_SETTINGS_TOOL_IDS });
|
|
249
274
|
}
|
|
250
275
|
//# sourceMappingURL=tool-registrations.js.map
|
|
@@ -110,8 +110,21 @@ export interface SetValueRequired {
|
|
|
110
110
|
requiredPermissionOverride?: string | undefined;
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
/**
|
|
114
|
-
export
|
|
113
|
+
/** Optional concurrency guard shared by `set` and `clear`. */
|
|
114
|
+
export interface SettingsValueWriteOptions {
|
|
115
|
+
/** Compare the addressed layer's stored seq, including cleared rows; 0 means no stored row.
|
|
116
|
+
* Omission preserves unconditional writes. Defaults, inherited values and definition revisions
|
|
117
|
+
* do not count. A mismatch throws SettingsRevisionConflictError and rolls back the write.
|
|
118
|
+
*/
|
|
119
|
+
readonly expectedRevisionSeq?: number | undefined;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Authorize -> validate -> value + revision in one transaction. An optional expectedRevisionSeq
|
|
123
|
+
* refuses a stale scoped value and rolls back its revision; omission keeps unconditional writes.
|
|
124
|
+
* @example await set({ deps, input }, { expectedRevisionSeq: snapshotSeq });
|
|
125
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
126
|
+
*/
|
|
127
|
+
export declare function set(required: SetValueRequired, optional?: SettingsValueWriteOptions): Promise<{
|
|
115
128
|
value: JsonValue;
|
|
116
129
|
revisionSeq: number;
|
|
117
130
|
}>;
|
|
@@ -135,8 +148,14 @@ export interface ClearValueRequired {
|
|
|
135
148
|
authWorkspaceId?: UUID | undefined;
|
|
136
149
|
};
|
|
137
150
|
}
|
|
138
|
-
/**
|
|
139
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Authorize -> cleared value + revision in one transaction; the optional concurrency guard has
|
|
153
|
+
* the same semantics as `set`. With skipTransaction, the enclosing caller must propagate errors
|
|
154
|
+
* so its transaction rolls back the appended revision on conflict.
|
|
155
|
+
* @example await clear({ deps, input }, { expectedRevisionSeq: snapshotSeq });
|
|
156
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
157
|
+
*/
|
|
158
|
+
export declare function clear(required: ClearValueRequired, optional?: SettingsValueWriteOptions): Promise<{
|
|
140
159
|
revisionSeq: number;
|
|
141
160
|
}>;
|
|
142
161
|
export interface ResetNamespaceRequired {
|
|
@@ -239,6 +258,25 @@ export declare function retypeDefinition(required: RetypeDefinitionRequired): Pr
|
|
|
239
258
|
settingId: string;
|
|
240
259
|
version: number;
|
|
241
260
|
}>;
|
|
261
|
+
/** Add source-declared scopes to a core definition without moving or rewriting existing values.
|
|
262
|
+
* Boot uses this when a workspace preference gains a personal override. Scope removal is a
|
|
263
|
+
* separate migration decision: this operation only adds bits and preserves definition identity.
|
|
264
|
+
* @throws DefinitionInvalidError for operator-owned definitions or invalid scope bits.
|
|
265
|
+
* @complexity O(1), one definition transaction and cache invalidation.
|
|
266
|
+
* @example await extendDefinitionScopes({ deps, input: { namespace, key, workspaceId: null,
|
|
267
|
+
* scopes: SCOPE_BIT.user, callerPrincipalId, authWorkspaceId } });
|
|
268
|
+
*/
|
|
269
|
+
export declare function extendDefinitionScopes({ deps, input }: {
|
|
270
|
+
deps: SettingsWriteServiceDeps;
|
|
271
|
+
input: {
|
|
272
|
+
namespace: string;
|
|
273
|
+
key: string;
|
|
274
|
+
workspaceId: UUID | null;
|
|
275
|
+
scopes: number;
|
|
276
|
+
callerPrincipalId: UUID;
|
|
277
|
+
authWorkspaceId: UUID;
|
|
278
|
+
};
|
|
279
|
+
}, _options?: {}): Promise<void>;
|
|
242
280
|
export interface ReconcileDefinitionDefaultRequired {
|
|
243
281
|
deps: SettingsWriteServiceDeps;
|
|
244
282
|
input: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nowIso as kernelNowIso } from "../primitives/index.js";
|
|
2
|
-
import { AliasDepthExceededError, DefinitionInvalidError, DefinitionNotFoundError, DefinitionTombstonedError, ForbiddenError, PrincipalNotFoundError, RenameRetypeConflictError, ScopeNotAllowedError, ValueValidationFailedError, } from "./errors.js";
|
|
2
|
+
import { AliasDepthExceededError, DefinitionInvalidError, DefinitionNotFoundError, DefinitionTombstonedError, ForbiddenError, PrincipalNotFoundError, RenameRetypeConflictError, ScopeNotAllowedError, SettingsRevisionConflictError, ValueValidationFailedError, } from "./errors.js";
|
|
3
3
|
import { invalidateDefinitionNamespaceCache, resolveDefinitionRaw, validateDefinitionInput, validateValueAgainstSchema, } from "./settings.js";
|
|
4
4
|
import { SCOPE_BIT } from "./types.js";
|
|
5
5
|
/**
|
|
@@ -148,8 +148,33 @@ async function assertTargetPrincipalInWorkspace(deps, input) {
|
|
|
148
148
|
throw new PrincipalNotFoundError(`principal '${input.principalId}' was not found in workspace '${input.workspaceId}'`, input.principalId, input.workspaceId);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
/**
|
|
152
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Check the addressed value after appendRevision has taken the ledger lock. On Postgres, a read
|
|
153
|
+
* before that lock can see the same old seq in two READ COMMITTED transactions and lose an update.
|
|
154
|
+
* The caller's enclosing transaction rolls the appended revision back on conflict.
|
|
155
|
+
* @complexity At most one scoped value read; no reads when the option is omitted.
|
|
156
|
+
*/
|
|
157
|
+
async function assertExpectedRevision(required, optional = {}) {
|
|
158
|
+
if (optional.expectedRevisionSeq === undefined)
|
|
159
|
+
return;
|
|
160
|
+
const { repo, settingId, input } = required;
|
|
161
|
+
const current = input.scope === "global"
|
|
162
|
+
? await repo.getGlobalValue(settingId)
|
|
163
|
+
: input.scope === "workspace"
|
|
164
|
+
? await repo.getWorkspaceValue({ workspaceId: input.workspaceId, settingId })
|
|
165
|
+
: await repo.getUserValue({ workspaceId: input.workspaceId, principalId: input.principalId ?? input.callerPrincipalId, settingId });
|
|
166
|
+
const actualRevisionSeq = current?.seq ?? 0;
|
|
167
|
+
if (actualRevisionSeq !== optional.expectedRevisionSeq) {
|
|
168
|
+
throw new SettingsRevisionConflictError({ expectedRevisionSeq: optional.expectedRevisionSeq, actualRevisionSeq });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Authorize -> validate -> value + revision in one transaction. An optional expectedRevisionSeq
|
|
173
|
+
* refuses a stale scoped value and rolls back its revision; omission keeps unconditional writes.
|
|
174
|
+
* @example await set({ deps, input }, { expectedRevisionSeq: snapshotSeq });
|
|
175
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
176
|
+
*/
|
|
177
|
+
export async function set(required, optional = {}) {
|
|
153
178
|
const { deps, input } = required;
|
|
154
179
|
const permission = input.requiredPermissionOverride ??
|
|
155
180
|
deriveRequiredPermission({
|
|
@@ -190,6 +215,7 @@ export async function set(required) {
|
|
|
190
215
|
changeSetId: null,
|
|
191
216
|
createdAt: now,
|
|
192
217
|
});
|
|
218
|
+
await assertExpectedRevision({ repo: deps.repo, settingId: definition.settingId, input }, optional);
|
|
193
219
|
const base = {
|
|
194
220
|
settingId: definition.settingId,
|
|
195
221
|
scope: input.scope,
|
|
@@ -222,8 +248,14 @@ export async function set(required) {
|
|
|
222
248
|
});
|
|
223
249
|
return result;
|
|
224
250
|
}
|
|
225
|
-
/**
|
|
226
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Authorize -> cleared value + revision in one transaction; the optional concurrency guard has
|
|
253
|
+
* the same semantics as `set`. With skipTransaction, the enclosing caller must propagate errors
|
|
254
|
+
* so its transaction rolls back the appended revision on conflict.
|
|
255
|
+
* @example await clear({ deps, input }, { expectedRevisionSeq: snapshotSeq });
|
|
256
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
257
|
+
*/
|
|
258
|
+
export async function clear(required, optional = {}) {
|
|
227
259
|
const { deps, input } = required;
|
|
228
260
|
if (!input.skipAuthorize) {
|
|
229
261
|
const permission = deriveRequiredPermission({
|
|
@@ -262,6 +294,7 @@ export async function clear(required) {
|
|
|
262
294
|
changeSetId: null,
|
|
263
295
|
createdAt: now,
|
|
264
296
|
});
|
|
297
|
+
await assertExpectedRevision({ repo: deps.repo, settingId: definition.settingId, input }, optional);
|
|
265
298
|
const base = {
|
|
266
299
|
settingId: definition.settingId,
|
|
267
300
|
scope: input.scope,
|
|
@@ -416,6 +449,11 @@ export async function renameDefinition(required) {
|
|
|
416
449
|
const { deps, input } = required;
|
|
417
450
|
await authorizeDefinitionsManage(deps, input.callerPrincipalId, input.authWorkspaceId);
|
|
418
451
|
const current = await resolveActiveDefinitionOrThrow(deps, input, "rename");
|
|
452
|
+
// A dispatched `rename` item that omits its target arrives here as `""` (see definitions-dispatch);
|
|
453
|
+
// without this guard the definition was silently moved to the empty name `.`.
|
|
454
|
+
if (!input.newNamespace || !input.newKey) {
|
|
455
|
+
throw new DefinitionInvalidError("rename requires a non-empty newNamespace and newKey");
|
|
456
|
+
}
|
|
419
457
|
if (input.namespace === input.newNamespace && input.key === input.newKey) {
|
|
420
458
|
throw new DefinitionInvalidError("rename requires newNamespace/newKey to differ from the current name");
|
|
421
459
|
}
|
|
@@ -564,6 +602,35 @@ export async function retypeDefinition(required) {
|
|
|
564
602
|
invalidateDefinitionNamespaceCache(deps.repo, input.namespace);
|
|
565
603
|
return result;
|
|
566
604
|
}
|
|
605
|
+
/** Add source-declared scopes to a core definition without moving or rewriting existing values.
|
|
606
|
+
* Boot uses this when a workspace preference gains a personal override. Scope removal is a
|
|
607
|
+
* separate migration decision: this operation only adds bits and preserves definition identity.
|
|
608
|
+
* @throws DefinitionInvalidError for operator-owned definitions or invalid scope bits.
|
|
609
|
+
* @complexity O(1), one definition transaction and cache invalidation.
|
|
610
|
+
* @example await extendDefinitionScopes({ deps, input: { namespace, key, workspaceId: null,
|
|
611
|
+
* scopes: SCOPE_BIT.user, callerPrincipalId, authWorkspaceId } });
|
|
612
|
+
*/
|
|
613
|
+
export async function extendDefinitionScopes({ deps, input }, _options = {}) {
|
|
614
|
+
await authorizeDefinitionsManage(deps, input.callerPrincipalId, input.authWorkspaceId);
|
|
615
|
+
const current = await resolveActiveDefinitionOrThrow(deps, input, "extend the scopes of");
|
|
616
|
+
const validBits = SCOPE_BIT.global | SCOPE_BIT.workspace | SCOPE_BIT.user;
|
|
617
|
+
if (current.ownerKind !== "core" || !Number.isInteger(input.scopes) || input.scopes <= 0 || input.scopes > validBits) {
|
|
618
|
+
throw new DefinitionInvalidError("Only core definitions can gain valid source-declared scope bits");
|
|
619
|
+
}
|
|
620
|
+
const scopes = current.scopes | input.scopes;
|
|
621
|
+
if (scopes === current.scopes)
|
|
622
|
+
return;
|
|
623
|
+
await deps.repo.transaction(async () => {
|
|
624
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
625
|
+
await deps.repo.saveDefinition({ ...current, scopes, updatedAt: now });
|
|
626
|
+
// Re-registration of metadata, not a value/schema change; retain the existing revision vocabulary.
|
|
627
|
+
await deps.repo.appendRevision({ entityKind: "definition", settingId: current.settingId,
|
|
628
|
+
scope: null, workspaceId: current.workspaceId, principalId: null, op: "register",
|
|
629
|
+
beforeJson: { scopes: current.scopes }, afterJson: { scopes }, defVersion: current.version,
|
|
630
|
+
actor: input.callerPrincipalId, originPluginId: null, changeSetId: null, createdAt: now });
|
|
631
|
+
});
|
|
632
|
+
invalidateDefinitionNamespaceCache(deps.repo, input.namespace);
|
|
633
|
+
}
|
|
567
634
|
/**
|
|
568
635
|
* Reconciles a stored `default_json` that has drifted from the default its
|
|
569
636
|
* source declares. Idempotent and a no-op when they already agree.
|
package/dist/target-scope.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type TargetResolution = 'current' | {
|
|
|
21
21
|
* @param required.isCurrentTarget - Check whether an unscoped tool names the current target.
|
|
22
22
|
* @param optional - Target input name, schema description, refusal text and a narrowing scope predicate.
|
|
23
23
|
* @returns A new array in the same order, preserving tools that already own the target input.
|
|
24
|
+
* Unscoped targets are also checked by normalizeInput, before policy or approval planning;
|
|
25
|
+
* the handler retains its check for direct handler callers.
|
|
24
26
|
* @throws ToolInputError from a wrapped handler for a non-string target or a refused foreign target.
|
|
25
27
|
* Host port errors propagate unchanged from the wrapped handler.
|
|
26
28
|
* @complexity O(n) over registrations, plus copying supported schemas' top-level properties.
|
package/dist/target-scope.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isNonArrayRecord } from './records.js';
|
|
2
|
-
import { isReadOnlyTool
|
|
2
|
+
import { isReadOnlyTool } from './tool-registry.js';
|
|
3
|
+
import { ToolInputError } from './tool-input-errors.js';
|
|
3
4
|
/** Narrow the shared record predicate to ordinary or null-prototype objects, excluding instances. */
|
|
4
5
|
function isPlainObject(value) {
|
|
5
6
|
if (!isNonArrayRecord({ value }))
|
|
@@ -25,6 +26,8 @@ function withTargetParam(schema, param, description) {
|
|
|
25
26
|
* @param required.isCurrentTarget - Check whether an unscoped tool names the current target.
|
|
26
27
|
* @param optional - Target input name, schema description, refusal text and a narrowing scope predicate.
|
|
27
28
|
* @returns A new array in the same order, preserving tools that already own the target input.
|
|
29
|
+
* Unscoped targets are also checked by normalizeInput, before policy or approval planning;
|
|
30
|
+
* the handler retains its check for direct handler callers.
|
|
28
31
|
* @throws ToolInputError from a wrapped handler for a non-string target or a refused foreign target.
|
|
29
32
|
* Host port errors propagate unchanged from the wrapped handler.
|
|
30
33
|
* @complexity O(n) over registrations, plus copying supported schemas' top-level properties.
|
|
@@ -39,10 +42,23 @@ export function applyTargetScope(required, optional = {}) {
|
|
|
39
42
|
if (isNonArrayRecord(properties) && Object.hasOwn(properties.value, param))
|
|
40
43
|
return registration;
|
|
41
44
|
const scoped = isReadOnlyTool({ descriptor }) && (optional.scoped?.({ registration }) ?? true);
|
|
42
|
-
const inputSchema =
|
|
45
|
+
const inputSchema = withTargetParam(descriptor.inputSchema, param, describeParam);
|
|
43
46
|
return {
|
|
44
47
|
...registration,
|
|
45
48
|
descriptor: inputSchema === descriptor.inputSchema ? descriptor : { ...descriptor, inputSchema },
|
|
49
|
+
normalizeInput: async ({ input }, options) => {
|
|
50
|
+
const normalized = registration.normalizeInput ? await registration.normalizeInput({ input }, options) : { input, notes: [] };
|
|
51
|
+
// Target refusal precedes policy and confirmation, including each merged plan item.
|
|
52
|
+
// Keep the key intact: downstream gates must authorize exactly this target.
|
|
53
|
+
if (!scoped && isPlainObject(normalized.input) && Object.hasOwn(normalized.input, param)) {
|
|
54
|
+
const target = normalized.input[param];
|
|
55
|
+
if (typeof target !== 'string')
|
|
56
|
+
throw new ToolInputError({ message: `${param} must be a string` });
|
|
57
|
+
if (!await required.isCurrentTarget({ target }))
|
|
58
|
+
throw new ToolInputError({ message: refuseMessage });
|
|
59
|
+
}
|
|
60
|
+
return normalized;
|
|
61
|
+
},
|
|
46
62
|
handler: async (ctx, options) => {
|
|
47
63
|
if (!isPlainObject(ctx.input) || !Object.hasOwn(ctx.input, param))
|
|
48
64
|
return registration.handler(ctx, options);
|
package/dist/text/escape.js
CHANGED
|
@@ -13,11 +13,11 @@ const XML_ENTITIES = { ...HTML_ENTITIES, "'": "'" };
|
|
|
13
13
|
const SPECIAL = /[&<>"']/g;
|
|
14
14
|
/** Escapes `& < > " '` for HTML text or a quoted attribute value. @complexity O(text length). */
|
|
15
15
|
export function escapeHtml({ text }, _optional = {}) {
|
|
16
|
-
return text.replace(SPECIAL, (char) => HTML_ENTITIES[char]
|
|
16
|
+
return text.replace(SPECIAL, (char) => HTML_ENTITIES[char]);
|
|
17
17
|
}
|
|
18
18
|
/** {@link escapeHtml} with the XML named entity `'` for the apostrophe (sitemap and feed XML).
|
|
19
19
|
* @complexity O(text length). */
|
|
20
20
|
export function escapeXml({ text }, _optional = {}) {
|
|
21
|
-
return text.replace(SPECIAL, (char) => XML_ENTITIES[char]
|
|
21
|
+
return text.replace(SPECIAL, (char) => XML_ENTITIES[char]);
|
|
22
22
|
}
|
|
23
23
|
//# sourceMappingURL=escape.js.map
|
|
@@ -8,6 +8,9 @@ export declare function stripControlSequences({ text }: {
|
|
|
8
8
|
text: string;
|
|
9
9
|
}): string;
|
|
10
10
|
export interface SanitizeTextOptions {
|
|
11
|
+
/** Exact IDs from trusted registration metadata, never inferred from error text or arguments.
|
|
12
|
+
* Credential rules still run first; prefixes and glued suffixes are never exempted. */
|
|
13
|
+
diagnosticToolIds?: readonly string[];
|
|
11
14
|
/** Maximum output length; longer input is truncated with a trailing marker. Defaults to 500. */
|
|
12
15
|
maxLength?: number;
|
|
13
16
|
}
|
|
@@ -15,6 +18,8 @@ export interface SanitizeTextOptions {
|
|
|
15
18
|
* Strip terminal control sequences, redact anything that looks like a secret, and cap the
|
|
16
19
|
* length of `text` — the one function every boundary that surfaces daemon/network-derived text
|
|
17
20
|
* to stderr/stdout should route through first.
|
|
21
|
+
* Trusted diagnosticToolIds exempt complete IDs (including dots) only, after credential masking.
|
|
22
|
+
* @complexity O(n + t) local time/space for n text bytes and t trusted ID bytes.
|
|
18
23
|
*/
|
|
19
24
|
export declare function sanitizeUntrustedText({ text }: {
|
|
20
25
|
text: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LABELED_SECRET_RE, OPAQUE_TOKEN_RE, NON_SECRET_TOKEN_RE } from "../redact.js";
|
|
1
|
+
import { redactSecrets, LABELED_SECRET_RE, OPAQUE_TOKEN_RE, NON_SECRET_TOKEN_RE } from "../redact.js";
|
|
2
2
|
/**
|
|
3
3
|
* @module redact
|
|
4
4
|
*
|
|
@@ -39,6 +39,38 @@ function isOtherControlCode(code) {
|
|
|
39
39
|
return true; // C1 controls
|
|
40
40
|
return false;
|
|
41
41
|
}
|
|
42
|
+
/** True when position `j` starts an OSC terminator: BEL, or ST (`ESC \\`). */
|
|
43
|
+
function isOscTerminatorAt(text, j) {
|
|
44
|
+
const code = text.charCodeAt(j);
|
|
45
|
+
return code === BEL || (code === ESC && text.charCodeAt(j + 1) === BACKSLASH);
|
|
46
|
+
}
|
|
47
|
+
/** Index of a CSI's final byte at or after `from`, or `text.length` when the CSI is truncated. */
|
|
48
|
+
function csiEnd(text, from) {
|
|
49
|
+
let j = from;
|
|
50
|
+
while (j < text.length && !isCsiFinalByte(text.charCodeAt(j)))
|
|
51
|
+
j++;
|
|
52
|
+
return j;
|
|
53
|
+
}
|
|
54
|
+
/** Index of the last code unit of an OSC body starting at `from`: the BEL, the `\\` of ST, or the
|
|
55
|
+
* final code unit when the OSC is never terminated. */
|
|
56
|
+
function oscEnd(text, from) {
|
|
57
|
+
let j = from;
|
|
58
|
+
while (j < text.length && !isOscTerminatorAt(text, j))
|
|
59
|
+
j++;
|
|
60
|
+
// charCodeAt past the end is NaN, so a truncated OSC never matches BEL here.
|
|
61
|
+
if (text.charCodeAt(j) === BEL)
|
|
62
|
+
return j;
|
|
63
|
+
return Math.min(j + 1, text.length - 1);
|
|
64
|
+
}
|
|
65
|
+
/** Index of the last code unit consumed by the escape sequence whose ESC sits at `start`. */
|
|
66
|
+
function escapeSequenceEnd(text, start) {
|
|
67
|
+
const next = text.charCodeAt(start + 1);
|
|
68
|
+
if (next === CSI_OPEN)
|
|
69
|
+
return csiEnd(text, start + 2);
|
|
70
|
+
if (next === OSC_OPEN)
|
|
71
|
+
return oscEnd(text, start + 2);
|
|
72
|
+
return start; // bare ESC (or an unrecognized ESC-prefixed byte): drop just the ESC byte
|
|
73
|
+
}
|
|
42
74
|
/**
|
|
43
75
|
* Strip ANSI/terminal escape sequences (CSI `ESC [ ... final-byte`, OSC `ESC ] ... BEL-or-ST`,
|
|
44
76
|
* and bare ESC-prefixed bytes) plus other C0/C1 control characters from `text`. Written as an
|
|
@@ -50,53 +82,41 @@ export function stripControlSequences({ text }) {
|
|
|
50
82
|
for (let i = 0; i < text.length; i++) {
|
|
51
83
|
const code = text.charCodeAt(i);
|
|
52
84
|
if (code === ESC) {
|
|
53
|
-
|
|
54
|
-
if (next === CSI_OPEN) {
|
|
55
|
-
let j = i + 2;
|
|
56
|
-
while (j < text.length && !isCsiFinalByte(text.charCodeAt(j)))
|
|
57
|
-
j++;
|
|
58
|
-
i = j; // land on the final byte; the outer loop's i++ steps past it
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (next === OSC_OPEN) {
|
|
62
|
-
let j = i + 2;
|
|
63
|
-
while (j < text.length &&
|
|
64
|
-
text.charCodeAt(j) !== BEL &&
|
|
65
|
-
!(text.charCodeAt(j) === ESC && text.charCodeAt(j + 1) === BACKSLASH)) {
|
|
66
|
-
j++;
|
|
67
|
-
}
|
|
68
|
-
if (j < text.length && text.charCodeAt(j) === BEL)
|
|
69
|
-
i = j;
|
|
70
|
-
else
|
|
71
|
-
i = Math.min(j + 1, text.length - 1);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
continue; // bare ESC (or an unrecognized ESC-prefixed byte): drop just the ESC byte
|
|
75
|
-
}
|
|
76
|
-
if (isOtherControlCode(code))
|
|
85
|
+
i = escapeSequenceEnd(text, i); // land on the last consumed byte; the loop's i++ steps past it
|
|
77
86
|
continue;
|
|
78
|
-
|
|
87
|
+
}
|
|
88
|
+
if (!isOtherControlCode(code))
|
|
89
|
+
out += text[i];
|
|
79
90
|
}
|
|
80
91
|
return out;
|
|
81
92
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
/** Exact diagnostic vocabulary only: unknown spellings and glued suffixes remain opaque. */
|
|
94
|
+
const DIAGNOSTIC_TOKENS = new Set([
|
|
95
|
+
'additionalProperties', 'additionalItems', 'unevaluatedProperties', 'unevaluatedItems',
|
|
96
|
+
'dependentRequired', 'dependentSchemas', 'exclusiveMinimum', 'exclusiveMaximum',
|
|
97
|
+
'minContains', 'maxContains', 'patternProperties', 'propertyNames', 'prefixItems',
|
|
98
|
+
'contentEncoding', 'contentMediaType', 'contentSchema',
|
|
99
|
+
]);
|
|
100
|
+
/** Mask credentials before preserving complete trusted IDs and UPPER_SNAKE diagnostics; redact opaque spans elsewhere. */
|
|
101
|
+
function redactSecretLike({ text, diagnosticToolIds }) {
|
|
102
|
+
return redactSecrets({ input: text }, { redactPii: false })
|
|
103
|
+
.replace(LABELED_SECRET_RE, match => match.replace(/([:=]\s*)\S+$/, '$1[redacted]'))
|
|
104
|
+
.replace(/\[REDACTED:[^\]\r\n]+\]/g, '[redacted]')
|
|
105
|
+
// Trust whole dotted IDs so their components cannot become a general redaction bypass.
|
|
106
|
+
.replace(/[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*/g, token => diagnosticToolIds.has(token) ? token
|
|
107
|
+
: token.replace(OPAQUE_TOKEN_RE, match => NON_SECRET_TOKEN_RE.test(match) || DIAGNOSTIC_TOKENS.has(match) ? match : '[redacted]'));
|
|
90
108
|
}
|
|
91
109
|
/**
|
|
92
110
|
* Strip terminal control sequences, redact anything that looks like a secret, and cap the
|
|
93
111
|
* length of `text` — the one function every boundary that surfaces daemon/network-derived text
|
|
94
112
|
* to stderr/stdout should route through first.
|
|
113
|
+
* Trusted diagnosticToolIds exempt complete IDs (including dots) only, after credential masking.
|
|
114
|
+
* @complexity O(n + t) local time/space for n text bytes and t trusted ID bytes.
|
|
95
115
|
*/
|
|
96
116
|
export function sanitizeUntrustedText({ text }, options = {}) {
|
|
97
117
|
const requestedLength = options.maxLength ?? DEFAULT_MAX_EXCERPT_LENGTH;
|
|
98
118
|
const maxLength = Number.isFinite(requestedLength) ? Math.max(0, Math.floor(requestedLength)) : DEFAULT_MAX_EXCERPT_LENGTH;
|
|
99
|
-
const sanitized = redactSecretLike({ text: stripControlSequences({ text }) });
|
|
119
|
+
const sanitized = redactSecretLike({ text: stripControlSequences({ text }), diagnosticToolIds: new Set(options.diagnosticToolIds) });
|
|
100
120
|
if (sanitized.length <= maxLength)
|
|
101
121
|
return sanitized;
|
|
102
122
|
// The marker is part of the output budget, including when the budget cannot fit the full marker.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Descriptor-only projections. Execution and current authorization remain with ToolExecutor. */
|
|
2
|
+
import type { ToolDescriptor, ToolRegistry } from './tool-registry.js';
|
|
3
|
+
export interface ToolNameMap {
|
|
4
|
+
wireNameFor(required: {
|
|
5
|
+
toolId: string;
|
|
6
|
+
}, optional?: Record<string, never>): string | undefined;
|
|
7
|
+
toolIdFor(required: {
|
|
8
|
+
wireName: string;
|
|
9
|
+
}, optional?: Record<string, never>): string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Build a deterministic, collision-checked mapping to conservative 64-character wire names.
|
|
13
|
+
* @param required - Registry IDs; duplicates share one mapping.
|
|
14
|
+
* @param optional - Names already occupied by transport-owned tools.
|
|
15
|
+
* @returns Read-only forward/reverse lookups, with no descriptors or execution authority.
|
|
16
|
+
* @complexity O(n log n + l + c) time, O(n + l) space for n IDs, l characters and c collision probes.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createToolNameMap({ toolIds }: {
|
|
19
|
+
toolIds: readonly string[];
|
|
20
|
+
}, { reservedNames }?: {
|
|
21
|
+
reservedNames?: readonly string[];
|
|
22
|
+
}): ToolNameMap;
|
|
23
|
+
export interface ExposedToolDescriptor {
|
|
24
|
+
readonly wireName: string;
|
|
25
|
+
readonly descriptor: ToolDescriptor;
|
|
26
|
+
}
|
|
27
|
+
export interface ToolExposureSnapshot {
|
|
28
|
+
readonly revision: number;
|
|
29
|
+
readonly tools: readonly ExposedToolDescriptor[];
|
|
30
|
+
/** UTF-8 bytes of JSON-serialized input schemas, excluding absent schemas. */
|
|
31
|
+
readonly schemaBytes: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ToolExposureSkipped {
|
|
34
|
+
readonly toolId: string;
|
|
35
|
+
readonly reason: 'not-found' | 'tool-limit' | 'schema-byte-limit';
|
|
36
|
+
}
|
|
37
|
+
export interface ToolExposurePromotion {
|
|
38
|
+
readonly snapshot: ToolExposureSnapshot;
|
|
39
|
+
readonly added: readonly string[];
|
|
40
|
+
readonly skipped: readonly ToolExposureSkipped[];
|
|
41
|
+
}
|
|
42
|
+
export interface ToolExposure {
|
|
43
|
+
/** Synchronous and idempotent; successful promotions are never evicted within this scope. */
|
|
44
|
+
promote(required: {
|
|
45
|
+
toolIds: readonly string[];
|
|
46
|
+
}, optional?: Record<string, never>): ToolExposurePromotion;
|
|
47
|
+
getSnapshot(required: Record<string, never>, optional?: Record<string, never>): ToolExposureSnapshot;
|
|
48
|
+
/** Resolves published names only. Callers must still invoke the authoritative executor. */
|
|
49
|
+
resolveToolId(required: {
|
|
50
|
+
wireName: string;
|
|
51
|
+
}, optional?: Record<string, never>): string | undefined;
|
|
52
|
+
}
|
|
53
|
+
export declare class ToolExposureError extends Error {
|
|
54
|
+
readonly code: 'invalid-limit' | 'invalid-descriptor';
|
|
55
|
+
constructor({ code }: {
|
|
56
|
+
code: 'invalid-limit' | 'invalid-descriptor';
|
|
57
|
+
}, options?: {
|
|
58
|
+
cause?: unknown;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create one run's bounded, append-only descriptor exposure over the existing registry.
|
|
63
|
+
* @param required - Descriptor source; a filtered registry view may enforce host visibility.
|
|
64
|
+
* @param optional - Count/schema-byte budgets and transport-owned reserved wire names.
|
|
65
|
+
* @returns Immutable snapshots and promotion deltas; no handlers, policies or execution path.
|
|
66
|
+
* @throws ToolExposureError for invalid limits or non-cloneable/non-serializable descriptors.
|
|
67
|
+
* @complexity Promotion O(r log r + s + b + c) time and O(r + s + b) space for r registry IDs,
|
|
68
|
+
* s exposed entries, b promoted descriptor bytes and c name collision probes. No I/O.
|
|
69
|
+
* @remarks Scope mutation is synchronous, so concurrent promise continuations cannot lose updates.
|
|
70
|
+
* Definitions stay pinned until this object is released; execution must recheck current registry policy.
|
|
71
|
+
*/
|
|
72
|
+
export declare function createToolExposure({ registry }: {
|
|
73
|
+
registry: Pick<ToolRegistry, 'list'>;
|
|
74
|
+
}, { maxTools, maxSchemaBytes, reservedNames }?: {
|
|
75
|
+
maxTools?: number;
|
|
76
|
+
maxSchemaBytes?: number;
|
|
77
|
+
reservedNames?: readonly string[];
|
|
78
|
+
}): ToolExposure;
|
|
79
|
+
//# sourceMappingURL=tool-exposure.d.ts.map
|