@tanstack/ai
Version:
Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.
80 lines (79 loc) • 2.76 kB
JavaScript
//#region src/activities/chat/middleware/capabilities.ts
/**
* Per-request bookkeeping: which capabilities were provided, plus the
* duplicate-provide notification. Capability VALUES live in per-capability
* WeakMaps (see `createCapability`), not here — this only tracks presence.
*/
var CapabilityRegistry = class {
provided = /* @__PURE__ */ new Set();
onDuplicate;
/** Register a callback fired when a handle is provided more than once. */
setOnDuplicate(cb) {
this.onDuplicate = cb;
}
/** Record that `handle` was provided; fire the duplicate callback on repeats. */
markProvided(handle) {
if (this.provided.has(handle)) this.onDuplicate?.(handle.capabilityName);
this.provided.add(handle);
}
has(handle) {
return this.provided.has(handle);
}
};
/**
* Create a capability. Returns a hybrid handle that destructures to
* `[get, provide]` and is itself the identity for `requires`/`provides`.
*
* Curried so the value type is supplied explicitly while the name literal is
* INFERRED from the argument: `createCapability<T>()('name')`. (A single call
* `createCapability<T>('name')` cannot work — supplying `T` explicitly stops
* TypeScript inferring the name, collapsing it to `string` and defeating the
* compile-time coverage check that keys on the literal name.)
*
* @example Provider + consumer middleware
* ```ts
* const counterCapability = createCapability<{ value: number }>()('counter')
* const [getCounter, provideCounter] = counterCapability
*
* const withCounter = defineChatMiddleware({
* name: 'counter',
* provides: [counterCapability],
* setup(ctx) { provideCounter(ctx, { value: 0 }) },
* })
*
* const readsCounter = defineChatMiddleware({
* name: 'reads-counter',
* requires: [counterCapability],
* onChunk(ctx) { getCounter(ctx).value++ },
* })
*
* chat({ adapter, messages, middleware: [withCounter, readsCounter] })
* ```
*
* @remarks Capability `name`s must be unique across your app: compile-time
* coverage tracking keys on the name literal (runtime keys on reference).
*/
function createCapability() {
return (name) => {
const values = /* @__PURE__ */ new WeakMap();
function get(ctx, opts) {
if (!values.has(ctx)) {
if (opts?.optional) return void 0;
throw new Error(`Capability "${name}" was requested but never provided. Ensure a middleware provides it in setup(), ordered before this consumer.`);
}
return values.get(ctx);
}
const provide = (ctx, value) => {
values.set(ctx, value);
ctx.capabilities.markProvided(handle);
};
const handle = Object.assign([get, provide], {
capabilityName: name,
has: (ctx) => values.has(ctx)
});
return handle;
};
}
//#endregion
export { CapabilityRegistry, createCapability };
//# sourceMappingURL=capabilities.js.map