@jini-ai/core 0.4.7 → 0.4.9
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 +11 -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 +9 -1
- package/dist/index.js +8 -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 +4 -0
- package/dist/redact.js +57 -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/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/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 +19 -0
- package/dist/settings/write-service.js +34 -0
- package/dist/target-scope.d.ts +52 -0
- package/dist/target-scope.js +82 -0
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @jini-ai/core
|
|
2
2
|
|
|
3
|
+
## 0.4.9 — 2026-10-22
|
|
4
|
+
|
|
5
|
+
- 2026-10-22: Provider registry, ObjectStore contract with object_store registry and deferObjectStore, default-on batch tool eligibility, coverage.
|
|
6
|
+
|
|
7
|
+
- New `./object-store` (also on the root): the one `ObjectStore` contract (put with atomic `ifAbsent` / get→null / head / delete, optional `publicAccess`), `ObjectStoreProvider.create()` returning `ProviderResult`, and `createObjectStoreRegistry()` (contract `object_store`, default `local`). Absorbs cms `BlobStorePort`, capability-providers `StorageProvider`, devops `ArtifactStorePort` and platform `BlobStorage` as views.
|
|
8
|
+
- New `./provider-registry` (also on the root): `createProviderRegistry<T>()` — keyed register/get/has/list/getDefault, frozen entries, core registrations throw while plugin contributions are recorded in `refusals()` — plus shared `ProviderResult<T>`, `ProviderContext` and `DomainRequirement` types. Frozen contract: `docs/provider-registry.md`. `contribute()` turns a throwing validator or provider/id getter into an `invalid_provider` refusal; every public method and the `providerOk`/`providerRejected`/`providerUnknown` factories take the reserved optional object.
|
|
9
|
+
|
|
10
|
+
## 0.4.8 — 2026-10-16
|
|
11
|
+
|
|
12
|
+
- `applyTargetScope`: one optional `site` input on read-only tools; the host resolves the target and a peer handler/principal, writes refuse a foreign target (3786c8f0).
|
|
13
|
+
|
|
3
14
|
## 0.4.7 — 2026-10-16
|
|
4
15
|
|
|
5
16
|
- `requireString`, `requireNumber` and `requireObject` errors list the keys the call actually received and flag unsupported `items[]` batching (a6ac9d7e, c7557838).
|
package/dist/agent-tools.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export type AgentToolSideEffect = "none" | "mutates-durable-state" | "deletes-du
|
|
|
16
16
|
export type AgentToolActorClassRule = "confirmer-must-equal-own-delegatedBy" | "user-only" | "none";
|
|
17
17
|
/** Structural catalog contract. Domains may require inputSchema or tighten their own metadata. */
|
|
18
18
|
export interface AgentToolDefinition {
|
|
19
|
-
readonly metadata?: import('./tool-metadata.js').ToolMetadata;
|
|
19
|
+
readonly metadata?: import('./tool-metadata-types.js').ToolMetadata;
|
|
20
20
|
name: string;
|
|
21
21
|
description: string;
|
|
22
22
|
sideEffects: AgentToolSideEffect;
|
package/dist/approval.d.ts
CHANGED
|
@@ -69,6 +69,21 @@ export interface ApprovalSteps<TPrepared, TDescription> {
|
|
|
69
69
|
export declare function createApprovalHandler<TPrepared, TDescription>(steps: ApprovalSteps<TPrepared, TDescription>, { remembered }?: {
|
|
70
70
|
remembered?: RememberedApprovalPort;
|
|
71
71
|
}): ToolHandler;
|
|
72
|
+
/** Prepare every item before asking once for the complete batch. Reuses the single-call approval
|
|
73
|
+
* lifecycle, including its detached inputs, authenticated confirmer and abort checks.
|
|
74
|
+
* Single inputs preserve their result shape. Batch effects run in order and stop on the first
|
|
75
|
+
* failure; earlier effects are not rolled back. No remembered approval is accepted for a batch.
|
|
76
|
+
* @throws ToolInputError for malformed, duplicate or over-cap batches, before preparation/effects.
|
|
77
|
+
* @complexity O(s + n p log p) local time and O(s) snapshot space for s input/description bytes,
|
|
78
|
+
* p top-level fields per item and n items (default cap 25), plus the injected preparation/effects. */
|
|
79
|
+
export declare function createBatchApprovalHandler<TPrepared, TDescription>({ steps, combineDescriptions }: {
|
|
80
|
+
steps: ApprovalSteps<TPrepared, TDescription>;
|
|
81
|
+
combineDescriptions(input: {
|
|
82
|
+
descriptions: readonly TDescription[];
|
|
83
|
+
}): TDescription;
|
|
84
|
+
}, { maxItems }?: {
|
|
85
|
+
maxItems?: number;
|
|
86
|
+
}): ToolHandler;
|
|
72
87
|
/** Page-local proposal transport for a browser that must return before its explicit reply arrives.
|
|
73
88
|
* It carries no principal or tool authorization: the host's execute port must re-check access.
|
|
74
89
|
* The private action is independent of the rendered snapshot; replies consume it before awaiting
|
package/dist/approval.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { humanConfirmedHandler } from './registration-kit.js';
|
|
1
|
+
import { humanConfirmedHandler, requireInputRecord } from './registration-kit.js';
|
|
2
|
+
import { ToolInputError } from './tool-registry.js';
|
|
2
3
|
/** The decision reducer shared by parked tool calls and the browser's explicit reply transport.
|
|
3
4
|
* An answer never revives an aborted operation. Transport failures propagate without an effect. */
|
|
4
5
|
export async function resolveApproval({ signal, askHuman }, _optional = {}) {
|
|
@@ -15,6 +16,8 @@ export async function resolveApproval({ signal, askHuman }, _optional = {}) {
|
|
|
15
16
|
signal.removeEventListener('abort', abandon);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
/** The confirmer is always the run's own delegating principal, as a user. */
|
|
20
|
+
const confirmerOf = (ctx) => ({ id: ctx.principal.id, kind: 'user' });
|
|
18
21
|
/** Extend the branded human-confirm owner with descriptions and optional remembered grants.
|
|
19
22
|
* Inputs and actor identity are detached before any asynchronous port runs. No input field can
|
|
20
23
|
* approve an action; only the host transport or the bound grant store can answer. Abort checks
|
|
@@ -27,6 +30,24 @@ export async function resolveApproval({ signal, askHuman }, _optional = {}) {
|
|
|
27
30
|
* @example createApprovalHandler({ prepare, describe, askHuman, run, notConfirmed });
|
|
28
31
|
*/
|
|
29
32
|
export function createApprovalHandler(steps, { remembered } = {}) {
|
|
33
|
+
const confirmed = () => ({ confirmed: true });
|
|
34
|
+
const abandoned = (prepared) => ({ confirmed: false, result: steps.notConfirmed({ reason: 'abandoned', prepared: prepared.value }) });
|
|
35
|
+
/** A remembered grant applies only when the plan offers a key and the host bound a store. */
|
|
36
|
+
const grantSlot = (plan) => plan.rememberKey !== undefined && remembered ? { store: remembered, key: plan.rememberKey } : undefined;
|
|
37
|
+
/** Ask the host transport; record the choice and any reusable grant only on an explicit yes. */
|
|
38
|
+
const askFresh = async ({ prepared, plan }, options = {}) => {
|
|
39
|
+
const { ctx } = prepared;
|
|
40
|
+
const answer = await resolveApproval({ signal: ctx.signal, askHuman: () => steps.askHuman({ ctx, description: plan.description }, options) });
|
|
41
|
+
if (ctx.signal.aborted)
|
|
42
|
+
return abandoned(prepared);
|
|
43
|
+
if (answer.confirmed !== true)
|
|
44
|
+
return { confirmed: false, result: steps.notConfirmed({ reason: answer.reason, prepared: prepared.value }) };
|
|
45
|
+
prepared.choice = answer.choice;
|
|
46
|
+
const slot = grantSlot(plan);
|
|
47
|
+
if (slot)
|
|
48
|
+
await slot.store.grant({ ctx, key: slot.key, confirmer: confirmerOf(ctx) });
|
|
49
|
+
return ctx.signal.aborted ? abandoned(prepared) : confirmed();
|
|
50
|
+
};
|
|
30
51
|
return humanConfirmedHandler({
|
|
31
52
|
prepare: async ({ ctx }, options = {}) => {
|
|
32
53
|
const snapshot = { ...ctx, input: structuredClone(ctx.input), principal: { ...ctx.principal }, run: { ...ctx.run } };
|
|
@@ -39,38 +60,82 @@ export function createApprovalHandler(steps, { remembered } = {}) {
|
|
|
39
60
|
return { ctx: snapshot, value, plan };
|
|
40
61
|
},
|
|
41
62
|
askHuman: async ({ prepared }, options = {}) => {
|
|
42
|
-
const { ctx, plan
|
|
43
|
-
const abandoned = () => ({ confirmed: false, result: steps.notConfirmed({ reason: 'abandoned', prepared: value }) });
|
|
63
|
+
const { ctx, plan } = prepared;
|
|
44
64
|
if (ctx.signal.aborted || !plan)
|
|
45
|
-
return abandoned();
|
|
65
|
+
return abandoned(prepared);
|
|
46
66
|
if (!plan.ask)
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
return confirmed();
|
|
68
|
+
const slot = grantSlot(plan);
|
|
69
|
+
if (slot) {
|
|
70
|
+
const approved = await slot.store.has({ ctx, key: slot.key });
|
|
50
71
|
if (ctx.signal.aborted)
|
|
51
|
-
return abandoned();
|
|
72
|
+
return abandoned(prepared);
|
|
52
73
|
if (approved)
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
const answer = await resolveApproval({ signal: ctx.signal, askHuman: () => steps.askHuman({ ctx, description: plan.description }, options) });
|
|
56
|
-
if (ctx.signal.aborted)
|
|
57
|
-
return abandoned();
|
|
58
|
-
if (answer.confirmed !== true)
|
|
59
|
-
return { confirmed: false, result: steps.notConfirmed({ reason: answer.reason, prepared: value }) };
|
|
60
|
-
prepared.choice = answer.choice;
|
|
61
|
-
if (plan.rememberKey !== undefined && remembered) {
|
|
62
|
-
await remembered.grant({ ctx, key: plan.rememberKey, confirmer: { id: ctx.principal.id, kind: 'user' } });
|
|
74
|
+
return confirmed();
|
|
63
75
|
}
|
|
64
|
-
|
|
65
|
-
return abandoned();
|
|
66
|
-
return { confirmed: true };
|
|
76
|
+
return askFresh({ prepared, plan }, options);
|
|
67
77
|
},
|
|
68
78
|
run: ({ prepared }, options = {}) => {
|
|
69
79
|
const { ctx, value, choice } = prepared;
|
|
70
80
|
if (ctx.signal.aborted)
|
|
71
81
|
return Promise.resolve(steps.notConfirmed({ reason: 'abandoned', prepared: value }));
|
|
72
|
-
return steps.run({ ctx, prepared: value, confirmer:
|
|
82
|
+
return steps.run({ ctx, prepared: value, confirmer: confirmerOf(ctx), choice }, options);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** Prepare every item before asking once for the complete batch. Reuses the single-call approval
|
|
87
|
+
* lifecycle, including its detached inputs, authenticated confirmer and abort checks.
|
|
88
|
+
* Single inputs preserve their result shape. Batch effects run in order and stop on the first
|
|
89
|
+
* failure; earlier effects are not rolled back. No remembered approval is accepted for a batch.
|
|
90
|
+
* @throws ToolInputError for malformed, duplicate or over-cap batches, before preparation/effects.
|
|
91
|
+
* @complexity O(s + n p log p) local time and O(s) snapshot space for s input/description bytes,
|
|
92
|
+
* p top-level fields per item and n items (default cap 25), plus the injected preparation/effects. */
|
|
93
|
+
export function createBatchApprovalHandler({ steps, combineDescriptions }, { maxItems = 25 } = {}) {
|
|
94
|
+
return createApprovalHandler({
|
|
95
|
+
prepare: async ({ ctx }, options) => {
|
|
96
|
+
const input = requireInputRecord({ input: ctx.input });
|
|
97
|
+
if (!Object.hasOwn(input, 'items'))
|
|
98
|
+
return { batch: false, entries: [{ ctx, prepared: await steps.prepare({ ctx }, options) }] };
|
|
99
|
+
const { items, ...defaults } = input;
|
|
100
|
+
if (!Array.isArray(items) || items.length < 1 || items.length > maxItems) {
|
|
101
|
+
throw new ToolInputError({ message: `items must contain between 1 and ${maxItems} inputs.` });
|
|
102
|
+
}
|
|
103
|
+
const contexts = items.map(item => {
|
|
104
|
+
const fields = requireInputRecord({ input: item });
|
|
105
|
+
if (Object.hasOwn(fields, 'items'))
|
|
106
|
+
throw new ToolInputError({ message: 'Nested items batches are not supported.' });
|
|
107
|
+
return { ...ctx, input: { ...defaults, ...fields } };
|
|
108
|
+
});
|
|
109
|
+
// These boundary inputs are records; sorting their keys also catches reordered scalar ids.
|
|
110
|
+
const keys = contexts.map(({ input }) => JSON.stringify(Object.entries(input).sort(([a], [b]) => a.localeCompare(b))));
|
|
111
|
+
if (new Set(keys).size !== keys.length)
|
|
112
|
+
throw new ToolInputError({ message: 'Duplicate batch inputs are not allowed.' });
|
|
113
|
+
const entries = [];
|
|
114
|
+
for (const itemCtx of contexts)
|
|
115
|
+
entries.push({ ctx: itemCtx, prepared: await steps.prepare({ ctx: itemCtx }, options) });
|
|
116
|
+
return { batch: true, entries };
|
|
117
|
+
},
|
|
118
|
+
describe: async ({ prepared }, options) => {
|
|
119
|
+
const plans = [];
|
|
120
|
+
for (const entry of prepared.entries)
|
|
121
|
+
plans.push(await steps.describe(entry, options));
|
|
122
|
+
if (!prepared.batch)
|
|
123
|
+
return plans[0];
|
|
124
|
+
return { ask: true, description: combineDescriptions({ descriptions: plans.map(plan => plan.description) }) };
|
|
125
|
+
},
|
|
126
|
+
askHuman: steps.askHuman,
|
|
127
|
+
run: async ({ prepared, confirmer, choice }, options) => {
|
|
128
|
+
if (!prepared.batch)
|
|
129
|
+
return steps.run({ ...prepared.entries[0], confirmer, choice }, options);
|
|
130
|
+
const results = [];
|
|
131
|
+
for (const entry of prepared.entries) {
|
|
132
|
+
if (entry.ctx.signal.aborted)
|
|
133
|
+
return steps.notConfirmed({ reason: 'abandoned' });
|
|
134
|
+
results.push(await steps.run({ ...entry, confirmer, choice }, options));
|
|
135
|
+
}
|
|
136
|
+
return { batch: true, total: results.length, results };
|
|
73
137
|
},
|
|
138
|
+
notConfirmed: ({ reason, prepared }) => steps.notConfirmed({ reason, prepared: prepared?.batch ? undefined : prepared?.entries[0]?.prepared }),
|
|
74
139
|
});
|
|
75
140
|
}
|
|
76
141
|
/** Page-local proposal transport for a browser that must return before its explicit reply arrives.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/** Shared sequential batching. Each merged item re-enters the invoking executor's gates. */
|
|
2
|
+
import { type ToolRegistration } from './tool-registry.js';
|
|
3
|
+
/** One tool that accepts `items`. `itemDefaults` sit under the caller's own fields: a batch that
|
|
4
|
+
* returns every item's full body could overflow one tool result, so a reader can default to a
|
|
5
|
+
* smaller window per item that the caller may still raise. */
|
|
6
|
+
export interface BatchableTool {
|
|
7
|
+
readonly toolId: string;
|
|
8
|
+
readonly maxItems?: number;
|
|
9
|
+
readonly itemDefaults?: Readonly<Record<string, unknown>>;
|
|
10
|
+
/** Host-specific result constraints, checked on the normalized item before its handler runs. */
|
|
11
|
+
readonly validateItem?: (required: {
|
|
12
|
+
input: unknown;
|
|
13
|
+
}) => void;
|
|
14
|
+
}
|
|
15
|
+
/** Where every batch descriptor's appended guidance starts. */
|
|
16
|
+
export declare const BATCH_GUIDANCE_MARKER = " Batch: ";
|
|
17
|
+
/** A description without {@link batchDescriptorFor}'s appended guidance, wherever it sits (a merged
|
|
18
|
+
* description may carry several), e.g. for a search index: the same boilerplate on most tools would
|
|
19
|
+
* otherwise dilute relevance. Other text is kept. @complexity O(n) in the description length. */
|
|
20
|
+
export declare function withoutBatchGuidance({ description }: {
|
|
21
|
+
description: string;
|
|
22
|
+
}, _optional?: {}): string;
|
|
23
|
+
/** Used when a {@link BatchableTool} names no `maxItems`. */
|
|
24
|
+
export declare const DEFAULT_MAX_BATCH_ITEMS = 25;
|
|
25
|
+
/** One item's outcome. `error` is the domain's own message without the appended input schema,
|
|
26
|
+
* which the tool already publishes and which would otherwise repeat once per failed item. */
|
|
27
|
+
export type BatchItemResult = {
|
|
28
|
+
readonly index: number;
|
|
29
|
+
readonly ok: true;
|
|
30
|
+
readonly result: unknown;
|
|
31
|
+
} | {
|
|
32
|
+
readonly index: number;
|
|
33
|
+
readonly ok: false;
|
|
34
|
+
readonly error: string;
|
|
35
|
+
readonly result?: unknown;
|
|
36
|
+
};
|
|
37
|
+
/** What a batch call returns. A failed item never stops the ones after it. */
|
|
38
|
+
export interface BatchResult {
|
|
39
|
+
readonly batch: true;
|
|
40
|
+
readonly outcome: 'completed' | 'partial_failure' | 'error';
|
|
41
|
+
readonly isError?: true;
|
|
42
|
+
readonly total: number;
|
|
43
|
+
readonly succeeded: number;
|
|
44
|
+
readonly failed: number;
|
|
45
|
+
readonly results: readonly BatchItemResult[];
|
|
46
|
+
}
|
|
47
|
+
/** Classify a batch result, including legacy envelopes, without dropping per-item evidence.
|
|
48
|
+
* @returns Undefined for non-batches; error when every item failed, partial_failure for a mix.
|
|
49
|
+
* @complexity O(n) in per-item results; no effects.
|
|
50
|
+
*/
|
|
51
|
+
export declare function batchOutcomeFor({ output }: {
|
|
52
|
+
output: unknown;
|
|
53
|
+
}, _options?: {}): BatchResult['outcome'] | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* The descriptor a batchable tool publishes: its own schema plus an `items` array, its top-level
|
|
56
|
+
* `required` list moved into the `items` description (a batch call sets those per item or once at
|
|
57
|
+
* the top level through the declared legacy normalization), and batch guidance appended to its description.
|
|
58
|
+
*
|
|
59
|
+
* @param required.descriptor - The tool's registered descriptor.
|
|
60
|
+
* @param required.tool - Its batch settings.
|
|
61
|
+
* @returns A new descriptor; the input is not mutated.
|
|
62
|
+
* @throws {Error} If the tool publishes no object schema, or already has an `items` property
|
|
63
|
+
* (the batch field would shadow it) — both developer errors caught at composition time.
|
|
64
|
+
* @complexity O(p) in the schema's top-level property count.
|
|
65
|
+
*/
|
|
66
|
+
export declare function batchDescriptorFor({ descriptor, tool }: {
|
|
67
|
+
descriptor: ToolRegistration["descriptor"];
|
|
68
|
+
tool: BatchableTool;
|
|
69
|
+
}, _optional?: Record<string, never>): ToolRegistration["descriptor"];
|
|
70
|
+
/**
|
|
71
|
+
* Wraps one registration so it also accepts `items`. Single calls retain their handler.
|
|
72
|
+
* Ordinary merged items re-enter the executor; plan batches normalize all items at the outer
|
|
73
|
+
* gate and retain their combined approval handler.
|
|
74
|
+
*
|
|
75
|
+
* @param required.registration - The registration to extend (already approval-wrapped).
|
|
76
|
+
* @param required.tool - Its batch settings.
|
|
77
|
+
* @param optional.mode - `plan` normalizes every merged item before policy, leaving the
|
|
78
|
+
* existing handler to prepare and confirm the combined plan once; `items` gates each item.
|
|
79
|
+
* @returns The registration with {@link batchDescriptorFor}'s descriptor and a batch-aware handler.
|
|
80
|
+
* @throws {Error} At composition time, per {@link batchDescriptorFor}. The returned handler throws
|
|
81
|
+
* {@link ToolInputError} for a malformed `items`, and `AbortError` if the call is cancelled between
|
|
82
|
+
* items; an item's own failure is reported in its result instead.
|
|
83
|
+
* @complexity O(n) bounded normalization/target checks for n items, plus the existing
|
|
84
|
+
* per-item executor or combined plan lifecycle. The default cap is 25.
|
|
85
|
+
*/
|
|
86
|
+
export declare function withBatchInput({ registration, tool }: {
|
|
87
|
+
registration: ToolRegistration;
|
|
88
|
+
tool: BatchableTool;
|
|
89
|
+
}, { mode }?: {
|
|
90
|
+
mode?: 'items' | 'plan';
|
|
91
|
+
}): ToolRegistration;
|
|
92
|
+
/**
|
|
93
|
+
* Applies {@link withBatchInput} to every listed tool present in `registrations`; every other
|
|
94
|
+
* registration is returned as the same object. A listed id that is not registered is skipped (a
|
|
95
|
+
* domain may be absent in a narrower composition, e.g. a test building one domain).
|
|
96
|
+
*
|
|
97
|
+
* @param required.registrations - The assembled registration list.
|
|
98
|
+
* @param optional.tools - Explicit host-owned batchable tool list.
|
|
99
|
+
* @returns A new list in the same order.
|
|
100
|
+
* @complexity O(r + b) for r registrations and b batchable tools.
|
|
101
|
+
*/
|
|
102
|
+
export declare function applyBatchInputs({ registrations }: {
|
|
103
|
+
registrations: readonly ToolRegistration[];
|
|
104
|
+
}, { tools }?: {
|
|
105
|
+
tools?: readonly BatchableTool[];
|
|
106
|
+
}): ToolRegistration[];
|
|
107
|
+
/** A tool a host keeps single-call by default, with the one-line reason a reviewer reads. */
|
|
108
|
+
export interface BatchOptOut {
|
|
109
|
+
readonly toolId: string;
|
|
110
|
+
readonly reason: string;
|
|
111
|
+
}
|
|
112
|
+
/** Why a registration is, or is not, batched by default. */
|
|
113
|
+
export type BatchEligibility = 'batchable' | 'opted-out' | 'takes-items' | 'no-object-schema' | 'list-read';
|
|
114
|
+
/**
|
|
115
|
+
* The default-on batch rule: every read-by-id and mutating tool takes `items` unless its host
|
|
116
|
+
* opts it out. Structural exclusions need no host entry: a tool with no object schema cannot take
|
|
117
|
+
* `items`, one that already has an `items` field would be shadowed by it, and a read-only tool with
|
|
118
|
+
* no required input (a list or search) has no per-item target to batch.
|
|
119
|
+
*
|
|
120
|
+
* @param required.registration - The registration to classify.
|
|
121
|
+
* @param optional.optOut - Host-owned tool ids kept single-call.
|
|
122
|
+
* @returns The first matching reason, or `batchable`.
|
|
123
|
+
* @complexity O(1) plus the schema's top-level `required` list; no effects.
|
|
124
|
+
*/
|
|
125
|
+
export declare function batchEligibilityFor({ registration }: {
|
|
126
|
+
registration: ToolRegistration;
|
|
127
|
+
}, { optOut }?: {
|
|
128
|
+
optOut?: ReadonlySet<string>;
|
|
129
|
+
}): BatchEligibility;
|
|
130
|
+
/**
|
|
131
|
+
* Every registration {@link batchEligibilityFor} classifies as batchable, as {@link BatchableTool}
|
|
132
|
+
* entries. A host's per-tool `settings` (a raised `maxItems`, item defaults, item validation) are
|
|
133
|
+
* merged over the default entry; a settings entry never makes an ineligible tool batchable.
|
|
134
|
+
*
|
|
135
|
+
* @param required.registrations - The assembled registration list.
|
|
136
|
+
* @param optional.optOut - Host-owned tools kept single-call, each with its reason.
|
|
137
|
+
* @param optional.settings - Host-owned per-tool batch settings.
|
|
138
|
+
* @returns The batchable tools, in registration order.
|
|
139
|
+
* @complexity O(r + o + s) for r registrations, o opt-outs and s settings entries.
|
|
140
|
+
*/
|
|
141
|
+
export declare function defaultBatchableTools({ registrations }: {
|
|
142
|
+
registrations: readonly ToolRegistration[];
|
|
143
|
+
}, { optOut, settings }?: {
|
|
144
|
+
optOut?: readonly BatchOptOut[];
|
|
145
|
+
settings?: readonly BatchableTool[];
|
|
146
|
+
}): BatchableTool[];
|
|
147
|
+
//# sourceMappingURL=batch-tool-inputs.d.ts.map
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/** Shared sequential batching. Each merged item re-enters the invoking executor's gates. */
|
|
2
|
+
import { createToolRegistry, isReadOnlyTool } from './tool-registry.js';
|
|
3
|
+
import { ToolInputError } from './tool-input-errors.js';
|
|
4
|
+
import { canonicalizeToolInputKeys, normalizeToolInput, withNormalizationNotes } from './tool-input-normalization.js';
|
|
5
|
+
/** Where every batch descriptor's appended guidance starts. */
|
|
6
|
+
export const BATCH_GUIDANCE_MARKER = " Batch: ";
|
|
7
|
+
const BATCH_GUIDANCE_END = "one item's failure does not stop the others.";
|
|
8
|
+
/** A description without {@link batchDescriptorFor}'s appended guidance, wherever it sits (a merged
|
|
9
|
+
* description may carry several), e.g. for a search index: the same boilerplate on most tools would
|
|
10
|
+
* otherwise dilute relevance. Other text is kept. @complexity O(n) in the description length. */
|
|
11
|
+
export function withoutBatchGuidance({ description }, _optional = {}) {
|
|
12
|
+
return description.split(`${BATCH_GUIDANCE_MARKER}to do several at once,`).map((part, index) => {
|
|
13
|
+
if (index === 0)
|
|
14
|
+
return part;
|
|
15
|
+
const end = part.indexOf(BATCH_GUIDANCE_END);
|
|
16
|
+
return end === -1 ? `${BATCH_GUIDANCE_MARKER}to do several at once,${part}` : part.slice(end + BATCH_GUIDANCE_END.length);
|
|
17
|
+
}).join("");
|
|
18
|
+
}
|
|
19
|
+
/** Used when a {@link BatchableTool} names no `maxItems`. */
|
|
20
|
+
export const DEFAULT_MAX_BATCH_ITEMS = 25;
|
|
21
|
+
/** Classify a batch result, including legacy envelopes, without dropping per-item evidence.
|
|
22
|
+
* @returns Undefined for non-batches; error when every item failed, partial_failure for a mix.
|
|
23
|
+
* @complexity O(n) in per-item results; no effects.
|
|
24
|
+
*/
|
|
25
|
+
export function batchOutcomeFor({ output }, _options = {}) {
|
|
26
|
+
if (!isPlainObject(output) || output.batch !== true || !Array.isArray(output.results) || output.results.length === 0)
|
|
27
|
+
return undefined;
|
|
28
|
+
const failed = output.results.filter(item => isPlainObject(item) && item.ok === false).length;
|
|
29
|
+
return failed === 0 ? 'completed' : failed === output.results.length ? 'error' : 'partial_failure';
|
|
30
|
+
}
|
|
31
|
+
/** A JSON object: not null, not an array. */
|
|
32
|
+
function isPlainObject(value) {
|
|
33
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The descriptor a batchable tool publishes: its own schema plus an `items` array, its top-level
|
|
37
|
+
* `required` list moved into the `items` description (a batch call sets those per item or once at
|
|
38
|
+
* the top level through the declared legacy normalization), and batch guidance appended to its description.
|
|
39
|
+
*
|
|
40
|
+
* @param required.descriptor - The tool's registered descriptor.
|
|
41
|
+
* @param required.tool - Its batch settings.
|
|
42
|
+
* @returns A new descriptor; the input is not mutated.
|
|
43
|
+
* @throws {Error} If the tool publishes no object schema, or already has an `items` property
|
|
44
|
+
* (the batch field would shadow it) — both developer errors caught at composition time.
|
|
45
|
+
* @complexity O(p) in the schema's top-level property count.
|
|
46
|
+
*/
|
|
47
|
+
export function batchDescriptorFor({ descriptor, tool }, _optional = {}) {
|
|
48
|
+
const schema = descriptor.inputSchema;
|
|
49
|
+
if (!isPlainObject(schema) || !isPlainObject(schema.properties)) {
|
|
50
|
+
throw new Error(`batch-tool-inputs.ts: '${descriptor.id}' publishes no object inputSchema, so it cannot take items`);
|
|
51
|
+
}
|
|
52
|
+
if (Object.hasOwn(schema.properties, "items")) {
|
|
53
|
+
throw new Error(`batch-tool-inputs.ts: '${descriptor.id}' already has an 'items' input, so the batch field would shadow it`);
|
|
54
|
+
}
|
|
55
|
+
const maxItems = tool.maxItems ?? DEFAULT_MAX_BATCH_ITEMS;
|
|
56
|
+
const requiredFields = schema.required ?? [];
|
|
57
|
+
const { required: _required, ...rest } = schema;
|
|
58
|
+
const needs = requiredFields.length > 0 ? ` Each item needs ${requiredFields.join(", ")} (inside each item).` : "";
|
|
59
|
+
const items = {
|
|
60
|
+
type: "array",
|
|
61
|
+
minItems: 1,
|
|
62
|
+
maxItems,
|
|
63
|
+
description: `Batch: up to ${maxItems} inputs run in ONE call, in order. Each item takes the same fields as a single call; ` +
|
|
64
|
+
`Canonical shape: {items:[{...}, ...]} only. Legacy shared top-level fields are normalized into each item before execution; an item's own value wins.${needs}`,
|
|
65
|
+
items: { type: "object", description: "One item: the same fields as a single call of this tool." },
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
...descriptor,
|
|
69
|
+
description: `${descriptor.description ?? ""}${BATCH_GUIDANCE_MARKER}to do several at once, pass items: [{...}, ...] (up to ${maxItems}) in ONE call instead of one call per item. ` +
|
|
70
|
+
"Canonical shape: {items:[{...}, ...]} only. Legacy shared top-level fields are normalized into each item; item values win. " +
|
|
71
|
+
"Returns { batch: true, outcome: completed | partial_failure | error, total, succeeded, failed, results: [{ index, ok, result | error }] }; one item's failure does not stop the others.",
|
|
72
|
+
inputSchema: { ...rest, properties: { ...schema.properties, items } },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** The domain message without `withSchemaOnRejection`'s appended schema: the text before the
|
|
76
|
+
* first schema marker, or the whole message when no schema was appended. */
|
|
77
|
+
function itemErrorMessage({ error, toolId }) {
|
|
78
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
79
|
+
const schemaAt = message.indexOf(` Schema for '${toolId}': `);
|
|
80
|
+
return schemaAt === -1 ? message : message.slice(0, schemaAt);
|
|
81
|
+
}
|
|
82
|
+
/** Validates the batch half of an input and returns its items, or throws a caller-actionable error. */
|
|
83
|
+
function readItems({ items, toolId, maxItems }) {
|
|
84
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
85
|
+
throw new ToolInputError({ message: `${toolId}: 'items' must be a non-empty array of inputs` });
|
|
86
|
+
}
|
|
87
|
+
if (items.length > maxItems) {
|
|
88
|
+
throw new ToolInputError({ message: `${toolId}: 'items' has ${items.length} entries; the most one call takes is ${maxItems}. Split them into several calls.` });
|
|
89
|
+
}
|
|
90
|
+
const bad = items.findIndex((item) => !isPlainObject(item));
|
|
91
|
+
if (bad !== -1)
|
|
92
|
+
throw new ToolInputError({ message: `${toolId}: items[${bad}] must be an object of this tool's input fields` });
|
|
93
|
+
return items;
|
|
94
|
+
}
|
|
95
|
+
/** Defaults < shared fields < item, after resolving spellings independently in each layer.
|
|
96
|
+
* Domain aliases, scalar conversion and closed-schema rejection run on the final merged input.
|
|
97
|
+
* @throws ToolInputError on a collision within any layer or invalid final input.
|
|
98
|
+
* @complexity Three bounded schema walks plus the registered normalizer's cost.
|
|
99
|
+
*/
|
|
100
|
+
async function normalizeBatchItem({ registration, defaults, shared, item }) {
|
|
101
|
+
const schema = registration.descriptor.inputSchema;
|
|
102
|
+
const layers = [defaults ?? {}, shared, item].map(input => canonicalizeToolInputKeys({ input, schema }));
|
|
103
|
+
const input = { ...layers[0].input, ...layers[1].input, ...layers[2].input };
|
|
104
|
+
const normalized = await normalizeToolInput({ input, registration });
|
|
105
|
+
return { input: normalized.input, notes: [...layers.flatMap(layer => layer.notes), ...normalized.notes] };
|
|
106
|
+
}
|
|
107
|
+
/** Classify one returned output without dropping error payloads or normalization notes.
|
|
108
|
+
* @complexity O(n) for a nested batch's n results, plus formatting its notes; no effects.
|
|
109
|
+
*/
|
|
110
|
+
function batchItemResult({ index, output, notes }, _options = {}) {
|
|
111
|
+
const outcome = batchOutcomeFor({ output });
|
|
112
|
+
if (isPlainObject(output) && (output.isError === true || outcome === 'error' || outcome === 'partial_failure')) {
|
|
113
|
+
return { index, ok: false, error: typeof output.error === 'string' ? output.error : 'item returned an error', result: output };
|
|
114
|
+
}
|
|
115
|
+
return { index, ok: true, result: withNormalizationNotes({ output, notes }) };
|
|
116
|
+
}
|
|
117
|
+
/** Summarize all item outcomes, retaining their order and evidence.
|
|
118
|
+
* @complexity O(n) time and space for n results; no effects.
|
|
119
|
+
*/
|
|
120
|
+
function summarizeBatchResults({ results }, _options = {}) {
|
|
121
|
+
const succeeded = results.filter((result) => result.ok).length;
|
|
122
|
+
const outcome = succeeded === results.length ? 'completed' : succeeded === 0 ? 'error' : 'partial_failure';
|
|
123
|
+
return { batch: true, outcome, ...(succeeded === 0 ? { isError: true } : {}), total: results.length, succeeded, failed: results.length - succeeded, results };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wraps one registration so it also accepts `items`. Single calls retain their handler.
|
|
127
|
+
* Ordinary merged items re-enter the executor; plan batches normalize all items at the outer
|
|
128
|
+
* gate and retain their combined approval handler.
|
|
129
|
+
*
|
|
130
|
+
* @param required.registration - The registration to extend (already approval-wrapped).
|
|
131
|
+
* @param required.tool - Its batch settings.
|
|
132
|
+
* @param optional.mode - `plan` normalizes every merged item before policy, leaving the
|
|
133
|
+
* existing handler to prepare and confirm the combined plan once; `items` gates each item.
|
|
134
|
+
* @returns The registration with {@link batchDescriptorFor}'s descriptor and a batch-aware handler.
|
|
135
|
+
* @throws {Error} At composition time, per {@link batchDescriptorFor}. The returned handler throws
|
|
136
|
+
* {@link ToolInputError} for a malformed `items`, and `AbortError` if the call is cancelled between
|
|
137
|
+
* items; an item's own failure is reported in its result instead.
|
|
138
|
+
* @complexity O(n) bounded normalization/target checks for n items, plus the existing
|
|
139
|
+
* per-item executor or combined plan lifecycle. The default cap is 25.
|
|
140
|
+
*/
|
|
141
|
+
export function withBatchInput({ registration, tool }, { mode = 'items' } = {}) {
|
|
142
|
+
const toolId = registration.descriptor.id;
|
|
143
|
+
const maxItems = tool.maxItems ?? DEFAULT_MAX_BATCH_ITEMS;
|
|
144
|
+
const descriptor = batchDescriptorFor({ descriptor: registration.descriptor, tool });
|
|
145
|
+
if (mode === 'plan')
|
|
146
|
+
return {
|
|
147
|
+
...registration, descriptor: { ...descriptor,
|
|
148
|
+
description: `${registration.descriptor.description ?? ''}${BATCH_GUIDANCE_MARKER}up to ${maxItems} inputs are normalized before authorization and passed to the existing combined approval plan.`,
|
|
149
|
+
},
|
|
150
|
+
// The plan owns one confirmation. Normalize every merged item before the outer gate so
|
|
151
|
+
// its policy and planner receive the same targets; never execute items to preflight them.
|
|
152
|
+
normalizeInput: async ({ input }) => {
|
|
153
|
+
if (!Object.hasOwn(input, 'items'))
|
|
154
|
+
return normalizeToolInput({ input, registration });
|
|
155
|
+
const { items: rawItems, ...shared } = input;
|
|
156
|
+
const items = readItems({ items: rawItems, toolId, maxItems });
|
|
157
|
+
const normalized = [];
|
|
158
|
+
for (const item of items) {
|
|
159
|
+
const result = await normalizeBatchItem({ registration, defaults: tool.itemDefaults, shared, item });
|
|
160
|
+
tool.validateItem?.({ input: result.input });
|
|
161
|
+
normalized.push(result);
|
|
162
|
+
}
|
|
163
|
+
return { input: { items: normalized.map(item => item.input) }, notes: normalized.flatMap(item => item.notes) };
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
const original = registration.handler;
|
|
167
|
+
const itemRegistry = createToolRegistry({});
|
|
168
|
+
itemRegistry.register(registration);
|
|
169
|
+
const handler = async (ctx, options) => {
|
|
170
|
+
if (!isPlainObject(ctx.input) || !Object.hasOwn(ctx.input, "items"))
|
|
171
|
+
return original(ctx, options);
|
|
172
|
+
const { items: rawItems, ...shared } = ctx.input;
|
|
173
|
+
const items = readItems({ items: rawItems, toolId, maxItems });
|
|
174
|
+
const results = [];
|
|
175
|
+
for (const [index, item] of items.entries()) {
|
|
176
|
+
ctx.signal.throwIfAborted();
|
|
177
|
+
let notes = [];
|
|
178
|
+
try {
|
|
179
|
+
const normalized = await normalizeBatchItem({ registration, defaults: tool.itemDefaults, shared, item });
|
|
180
|
+
notes = normalized.notes;
|
|
181
|
+
tool.validateItem?.({ input: normalized.input });
|
|
182
|
+
if (!options?.invokeTool)
|
|
183
|
+
throw new Error(`${toolId}: batch items require the gated ToolExecutor invocation port`);
|
|
184
|
+
const output = await options.invokeTool({ toolId, input: normalized.input }, { registry: itemRegistry });
|
|
185
|
+
results.push(batchItemResult({ index, output, notes: normalized.notes }));
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
results.push({ index, ok: false, error: withNormalizationNotes({ output: itemErrorMessage({ error, toolId }), notes }) });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return summarizeBatchResults({ results });
|
|
192
|
+
};
|
|
193
|
+
return { ...registration, descriptor, handler,
|
|
194
|
+
// Domain normalization belongs to each merged item, not the incomplete shared layer.
|
|
195
|
+
normalizeInput: async ({ input }, options) => Object.hasOwn(input, 'items') || !registration.normalizeInput
|
|
196
|
+
? { input, notes: [] }
|
|
197
|
+
: registration.normalizeInput({ input }, options),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Applies {@link withBatchInput} to every listed tool present in `registrations`; every other
|
|
202
|
+
* registration is returned as the same object. A listed id that is not registered is skipped (a
|
|
203
|
+
* domain may be absent in a narrower composition, e.g. a test building one domain).
|
|
204
|
+
*
|
|
205
|
+
* @param required.registrations - The assembled registration list.
|
|
206
|
+
* @param optional.tools - Explicit host-owned batchable tool list.
|
|
207
|
+
* @returns A new list in the same order.
|
|
208
|
+
* @complexity O(r + b) for r registrations and b batchable tools.
|
|
209
|
+
*/
|
|
210
|
+
export function applyBatchInputs({ registrations }, { tools = [] } = {}) {
|
|
211
|
+
const byId = new Map(tools.map((tool) => [tool.toolId, tool]));
|
|
212
|
+
return registrations.map((registration) => {
|
|
213
|
+
const tool = byId.get(registration.descriptor.id);
|
|
214
|
+
return tool ? withBatchInput({ registration, tool }) : registration;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The default-on batch rule: every read-by-id and mutating tool takes `items` unless its host
|
|
219
|
+
* opts it out. Structural exclusions need no host entry: a tool with no object schema cannot take
|
|
220
|
+
* `items`, one that already has an `items` field would be shadowed by it, and a read-only tool with
|
|
221
|
+
* no required input (a list or search) has no per-item target to batch.
|
|
222
|
+
*
|
|
223
|
+
* @param required.registration - The registration to classify.
|
|
224
|
+
* @param optional.optOut - Host-owned tool ids kept single-call.
|
|
225
|
+
* @returns The first matching reason, or `batchable`.
|
|
226
|
+
* @complexity O(1) plus the schema's top-level `required` list; no effects.
|
|
227
|
+
*/
|
|
228
|
+
export function batchEligibilityFor({ registration }, { optOut = new Set() } = {}) {
|
|
229
|
+
const schema = registration.descriptor.inputSchema;
|
|
230
|
+
if (optOut.has(registration.descriptor.id))
|
|
231
|
+
return 'opted-out';
|
|
232
|
+
if (!isPlainObject(schema) || !isPlainObject(schema.properties))
|
|
233
|
+
return 'no-object-schema';
|
|
234
|
+
if (Object.hasOwn(schema.properties, 'items'))
|
|
235
|
+
return 'takes-items';
|
|
236
|
+
const hasRequired = Array.isArray(schema.required) && schema.required.length > 0;
|
|
237
|
+
return hasRequired || !isReadOnlyTool({ descriptor: registration.descriptor }) ? 'batchable' : 'list-read';
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Every registration {@link batchEligibilityFor} classifies as batchable, as {@link BatchableTool}
|
|
241
|
+
* entries. A host's per-tool `settings` (a raised `maxItems`, item defaults, item validation) are
|
|
242
|
+
* merged over the default entry; a settings entry never makes an ineligible tool batchable.
|
|
243
|
+
*
|
|
244
|
+
* @param required.registrations - The assembled registration list.
|
|
245
|
+
* @param optional.optOut - Host-owned tools kept single-call, each with its reason.
|
|
246
|
+
* @param optional.settings - Host-owned per-tool batch settings.
|
|
247
|
+
* @returns The batchable tools, in registration order.
|
|
248
|
+
* @complexity O(r + o + s) for r registrations, o opt-outs and s settings entries.
|
|
249
|
+
*/
|
|
250
|
+
export function defaultBatchableTools({ registrations }, { optOut = [], settings = [] } = {}) {
|
|
251
|
+
const optedOut = new Set(optOut.map((entry) => entry.toolId));
|
|
252
|
+
const settingsById = new Map(settings.map((tool) => [tool.toolId, tool]));
|
|
253
|
+
return registrations
|
|
254
|
+
.filter((registration) => batchEligibilityFor({ registration }, { optOut: optedOut }) === 'batchable')
|
|
255
|
+
.map((registration) => settingsById.get(registration.descriptor.id) ?? { toolId: registration.descriptor.id });
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=batch-tool-inputs.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** A boolean rollout decision with its winning layer; feature flags never grant permissions. */
|
|
2
|
+
export interface FeatureFlagDecision {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
source: 'environment' | 'runtime' | 'workspace' | 'default';
|
|
5
|
+
}
|
|
6
|
+
/** Evaluate one flag. Environment is the deployment override; runtime precedes persisted workspace.
|
|
7
|
+
* An explicitly malformed layer disables the feature instead of falling through to an enabling layer.
|
|
8
|
+
* @param requiredArgs - Flag key for a stable caller contract (the host owns lookup).
|
|
9
|
+
* @param optionalArgs - Layer values, with absent layers represented by undefined. Defaults off.
|
|
10
|
+
* @returns The winning decision and source, with no I/O or mutation.
|
|
11
|
+
* @complexity O(1) time and space.
|
|
12
|
+
*/
|
|
13
|
+
export declare function evaluateFeatureFlag(_requiredArgs: {
|
|
14
|
+
key: string;
|
|
15
|
+
}, { environment, runtime, workspace, defaultEnabled }?: {
|
|
16
|
+
environment?: unknown;
|
|
17
|
+
runtime?: unknown;
|
|
18
|
+
workspace?: unknown;
|
|
19
|
+
defaultEnabled?: boolean;
|
|
20
|
+
}): FeatureFlagDecision;
|
|
21
|
+
//# sourceMappingURL=feature-flags.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Evaluate one flag. Environment is the deployment override; runtime precedes persisted workspace.
|
|
2
|
+
* An explicitly malformed layer disables the feature instead of falling through to an enabling layer.
|
|
3
|
+
* @param requiredArgs - Flag key for a stable caller contract (the host owns lookup).
|
|
4
|
+
* @param optionalArgs - Layer values, with absent layers represented by undefined. Defaults off.
|
|
5
|
+
* @returns The winning decision and source, with no I/O or mutation.
|
|
6
|
+
* @complexity O(1) time and space.
|
|
7
|
+
*/
|
|
8
|
+
export function evaluateFeatureFlag(_requiredArgs, { environment, runtime, workspace, defaultEnabled = false } = {}) {
|
|
9
|
+
if (environment !== undefined)
|
|
10
|
+
return { enabled: environment === 'true' || environment === '1', source: 'environment' };
|
|
11
|
+
if (runtime !== undefined)
|
|
12
|
+
return { enabled: runtime === true, source: 'runtime' };
|
|
13
|
+
if (workspace !== undefined)
|
|
14
|
+
return { enabled: workspace === true, source: 'workspace' };
|
|
15
|
+
return { enabled: defaultEnabled === true, source: 'default' };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=feature-flags.js.map
|
package/dist/fetch/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Framework-neutral query contracts and cache; hosts supply transport callbacks and environment ports. */
|
|
2
2
|
export { DEFAULT_STALE_TIME, IDLE_RETENTION, FetchQueryCache, toQueryError } from './cache.js';
|
|
3
3
|
export type { QueryCacheSchedulerPort, FetchQueryEnvironmentPort, FetchQueryCacheOptions, QueryState, QueryObserver, CacheEntry } from './cache.js';
|
|
4
|
-
export type { QueryKey, QueryStatus, QueryResult, FetchQueryOptions, MutationStatus, MutationResult, FetchMutationOptions, CachedLoaderOptions, CachedLoader, FetchQueryAdapter } from './types.js';
|
|
4
|
+
export type { QueryKey, QueryStatus, QueryResult, FetchQueryOptions, MutationStatus, MutationResult, FetchMutationOptions, CachedLoaderOptions, CachedLoaderDefaults, CachedLoader, FetchQueryAdapter } from './types.js';
|
|
5
|
+
export { missingCachedLoaderFetchError, withCachedLoaderFetch } from './loader-read.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|