@dudousxd/nestjs-agent-core 0.1.0 → 0.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/dist/index.d.cts CHANGED
@@ -89,6 +89,18 @@ interface QuotaState {
89
89
  limitTokens: number;
90
90
  withinLimit: boolean;
91
91
  }
92
+ /**
93
+ * The read-model the quota-today endpoint returns to a client — a superset of {@link QuotaState}
94
+ * for rendering a usage badge. `limitTokens` is `null` when no quota is configured (unlimited, so
95
+ * `withinLimit` is always true); `costUsd` is the day's summed provider-reported USD spend (`0`
96
+ * when only tokens were reported).
97
+ */
98
+ interface QuotaView {
99
+ usedTokens: number;
100
+ limitTokens: number | null;
101
+ withinLimit: boolean;
102
+ costUsd: number;
103
+ }
92
104
  /** A human decision on a pending action tool call. */
93
105
  interface Decision {
94
106
  approved: boolean;
@@ -107,37 +119,35 @@ interface PageContext {
107
119
  [key: string]: unknown;
108
120
  }
109
121
  /**
110
- * Inputs a {@link PromptBuilder} may use to compose the effective system prompt for a turn.
111
- * `basePrompt` is the agent's own (already-resolved) base prompt, so a persona builder can wrap
112
- * or extend it rather than replace it.
122
+ * Inputs a {@link PromptBuilder} or {@link PromptContributor} may use to compose the system prompt
123
+ * for a turn. Resolved once per turn from stable inputs (actor / agent / pageContext) so it stays
124
+ * replay-safe.
113
125
  */
114
126
  interface PromptContext {
115
127
  actor: Actor;
116
- persona?: Persona;
128
+ /** The selected agent's name. */
129
+ agentName: string;
117
130
  pageContext?: PageContext;
118
- basePrompt: string;
119
131
  }
120
132
  /**
121
- * A dynamic system prompt. Return a string (optionally async) built from the turn's context —
122
- * e.g. injecting the actor, the current page, or a data-shape description. The loop resolves it
123
- * once per turn from stable inputs (actor/persona/pageContext), so it stays replay-safe.
133
+ * An agent's base system prompt. Return a string (optionally async) built from the turn's context —
134
+ * e.g. injecting the actor, the current page, or a data-shape description. Set on an `@Agent` class
135
+ * via a `@SystemPrompt()` method (or a flat string).
124
136
  */
125
137
  type PromptBuilder = (ctx: PromptContext) => string | Promise<string>;
126
- interface Persona {
127
- id: string;
128
- label: string;
129
- /** A flat prompt, or a {@link PromptBuilder} composed per request from {@link PromptContext}. */
130
- systemPrompt: string | PromptBuilder;
131
- /** If set, only these tool names are offered (after role filtering). */
132
- allowedTools?: string[];
133
- }
138
+ /**
139
+ * A cross-agent system-prompt contributor. Returns an ordered section to APPEND to the composed
140
+ * prompt (after the agent's base), or `null` to contribute nothing this turn — so conditional
141
+ * sections (base-scope, a mentions legend, schema hints) stay clean when they don't apply.
142
+ * Registered app-wide via `@SystemPromptContributor()`; the loop runs every contributor in order.
143
+ */
144
+ type PromptContributor = (ctx: PromptContext) => string | null | Promise<string | null>;
134
145
  /** Everything needed to run one agent turn. */
135
146
  interface AgentRunInput {
136
147
  threadId: string;
137
148
  actor: Actor;
138
149
  /** The latest user message text. */
139
150
  userText: string;
140
- persona?: Persona;
141
151
  pageContext?: PageContext;
142
152
  /** YYYY-MM-DD stamped by the runner so quota/day stays deterministic under durable replay. */
143
153
  day?: string;
@@ -163,10 +173,11 @@ interface AgentRunInput {
163
173
  regenerate?: boolean;
164
174
  }
165
175
  /**
166
- * A named agent: its prompt, the tools it may use, and its personas. Multiple definitions are
167
- * registered via `AgentModule.forFeature([...])`; an orchestrator delegates to others through
168
- * `ctx.runAgent(name, task)`. Model/store/sink/governance are shared from the module unless
169
- * overridden here.
176
+ * A named agent: its prompt, the tools it may use, and who it can hand off to. This is the
177
+ * internal record the loop and `AgentDepsFactory` consume; in an app it is authored as an
178
+ * `@Agent`-decorated class and populated into the `AgentRegistry` by discovery (name, base prompt
179
+ * from `@SystemPrompt`, tool allow-list, handoff targets). An orchestrator hands off to others via
180
+ * `ctx.handoff(OtherAgent)`. Model/store/sink/governance are shared from the module.
170
181
  */
171
182
  interface AgentDefinition {
172
183
  name: string;
@@ -174,17 +185,14 @@ interface AgentDefinition {
174
185
  systemPrompt?: string | PromptBuilder;
175
186
  /** Allow-list of tool names this agent may use (subset of all registered tools). */
176
187
  tools?: string[];
177
- /** Names of other agents this agent may delegate to (auto-registered as `agent`-kind tools). */
188
+ /** Names of other agents this agent may hand off to (auto-registered as `agent`-kind tools). */
178
189
  delegatesTo?: string[];
179
- personas?: Persona[];
180
- defaultPersona?: string;
181
190
  modelId?: string;
182
191
  maxSteps?: number;
183
192
  }
184
193
  interface ThreadSummary {
185
194
  id: string;
186
195
  title: string;
187
- persona: string;
188
196
  transient: boolean;
189
197
  createdAt: string;
190
198
  updatedAt: string;
@@ -194,6 +202,8 @@ interface StoredMessage {
194
202
  id: string;
195
203
  role: MessageRole;
196
204
  content: string;
205
+ /** Which agent produced this message (assistant messages) — provenance for replay / UI / telescope. */
206
+ agentName?: string;
197
207
  toolCalls?: ToolCallRequest[];
198
208
  toolResults?: ToolResult[];
199
209
  followUps?: string[];
@@ -242,6 +252,8 @@ declare const AGENT_RETRIEVER: unique symbol;
242
252
  /** The embedding provider (`EmbeddingProvider`) — text→vector for retrieval + ingestion. */
243
253
  declare const AGENT_EMBEDDING_PROVIDER: unique symbol;
244
254
  declare const AGENT_DEPS_FACTORY: unique symbol;
255
+ /** App-wide, ordered `@SystemPromptContributor()` functions the loop appends after the agent base. */
256
+ declare const AGENT_PROMPT_CONTRIBUTORS: unique symbol;
245
257
 
246
258
  /**
247
259
  * Per-invocation context handed to a tool handler. Host-supplied bits are optional. Identity lives
@@ -253,7 +265,8 @@ interface AiToolCtx {
253
265
  threadId: string;
254
266
  runId: string;
255
267
  requestId: string;
256
- persona?: Persona;
268
+ /** The name of the agent running this turn — provenance a tool can scope on (e.g. capability sets). */
269
+ agentName?: string;
257
270
  pageContext?: PageContext;
258
271
  /** Optional host handle (e.g. an ORM EntityManager) the app threads through options. */
259
272
  host?: unknown;
@@ -348,7 +361,6 @@ interface ModelProvider {
348
361
 
349
362
  interface CreateThreadInput {
350
363
  actor: Actor;
351
- persona: string;
352
364
  transient?: boolean;
353
365
  title?: string;
354
366
  }
@@ -356,7 +368,8 @@ interface AppendMessageInput {
356
368
  threadId: string;
357
369
  role: StoredMessage['role'];
358
370
  content: string;
359
- persona?: string;
371
+ /** Which agent produced this message (assistant messages) — provenance. */
372
+ agentName?: string;
360
373
  toolCalls?: ToolCallRequest[];
361
374
  toolResults?: ToolResult[];
362
375
  followUps?: string[];
@@ -396,6 +409,12 @@ interface AgentStore {
396
409
  softDeleteThread(threadId: string): Promise<void>;
397
410
  forkThread(threadId: string, fromMessageId: string): Promise<ThreadSummary>;
398
411
  setTitle(threadId: string, title: string): Promise<void>;
412
+ /**
413
+ * Promote a transient thread to a persistent one so it shows up in {@link listThreads}. A
414
+ * transient thread is a scratch conversation the caller has not chosen to keep; "saving" it
415
+ * clears the flag. Idempotent — promoting an already-persistent thread is a no-op.
416
+ */
417
+ promoteThread(threadId: string): Promise<void>;
399
418
  setActiveStream(threadId: string, runId: string | null): Promise<void>;
400
419
  /**
401
420
  * The `actorRef` that owns a thread, or `null` if no such thread exists. The authorization seam
@@ -427,8 +446,14 @@ interface AgentStore {
427
446
  recordToolCall(input: RecordToolCallInput): Promise<void>;
428
447
  updateToolCall(input: UpdateToolCallInput): Promise<void>;
429
448
  recordUsage(input: RecordUsageInput): Promise<void>;
449
+ /**
450
+ * The actor's spend for `day` (UTC): total tokens plus the summed provider-reported USD cost.
451
+ * `costUsd` is `0` when no turn on that day reported a cost (token-only providers). Feeds both
452
+ * quota enforcement (via {@link QuotaStore}) and the quota-today view.
453
+ */
430
454
  quotaToday(actorRef: string, day: string): Promise<{
431
455
  usedTokens: number;
456
+ costUsd: number;
432
457
  }>;
433
458
  }
434
459
 
@@ -646,8 +671,8 @@ interface AgentGovernanceQueries {
646
671
 
647
672
  /** First filter layer: drop tools the actor's role may not invoke. `can` may be async (authz). */
648
673
  declare function filterToolsByRole(tools: ToolSpec[], actor: Actor, policy: RolesPolicy): Promise<ToolSpec[]>;
649
- /** Second filter layer: if the persona pins an allow-list, keep only those tool names. */
650
- declare function personaFilterTools(tools: ToolSpec[], allowedTools: string[] | undefined): ToolSpec[];
674
+ /** Second filter layer: if the agent pins an allow-list, keep only those tool names. */
675
+ declare function filterToolsByAllowList(tools: ToolSpec[], allowedTools: string[] | undefined): ToolSpec[];
651
676
 
652
677
  /** Holds the named agent definitions registered via `AgentModule.forFeature([...])`. */
653
678
  declare class AgentRegistry {
@@ -687,7 +712,7 @@ declare class ToolRegistry {
687
712
  has(name: string): boolean;
688
713
  spec(name: string): ToolSpec | undefined;
689
714
  allSpecs(): ToolSpec[];
690
- /** The tools to offer the model for this actor+persona, after the two filter layers. */
715
+ /** The tools to offer the model for this actor+agent, after the two filter layers. */
691
716
  definitionsFor(actor: Actor, policy: RolesPolicy, allowedTools?: string[]): Promise<ToolDefinition[]>;
692
717
  /** Run a tool. Re-checks the role (defense-in-depth) and re-parses the input via Zod. */
693
718
  invoke(name: string, input: unknown, ctx: AiToolCtx, policy: RolesPolicy): Promise<unknown>;
@@ -714,10 +739,16 @@ interface AgentLoopDeps {
714
739
  day: string;
715
740
  /** The agent's base prompt. A flat string, or a {@link PromptBuilder} resolved per turn. */
716
741
  systemPrompt: string | PromptBuilder;
742
+ /**
743
+ * Cross-agent system-prompt contributors, applied in order AFTER the agent's base prompt. Each
744
+ * returns a section to append (or `null` to skip this turn). The app registers them via
745
+ * `@SystemPromptContributor()`; the loop composes base + contributors into the effective prompt.
746
+ */
747
+ promptContributors?: PromptContributor[];
717
748
  maxSteps?: number;
718
749
  /** Optional host handle threaded to tool ctx (e.g. an ORM EntityManager). */
719
750
  host?: unknown;
720
- /** Agent-level tool allow-list (intersected with the persona's). Undefined → all tools. */
751
+ /** Agent-level tool allow-list. Undefined → all tools (after role filtering). */
721
752
  toolAllowList?: string[];
722
753
  /**
723
754
  * Per-tool execution timeout in ms. A tool that runs longer is aborted and recorded as failed
@@ -777,7 +808,8 @@ interface AgentRunStarted {
777
808
  runId: string;
778
809
  threadId: string;
779
810
  actorId: string;
780
- persona?: string;
811
+ /** Which agent is handling the run. */
812
+ agentName?: string;
781
813
  }
782
814
  interface AgentMessageEvent {
783
815
  runId: string;
@@ -845,4 +877,4 @@ declare function publishAgentRunFailed(payload: AgentRunFailed): void;
845
877
  declare function publishAgentDelegated(payload: AgentDelegated): void;
846
878
  declare function publishAgentRetrieved(payload: AgentRetrieved): void;
847
879
 
848
- export { AGENT_ACTOR_RESOLVER, AGENT_DEPS_FACTORY, AGENT_DURABLE_RUNNER, AGENT_EMBEDDING_PROVIDER, AGENT_GOVERNANCE_QUERIES, AGENT_MODEL, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_QUOTA_STORE, AGENT_REGISTRY, AGENT_RETRIEVER, AGENT_ROLES_POLICY, AGENT_RUNNER, AGENT_SINK, AGENT_STORE, AGENT_TOOL_REGISTRY, type Actor, type ActorResolver, type ActorSpendRow, type AgentDefinition, type AgentDelegated, type AgentGovernanceQueries, type AgentLoopDeps, type AgentLoopHooks, type AgentMessageEvent, type AgentPricingStore, type AgentQuotaExceeded, AgentRegistry, type AgentRetrieved, type AgentRunFailed, type AgentRunFinished, type AgentRunInput, type AgentRunStarted, type AgentRunner, type AgentStore, AgentStreamError, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, type CreateThreadInput, type CurrentModelPrice, type Decision, DefaultRolesPolicy, type EmbeddingProvider, type GovernanceRange, type MessageRole, type MessageUsage, type ModelMessage, type ModelPriceInput, type ModelProvider, type ModelSpendRow, type ModelTurnArgs, type ModelTurnResult, type PageContext, type Passage, type Persona, type PromptBuilder, type PromptContext, QuotaExceededError, type QuotaState, type QuotaStore, type RecordToolCallInput, type RecordUsageInput, type RerankOptions, type Reranker, type RetrieveOptions, type Retriever, type RolesPolicy, type SinkWriter, type StoredMessage, type StreamError, type ThreadActivityRow, type ThreadDetail, type ThreadSummary, type TokenStreamSink, type ToolCallActivityRow, type ToolCallRequest, type ToolCallStatus, type ToolDefinition, ToolForbiddenError, type ToolHandler, ToolInputInvalidError, type ToolKind, ToolNotFoundError, ToolRegistry, type ToolResult, type ToolSpec, type UpdateToolCallInput, type UsagePurpose, type UsageTrendPoint, filterToolsByRole, personaFilterTools, publishAgentDelegated, publishAgentMessage, publishAgentQuotaExceeded, publishAgentRetrieved, publishAgentRunFailed, publishAgentRunFinished, publishAgentRunStarted, publishAgentToolCall, runAgentLoop, seedModelPrices };
880
+ export { AGENT_ACTOR_RESOLVER, AGENT_DEPS_FACTORY, AGENT_DURABLE_RUNNER, AGENT_EMBEDDING_PROVIDER, AGENT_GOVERNANCE_QUERIES, AGENT_MODEL, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_QUOTA_STORE, AGENT_REGISTRY, AGENT_RETRIEVER, AGENT_ROLES_POLICY, AGENT_RUNNER, AGENT_SINK, AGENT_STORE, AGENT_TOOL_REGISTRY, type Actor, type ActorResolver, type ActorSpendRow, type AgentDefinition, type AgentDelegated, type AgentGovernanceQueries, type AgentLoopDeps, type AgentLoopHooks, type AgentMessageEvent, type AgentPricingStore, type AgentQuotaExceeded, AgentRegistry, type AgentRetrieved, type AgentRunFailed, type AgentRunFinished, type AgentRunInput, type AgentRunStarted, type AgentRunner, type AgentStore, AgentStreamError, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, type CreateThreadInput, type CurrentModelPrice, type Decision, DefaultRolesPolicy, type EmbeddingProvider, type GovernanceRange, type MessageRole, type MessageUsage, type ModelMessage, type ModelPriceInput, type ModelProvider, type ModelSpendRow, type ModelTurnArgs, type ModelTurnResult, type PageContext, type Passage, type PromptBuilder, type PromptContext, type PromptContributor, QuotaExceededError, type QuotaState, type QuotaStore, type QuotaView, type RecordToolCallInput, type RecordUsageInput, type RerankOptions, type Reranker, type RetrieveOptions, type Retriever, type RolesPolicy, type SinkWriter, type StoredMessage, type StreamError, type ThreadActivityRow, type ThreadDetail, type ThreadSummary, type TokenStreamSink, type ToolCallActivityRow, type ToolCallRequest, type ToolCallStatus, type ToolDefinition, ToolForbiddenError, type ToolHandler, ToolInputInvalidError, type ToolKind, ToolNotFoundError, ToolRegistry, type ToolResult, type ToolSpec, type UpdateToolCallInput, type UsagePurpose, type UsageTrendPoint, filterToolsByAllowList, filterToolsByRole, publishAgentDelegated, publishAgentMessage, publishAgentQuotaExceeded, publishAgentRetrieved, publishAgentRunFailed, publishAgentRunFinished, publishAgentRunStarted, publishAgentToolCall, runAgentLoop, seedModelPrices };
package/dist/index.d.ts CHANGED
@@ -89,6 +89,18 @@ interface QuotaState {
89
89
  limitTokens: number;
90
90
  withinLimit: boolean;
91
91
  }
92
+ /**
93
+ * The read-model the quota-today endpoint returns to a client — a superset of {@link QuotaState}
94
+ * for rendering a usage badge. `limitTokens` is `null` when no quota is configured (unlimited, so
95
+ * `withinLimit` is always true); `costUsd` is the day's summed provider-reported USD spend (`0`
96
+ * when only tokens were reported).
97
+ */
98
+ interface QuotaView {
99
+ usedTokens: number;
100
+ limitTokens: number | null;
101
+ withinLimit: boolean;
102
+ costUsd: number;
103
+ }
92
104
  /** A human decision on a pending action tool call. */
93
105
  interface Decision {
94
106
  approved: boolean;
@@ -107,37 +119,35 @@ interface PageContext {
107
119
  [key: string]: unknown;
108
120
  }
109
121
  /**
110
- * Inputs a {@link PromptBuilder} may use to compose the effective system prompt for a turn.
111
- * `basePrompt` is the agent's own (already-resolved) base prompt, so a persona builder can wrap
112
- * or extend it rather than replace it.
122
+ * Inputs a {@link PromptBuilder} or {@link PromptContributor} may use to compose the system prompt
123
+ * for a turn. Resolved once per turn from stable inputs (actor / agent / pageContext) so it stays
124
+ * replay-safe.
113
125
  */
114
126
  interface PromptContext {
115
127
  actor: Actor;
116
- persona?: Persona;
128
+ /** The selected agent's name. */
129
+ agentName: string;
117
130
  pageContext?: PageContext;
118
- basePrompt: string;
119
131
  }
120
132
  /**
121
- * A dynamic system prompt. Return a string (optionally async) built from the turn's context —
122
- * e.g. injecting the actor, the current page, or a data-shape description. The loop resolves it
123
- * once per turn from stable inputs (actor/persona/pageContext), so it stays replay-safe.
133
+ * An agent's base system prompt. Return a string (optionally async) built from the turn's context —
134
+ * e.g. injecting the actor, the current page, or a data-shape description. Set on an `@Agent` class
135
+ * via a `@SystemPrompt()` method (or a flat string).
124
136
  */
125
137
  type PromptBuilder = (ctx: PromptContext) => string | Promise<string>;
126
- interface Persona {
127
- id: string;
128
- label: string;
129
- /** A flat prompt, or a {@link PromptBuilder} composed per request from {@link PromptContext}. */
130
- systemPrompt: string | PromptBuilder;
131
- /** If set, only these tool names are offered (after role filtering). */
132
- allowedTools?: string[];
133
- }
138
+ /**
139
+ * A cross-agent system-prompt contributor. Returns an ordered section to APPEND to the composed
140
+ * prompt (after the agent's base), or `null` to contribute nothing this turn — so conditional
141
+ * sections (base-scope, a mentions legend, schema hints) stay clean when they don't apply.
142
+ * Registered app-wide via `@SystemPromptContributor()`; the loop runs every contributor in order.
143
+ */
144
+ type PromptContributor = (ctx: PromptContext) => string | null | Promise<string | null>;
134
145
  /** Everything needed to run one agent turn. */
135
146
  interface AgentRunInput {
136
147
  threadId: string;
137
148
  actor: Actor;
138
149
  /** The latest user message text. */
139
150
  userText: string;
140
- persona?: Persona;
141
151
  pageContext?: PageContext;
142
152
  /** YYYY-MM-DD stamped by the runner so quota/day stays deterministic under durable replay. */
143
153
  day?: string;
@@ -163,10 +173,11 @@ interface AgentRunInput {
163
173
  regenerate?: boolean;
164
174
  }
165
175
  /**
166
- * A named agent: its prompt, the tools it may use, and its personas. Multiple definitions are
167
- * registered via `AgentModule.forFeature([...])`; an orchestrator delegates to others through
168
- * `ctx.runAgent(name, task)`. Model/store/sink/governance are shared from the module unless
169
- * overridden here.
176
+ * A named agent: its prompt, the tools it may use, and who it can hand off to. This is the
177
+ * internal record the loop and `AgentDepsFactory` consume; in an app it is authored as an
178
+ * `@Agent`-decorated class and populated into the `AgentRegistry` by discovery (name, base prompt
179
+ * from `@SystemPrompt`, tool allow-list, handoff targets). An orchestrator hands off to others via
180
+ * `ctx.handoff(OtherAgent)`. Model/store/sink/governance are shared from the module.
170
181
  */
171
182
  interface AgentDefinition {
172
183
  name: string;
@@ -174,17 +185,14 @@ interface AgentDefinition {
174
185
  systemPrompt?: string | PromptBuilder;
175
186
  /** Allow-list of tool names this agent may use (subset of all registered tools). */
176
187
  tools?: string[];
177
- /** Names of other agents this agent may delegate to (auto-registered as `agent`-kind tools). */
188
+ /** Names of other agents this agent may hand off to (auto-registered as `agent`-kind tools). */
178
189
  delegatesTo?: string[];
179
- personas?: Persona[];
180
- defaultPersona?: string;
181
190
  modelId?: string;
182
191
  maxSteps?: number;
183
192
  }
184
193
  interface ThreadSummary {
185
194
  id: string;
186
195
  title: string;
187
- persona: string;
188
196
  transient: boolean;
189
197
  createdAt: string;
190
198
  updatedAt: string;
@@ -194,6 +202,8 @@ interface StoredMessage {
194
202
  id: string;
195
203
  role: MessageRole;
196
204
  content: string;
205
+ /** Which agent produced this message (assistant messages) — provenance for replay / UI / telescope. */
206
+ agentName?: string;
197
207
  toolCalls?: ToolCallRequest[];
198
208
  toolResults?: ToolResult[];
199
209
  followUps?: string[];
@@ -242,6 +252,8 @@ declare const AGENT_RETRIEVER: unique symbol;
242
252
  /** The embedding provider (`EmbeddingProvider`) — text→vector for retrieval + ingestion. */
243
253
  declare const AGENT_EMBEDDING_PROVIDER: unique symbol;
244
254
  declare const AGENT_DEPS_FACTORY: unique symbol;
255
+ /** App-wide, ordered `@SystemPromptContributor()` functions the loop appends after the agent base. */
256
+ declare const AGENT_PROMPT_CONTRIBUTORS: unique symbol;
245
257
 
246
258
  /**
247
259
  * Per-invocation context handed to a tool handler. Host-supplied bits are optional. Identity lives
@@ -253,7 +265,8 @@ interface AiToolCtx {
253
265
  threadId: string;
254
266
  runId: string;
255
267
  requestId: string;
256
- persona?: Persona;
268
+ /** The name of the agent running this turn — provenance a tool can scope on (e.g. capability sets). */
269
+ agentName?: string;
257
270
  pageContext?: PageContext;
258
271
  /** Optional host handle (e.g. an ORM EntityManager) the app threads through options. */
259
272
  host?: unknown;
@@ -348,7 +361,6 @@ interface ModelProvider {
348
361
 
349
362
  interface CreateThreadInput {
350
363
  actor: Actor;
351
- persona: string;
352
364
  transient?: boolean;
353
365
  title?: string;
354
366
  }
@@ -356,7 +368,8 @@ interface AppendMessageInput {
356
368
  threadId: string;
357
369
  role: StoredMessage['role'];
358
370
  content: string;
359
- persona?: string;
371
+ /** Which agent produced this message (assistant messages) — provenance. */
372
+ agentName?: string;
360
373
  toolCalls?: ToolCallRequest[];
361
374
  toolResults?: ToolResult[];
362
375
  followUps?: string[];
@@ -396,6 +409,12 @@ interface AgentStore {
396
409
  softDeleteThread(threadId: string): Promise<void>;
397
410
  forkThread(threadId: string, fromMessageId: string): Promise<ThreadSummary>;
398
411
  setTitle(threadId: string, title: string): Promise<void>;
412
+ /**
413
+ * Promote a transient thread to a persistent one so it shows up in {@link listThreads}. A
414
+ * transient thread is a scratch conversation the caller has not chosen to keep; "saving" it
415
+ * clears the flag. Idempotent — promoting an already-persistent thread is a no-op.
416
+ */
417
+ promoteThread(threadId: string): Promise<void>;
399
418
  setActiveStream(threadId: string, runId: string | null): Promise<void>;
400
419
  /**
401
420
  * The `actorRef` that owns a thread, or `null` if no such thread exists. The authorization seam
@@ -427,8 +446,14 @@ interface AgentStore {
427
446
  recordToolCall(input: RecordToolCallInput): Promise<void>;
428
447
  updateToolCall(input: UpdateToolCallInput): Promise<void>;
429
448
  recordUsage(input: RecordUsageInput): Promise<void>;
449
+ /**
450
+ * The actor's spend for `day` (UTC): total tokens plus the summed provider-reported USD cost.
451
+ * `costUsd` is `0` when no turn on that day reported a cost (token-only providers). Feeds both
452
+ * quota enforcement (via {@link QuotaStore}) and the quota-today view.
453
+ */
430
454
  quotaToday(actorRef: string, day: string): Promise<{
431
455
  usedTokens: number;
456
+ costUsd: number;
432
457
  }>;
433
458
  }
434
459
 
@@ -646,8 +671,8 @@ interface AgentGovernanceQueries {
646
671
 
647
672
  /** First filter layer: drop tools the actor's role may not invoke. `can` may be async (authz). */
648
673
  declare function filterToolsByRole(tools: ToolSpec[], actor: Actor, policy: RolesPolicy): Promise<ToolSpec[]>;
649
- /** Second filter layer: if the persona pins an allow-list, keep only those tool names. */
650
- declare function personaFilterTools(tools: ToolSpec[], allowedTools: string[] | undefined): ToolSpec[];
674
+ /** Second filter layer: if the agent pins an allow-list, keep only those tool names. */
675
+ declare function filterToolsByAllowList(tools: ToolSpec[], allowedTools: string[] | undefined): ToolSpec[];
651
676
 
652
677
  /** Holds the named agent definitions registered via `AgentModule.forFeature([...])`. */
653
678
  declare class AgentRegistry {
@@ -687,7 +712,7 @@ declare class ToolRegistry {
687
712
  has(name: string): boolean;
688
713
  spec(name: string): ToolSpec | undefined;
689
714
  allSpecs(): ToolSpec[];
690
- /** The tools to offer the model for this actor+persona, after the two filter layers. */
715
+ /** The tools to offer the model for this actor+agent, after the two filter layers. */
691
716
  definitionsFor(actor: Actor, policy: RolesPolicy, allowedTools?: string[]): Promise<ToolDefinition[]>;
692
717
  /** Run a tool. Re-checks the role (defense-in-depth) and re-parses the input via Zod. */
693
718
  invoke(name: string, input: unknown, ctx: AiToolCtx, policy: RolesPolicy): Promise<unknown>;
@@ -714,10 +739,16 @@ interface AgentLoopDeps {
714
739
  day: string;
715
740
  /** The agent's base prompt. A flat string, or a {@link PromptBuilder} resolved per turn. */
716
741
  systemPrompt: string | PromptBuilder;
742
+ /**
743
+ * Cross-agent system-prompt contributors, applied in order AFTER the agent's base prompt. Each
744
+ * returns a section to append (or `null` to skip this turn). The app registers them via
745
+ * `@SystemPromptContributor()`; the loop composes base + contributors into the effective prompt.
746
+ */
747
+ promptContributors?: PromptContributor[];
717
748
  maxSteps?: number;
718
749
  /** Optional host handle threaded to tool ctx (e.g. an ORM EntityManager). */
719
750
  host?: unknown;
720
- /** Agent-level tool allow-list (intersected with the persona's). Undefined → all tools. */
751
+ /** Agent-level tool allow-list. Undefined → all tools (after role filtering). */
721
752
  toolAllowList?: string[];
722
753
  /**
723
754
  * Per-tool execution timeout in ms. A tool that runs longer is aborted and recorded as failed
@@ -777,7 +808,8 @@ interface AgentRunStarted {
777
808
  runId: string;
778
809
  threadId: string;
779
810
  actorId: string;
780
- persona?: string;
811
+ /** Which agent is handling the run. */
812
+ agentName?: string;
781
813
  }
782
814
  interface AgentMessageEvent {
783
815
  runId: string;
@@ -845,4 +877,4 @@ declare function publishAgentRunFailed(payload: AgentRunFailed): void;
845
877
  declare function publishAgentDelegated(payload: AgentDelegated): void;
846
878
  declare function publishAgentRetrieved(payload: AgentRetrieved): void;
847
879
 
848
- export { AGENT_ACTOR_RESOLVER, AGENT_DEPS_FACTORY, AGENT_DURABLE_RUNNER, AGENT_EMBEDDING_PROVIDER, AGENT_GOVERNANCE_QUERIES, AGENT_MODEL, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_QUOTA_STORE, AGENT_REGISTRY, AGENT_RETRIEVER, AGENT_ROLES_POLICY, AGENT_RUNNER, AGENT_SINK, AGENT_STORE, AGENT_TOOL_REGISTRY, type Actor, type ActorResolver, type ActorSpendRow, type AgentDefinition, type AgentDelegated, type AgentGovernanceQueries, type AgentLoopDeps, type AgentLoopHooks, type AgentMessageEvent, type AgentPricingStore, type AgentQuotaExceeded, AgentRegistry, type AgentRetrieved, type AgentRunFailed, type AgentRunFinished, type AgentRunInput, type AgentRunStarted, type AgentRunner, type AgentStore, AgentStreamError, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, type CreateThreadInput, type CurrentModelPrice, type Decision, DefaultRolesPolicy, type EmbeddingProvider, type GovernanceRange, type MessageRole, type MessageUsage, type ModelMessage, type ModelPriceInput, type ModelProvider, type ModelSpendRow, type ModelTurnArgs, type ModelTurnResult, type PageContext, type Passage, type Persona, type PromptBuilder, type PromptContext, QuotaExceededError, type QuotaState, type QuotaStore, type RecordToolCallInput, type RecordUsageInput, type RerankOptions, type Reranker, type RetrieveOptions, type Retriever, type RolesPolicy, type SinkWriter, type StoredMessage, type StreamError, type ThreadActivityRow, type ThreadDetail, type ThreadSummary, type TokenStreamSink, type ToolCallActivityRow, type ToolCallRequest, type ToolCallStatus, type ToolDefinition, ToolForbiddenError, type ToolHandler, ToolInputInvalidError, type ToolKind, ToolNotFoundError, ToolRegistry, type ToolResult, type ToolSpec, type UpdateToolCallInput, type UsagePurpose, type UsageTrendPoint, filterToolsByRole, personaFilterTools, publishAgentDelegated, publishAgentMessage, publishAgentQuotaExceeded, publishAgentRetrieved, publishAgentRunFailed, publishAgentRunFinished, publishAgentRunStarted, publishAgentToolCall, runAgentLoop, seedModelPrices };
880
+ export { AGENT_ACTOR_RESOLVER, AGENT_DEPS_FACTORY, AGENT_DURABLE_RUNNER, AGENT_EMBEDDING_PROVIDER, AGENT_GOVERNANCE_QUERIES, AGENT_MODEL, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_QUOTA_STORE, AGENT_REGISTRY, AGENT_RETRIEVER, AGENT_ROLES_POLICY, AGENT_RUNNER, AGENT_SINK, AGENT_STORE, AGENT_TOOL_REGISTRY, type Actor, type ActorResolver, type ActorSpendRow, type AgentDefinition, type AgentDelegated, type AgentGovernanceQueries, type AgentLoopDeps, type AgentLoopHooks, type AgentMessageEvent, type AgentPricingStore, type AgentQuotaExceeded, AgentRegistry, type AgentRetrieved, type AgentRunFailed, type AgentRunFinished, type AgentRunInput, type AgentRunStarted, type AgentRunner, type AgentStore, AgentStreamError, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, type CreateThreadInput, type CurrentModelPrice, type Decision, DefaultRolesPolicy, type EmbeddingProvider, type GovernanceRange, type MessageRole, type MessageUsage, type ModelMessage, type ModelPriceInput, type ModelProvider, type ModelSpendRow, type ModelTurnArgs, type ModelTurnResult, type PageContext, type Passage, type PromptBuilder, type PromptContext, type PromptContributor, QuotaExceededError, type QuotaState, type QuotaStore, type QuotaView, type RecordToolCallInput, type RecordUsageInput, type RerankOptions, type Reranker, type RetrieveOptions, type Retriever, type RolesPolicy, type SinkWriter, type StoredMessage, type StreamError, type ThreadActivityRow, type ThreadDetail, type ThreadSummary, type TokenStreamSink, type ToolCallActivityRow, type ToolCallRequest, type ToolCallStatus, type ToolDefinition, ToolForbiddenError, type ToolHandler, ToolInputInvalidError, type ToolKind, ToolNotFoundError, ToolRegistry, type ToolResult, type ToolSpec, type UpdateToolCallInput, type UsagePurpose, type UsageTrendPoint, filterToolsByAllowList, filterToolsByRole, publishAgentDelegated, publishAgentMessage, publishAgentQuotaExceeded, publishAgentRetrieved, publishAgentRunFailed, publishAgentRunFinished, publishAgentRunStarted, publishAgentToolCall, runAgentLoop, seedModelPrices };
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ var AGENT_PRICING_STORE = Symbol.for("@dudousxd/nestjs-agent:pricing-store");
18
18
  var AGENT_RETRIEVER = Symbol.for("@dudousxd/nestjs-agent:retriever");
19
19
  var AGENT_EMBEDDING_PROVIDER = Symbol.for("@dudousxd/nestjs-agent:embedding-provider");
20
20
  var AGENT_DEPS_FACTORY = Symbol.for("@dudousxd/nestjs-agent:deps-factory");
21
+ var AGENT_PROMPT_CONTRIBUTORS = Symbol.for("@dudousxd/nestjs-agent:prompt-contributors");
21
22
 
22
23
  // src/spi/token-stream-sink.ts
23
24
  var AgentStreamError = class extends Error {
@@ -40,7 +41,7 @@ async function seedModelPrices(store, prices) {
40
41
  }
41
42
  __name(seedModelPrices, "seedModelPrices");
42
43
 
43
- // src/personas.ts
44
+ // src/tool-filters.ts
44
45
  async function filterToolsByRole(tools, actor, policy) {
45
46
  const checked = await Promise.all(tools.map(async (tool) => ({
46
47
  tool,
@@ -49,14 +50,14 @@ async function filterToolsByRole(tools, actor, policy) {
49
50
  return checked.filter((entry) => entry.allowed).map((entry) => entry.tool);
50
51
  }
51
52
  __name(filterToolsByRole, "filterToolsByRole");
52
- function personaFilterTools(tools, allowedTools) {
53
+ function filterToolsByAllowList(tools, allowedTools) {
53
54
  if (allowedTools === void 0) {
54
55
  return tools;
55
56
  }
56
57
  const allowed = new Set(allowedTools);
57
58
  return tools.filter((tool) => allowed.has(tool.name));
58
59
  }
59
- __name(personaFilterTools, "personaFilterTools");
60
+ __name(filterToolsByAllowList, "filterToolsByAllowList");
60
61
 
61
62
  // src/agent-registry.ts
62
63
  var AgentRegistry = class {
@@ -134,11 +135,11 @@ var ToolRegistry = class {
134
135
  ...this.entries.values()
135
136
  ].map((entry) => entry.spec);
136
137
  }
137
- /** The tools to offer the model for this actor+persona, after the two filter layers. */
138
+ /** The tools to offer the model for this actor+agent, after the two filter layers. */
138
139
  async definitionsFor(actor, policy, allowedTools) {
139
140
  const roleScoped = await filterToolsByRole(this.allSpecs(), actor, policy);
140
- const personaScoped = personaFilterTools(roleScoped, allowedTools);
141
- return personaScoped.map((spec) => ({
141
+ const allowScoped = filterToolsByAllowList(roleScoped, allowedTools);
142
+ return allowScoped.map((spec) => ({
142
143
  name: spec.name,
143
144
  kind: spec.kind,
144
145
  description: spec.description,
@@ -224,17 +225,6 @@ ${items}
224
225
  Use the retrieved context above to answer when relevant, and cite sources by their bracket number.`;
225
226
  }
226
227
  __name(buildContextBlock, "buildContextBlock");
227
- function intersectAllow(a, b) {
228
- if (a === void 0) {
229
- return b;
230
- }
231
- if (b === void 0) {
232
- return a;
233
- }
234
- const second = new Set(b);
235
- return a.filter((name) => second.has(name));
236
- }
237
- __name(intersectAllow, "intersectAllow");
238
228
  var QuotaExceededError = class extends Error {
239
229
  static {
240
230
  __name(this, "QuotaExceededError");
@@ -250,26 +240,23 @@ async function resolvePrompt(prompt, ctx) {
250
240
  }
251
241
  __name(resolvePrompt, "resolvePrompt");
252
242
  async function resolveSystemPrompt(deps, input) {
253
- const base = {
243
+ const ctx = {
254
244
  actor: input.actor,
255
- ...input.persona !== void 0 ? {
256
- persona: input.persona
257
- } : {},
245
+ agentName: input.agentName ?? "default",
258
246
  ...input.pageContext !== void 0 ? {
259
247
  pageContext: input.pageContext
260
248
  } : {}
261
249
  };
262
- const basePrompt = await resolvePrompt(deps.systemPrompt, {
263
- ...base,
264
- basePrompt: ""
265
- });
266
- if (input.persona === void 0) {
267
- return basePrompt;
250
+ const sections = [
251
+ await resolvePrompt(deps.systemPrompt, ctx)
252
+ ];
253
+ for (const contribute of deps.promptContributors ?? []) {
254
+ const section = await contribute(ctx);
255
+ if (section !== null && section.length > 0) {
256
+ sections.push(section);
257
+ }
268
258
  }
269
- return resolvePrompt(input.persona.systemPrompt, {
270
- ...base,
271
- basePrompt
272
- });
259
+ return sections.join("\n\n");
273
260
  }
274
261
  __name(resolveSystemPrompt, "resolveSystemPrompt");
275
262
  function extractTask(input) {
@@ -347,7 +334,6 @@ async function generateFollowUps(model, messages, count) {
347
334
  __name(generateFollowUps, "generateFollowUps");
348
335
  async function runAgentLoop(deps, input, hooks) {
349
336
  const maxSteps = deps.maxSteps ?? 8;
350
- const persona = input.persona;
351
337
  let system = await resolveSystemPrompt(deps, input);
352
338
  if (deps.quota !== void 0) {
353
339
  const quota = deps.quota;
@@ -381,10 +367,7 @@ async function runAgentLoop(deps, input, hooks) {
381
367
  await hooks.step("persist:user", () => deps.store.appendMessage({
382
368
  threadId: input.threadId,
383
369
  role: "user",
384
- content: input.userText,
385
- ...persona !== void 0 ? {
386
- persona: persona.id
387
- } : {}
370
+ content: input.userText
388
371
  }));
389
372
  }
390
373
  const thread = await hooks.step("load:thread", () => deps.store.getThread(input.threadId));
@@ -407,8 +390,8 @@ async function runAgentLoop(deps, input, hooks) {
407
390
  runId: hooks.runId,
408
391
  threadId: input.threadId,
409
392
  actorId: input.actor.id,
410
- ...persona !== void 0 ? {
411
- persona: persona.id
393
+ ...input.agentName !== void 0 ? {
394
+ agentName: input.agentName
412
395
  } : {}
413
396
  });
414
397
  let injectedPassages;
@@ -430,7 +413,7 @@ ${buildContextBlock(passages)}`;
430
413
  });
431
414
  }
432
415
  for (let i = 0; i < maxSteps; i += 1) {
433
- const tools = await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, intersectAllow(persona?.allowedTools, deps.toolAllowList));
416
+ const tools = await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList);
434
417
  const turn = await hooks.step(`llm:${i}`, () => deps.model.runTurn({
435
418
  system,
436
419
  messages: modelMessages,
@@ -490,8 +473,8 @@ ${buildContextBlock(passages)}`;
490
473
  role: "assistant",
491
474
  content: turn.text,
492
475
  usage: turn.usage,
493
- ...persona !== void 0 ? {
494
- persona: persona.id
476
+ ...input.agentName !== void 0 ? {
477
+ agentName: input.agentName
495
478
  } : {},
496
479
  ...turn.toolCalls.length > 0 ? {
497
480
  toolCalls: turn.toolCalls
@@ -542,8 +525,8 @@ ${buildContextBlock(passages)}`;
542
525
  threadId: input.threadId,
543
526
  runId: hooks.runId,
544
527
  requestId: hooks.runId,
545
- ...persona !== void 0 ? {
546
- persona
528
+ ...input.agentName !== void 0 ? {
529
+ agentName: input.agentName
547
530
  } : {},
548
531
  ...input.pageContext !== void 0 ? {
549
532
  pageContext: input.pageContext
@@ -721,6 +704,7 @@ export {
721
704
  AGENT_MODEL,
722
705
  AGENT_OPTIONS,
723
706
  AGENT_PRICING_STORE,
707
+ AGENT_PROMPT_CONTRIBUTORS,
724
708
  AGENT_QUOTA_STORE,
725
709
  AGENT_REGISTRY,
726
710
  AGENT_RETRIEVER,
@@ -737,8 +721,8 @@ export {
737
721
  ToolInputInvalidError,
738
722
  ToolNotFoundError,
739
723
  ToolRegistry,
724
+ filterToolsByAllowList,
740
725
  filterToolsByRole,
741
- personaFilterTools,
742
726
  publishAgentDelegated,
743
727
  publishAgentMessage,
744
728
  publishAgentQuotaExceeded,