@dudousxd/nestjs-agent 1.6.0 → 1.8.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
@@ -144,6 +144,60 @@ one of the caller's threads (`null` clears it) and answers `{ feedback }`; `GET
144
144
  returns it on the message. It needs a store with `threadOfMessage` + `setMessageFeedback` (both
145
145
  bundled stores and `InMemoryAgentStore` have them) — `501` otherwise.
146
146
 
147
+ ### Letting callers pick a model
148
+
149
+ ```ts
150
+ AgentModule.forRoot({
151
+ model: aiSdkModel('openai/gpt-4o-mini', { resolveModel: (id) => gateway(id) }),
152
+ models: {
153
+ list: ({ actor }) => ({
154
+ default: 'openai/gpt-4o-mini',
155
+ providers: [
156
+ {
157
+ id: 'openai',
158
+ label: 'OpenAI',
159
+ models: [
160
+ { id: 'openai/gpt-4o-mini', label: 'GPT-4o mini', badges: ['fast'], available: true },
161
+ {
162
+ id: 'openai/o3',
163
+ label: 'o3',
164
+ badges: ['reasoning'],
165
+ available: actor.roles?.includes('PRO') === true,
166
+ unavailableReason: 'Pro plan',
167
+ },
168
+ ],
169
+ },
170
+ ],
171
+ }),
172
+ },
173
+ });
174
+ ```
175
+
176
+ `GET /agent/models?agent=` answers the catalog for the caller (empty without one). A turn runs on
177
+ the send's `model`, else the thread's pinned one (`PATCH /agent/threads/:id { model }`), else the
178
+ provider's default; the id reaches the provider as `ModelTurnArgs.model` for every call of the turn.
179
+ A model the catalog does not list as available for that actor and agent is refused with `400` —
180
+ when pinned and again on each turn. `staticModelCatalog(view)` wraps a fixed list.
181
+
182
+ ### Budgets: `GET /agent/quota`
183
+
184
+ `GET /agent/quota` reports `{ windows: [{ period: 'day' | 'month', usedTokens, limitTokens?,
185
+ usedUsd, limitUsd?, resetsAt? }], blocked? }` from a `QuotaProvider`. The default,
186
+ `LedgerQuotaProvider`, reads the usage ledger (day, plus month when the store has `usageBetween`)
187
+ and only reports. Configure it and it also gates sends — a `blocked` report refuses `POST
188
+ /agent/chat` with `429 { code: 'quota_exceeded', period }` before the turn starts:
189
+
190
+ ```ts
191
+ AgentModule.forRoot({
192
+ // ceilings on the default ledger provider
193
+ quotaLimits: { day: { tokens: 200_000 }, month: { usd: 20 } },
194
+ // …or your own budget, e.g. an AI-gateway spend cap:
195
+ // quotaProvider: { report: async ({ actor }) => myGatewayBudget(actor.id) },
196
+ });
197
+ ```
198
+
199
+ `GET /agent/quota/today` stays as it was.
200
+
147
201
  ### Who approves an action, and for how long
148
202
 
149
203
  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, 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
 
