@dudousxd/nestjs-agent 0.8.2 → 0.10.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 +36 -0
- package/dist/{agent-deps.factory-BlC9tumI.d.cts → agent-deps.factory-D7GdhqdK.d.cts} +49 -2
- package/dist/{agent-deps.factory-BlC9tumI.d.ts → agent-deps.factory-D7GdhqdK.d.ts} +49 -2
- package/dist/durable/index.cjs +217 -146
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +50 -12
- package/dist/durable/index.d.ts +50 -12
- package/dist/durable/index.js +216 -145
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +17 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +17 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -41,6 +41,42 @@ 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
|
+
### Deploying split API/worker pods
|
|
45
|
+
|
|
46
|
+
By default `AgentModule`/`AgentDurableModule` wire everything: every controller, the `agent.run`
|
|
47
|
+
durable workflow, and its dispatched steps (`AgentRunSteps.llm`/`.tool`). Fine for a single combined
|
|
48
|
+
pod — but an API/dashboard pod and a worker pod each importing the FULL module means the API pod also
|
|
49
|
+
registers the dispatched-step handlers, subscribing their queues and running LLM/tool work it has no
|
|
50
|
+
business doing (and a worker pod mounts HTTP routes nothing ever calls).
|
|
51
|
+
|
|
52
|
+
`surface: 'http' | 'engine' | 'both'` (default `'both'`) splits that in two:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// worker container: no HTTP routes, but the workflow + dispatched steps ARE registered
|
|
56
|
+
imports: [
|
|
57
|
+
DurableModule.forRoot({ store, transport }),
|
|
58
|
+
AgentModule.forRoot({ model, store, actorResolver, durable: true, surface: 'engine' }),
|
|
59
|
+
AgentDurableModule.forRoot({ surface: 'engine' }),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
// api container: every controller works, but AgentRunSteps is never registered here —
|
|
63
|
+
// pair this with the durable `drive: false` enqueue-only config so this pod also never
|
|
64
|
+
// polls/executes work on its own.
|
|
65
|
+
imports: [
|
|
66
|
+
DurableModule.forRoot({ store, transport, drive: false }),
|
|
67
|
+
AgentModule.forRoot({ model, store, actorResolver, durable: true, surface: 'http' }),
|
|
68
|
+
AgentDurableModule.forRoot({ surface: 'http' }),
|
|
69
|
+
]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`AgentDurableModule.forRoot({ surface })` mirrors `AgentModule`'s own `surface` — set it in both
|
|
73
|
+
places (or use the `agentDurable(options)` helper, which threads one `surface` to both calls). The
|
|
74
|
+
`'http'` side still registers the `agent.run` WORKFLOW (so starting a run succeeds) but never the
|
|
75
|
+
step handlers, so it can never pick up LLM/tool work meant for the worker fleet. This is exactly the
|
|
76
|
+
fix for a durable skew-protection crash-loop where a bare-imported module on the wrong pod type
|
|
77
|
+
subscribed queues it should never have served. flip composes this the same way `APP_TYPE` composes
|
|
78
|
+
its other modules: the worker container loads the `engine` surface, the API container loads `http`.
|
|
79
|
+
|
|
44
80
|
See the [monorepo README](https://github.com/DavideCarvalho/aviary) for the full guide (durability,
|
|
45
81
|
multi-agent, authz, governed SQL, the React frontend, diagnostics).
|
|
46
82
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, Retriever, AgentPricingStore, ToolSpec, ToolHandler, ActorResolver, AgentRegistry } from '@dudousxd/nestjs-agent-core';
|
|
1
|
+
import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, ToolSpec, ToolHandler, ActorResolver, AgentRegistry } from '@dudousxd/nestjs-agent-core';
|
|
2
2
|
import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
|
|
3
3
|
|
|
4
4
|
/** Everything `runAgentLoop` needs, minus the per-run `day` the runner stamps. */
|
|
@@ -19,6 +19,11 @@ interface AgentDeps {
|
|
|
19
19
|
toolAllowList?: string[];
|
|
20
20
|
/** Per-tool execution timeout in ms (from module options). Undefined → no timeout. */
|
|
21
21
|
toolTimeoutMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Transient-tool-error retry policy (from module options). Undefined → default ON
|
|
24
|
+
* (`{ attempts: 2, backoffMs: 150 }` with the default classifier); `false` → disabled.
|
|
25
|
+
*/
|
|
26
|
+
toolTransientRetry?: ToolTransientRetrySetting;
|
|
22
27
|
/** How many follow-up suggestions to generate after the final turn. Undefined/0 → off. */
|
|
23
28
|
followUpsCount?: number;
|
|
24
29
|
/** Inject-mode retriever (from `forRoot({ retrieval })`). Undefined → no prompt augmentation. */
|
|
@@ -51,6 +56,26 @@ interface FunctionalTool {
|
|
|
51
56
|
declare function provideAgentTool(tool: FunctionalTool): Provider;
|
|
52
57
|
declare function provideAgentTool(factory: (...deps: never[]) => FunctionalTool, inject?: FactoryProvider['inject']): Provider;
|
|
53
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Which half of the module this process mounts — the split that lets an API pod and a WORKER pod
|
|
61
|
+
* each load `AgentModule` without either one doing the other's job:
|
|
62
|
+
*
|
|
63
|
+
* - `'both'` (default): today's behavior, unconditionally — every controller AND the full durable
|
|
64
|
+
* engine wiring (when `durable: true`). Zero-config single-pod deployments never touch this.
|
|
65
|
+
* - `'http'`: mounts every controller (chat/threads/tool-call/quota/agents/attachments) so the HTTP
|
|
66
|
+
* surface is fully functional, but under `durable: true` this pod must never EXECUTE agent work —
|
|
67
|
+
* see `AgentDurableModuleOptions.surface` (`@dudousxd/nestjs-agent/durable`), which is where the
|
|
68
|
+
* actual step-handler exclusion happens; this flag only controls the controllers here.
|
|
69
|
+
* - `'engine'`: mounts NO controllers (an API pod's routes have no business existing on a worker
|
|
70
|
+
* pod that never receives HTTP traffic) — everything else (registry/tools/model/store/sinks, the
|
|
71
|
+
* `agent.run` workflow, and its dispatched steps under `AgentDurableModule`) stays wired exactly
|
|
72
|
+
* as `'both'`.
|
|
73
|
+
*
|
|
74
|
+
* A durable deployment mirrors this on `AgentDurableModule.forRoot({ surface })` too — see that
|
|
75
|
+
* module's doc for why the split can't be inferred from this option alone (Nest's static module
|
|
76
|
+
* metadata can't be computed from a runtime-injected value).
|
|
77
|
+
*/
|
|
78
|
+
type AgentSurface = 'http' | 'engine' | 'both';
|
|
54
79
|
/**
|
|
55
80
|
* `AgentModuleOptions.attachments` — bounds and content-type allowlist for the optional
|
|
56
81
|
* `POST /agent/attachments` upload controller (see `attachments.upload`).
|
|
@@ -138,6 +163,16 @@ interface AgentModuleOptions {
|
|
|
138
163
|
* model receives the timeout as its result and can adapt) instead of hanging the turn. Omit → none.
|
|
139
164
|
*/
|
|
140
165
|
toolTimeoutMs?: number;
|
|
166
|
+
/**
|
|
167
|
+
* Retries a tool's own invocation, in place, when it throws a classified-transient error (a DB
|
|
168
|
+
* deadlock, a lock-wait timeout, a serialization failure) — never a new durable checkpoint, just
|
|
169
|
+
* repeated attempts inside the same tool-call step. Default ON: `{ attempts: 2, backoffMs: 150 }`
|
|
170
|
+
* with the default classifier. Set `{ classify }` to widen/narrow which errors count as transient
|
|
171
|
+
* (checked in BOTH the inline and durable-dispatched execution paths via DI, since the classify
|
|
172
|
+
* function itself can't ride a durable step's wire envelope), or `false` to disable entirely. A
|
|
173
|
+
* tool's other (non-transient) failures are unaffected — they remain a one-shot business outcome.
|
|
174
|
+
*/
|
|
175
|
+
toolTransientRetry?: ToolTransientRetrySetting;
|
|
141
176
|
/**
|
|
142
177
|
* Suggest follow-up questions after the final turn. `true` → 3 suggestions; `{ count }` → that many.
|
|
143
178
|
* Costs one extra model call per turn (recorded as `follow_ups` usage), stored on the assistant
|
|
@@ -173,6 +208,12 @@ interface AgentModuleOptions {
|
|
|
173
208
|
guards?: Type<CanActivate>[];
|
|
174
209
|
/** Bounds/allowlist for the optional attachment-upload controller. Omit → 20 MiB, the documented default types, not mounted. */
|
|
175
210
|
attachments?: AgentAttachmentsOptions;
|
|
211
|
+
/**
|
|
212
|
+
* Which half of the module this process mounts (see {@link AgentSurface}). Omit → `'both'`,
|
|
213
|
+
* today's behavior with zero change. STATIC top-level field (like `durable`/`path`) — it decides
|
|
214
|
+
* which controllers exist at module-build time, not something a request can flip.
|
|
215
|
+
*/
|
|
216
|
+
surface?: AgentSurface;
|
|
176
217
|
}
|
|
177
218
|
interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
178
219
|
inject?: Array<InjectionToken | OptionalFactoryDependency>;
|
|
@@ -224,6 +265,12 @@ interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
|
224
265
|
* only the yes/no mount decision has to live here.
|
|
225
266
|
*/
|
|
226
267
|
attachmentsUpload?: boolean;
|
|
268
|
+
/**
|
|
269
|
+
* Which half of the module this process mounts (see {@link AgentSurface}). Same static-wiring
|
|
270
|
+
* reasoning as `attachmentsUpload`/`durable` above — `useFactory` resolves too late to decide
|
|
271
|
+
* which controllers exist. Omit → `'both'`, today's behavior with zero change.
|
|
272
|
+
*/
|
|
273
|
+
surface?: AgentSurface;
|
|
227
274
|
}
|
|
228
275
|
|
|
229
276
|
/** The synthesized `agent`-kind tool name an orchestrator uses to hand off to `target`. */
|
|
@@ -252,4 +299,4 @@ declare class AgentDepsFactory {
|
|
|
252
299
|
private followUpsCount;
|
|
253
300
|
}
|
|
254
301
|
|
|
255
|
-
export { AgentDepsFactory as A, type FunctionalTool as F, type
|
|
302
|
+
export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, AGENT_TOOL_BRAND as d, type AgentAttachmentsOptions as e, type AgentDeps as f, delegateToolName as g, provideAgentTool as p, utcDay as u };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, Retriever, AgentPricingStore, ToolSpec, ToolHandler, ActorResolver, AgentRegistry } from '@dudousxd/nestjs-agent-core';
|
|
1
|
+
import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, ToolSpec, ToolHandler, ActorResolver, AgentRegistry } from '@dudousxd/nestjs-agent-core';
|
|
2
2
|
import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
|
|
3
3
|
|
|
4
4
|
/** Everything `runAgentLoop` needs, minus the per-run `day` the runner stamps. */
|
|
@@ -19,6 +19,11 @@ interface AgentDeps {
|
|
|
19
19
|
toolAllowList?: string[];
|
|
20
20
|
/** Per-tool execution timeout in ms (from module options). Undefined → no timeout. */
|
|
21
21
|
toolTimeoutMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Transient-tool-error retry policy (from module options). Undefined → default ON
|
|
24
|
+
* (`{ attempts: 2, backoffMs: 150 }` with the default classifier); `false` → disabled.
|
|
25
|
+
*/
|
|
26
|
+
toolTransientRetry?: ToolTransientRetrySetting;
|
|
22
27
|
/** How many follow-up suggestions to generate after the final turn. Undefined/0 → off. */
|
|
23
28
|
followUpsCount?: number;
|
|
24
29
|
/** Inject-mode retriever (from `forRoot({ retrieval })`). Undefined → no prompt augmentation. */
|
|
@@ -51,6 +56,26 @@ interface FunctionalTool {
|
|
|
51
56
|
declare function provideAgentTool(tool: FunctionalTool): Provider;
|
|
52
57
|
declare function provideAgentTool(factory: (...deps: never[]) => FunctionalTool, inject?: FactoryProvider['inject']): Provider;
|
|
53
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Which half of the module this process mounts — the split that lets an API pod and a WORKER pod
|
|
61
|
+
* each load `AgentModule` without either one doing the other's job:
|
|
62
|
+
*
|
|
63
|
+
* - `'both'` (default): today's behavior, unconditionally — every controller AND the full durable
|
|
64
|
+
* engine wiring (when `durable: true`). Zero-config single-pod deployments never touch this.
|
|
65
|
+
* - `'http'`: mounts every controller (chat/threads/tool-call/quota/agents/attachments) so the HTTP
|
|
66
|
+
* surface is fully functional, but under `durable: true` this pod must never EXECUTE agent work —
|
|
67
|
+
* see `AgentDurableModuleOptions.surface` (`@dudousxd/nestjs-agent/durable`), which is where the
|
|
68
|
+
* actual step-handler exclusion happens; this flag only controls the controllers here.
|
|
69
|
+
* - `'engine'`: mounts NO controllers (an API pod's routes have no business existing on a worker
|
|
70
|
+
* pod that never receives HTTP traffic) — everything else (registry/tools/model/store/sinks, the
|
|
71
|
+
* `agent.run` workflow, and its dispatched steps under `AgentDurableModule`) stays wired exactly
|
|
72
|
+
* as `'both'`.
|
|
73
|
+
*
|
|
74
|
+
* A durable deployment mirrors this on `AgentDurableModule.forRoot({ surface })` too — see that
|
|
75
|
+
* module's doc for why the split can't be inferred from this option alone (Nest's static module
|
|
76
|
+
* metadata can't be computed from a runtime-injected value).
|
|
77
|
+
*/
|
|
78
|
+
type AgentSurface = 'http' | 'engine' | 'both';
|
|
54
79
|
/**
|
|
55
80
|
* `AgentModuleOptions.attachments` — bounds and content-type allowlist for the optional
|
|
56
81
|
* `POST /agent/attachments` upload controller (see `attachments.upload`).
|
|
@@ -138,6 +163,16 @@ interface AgentModuleOptions {
|
|
|
138
163
|
* model receives the timeout as its result and can adapt) instead of hanging the turn. Omit → none.
|
|
139
164
|
*/
|
|
140
165
|
toolTimeoutMs?: number;
|
|
166
|
+
/**
|
|
167
|
+
* Retries a tool's own invocation, in place, when it throws a classified-transient error (a DB
|
|
168
|
+
* deadlock, a lock-wait timeout, a serialization failure) — never a new durable checkpoint, just
|
|
169
|
+
* repeated attempts inside the same tool-call step. Default ON: `{ attempts: 2, backoffMs: 150 }`
|
|
170
|
+
* with the default classifier. Set `{ classify }` to widen/narrow which errors count as transient
|
|
171
|
+
* (checked in BOTH the inline and durable-dispatched execution paths via DI, since the classify
|
|
172
|
+
* function itself can't ride a durable step's wire envelope), or `false` to disable entirely. A
|
|
173
|
+
* tool's other (non-transient) failures are unaffected — they remain a one-shot business outcome.
|
|
174
|
+
*/
|
|
175
|
+
toolTransientRetry?: ToolTransientRetrySetting;
|
|
141
176
|
/**
|
|
142
177
|
* Suggest follow-up questions after the final turn. `true` → 3 suggestions; `{ count }` → that many.
|
|
143
178
|
* Costs one extra model call per turn (recorded as `follow_ups` usage), stored on the assistant
|
|
@@ -173,6 +208,12 @@ interface AgentModuleOptions {
|
|
|
173
208
|
guards?: Type<CanActivate>[];
|
|
174
209
|
/** Bounds/allowlist for the optional attachment-upload controller. Omit → 20 MiB, the documented default types, not mounted. */
|
|
175
210
|
attachments?: AgentAttachmentsOptions;
|
|
211
|
+
/**
|
|
212
|
+
* Which half of the module this process mounts (see {@link AgentSurface}). Omit → `'both'`,
|
|
213
|
+
* today's behavior with zero change. STATIC top-level field (like `durable`/`path`) — it decides
|
|
214
|
+
* which controllers exist at module-build time, not something a request can flip.
|
|
215
|
+
*/
|
|
216
|
+
surface?: AgentSurface;
|
|
176
217
|
}
|
|
177
218
|
interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
178
219
|
inject?: Array<InjectionToken | OptionalFactoryDependency>;
|
|
@@ -224,6 +265,12 @@ interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
|
224
265
|
* only the yes/no mount decision has to live here.
|
|
225
266
|
*/
|
|
226
267
|
attachmentsUpload?: boolean;
|
|
268
|
+
/**
|
|
269
|
+
* Which half of the module this process mounts (see {@link AgentSurface}). Same static-wiring
|
|
270
|
+
* reasoning as `attachmentsUpload`/`durable` above — `useFactory` resolves too late to decide
|
|
271
|
+
* which controllers exist. Omit → `'both'`, today's behavior with zero change.
|
|
272
|
+
*/
|
|
273
|
+
surface?: AgentSurface;
|
|
227
274
|
}
|
|
228
275
|
|
|
229
276
|
/** The synthesized `agent`-kind tool name an orchestrator uses to hand off to `target`. */
|
|
@@ -252,4 +299,4 @@ declare class AgentDepsFactory {
|
|
|
252
299
|
private followUpsCount;
|
|
253
300
|
}
|
|
254
301
|
|
|
255
|
-
export { AgentDepsFactory as A, type FunctionalTool as F, type
|
|
302
|
+
export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, AGENT_TOOL_BRAND as d, type AgentAttachmentsOptions as e, type AgentDeps as f, delegateToolName as g, provideAgentTool as p, utcDay as u };
|