@dudousxd/nestjs-agent 1.1.2 → 1.3.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
@@ -41,6 +41,35 @@ The module mounts SSE + REST endpoints under `/agent` (`POST /agent/chat`, tool-
41
41
  threads, quota). Add `durable: true` + `AgentDurableModule` for the durable runner;
42
42
  `AgentModule.forFeature([…])` for multi-agent orchestration.
43
43
 
44
+ ### How a chat talks about a tool
45
+
46
+ A tool's name is an identifier, not copy. Declare how a person-facing surface narrates it beside its
47
+ input schema, so whoever renames an input field is the one who re-words the sentence that mentions
48
+ it:
49
+
50
+ ```ts
51
+ @AiTool({
52
+ name: 'purgeCache',
53
+ kind: 'action',
54
+ description: 'Purge a cache key.',
55
+ input: z.object({ key: z.string() }),
56
+ presentation: {
57
+ label: 'Cache purge',
58
+ running: 'Purging {key}', // templates over the call's INPUT
59
+ done: 'Purged {key}',
60
+ icon: 'cache', // a key into the client's own glyph map
61
+ tone: 'destructive',
62
+ confirm: { title: 'Purge {key}?', verb: 'Purge' },
63
+ result: { kind: 'metrics', fields: [{ path: 'evicted', label: 'Evicted' }] }, // over the OUTPUT
64
+ },
65
+ })
66
+ ```
67
+
68
+ `GET /agent/tools?agent=<name>` returns `ToolCatalogEntry[]` — `{ name, kind, presentation? }` for the
69
+ tools THIS actor can reach through that agent (the default agent when omitted; `404` for an unknown
70
+ one), built by `ToolRegistry.visibleSpecs`, the same gates the model is offered tools through. The
71
+ React package's `useToolCatalog` reads it. The model never sees `presentation`.
72
+
44
73
  ### Message attachments
45
74
 
46
75
  A chat turn attaches a file by **`mediaId` only**:
@@ -101,6 +130,36 @@ nobody is streaming (including one that has already finished). In-process caller
101
130
  authorized elsewhere use `AgentService.subscribe(runId)`; anything reachable from a request must go
102
131
  through `AgentService.subscribeAs(actor, runId)`.
103
132
 
133
+ ### Who approves an action, and for how long
134
+
135
+ Every `action` tool call waits on the person chatting by default. `approvalPolicy` changes that per
136
+ call:
137
+
138
+ ```ts
139
+ AgentModule.forRoot({
140
+ // …
141
+ approvalPolicy: {
142
+ requirementFor: (tool, actor) =>
143
+ tool.name === 'issueRefund'
144
+ ? { required: true, approver: 'finance', ttlMs: 15 * 60_000 }
145
+ : { required: true, approver: 'requester' },
146
+ // Optional. Default: 'requester' = the thread's own actor, anything else = a role the decider holds.
147
+ // canDecide: (actor, { approver, requesterRef }) => gate.forUser(actor).allows('approve', approver),
148
+ },
149
+ });
150
+ ```
151
+
152
+ - `POST /agent/tool-call/approve` `{ toolCallId, remember?, via? }` and `…/reject` `{ toolCallId,
153
+ reason?, via? }` enforce the recorded approver (`403` otherwise) and refuse a request that has
154
+ lapsed (`410`). The decider is recorded as `executedByRef`, `via` as `decided_via` (`'web'` when
155
+ omitted; the console approvals port takes `decidedVia` too).
156
+ - `remember: true` approves later calls of the same tool in the same thread without asking.
157
+ - A `ttlMs` becomes the approval wait's timeout — `ctx.waitForSignal(…, { timeoutMs })` on the
158
+ durable runner, a timer inline. On expiry the call settles `expired`, the model is told nobody
159
+ approved it in time, and the stream carries `tool-output-denied` + `approval-settled`.
160
+ - The tool-call row gains `approver`, `expires_at`, `remember` and `decided_via`; a reloaded thread
161
+ carries them as `StoredMessage.approvals`.
162
+
104
163
  ### A durable turn's model call and tools run in a worker
105
164
 
106
165
  Under `durable: true` the turn's model call and each of its tool executions are **dispatched steps**
@@ -1,4 +1,4 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, 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, 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
 
@@ -8,6 +8,8 @@ interface AgentDeps {
8
8
  store: AgentStore;
9
9
  registry: ToolRegistry;
10
10
  rolesPolicy: RolesPolicy;
11
+ /** Who approves an action call, and for how long (from `forRoot({ approvalPolicy })`). Undefined → the requester, no expiry. */
12
+ approvalPolicy?: ApprovalPolicy;
11
13
  quota?: QuotaStore;
12
14
  sink: TokenStreamSink;
13
15
  /** Fallback accounting label; the provider's turn result overrides it when set. */
@@ -205,6 +207,13 @@ interface AgentModuleOptions {
205
207
  quotaLimitTokens?: number;
206
208
  /** Tool authorization gate. Defaults to role-in-`defaultRoles`. */
207
209
  rolesPolicy?: RolesPolicy;
210
+ /**
211
+ * Who has to approve an `action` tool call, and how long the request stays open. Defaults to the
212
+ * requester (the thread's own actor) with no expiry. A requirement naming another approver (a
213
+ * role) is enforced by `POST tool-call/approve|reject` through `canDecide` — by default, the
214
+ * deciding actor must hold that role. See `ApprovalPolicy`.
215
+ */
216
+ approvalPolicy?: ApprovalPolicy;
208
217
  /** Default roles a tool requires when its `roles` is omitted. Defaults to `['ADMIN']`. */
209
218
  defaultRoles?: string[];
210
219
  /**
@@ -1,4 +1,4 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, 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, 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
 
@@ -8,6 +8,8 @@ interface AgentDeps {
8
8
  store: AgentStore;
9
9
  registry: ToolRegistry;
10
10
  rolesPolicy: RolesPolicy;
11
+ /** Who approves an action call, and for how long (from `forRoot({ approvalPolicy })`). Undefined → the requester, no expiry. */
12
+ approvalPolicy?: ApprovalPolicy;
11
13
  quota?: QuotaStore;
12
14
  sink: TokenStreamSink;
13
15
  /** Fallback accounting label; the provider's turn result overrides it when set. */
@@ -205,6 +207,13 @@ interface AgentModuleOptions {
205
207
  quotaLimitTokens?: number;
206
208
  /** Tool authorization gate. Defaults to role-in-`defaultRoles`. */
207
209
  rolesPolicy?: RolesPolicy;
210
+ /**
211
+ * Who has to approve an `action` tool call, and how long the request stays open. Defaults to the
212
+ * requester (the thread's own actor) with no expiry. A requirement naming another approver (a
213
+ * role) is enforced by `POST tool-call/approve|reject` through `canDecide` — by default, the
214
+ * deciding actor must hold that role. See `ApprovalPolicy`.
215
+ */
216
+ approvalPolicy?: ApprovalPolicy;
208
217
  /** Default roles a tool requires when its `roles` is omitted. Defaults to `['ADMIN']`. */
209
218
  defaultRoles?: string[];
210
219
  /**