@dudousxd/nestjs-agent 1.7.0 → 1.9.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
@@ -70,6 +70,39 @@ tools THIS actor can reach through that agent (the default agent when omitted; `
70
70
  one), built by `ToolRegistry.visibleSpecs`, the same gates the model is offered tools through. The
71
71
  React package's `useToolCatalog` reads it. The model never sees `presentation`.
72
72
 
73
+ ### Generative UI
74
+
75
+ A tool pushes a component into the answer with `ctx.emitUi(component, props)` — always present; a
76
+ no-op where there is no conversation (MCP). To let the MODEL compose UI from a catalog, import
77
+ `AgentGenuiModule` from `@dudousxd/nestjs-agent/genui` next to `AgentModule`:
78
+
79
+ ```ts
80
+ // catalog.ts — isomorphic: the React app imports this same file
81
+ import { defineCatalog, defineComponent } from '@dudousxd/nestjs-agent-core/genui';
82
+ import { BUILTIN_COMPONENTS } from '@dudousxd/nestjs-agent-core/genui/builtins';
83
+ export const catalog = defineCatalog([...BUILTIN_COMPONENTS, DealCard]);
84
+
85
+ // app.module.ts
86
+ AgentGenuiModule.forRoot({ catalog, mode: 'tree', terminal: true });
87
+ ```
88
+
89
+ It registers the tools (`ui__show_<component>` per component by default, one `ui__render` in `tree`
90
+ mode, plus a generic `ui__show` taking `{ component, props }` with `showTool: true`), validating
91
+ every call against the catalog. Options: `mode`, `terminal`, `treeToolName`, `treeInstructions`,
92
+ `treeLimits`, `namePrefix`, `showTool`, `showInstructions`, `roles`, `presentation`.
93
+ `forRootAsync({ imports, inject, useFactory })` builds them from config. The catalog is injectable
94
+ (`@InjectGenuiCatalog()`, token `GENUI_CATALOG`) and replaceable in a test with
95
+ `overrideProvider(GENUI_CATALOG)` — the tools are built from the injected one.
96
+
97
+ **Per-request catalogs.** Pass `resolver` (a class, resolved with DI, or an instance) extending
98
+ `GenuiCatalogResolver` — `resolve({ actor, threadId, tenant, agentName }) → Catalog | Promise<Catalog>`
99
+ (`tenant` is `actor.tenantRef`). It is consulted when a call is validated and when a turn's tool list
100
+ is described to the model (the core's `ToolHandler.describe`), so tenant-defined, versioned
101
+ components work without rebuilding tools at boot: the tree tool's and the show tool's descriptions
102
+ and schemas list the tenant's components for that turn, and a pushed component carries the resolved
103
+ definition's `version`. A tenant's own components are reached through the tree tool or
104
+ `showTool: true` — no boot-time tool can name them. Cache inside `resolve` if it costs a round trip.
105
+
73
106
  ### Message attachments
74
107
 
75
108
  A chat turn attaches a file by **`mediaId` only**:
@@ -179,6 +212,25 @@ provider's default; the id reaches the provider as `ModelTurnArgs.model` for eve
179
212
  A model the catalog does not list as available for that actor and agent is refused with `400` —
180
213
  when pinned and again on each turn. `staticModelCatalog(view)` wraps a fixed list.
181
214
 
215
+ ### Budgets: `GET /agent/quota`
216
+
217
+ `GET /agent/quota` reports `{ windows: [{ period: 'day' | 'month', usedTokens, limitTokens?,
218
+ usedUsd, limitUsd?, resetsAt? }], blocked? }` from a `QuotaProvider`. The default,
219
+ `LedgerQuotaProvider`, reads the usage ledger (day, plus month when the store has `usageBetween`)
220
+ and only reports. Configure it and it also gates sends — a `blocked` report refuses `POST
221
+ /agent/chat` with `429 { code: 'quota_exceeded', period }` before the turn starts:
222
+
223
+ ```ts
224
+ AgentModule.forRoot({
225
+ // ceilings on the default ledger provider
226
+ quotaLimits: { day: { tokens: 200_000 }, month: { usd: 20 } },
227
+ // …or your own budget, e.g. an AI-gateway spend cap:
228
+ // quotaProvider: { report: async ({ actor }) => myGatewayBudget(actor.id) },
229
+ });
230
+ ```
231
+
232
+ `GET /agent/quota/today` stays as it was.
233
+
182
234
  ### Who approves an action, and for how long
183
235
 
184
236
  Every `action` tool call waits on the person chatting by default. `approvalPolicy` changes that per
@@ -1,4 +1,4 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ModelCatalog, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
1
+ import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, QuotaProvider, QuotaQuery, QuotaReport, ModelCatalog, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
4
4
 
@@ -86,6 +86,42 @@ interface FunctionalTool {
86
86
  */
87
87
  declare function provideAgentTool(tool: FunctionalTool): Provider;
88
88
  declare function provideAgentTool(factory: (...deps: never[]) => FunctionalTool, inject?: FactoryProvider['inject']): Provider;
89
+ /**
90
+ * Registers a LIST of functional tools from one factory — for a producer whose tool count is only
91
+ * known once its dependencies resolve (a catalog loaded from config, one tool per entry). The
92
+ * factory's result is branded element by element; `AiToolDiscoveryService` registers each.
93
+ */
94
+ declare function provideAgentTools(factory: (...deps: never[]) => readonly FunctionalTool[] | Promise<readonly FunctionalTool[]>, inject?: FactoryProvider['inject']): Provider;
95
+
96
+ /** Ceilings for a {@link LedgerQuotaProvider} window. Either, both, or neither. */
97
+ interface QuotaWindowLimits {
98
+ tokens?: number;
99
+ usd?: number;
100
+ }
101
+ /** Per-window ceilings: `AgentModule.forRoot({ quotaLimits })`. */
102
+ interface QuotaLimits {
103
+ day?: QuotaWindowLimits;
104
+ month?: QuotaWindowLimits;
105
+ }
106
+ /**
107
+ * The default {@link QuotaProvider}: windows read off the usage ledger the loop already writes.
108
+ *
109
+ * - `day` — always; `usedTokens`/limit come from the bound {@link QuotaStore} when there is one (so
110
+ * the report matches what the loop enforces), spend from the ledger.
111
+ * - `month` — when the store implements `usageBetween`.
112
+ *
113
+ * `limits` add ceilings per window (tokens and/or USD). A window that reaches one makes the report
114
+ * `blocked`.
115
+ */
116
+ declare class LedgerQuotaProvider implements QuotaProvider {
117
+ private readonly store;
118
+ private readonly quota?;
119
+ private readonly limits;
120
+ constructor(store: AgentStore, quota?: QuotaStore | undefined, limits?: QuotaLimits);
121
+ report(query: QuotaQuery): Promise<QuotaReport>;
122
+ private dayWindow;
123
+ private window;
124
+ }
89
125
 
90
126
  /**
91
127
  * Which half of the module this process mounts — the split that lets an API pod and a WORKER pod
@@ -205,6 +241,17 @@ interface AgentModuleOptions {
205
241
  * `ModelTurnArgs.model`. Omit → an empty catalog, and a request naming a model is refused.
206
242
  */
207
243
  models?: ModelCatalog;
244
+ /**
245
+ * The actor's budget across windows, for `GET <base>/quota` — and, once set, a gate: a send whose
246
+ * report comes back `blocked` is refused with `429` before the turn starts. Default: a
247
+ * {@link LedgerQuotaProvider} over the usage ledger (reporting only, unless `quotaLimits` is set).
248
+ */
249
+ quotaProvider?: QuotaProvider;
250
+ /**
251
+ * Ceilings for the default ledger provider's windows — `{ day?: { tokens?, usd? }, month?: {
252
+ * tokens?, usd? } }`. Setting it turns the send gate on. Ignored when `quotaProvider` is given.
253
+ */
254
+ quotaLimits?: QuotaLimits;
208
255
  /**
209
256
  * Daily per-actor token budget, enforced against the persisted usage ledger by the built-in
210
257
  * `LedgerQuotaStore`. A convenience over wiring a {@link QuotaStore} by hand — set this and quotas
@@ -479,4 +526,4 @@ declare class AgentDepsFactory {
479
526
  private followUpsCount;
480
527
  }
481
528
 
482
- export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, type AgentSkillsOptions as d, AGENT_TOOL_BRAND as e, type AgentAttachmentsOptions as f, type AgentDeps as g, type AgentMemoryOptions as h, delegateToolName as i, provideAgentTool as p, utcDay as u };
529
+ export { AgentDepsFactory as A, type FunctionalTool as F, LedgerQuotaProvider as L, type QuotaLimits as Q, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, type AgentSkillsOptions as d, AGENT_TOOL_BRAND as e, type AgentAttachmentsOptions as f, type AgentDeps as g, type AgentMemoryOptions as h, type QuotaWindowLimits as i, delegateToolName as j, provideAgentTools as k, provideAgentTool as p, utcDay as u };
@@ -1,4 +1,4 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ModelCatalog, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
1
+ import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, QuotaProvider, QuotaQuery, QuotaReport, ModelCatalog, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
4
4
 
@@ -86,6 +86,42 @@ interface FunctionalTool {
86
86
  */
87
87
  declare function provideAgentTool(tool: FunctionalTool): Provider;
88
88
  declare function provideAgentTool(factory: (...deps: never[]) => FunctionalTool, inject?: FactoryProvider['inject']): Provider;
89
+ /**
90
+ * Registers a LIST of functional tools from one factory — for a producer whose tool count is only
91
+ * known once its dependencies resolve (a catalog loaded from config, one tool per entry). The
92
+ * factory's result is branded element by element; `AiToolDiscoveryService` registers each.
93
+ */
94
+ declare function provideAgentTools(factory: (...deps: never[]) => readonly FunctionalTool[] | Promise<readonly FunctionalTool[]>, inject?: FactoryProvider['inject']): Provider;
95
+
96
+ /** Ceilings for a {@link LedgerQuotaProvider} window. Either, both, or neither. */
97
+ interface QuotaWindowLimits {
98
+ tokens?: number;
99
+ usd?: number;
100
+ }
101
+ /** Per-window ceilings: `AgentModule.forRoot({ quotaLimits })`. */
102
+ interface QuotaLimits {
103
+ day?: QuotaWindowLimits;
104
+ month?: QuotaWindowLimits;
105
+ }
106
+ /**
107
+ * The default {@link QuotaProvider}: windows read off the usage ledger the loop already writes.
108
+ *
109
+ * - `day` — always; `usedTokens`/limit come from the bound {@link QuotaStore} when there is one (so
110
+ * the report matches what the loop enforces), spend from the ledger.
111
+ * - `month` — when the store implements `usageBetween`.
112
+ *
113
+ * `limits` add ceilings per window (tokens and/or USD). A window that reaches one makes the report
114
+ * `blocked`.
115
+ */
116
+ declare class LedgerQuotaProvider implements QuotaProvider {
117
+ private readonly store;
118
+ private readonly quota?;
119
+ private readonly limits;
120
+ constructor(store: AgentStore, quota?: QuotaStore | undefined, limits?: QuotaLimits);
121
+ report(query: QuotaQuery): Promise<QuotaReport>;
122
+ private dayWindow;
123
+ private window;
124
+ }
89
125
 
90
126
  /**
91
127
  * Which half of the module this process mounts — the split that lets an API pod and a WORKER pod
@@ -205,6 +241,17 @@ interface AgentModuleOptions {
205
241
  * `ModelTurnArgs.model`. Omit → an empty catalog, and a request naming a model is refused.
206
242
  */
207
243
  models?: ModelCatalog;
244
+ /**
245
+ * The actor's budget across windows, for `GET <base>/quota` — and, once set, a gate: a send whose
246
+ * report comes back `blocked` is refused with `429` before the turn starts. Default: a
247
+ * {@link LedgerQuotaProvider} over the usage ledger (reporting only, unless `quotaLimits` is set).
248
+ */
249
+ quotaProvider?: QuotaProvider;
250
+ /**
251
+ * Ceilings for the default ledger provider's windows — `{ day?: { tokens?, usd? }, month?: {
252
+ * tokens?, usd? } }`. Setting it turns the send gate on. Ignored when `quotaProvider` is given.
253
+ */
254
+ quotaLimits?: QuotaLimits;
208
255
  /**
209
256
  * Daily per-actor token budget, enforced against the persisted usage ledger by the built-in
210
257
  * `LedgerQuotaStore`. A convenience over wiring a {@link QuotaStore} by hand — set this and quotas
@@ -479,4 +526,4 @@ declare class AgentDepsFactory {
479
526
  private followUpsCount;
480
527
  }
481
528
 
482
- export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, type AgentSkillsOptions as d, AGENT_TOOL_BRAND as e, type AgentAttachmentsOptions as f, type AgentDeps as g, type AgentMemoryOptions as h, delegateToolName as i, provideAgentTool as p, utcDay as u };
529
+ export { AgentDepsFactory as A, type FunctionalTool as F, LedgerQuotaProvider as L, type QuotaLimits as Q, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, type AgentSkillsOptions as d, AGENT_TOOL_BRAND as e, type AgentAttachmentsOptions as f, type AgentDeps as g, type AgentMemoryOptions as h, type QuotaWindowLimits as i, delegateToolName as j, provideAgentTools as k, provideAgentTool as p, utcDay as u };