@@ -87,6 +87,36 @@ interface FunctionalTool {
87
87
  declare function provideAgentTool(tool: FunctionalTool): Provider;
88
88
  declare function provideAgentTool(factory: (...deps: never[]) => FunctionalTool, inject?: FactoryProvider['inject']): Provider;
89
89
 
90
+ /** Ceilings for a {@link LedgerQuotaProvider} window. Either, both, or neither. */
91
+ interface QuotaWindowLimits {
92
+ tokens?: number;
93
+ usd?: number;
94
+ }
95
+ /** Per-window ceilings: `AgentModule.forRoot({ quotaLimits })`. */
96
+ interface QuotaLimits {
97
+ day?: QuotaWindowLimits;
98
+ month?: QuotaWindowLimits;
99
+ }
100
+ /**
101
+ * The default {@link QuotaProvider}: windows read off the usage ledger the loop already writes.
102
+ *
103
+ * - `day` — always; `usedTokens`/limit come from the bound {@link QuotaStore} when there is one (so
104
+ * the report matches what the loop enforces), spend from the ledger.
105
+ * - `month` — when the store implements `usageBetween`.
106
+ *
107
+ * `limits` add ceilings per window (tokens and/or USD). A window that reaches one makes the report
108
+ * `blocked`.
109
+ */
110
+ declare class LedgerQuotaProvider implements QuotaProvider {
111
+ private readonly store;
112
+ private readonly quota?;
113
+ private readonly limits;
114
+ constructor(store: AgentStore, quota?: QuotaStore | undefined, limits?: QuotaLimits);
115
+ report(query: QuotaQuery): Promise<QuotaReport>;
116
+ private dayWindow;
117
+ private window;
118
+ }
119
+
90
120
  /**
91
121
  * Which half of the module this process mounts — the split that lets an API pod and a WORKER pod
92
122
  * each load `AgentModule` without either one doing the other's job:
@@ -199,6 +229,23 @@ interface AgentModuleOptions {
199
229
  * `quotaLimitTokens` instead, which binds the built-in ledger-backed store.
200
230
  */
201
231
  quota?: QuotaStore;
232
+ /**
233
+ * Which models a caller may pick — `GET <base>/models`, a send's `model`, a thread's pinned model
234
+ * (`PATCH <base>/threads/:id { model }`). The picked id reaches the {@link ModelProvider} as
235
+ * `ModelTurnArgs.model`. Omit → an empty catalog, and a request naming a model is refused.
236
+ */
237
+ models?: ModelCatalog;
238
+ /**
239
+ * The actor's budget across windows, for `GET <base>/quota` — and, once set, a gate: a send whose
240
+ * report comes back `blocked` is refused with `429` before the turn starts. Default: a
241
+ * {@link LedgerQuotaProvider} over the usage ledger (reporting only, unless `quotaLimits` is set).
242
+ */
243
+ quotaProvider?: QuotaProvider;
244
+ /**
245
+ * Ceilings for the default ledger provider's windows — `{ day?: { tokens?, usd? }, month?: {
246
+ * tokens?, usd? } }`. Setting it turns the send gate on. Ignored when `quotaProvider` is given.
247
+ */
248
+ quotaLimits?: QuotaLimits;
202
249
  /**
203
250
  * Daily per-actor token budget, enforced against the persisted usage ledger by the built-in
204
251
  * `LedgerQuotaStore`. A convenience over wiring a {@link QuotaStore} by hand — set this and quotas
@@ -473,4 +520,4 @@ declare class AgentDepsFactory {
473
520
  private followUpsCount;
474
521
  }
475
522
 
476
- 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 };
523
+ 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, 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, 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
 
@@ -87,6 +87,36 @@ interface FunctionalTool {
87
87
  declare function provideAgentTool(tool: FunctionalTool): Provider;
88
88
  declare function provideAgentTool(factory: (...deps: never[]) => FunctionalTool, inject?: FactoryProvider['inject']): Provider;
89
89
 
90
+ /** Ceilings for a {@link LedgerQuotaProvider} window. Either, both, or neither. */
91
+ interface QuotaWindowLimits {
92
+ tokens?: number;
93
+ usd?: number;
94
+ }
95
+ /** Per-window ceilings: `AgentModule.forRoot({ quotaLimits })`. */
96
+ interface QuotaLimits {
97
+ day?: QuotaWindowLimits;
98
+ month?: QuotaWindowLimits;
99
+ }
100
+ /**
101
+ * The default {@link QuotaProvider}: windows read off the usage ledger the loop already writes.
102
+ *
103
+ * - `day` — always; `usedTokens`/limit come from the bound {@link QuotaStore} when there is one (so
104
+ * the report matches what the loop enforces), spend from the ledger.
105
+ * - `month` — when the store implements `usageBetween`.
106
+ *
107
+ * `limits` add ceilings per window (tokens and/or USD). A window that reaches one makes the report
108
+ * `blocked`.
109
+ */
110
+ declare class LedgerQuotaProvider implements QuotaProvider {
111
+ private readonly store;
112
+ private readonly quota?;
113
+ private readonly limits;
114
+ constructor(store: AgentStore, quota?: QuotaStore | undefined, limits?: QuotaLimits);
115
+ report(query: QuotaQuery): Promise<QuotaReport>;
116
+ private dayWindow;
117
+ private window;
118
+ }
119
+
90
120
  /**
91
121
  * Which half of the module this process mounts — the split that lets an API pod and a WORKER pod
92
122
  * each load `AgentModule` without either one doing the other's job:
@@ -199,6 +229,23 @@ interface AgentModuleOptions {
199
229
  * `quotaLimitTokens` instead, which binds the built-in ledger-backed store.
200
230
  */
201
231
  quota?: QuotaStore;
232
+ /**
233
+ * Which models a caller may pick — `GET <base>/models`, a send's `model`, a thread's pinned model
234
+ * (`PATCH <base>/threads/:id { model }`). The picked id reaches the {@link ModelProvider} as
235
+ * `ModelTurnArgs.model`. Omit → an empty catalog, and a request naming a model is refused.
236
+ */
237
+ models?: ModelCatalog;
238
+ /**
239
+ * The actor's budget across windows, for `GET <base>/quota` — and, once set, a gate: a send whose
240
+ * report comes back `blocked` is refused with `429` before the turn starts. Default: a
241
+ * {@link LedgerQuotaProvider} over the usage ledger (reporting only, unless `quotaLimits` is set).
242
+ */
243
+ quotaProvider?: QuotaProvider;
244
+ /**
245
+ * Ceilings for the default ledger provider's windows — `{ day?: { tokens?, usd? }, month?: {
246
+ * tokens?, usd? } }`. Setting it turns the send gate on. Ignored when `quotaProvider` is given.
247
+ */
248
+ quotaLimits?: QuotaLimits;
202
249
  /**
203
250
  * Daily per-actor token budget, enforced against the persisted usage ledger by the built-in
204
251
  * `LedgerQuotaStore`. A convenience over wiring a {@link QuotaStore} by hand — set this and quotas
@@ -473,4 +520,4 @@ declare class AgentDepsFactory {
473
520
  private followUpsCount;
474
521
  }
475
522
 
476
- 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 };
523
+ 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, provideAgentTool as p, utcDay as u };