@dudousxd/nestjs-agent-core 0.25.0 → 0.27.0

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/README.md CHANGED
@@ -19,7 +19,9 @@ import type { ModelProvider, AgentStore, ToolSpec, RolesPolicy } from '@dudousxd
19
19
  ## Key types
20
20
 
21
21
  - `ToolSpec` — `{ name, kind: 'read' | 'action' | 'agent', description, inputSchema, roles?, ability?, targetAgent?, detached?, terminal? }`. (`ToolKind` has three further members — `'ask'`, `'skill'` and `'memory'` — which no `ToolSpec` carries: they belong to the built-in tools the loop serves itself rather than from a handler. `ask` settles against a person, `skill` against the turn's journaled catalog, `remember` against the turn's journaled memory digest.) `inputSchema` is a [Standard Schema](https://standardschema.dev) (Zod, Valibot, ArkType); the loop validates via `~standard.validate`, throwing `ToolInputInvalidError` on failure.
22
- - `AiToolCtx.emitUi(component, props, { id?, version? })` — **a tool pushes generative UI.** The `ui` frame streams live (inline, durable, and from a dispatched tool step's worker) and the component is persisted on the assistant message through the optional `AgentStore.setMessageUi`, once per step, after the step's tools settle. `id` defaults to `<toolCallId>:ui:<n>`, so a retried call replaces what it pushed. The pushes ride the tool step's journaled result (`wrapToolStepOutput` / `unwrapToolStepOutput`; the wrapper is only used when something was pushed), so a replay neither re-streams nor re-persists them. `ToolSpec.terminal` ends the turn after a successful call, settled in `persist:toolcall` like `detached`.
22
+ - `AiToolCtx.emitUi(component, props, { id?, version? })` — **a tool pushes generative UI.** The `ui` frame streams live (inline, durable, and from a dispatched tool step's worker) and the component is persisted on the assistant message through the optional `AgentStore.setMessageUi`, once per step, after the step's tools settle. `id` defaults to `<toolCallId>:ui:<n>`, so a retried call replaces what it pushed. The pushes ride the tool step's journaled result (`wrapToolStepOutput` / `unwrapToolStepOutput`; the wrapper is only used when something was pushed), so a replay neither re-streams nor re-persists them. `ToolSpec.terminal` ends the turn after a successful call, settled in `persist:toolcall` like `detached`. `emitUi` is always present: where there is no conversation (the MCP server, a bare `registry.invoke`) it is `createNoopEmitUi()`, which pushes nothing and still resolves to an id.
23
+ - `ToolHandler.describe?({ actor, threadId?, agentName? })` — **a per-turn description.** Called when the turn's tool list is built, after every gate; what it returns replaces the spec's `description` / `inputSchema` in what the model sees (`registry.definitionsFor(actor, policy, allowList, { threadId, agentName })`). The registry still validates calls against the registered schema, so a tool whose input varies per turn registers a permissive one and validates in `execute`.
24
+ - `@dudousxd/nestjs-agent-core/genui` (+ `/genui/builtins`) — **the generative-UI catalog**, an isomorphic entry (no server-only import; a spec holds that line) a browser imports too: `defineComponent` / `defineCatalog` (Standard Schema or JSON Schema props), validation, `catalogToModelText`, `componentToText` fallbacks, tree helpers, and `genuiTools(catalog, { mode, terminal, showTool, resolveCatalog, … })` — the tools that push components, with an optional per-request catalog resolver consulted on every call and every turn's description. NestJS apps use `AgentGenuiModule` (`@dudousxd/nestjs-agent/genui`) instead of calling `genuiTools` themselves.
23
25
  - `AgentDefinition` — a named agent (`systemPrompt` string | `PromptBuilder`, `tools`, `delegatesTo`, `personas`, …) for multi-agent setups. `delegatesTo` holds `AgentDelegation` entries: a bare target name for the delegation that waits, `{ agent, detached: true }` for one that does not.
24
26
  - `detachedStarted` / `detachedDelivered` / `settleUnsettledDelegation` / `AgentLoopHooks.startAgent` / `AgentRunInput.deliverTo` — **delegation that does not block the chat.** An `agent`-kind call whose spec says `detached` is STARTED rather than awaited (`hooks.startAgent`, which the durable runner maps to `ctx.startChild` and the inline one to a loop nobody awaits), and the turn ends with a `DetachedDelegationReceipt` as the call's result instead of an answer. The started run carries a `deliverTo` address — the delegating thread and the call that started it — and posts its answer there as a message of its own, stamped with its own `runId` and `agentName`, so a client renders "the research agent finished" rather than the assistant's next reply. Whether a call detaches is settled INSIDE `persist:toolcall` alongside its kind and target, never from a live registry lookup, so a replay reads the branch back rather than re-deciding it; the loop writes the same checkpoint names either way, and only the runner's own positions (`spawn:` versus the awaited child's `signal:child:`) differ. A detached run streams into its OWN sink and its `action` tools park on its OWN run, so its approval reaches the pending-approvals surface instead of an inline card in whatever turn happens to be open. `settleUnsettledDelegation` is the runner's half: a run that crashed or was stopped posts a message saying so, because "started" is the one state a reader can neither wait on nor act on. `AgentRunInput.parentRunId` / `RecordRunStartInput.parentRunId` record the edge, so a delegation is not a run row with nothing pointing at it.
25
27
  - `RolesPolicy.can(actor, tool): boolean | Promise<boolean>` — the tool authorization seam
@@ -0,0 +1,147 @@
1
+ import { StandardSchemaV1 } from '@standard-schema/spec';
2
+
3
+ /** A JSON Schema document (draft-07 / 2020-12 subset). Plain data, so a catalog can be stored and shipped. */
4
+ type JsonSchema = Record<string, unknown>;
5
+ /** One problem with a value, addressed by its path from the root of the value. */
6
+ interface GenuiIssue {
7
+ path: (string | number)[];
8
+ message: string;
9
+ }
10
+ type GenuiValidation<T = unknown> = {
11
+ ok: true;
12
+ value: T;
13
+ } | {
14
+ ok: false;
15
+ issues: GenuiIssue[];
16
+ };
17
+ /**
18
+ * Validates a value against a JSON Schema. The package ships {@link builtinJsonSchemaValidator} (a
19
+ * dependency-free subset that covers what component props use); pass {@link ajvValidator} to a
20
+ * catalog for full JSON Schema.
21
+ */
22
+ interface JsonSchemaValidator {
23
+ validate(schema: JsonSchema, value: unknown): GenuiIssue[];
24
+ }
25
+ /** A component's props schema: any Standard Schema (Zod, Valibot, ArkType) or a JSON Schema object. */
26
+ type PropsSchema = StandardSchemaV1 | JsonSchema;
27
+ declare function isStandardSchema(schema: unknown): schema is StandardSchemaV1;
28
+ /**
29
+ * The JSON Schema a props schema presents to a model: the schema itself for a JSON Schema, the
30
+ * Standard JSON Schema converter's output for a Standard Schema that has one (Zod 4, Valibot,
31
+ * ArkType). `undefined` when neither is available (e.g. Zod 3) — the model then gets the
32
+ * component's description only.
33
+ */
34
+ declare function toJsonSchema(schema: PropsSchema): JsonSchema | undefined;
35
+ /** Validate against either kind of props schema. A Standard Schema's own (possibly transformed) output wins. */
36
+ declare function validateProps(schema: PropsSchema, value: unknown, validator: JsonSchemaValidator): Promise<GenuiValidation>;
37
+ /**
38
+ * {@link validateProps} without waiting: the verdict when it is available synchronously (always
39
+ * for JSON Schema, and for every Standard Schema whose `validate` does not return a promise — Zod,
40
+ * Valibot and ArkType all answer synchronously for synchronous schemas), `undefined` otherwise. What
41
+ * a renderer uses to decide on its first paint instead of flashing a placeholder.
42
+ */
43
+ declare function validatePropsSync(schema: PropsSchema, value: unknown, validator: JsonSchemaValidator): GenuiValidation | undefined;
44
+ /** `a.b[0].c: message; …` — what a model is handed back when its props were refused. */
45
+ declare function formatIssues(issues: readonly GenuiIssue[]): string;
46
+ /**
47
+ * Structural type of an Ajv instance, so the package never imports Ajv: `new Ajv({ allErrors: true })`
48
+ * satisfies it.
49
+ */
50
+ interface AjvLike {
51
+ compile(schema: object): ((data: unknown) => boolean | Promise<unknown>) & {
52
+ errors?: {
53
+ instancePath?: string;
54
+ message?: string;
55
+ }[] | null;
56
+ };
57
+ }
58
+ /**
59
+ * Full JSON Schema validation through the app's own Ajv instance. Compiled validators are cached
60
+ * per schema object.
61
+ */
62
+ declare function ajvValidator(ajv: AjvLike): JsonSchemaValidator;
63
+ /**
64
+ * A dependency-free JSON Schema validator for the keywords component props actually use: `type`
65
+ * (incl. `integer`, `null`, type arrays), `enum`, `const`, `properties`, `required`,
66
+ * `additionalProperties`, `items`, `minItems`/`maxItems`, `minLength`/`maxLength`/`pattern`,
67
+ * `minimum`/`maximum`, `anyOf`/`oneOf`/`allOf`, `not`, and local `$ref`s (`#`, `#/$defs/…`,
68
+ * `#/definitions/…`). Unknown keywords (e.g. `format`) are ignored. Use {@link ajvValidator} when
69
+ * you need the rest.
70
+ */
71
+ declare const builtinJsonSchemaValidator: JsonSchemaValidator;
72
+
73
+ /**
74
+ * One component a server may push into a conversation. Data only — how it LOOKS is each app's own
75
+ * renderer, looked up by {@link name}. The same definition serves the model (description + props
76
+ * schema), the server (validation), text-only channels ({@link fallbackText}) and the client
77
+ * (validation before rendering).
78
+ */
79
+ interface ComponentDefinition<P = Record<string, unknown>> {
80
+ /** Registry key, e.g. `DataTable`. What a `ui` frame's `component` names. */
81
+ name: string;
82
+ /** Human label: "Data table". */
83
+ title: string;
84
+ /** Shown to the model: what the component is for and when to use it. */
85
+ description: string;
86
+ /** The props schema: a Standard Schema (Zod, Valibot, ArkType) or a JSON Schema object. */
87
+ props: PropsSchema;
88
+ /**
89
+ * The component takes nested elements (a layout: `Stack`, `Card`). Only meaningful in tree mode,
90
+ * where a node's `children` are validated against the catalog too.
91
+ */
92
+ children?: boolean;
93
+ /**
94
+ * Plain text / Slack mrkdwn rendering of these props, for a channel that cannot draw the
95
+ * component. Absent → {@link componentToText} prints the props as JSON.
96
+ */
97
+ fallbackText?: (props: P) => string;
98
+ /**
99
+ * Pushed by the server (e.g. an approval card), never offered to the model. Excluded from
100
+ * {@link catalogToModelText} and from the tool factories.
101
+ */
102
+ internal?: boolean;
103
+ /**
104
+ * Schema version of {@link props}. Stamped on every `ui` frame the component is pushed with, so a
105
+ * client can keep rendering messages persisted under an older shape.
106
+ */
107
+ version?: number;
108
+ }
109
+ /** Component names: an identifier a registry key, a tool name and a snake_case slug can all be derived from. */
110
+ declare const COMPONENT_NAME: RegExp;
111
+ /** Declare a component. Validates the name and returns the definition unchanged (typed). */
112
+ declare function defineComponent<P = Record<string, unknown>>(definition: ComponentDefinition<P>): ComponentDefinition<P>;
113
+ interface CatalogOptions {
114
+ /** How JSON Schema props are checked. Default: {@link builtinJsonSchemaValidator}; pass `ajvValidator(new Ajv())` for full JSON Schema. */
115
+ jsonSchemaValidator?: JsonSchemaValidator;
116
+ }
117
+ /** An immutable set of component definitions, looked up by name. */
118
+ interface Catalog {
119
+ readonly components: readonly ComponentDefinition<any>[];
120
+ readonly validator: JsonSchemaValidator;
121
+ get(name: string): ComponentDefinition<any> | undefined;
122
+ has(name: string): boolean;
123
+ /** The components a model may be offered (every non-`internal` one). */
124
+ modelComponents(): ComponentDefinition<any>[];
125
+ /** Validate `props` for `component`. An unknown component is a validation failure, not a throw. */
126
+ validate(component: string, props: unknown): Promise<GenuiValidation<Record<string, unknown>>>;
127
+ /**
128
+ * {@link validate} without waiting — `undefined` only when the component's Standard Schema
129
+ * validates asynchronously. An unknown component is a failure, as in `validate`.
130
+ */
131
+ validateSync(component: string, props: unknown): GenuiValidation<Record<string, unknown>> | undefined;
132
+ /** The component's props as JSON Schema, when it can be derived (see {@link toJsonSchema}). */
133
+ jsonSchemaFor(component: string): JsonSchema | undefined;
134
+ /** A new catalog with these components added (a later definition replaces an earlier one of the same name). */
135
+ extend(components: readonly ComponentDefinition<any>[]): Catalog;
136
+ }
137
+ /**
138
+ * Collect component definitions into a {@link Catalog}. Names must be unique; to override a
139
+ * definition (e.g. a builtin), use {@link Catalog.extend}.
140
+ */
141
+ declare function defineCatalog(components: readonly ComponentDefinition<any>[], options?: CatalogOptions): Catalog;
142
+ /** `DataTable` → `data_table`, `KPICards` → `kpi_cards`. */
143
+ declare function toSnakeCase(name: string): string;
144
+ /** The per-component tool name: `ui__show_data_table` with the default prefix. */
145
+ declare function toolNameFor(component: string, prefix?: string): string;
146
+
147
+ export { type AjvLike as A, type ComponentDefinition as C, type GenuiValidation as G, type JsonSchema as J, type PropsSchema as P, type Catalog as a, type GenuiIssue as b, COMPONENT_NAME as c, type CatalogOptions as d, type JsonSchemaValidator as e, ajvValidator as f, builtinJsonSchemaValidator as g, defineCatalog as h, defineComponent as i, formatIssues as j, isStandardSchema as k, toSnakeCase as l, toolNameFor as m, validatePropsSync as n, toJsonSchema as t, validateProps as v };
@@ -0,0 +1,147 @@
1
+ import { StandardSchemaV1 } from '@standard-schema/spec';
2
+
3
+ /** A JSON Schema document (draft-07 / 2020-12 subset). Plain data, so a catalog can be stored and shipped. */
4
+ type JsonSchema = Record<string, unknown>;
5
+ /** One problem with a value, addressed by its path from the root of the value. */
6
+ interface GenuiIssue {
7
+ path: (string | number)[];
8
+ message: string;
9
+ }
10
+ type GenuiValidation<T = unknown> = {
11
+ ok: true;
12
+ value: T;
13
+ } | {
14
+ ok: false;
15
+ issues: GenuiIssue[];
16
+ };
17
+ /**
18
+ * Validates a value against a JSON Schema. The package ships {@link builtinJsonSchemaValidator} (a
19
+ * dependency-free subset that covers what component props use); pass {@link ajvValidator} to a
20
+ * catalog for full JSON Schema.
21
+ */
22
+ interface JsonSchemaValidator {
23
+ validate(schema: JsonSchema, value: unknown): GenuiIssue[];
24
+ }
25
+ /** A component's props schema: any Standard Schema (Zod, Valibot, ArkType) or a JSON Schema object. */
26
+ type PropsSchema = StandardSchemaV1 | JsonSchema;
27
+ declare function isStandardSchema(schema: unknown): schema is StandardSchemaV1;
28
+ /**
29
+ * The JSON Schema a props schema presents to a model: the schema itself for a JSON Schema, the
30
+ * Standard JSON Schema converter's output for a Standard Schema that has one (Zod 4, Valibot,
31
+ * ArkType). `undefined` when neither is available (e.g. Zod 3) — the model then gets the
32
+ * component's description only.
33
+ */
34
+ declare function toJsonSchema(schema: PropsSchema): JsonSchema | undefined;
35
+ /** Validate against either kind of props schema. A Standard Schema's own (possibly transformed) output wins. */
36
+ declare function validateProps(schema: PropsSchema, value: unknown, validator: JsonSchemaValidator): Promise<GenuiValidation>;
37
+ /**
38
+ * {@link validateProps} without waiting: the verdict when it is available synchronously (always
39
+ * for JSON Schema, and for every Standard Schema whose `validate` does not return a promise — Zod,
40
+ * Valibot and ArkType all answer synchronously for synchronous schemas), `undefined` otherwise. What
41
+ * a renderer uses to decide on its first paint instead of flashing a placeholder.
42
+ */
43
+ declare function validatePropsSync(schema: PropsSchema, value: unknown, validator: JsonSchemaValidator): GenuiValidation | undefined;
44
+ /** `a.b[0].c: message; …` — what a model is handed back when its props were refused. */
45
+ declare function formatIssues(issues: readonly GenuiIssue[]): string;
46
+ /**
47
+ * Structural type of an Ajv instance, so the package never imports Ajv: `new Ajv({ allErrors: true })`
48
+ * satisfies it.
49
+ */
50
+ interface AjvLike {
51
+ compile(schema: object): ((data: unknown) => boolean | Promise<unknown>) & {
52
+ errors?: {
53
+ instancePath?: string;
54
+ message?: string;
55
+ }[] | null;
56
+ };
57
+ }
58
+ /**
59
+ * Full JSON Schema validation through the app's own Ajv instance. Compiled validators are cached
60
+ * per schema object.
61
+ */
62
+ declare function ajvValidator(ajv: AjvLike): JsonSchemaValidator;
63
+ /**
64
+ * A dependency-free JSON Schema validator for the keywords component props actually use: `type`
65
+ * (incl. `integer`, `null`, type arrays), `enum`, `const`, `properties`, `required`,
66
+ * `additionalProperties`, `items`, `minItems`/`maxItems`, `minLength`/`maxLength`/`pattern`,
67
+ * `minimum`/`maximum`, `anyOf`/`oneOf`/`allOf`, `not`, and local `$ref`s (`#`, `#/$defs/…`,
68
+ * `#/definitions/…`). Unknown keywords (e.g. `format`) are ignored. Use {@link ajvValidator} when
69
+ * you need the rest.
70
+ */
71
+ declare const builtinJsonSchemaValidator: JsonSchemaValidator;
72
+
73
+ /**
74
+ * One component a server may push into a conversation. Data only — how it LOOKS is each app's own
75
+ * renderer, looked up by {@link name}. The same definition serves the model (description + props
76
+ * schema), the server (validation), text-only channels ({@link fallbackText}) and the client
77
+ * (validation before rendering).
78
+ */
79
+ interface ComponentDefinition<P = Record<string, unknown>> {
80
+ /** Registry key, e.g. `DataTable`. What a `ui` frame's `component` names. */
81
+ name: string;
82
+ /** Human label: "Data table". */
83
+ title: string;
84
+ /** Shown to the model: what the component is for and when to use it. */
85
+ description: string;
86
+ /** The props schema: a Standard Schema (Zod, Valibot, ArkType) or a JSON Schema object. */
87
+ props: PropsSchema;
88
+ /**
89
+ * The component takes nested elements (a layout: `Stack`, `Card`). Only meaningful in tree mode,
90
+ * where a node's `children` are validated against the catalog too.
91
+ */
92
+ children?: boolean;
93
+ /**
94
+ * Plain text / Slack mrkdwn rendering of these props, for a channel that cannot draw the
95
+ * component. Absent → {@link componentToText} prints the props as JSON.
96
+ */
97
+ fallbackText?: (props: P) => string;
98
+ /**
99
+ * Pushed by the server (e.g. an approval card), never offered to the model. Excluded from
100
+ * {@link catalogToModelText} and from the tool factories.
101
+ */
102
+ internal?: boolean;
103
+ /**
104
+ * Schema version of {@link props}. Stamped on every `ui` frame the component is pushed with, so a
105
+ * client can keep rendering messages persisted under an older shape.
106
+ */
107
+ version?: number;
108
+ }
109
+ /** Component names: an identifier a registry key, a tool name and a snake_case slug can all be derived from. */
110
+ declare const COMPONENT_NAME: RegExp;
111
+ /** Declare a component. Validates the name and returns the definition unchanged (typed). */
112
+ declare function defineComponent<P = Record<string, unknown>>(definition: ComponentDefinition<P>): ComponentDefinition<P>;
113
+ interface CatalogOptions {
114
+ /** How JSON Schema props are checked. Default: {@link builtinJsonSchemaValidator}; pass `ajvValidator(new Ajv())` for full JSON Schema. */
115
+ jsonSchemaValidator?: JsonSchemaValidator;
116
+ }
117
+ /** An immutable set of component definitions, looked up by name. */
118
+ interface Catalog {
119
+ readonly components: readonly ComponentDefinition<any>[];
120
+ readonly validator: JsonSchemaValidator;
121
+ get(name: string): ComponentDefinition<any> | undefined;
122
+ has(name: string): boolean;
123
+ /** The components a model may be offered (every non-`internal` one). */
124
+ modelComponents(): ComponentDefinition<any>[];
125
+ /** Validate `props` for `component`. An unknown component is a validation failure, not a throw. */
126
+ validate(component: string, props: unknown): Promise<GenuiValidation<Record<string, unknown>>>;
127
+ /**
128
+ * {@link validate} without waiting — `undefined` only when the component's Standard Schema
129
+ * validates asynchronously. An unknown component is a failure, as in `validate`.
130
+ */
131
+ validateSync(component: string, props: unknown): GenuiValidation<Record<string, unknown>> | undefined;
132
+ /** The component's props as JSON Schema, when it can be derived (see {@link toJsonSchema}). */
133
+ jsonSchemaFor(component: string): JsonSchema | undefined;
134
+ /** A new catalog with these components added (a later definition replaces an earlier one of the same name). */
135
+ extend(components: readonly ComponentDefinition<any>[]): Catalog;
136
+ }
137
+ /**
138
+ * Collect component definitions into a {@link Catalog}. Names must be unique; to override a
139
+ * definition (e.g. a builtin), use {@link Catalog.extend}.
140
+ */
141
+ declare function defineCatalog(components: readonly ComponentDefinition<any>[], options?: CatalogOptions): Catalog;
142
+ /** `DataTable` → `data_table`, `KPICards` → `kpi_cards`. */
143
+ declare function toSnakeCase(name: string): string;
144
+ /** The per-component tool name: `ui__show_data_table` with the default prefix. */
145
+ declare function toolNameFor(component: string, prefix?: string): string;
146
+
147
+ export { type AjvLike as A, type ComponentDefinition as C, type GenuiValidation as G, type JsonSchema as J, type PropsSchema as P, type Catalog as a, type GenuiIssue as b, COMPONENT_NAME as c, type CatalogOptions as d, type JsonSchemaValidator as e, ajvValidator as f, builtinJsonSchemaValidator as g, defineCatalog as h, defineComponent as i, formatIssues as j, isStandardSchema as k, toSnakeCase as l, toolNameFor as m, validatePropsSync as n, toJsonSchema as t, validateProps as v };