@dudousxd/nestjs-agent 1.19.2 → 1.20.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.cjs CHANGED
@@ -74,6 +74,7 @@ __export(index_exports, {
74
74
  requestUserActorResolver: () => requestUserActorResolver,
75
75
  resolveSkillsConfig: () => resolveSkillsConfig,
76
76
  skillScope: () => skillScope,
77
+ threadPersona: () => threadPersona,
77
78
  toolNameFromClass: () => toolNameFromClass,
78
79
  utcDay: () => utcDay
79
80
  });
@@ -185,8 +186,64 @@ var AgentDepsFactory2 = class {
185
186
  ...delegated
186
187
  ];
187
188
  }
189
+ /**
190
+ * The agent a name stands for: itself when it is a registered agent (or unknown — the bare
191
+ * assistant), else the agent whose persona took the name over (`Persona.aliases`) and that persona.
192
+ */
193
+ resolveAgent(name) {
194
+ if (this.agents.has(name)) {
195
+ return {
196
+ agentName: name
197
+ };
198
+ }
199
+ const alias = (0, import_nestjs_agent_core.resolvePersonaAlias)(this.agents.list(), name);
200
+ return alias === void 0 ? {
201
+ agentName: name
202
+ } : {
203
+ agentName: alias.agent,
204
+ persona: alias.persona
205
+ };
206
+ }
207
+ /** `agentName`'s personas as a picker reads them. Empty when it declares none. */
208
+ personaCatalog(agentName) {
209
+ return (this.agents.get(agentName)?.personas ?? []).map(import_nestjs_agent_core.personaCatalogEntry);
210
+ }
211
+ /** The persona `agentName` runs under when nothing names one. Undefined → none. */
212
+ defaultPersona(agentName) {
213
+ return this.agents.get(agentName)?.defaultPersona;
214
+ }
215
+ /**
216
+ * The persona a turn of `agentName` runs under, most specific first: the one the send names
217
+ * (refused with `400 persona_not_found` when the agent does not declare it), else the thread's
218
+ * pinned one when this agent declares it, else the agent's default. Undefined → none.
219
+ */
220
+ resolvePersona(args) {
221
+ const definition = this.agents.get(args.agentName);
222
+ if (args.requested !== void 0) {
223
+ if ((0, import_nestjs_agent_core.findPersona)(definition, args.requested) === void 0) {
224
+ throw new import_common.BadRequestException({
225
+ statusCode: 400,
226
+ code: "persona_not_found",
227
+ message: `agent "${args.agentName}" has no persona "${args.requested}"`
228
+ });
229
+ }
230
+ return args.requested;
231
+ }
232
+ const pinned = args.threadPersona ?? void 0;
233
+ if (pinned !== void 0 && (0, import_nestjs_agent_core.findPersona)(definition, pinned) !== void 0) {
234
+ return pinned;
235
+ }
236
+ return definition?.defaultPersona;
237
+ }
188
238
  forAgent(agentName) {
189
239
  const name = agentName ?? this.defaultAgentName();
240
+ if (agentName !== void 0 && !this.agents.has(agentName)) {
241
+ const alias = (0, import_nestjs_agent_core.resolvePersonaAlias)(this.agents.list(), agentName);
242
+ const persona = alias === void 0 ? void 0 : (0, import_nestjs_agent_core.findPersona)(this.agents.get(alias.agent), alias.persona);
243
+ if (alias !== void 0 && persona !== void 0) {
244
+ return this.withPersona(this.forAgent(alias.agent), persona);
245
+ }
246
+ }
190
247
  const definition = this.agents.get(name);
191
248
  const toolAllowList = this.effectiveTools(definition);
192
249
  const followUpsCount = this.followUpsCount();
@@ -251,6 +308,9 @@ var AgentDepsFactory2 = class {
251
308
  ...toolAllowList !== void 0 ? {
252
309
  toolAllowList
253
310
  } : {},
311
+ ...definition?.personas !== void 0 ? {
312
+ personas: definition.personas
313
+ } : {},
254
314
  ...this.options.toolTimeoutMs !== void 0 ? {
255
315
  toolTimeoutMs: this.options.toolTimeoutMs
256
316
  } : {},
@@ -269,6 +329,45 @@ var AgentDepsFactory2 = class {
269
329
  };
270
330
  }
271
331
  /**
332
+ * A persona baked into an agent's deps — how a run journaled under an agent name that a persona
333
+ * has since taken over (`Persona.aliases`) is served. That run names no persona of its own (it
334
+ * predates them, and its input cannot change), so the persona applies the way the old agent's own
335
+ * config did: as config, re-read on every replay, with no checkpoint of its own.
336
+ */
337
+ withPersona(deps, persona) {
338
+ const base = deps.systemPrompt;
339
+ const ref = {
340
+ id: persona.id,
341
+ label: persona.label
342
+ };
343
+ const resolve = /* @__PURE__ */ __name(async (prompt, ctx) => typeof prompt === "function" ? prompt(ctx) : prompt, "resolve");
344
+ const own = persona.systemPrompt;
345
+ const systemPrompt = /* @__PURE__ */ __name(async (ctx) => {
346
+ const scoped = {
347
+ ...ctx,
348
+ persona: ref
349
+ };
350
+ if (own === void 0) {
351
+ return resolve(base, scoped);
352
+ }
353
+ const basePrompt = typeof own === "function" ? await resolve(base, scoped) : void 0;
354
+ return resolve(own, {
355
+ ...scoped,
356
+ ...basePrompt !== void 0 ? {
357
+ basePrompt
358
+ } : {}
359
+ });
360
+ }, "systemPrompt");
361
+ const toolAllowList = (0, import_nestjs_agent_core.intersectAllowLists)(deps.toolAllowList, persona.allowedTools);
362
+ return {
363
+ ...deps,
364
+ systemPrompt,
365
+ ...toolAllowList !== void 0 ? {
366
+ toolAllowList
367
+ } : {}
368
+ };
369
+ }
370
+ /**
272
371
  * The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
273
372
  * own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
274
373
  * `history` window. Undefined → unbounded, the behaviour of a module that configures none.
@@ -371,6 +470,18 @@ __name(childSinkWriter, "childSinkWriter");
371
470
  // src/queue/chat-queue.service.ts
372
471
  var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
373
472
  var import_common2 = require("@nestjs/common");
473
+
474
+ // src/thread-persona.ts
475
+ async function threadPersona(store, threadId) {
476
+ const projecting = store;
477
+ if (typeof projecting.personaForThread === "function") {
478
+ return projecting.personaForThread(threadId);
479
+ }
480
+ return (await store.getThread(threadId))?.persona ?? null;
481
+ }
482
+ __name(threadPersona, "threadPersona");
483
+
484
+ // src/queue/chat-queue.service.ts
374
485
  function _ts_decorate2(decorators, target, key, desc) {
375
486
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
376
487
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -407,18 +518,20 @@ var ChatQueueService = class _ChatQueueService {
407
518
  quotaProvider;
408
519
  options;
409
520
  staging;
521
+ deps;
410
522
  logger = new import_common2.Logger(_ChatQueueService.name);
411
523
  /**
412
524
  * Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
413
525
  * alive whatever the runner says — it simply has not registered the run yet.
414
526
  */
415
527
  starting = /* @__PURE__ */ new Set();
416
- constructor(store, sink, quotaProvider, options, staging) {
528
+ constructor(store, sink, quotaProvider, options, staging, deps) {
417
529
  this.store = store;
418
530
  this.sink = sink;
419
531
  this.quotaProvider = quotaProvider;
420
532
  this.options = options;
421
533
  this.staging = staging;
534
+ this.deps = deps;
422
535
  }
423
536
  /** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
424
537
  get supported() {
@@ -716,13 +829,17 @@ var ChatQueueService = class _ChatQueueService {
716
829
  */
717
830
  async inputFor(message) {
718
831
  const attachments = await this.freshAttachments(message.actor, message.attachments ?? []);
832
+ const { agentName, persona } = await this.targetFor(message);
719
833
  return {
720
834
  threadId: message.threadId,
721
835
  actor: message.actor,
722
836
  userText: message.content,
723
837
  day: utcDay(),
724
- ...message.agentName !== void 0 ? {
725
- agentName: message.agentName
838
+ ...agentName !== void 0 ? {
839
+ agentName
840
+ } : {},
841
+ ...persona !== void 0 ? {
842
+ persona
726
843
  } : {},
727
844
  ...attachments.length > 0 ? {
728
845
  attachments
@@ -735,6 +852,37 @@ var ChatQueueService = class _ChatQueueService {
735
852
  } : {}
736
853
  };
737
854
  }
855
+ /**
856
+ * The agent and persona a queued message starts as. A message carries the persona its send
857
+ * resolved; one that carries none — queued before personas existed, or by a host that sets none —
858
+ * resolves it now, exactly as a send would: an agent name a persona took over, then the thread's
859
+ * pinned persona, then the agent's default. Never refused here: nobody is waiting on a 400.
860
+ */
861
+ async targetFor(message) {
862
+ const deps = this.deps;
863
+ if (message.persona !== void 0 || deps === void 0 || message.agentName === void 0) {
864
+ return {
865
+ ...message.agentName !== void 0 ? {
866
+ agentName: message.agentName
867
+ } : {},
868
+ ...message.persona !== void 0 ? {
869
+ persona: message.persona
870
+ } : {}
871
+ };
872
+ }
873
+ const target = deps.resolveAgent(message.agentName);
874
+ const hasPersonas = deps.personaCatalog(target.agentName).length > 0;
875
+ const persona = target.persona ?? (hasPersonas ? deps.resolvePersona({
876
+ agentName: target.agentName,
877
+ threadPersona: await threadPersona(this.store, message.threadId)
878
+ }) : void 0);
879
+ return {
880
+ agentName: target.agentName,
881
+ ...persona !== void 0 ? {
882
+ persona
883
+ } : {}
884
+ };
885
+ }
738
886
  async freshAttachments(actor, attachments) {
739
887
  const staging = this.staging;
740
888
  if (attachments.length === 0 || staging === void 0 || typeof staging.resolve !== "function") {
@@ -762,13 +910,16 @@ ChatQueueService = _ts_decorate2([
762
910
  _ts_param2(3, (0, import_common2.Inject)(import_nestjs_agent_core3.AGENT_OPTIONS)),
763
911
  _ts_param2(4, (0, import_common2.Optional)()),
764
912
  _ts_param2(4, (0, import_common2.Inject)(import_nestjs_agent_core3.AGENT_ATTACHMENT_STAGING)),
913
+ _ts_param2(5, (0, import_common2.Optional)()),
914
+ _ts_param2(5, (0, import_common2.Inject)(import_nestjs_agent_core3.AGENT_DEPS_FACTORY)),
765
915
  _ts_metadata2("design:type", Function),
766
916
  _ts_metadata2("design:paramtypes", [
767
917
  typeof AgentStore === "undefined" ? Object : AgentStore,
768
918
  typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
769
919
  typeof QuotaProvider === "undefined" ? Object : QuotaProvider,
770
920
  typeof Pick === "undefined" ? Object : Pick,
771
- typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore
921
+ typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore,
922
+ typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory
772
923
  ])
773
924
  ], ChatQueueService);
774
925
  function pauseReasonFor(outcome, head) {
@@ -1022,7 +1173,9 @@ var AgentService = class {
1022
1173
  */
1023
1174
  async send(params) {
1024
1175
  await this.assertWithinQuota(params.actor);
1025
- const agentName = await this.resolveAgentName(params.agentName, params.threadId);
1176
+ const target = this.deps.resolveAgent(await this.resolveAgentName(params.agentName, params.threadId));
1177
+ const agentName = target.agentName;
1178
+ const persona = await this.resolvePersona(agentName, params.personaId ?? target.persona, params.threadId);
1026
1179
  const model = await this.resolveModel(params.actor, agentName, params.model, params.threadId);
1027
1180
  const attachments = await this.resolveAttachments(params.actor, params.attachments ?? []);
1028
1181
  let threadId = params.threadId;
@@ -1037,11 +1190,17 @@ var AgentService = class {
1037
1190
  } : {},
1038
1191
  ...params.newThreadId !== void 0 ? {
1039
1192
  id: params.newThreadId
1193
+ } : {},
1194
+ ...params.personaId !== void 0 ? {
1195
+ persona: params.personaId
1040
1196
  } : {}
1041
1197
  });
1042
1198
  threadId = created.id;
1043
1199
  } else {
1044
1200
  await this.assertOwnsThread(params.actor, threadId);
1201
+ if (params.personaId !== void 0) {
1202
+ await this.pinThreadPersona(threadId, params.personaId);
1203
+ }
1045
1204
  }
1046
1205
  const input = {
1047
1206
  threadId,
@@ -1049,6 +1208,9 @@ var AgentService = class {
1049
1208
  userText: params.message,
1050
1209
  day: utcDay(),
1051
1210
  agentName,
1211
+ ...persona !== void 0 ? {
1212
+ persona
1213
+ } : {},
1052
1214
  ...params.regenerate === true ? {
1053
1215
  regenerate: true
1054
1216
  } : {},
@@ -1135,6 +1297,9 @@ var AgentService = class {
1135
1297
  ...input.agentName !== void 0 ? {
1136
1298
  agentName: input.agentName
1137
1299
  } : {},
1300
+ ...input.persona !== void 0 ? {
1301
+ persona: input.persona
1302
+ } : {},
1138
1303
  ...input.model !== void 0 ? {
1139
1304
  model: input.model
1140
1305
  } : {},
@@ -1529,16 +1694,22 @@ var AgentService = class {
1529
1694
  async updateThread(actor, threadId, patch) {
1530
1695
  const title = patch.title !== void 0 ? this.validateTitle(patch.title) : void 0;
1531
1696
  await this.assertOwnsThread(actor, threadId);
1532
- if (patch.defaultAgent === void 0 && patch.model === void 0) {
1697
+ if (patch.defaultAgent === void 0 && patch.model === void 0 && patch.persona === void 0) {
1533
1698
  if (title !== void 0) {
1534
1699
  await this.store.setTitle(threadId, title);
1535
1700
  }
1536
1701
  return;
1537
1702
  }
1538
1703
  if (this.store.updateThread === void 0) {
1539
- throw new import_common4.NotImplementedException("Setting a thread's defaultAgent or model requires an AgentStore that implements updateThread(); the bound store does not support it.");
1704
+ throw new import_common4.NotImplementedException("Setting a thread's defaultAgent, model or persona requires an AgentStore that implements updateThread(); the bound store does not support it.");
1540
1705
  }
1541
1706
  const model = patch.model === void 0 || patch.model === null ? patch.model : await this.assertModelAllowed(actor, patch.defaultAgent ?? await this.resolveAgentName(void 0, threadId), patch.model);
1707
+ if (patch.persona !== void 0 && patch.persona !== null) {
1708
+ this.deps.resolvePersona({
1709
+ agentName: this.deps.resolveAgent(patch.defaultAgent ?? await this.resolveAgentName(void 0, threadId)).agentName,
1710
+ requested: patch.persona
1711
+ });
1712
+ }
1542
1713
  await this.store.updateThread(threadId, {
1543
1714
  ...title !== void 0 ? {
1544
1715
  title
@@ -1548,9 +1719,39 @@ var AgentService = class {
1548
1719
  } : {},
1549
1720
  ...model !== void 0 ? {
1550
1721
  model
1722
+ } : {},
1723
+ ...patch.persona !== void 0 ? {
1724
+ persona: patch.persona
1551
1725
  } : {}
1552
1726
  });
1553
1727
  }
1728
+ /**
1729
+ * The persona this send runs under — see {@link AgentDepsFactory.resolvePersona}. The thread's
1730
+ * pinned persona is read only when the send names none AND the agent has personas to pick from.
1731
+ */
1732
+ async resolvePersona(agentName, requested, threadId) {
1733
+ const needsThread = requested === void 0 && threadId !== void 0 && this.deps.personaCatalog(agentName).length > 0;
1734
+ return this.deps.resolvePersona({
1735
+ agentName,
1736
+ ...requested !== void 0 ? {
1737
+ requested
1738
+ } : {},
1739
+ ...needsThread ? {
1740
+ threadPersona: await threadPersona(this.store, threadId)
1741
+ } : {}
1742
+ });
1743
+ }
1744
+ /** Pin `persona` on the thread when it is not already — a no-op on a store that cannot. */
1745
+ async pinThreadPersona(threadId, persona) {
1746
+ if (this.store.updateThread === void 0) {
1747
+ return;
1748
+ }
1749
+ if (await threadPersona(this.store, threadId) !== persona) {
1750
+ await this.store.updateThread(threadId, {
1751
+ persona
1752
+ });
1753
+ }
1754
+ }
1554
1755
  validateTitle(title) {
1555
1756
  const trimmed = title.trim();
1556
1757
  if (trimmed.length === 0) {
@@ -1596,6 +1797,7 @@ var AgentService = class {
1596
1797
  ...thread,
1597
1798
  defaultAgent: thread.defaultAgent ?? null,
1598
1799
  model: thread.model ?? null,
1800
+ persona: thread.persona ?? null,
1599
1801
  activeRunId: await this.store.activeRunForThread?.(thread.id) ?? thread.activeRunId ?? null
1600
1802
  };
1601
1803
  }
@@ -1997,13 +2199,22 @@ var AgentsController = class {
1997
2199
  }
1998
2200
  list() {
1999
2201
  const defaultAgentName = this.depsFactory.defaultAgentName();
2000
- return this.agents.list().map((definition) => ({
2001
- name: definition.name,
2002
- description: definition.description ?? "",
2003
- ...definition.name === defaultAgentName ? {
2004
- isDefault: true
2005
- } : {}
2006
- }));
2202
+ return this.agents.list().map((definition) => {
2203
+ const personas = this.depsFactory.personaCatalog(definition.name);
2204
+ return {
2205
+ name: definition.name,
2206
+ description: definition.description ?? "",
2207
+ ...definition.name === defaultAgentName ? {
2208
+ isDefault: true
2209
+ } : {},
2210
+ ...personas.length > 0 ? {
2211
+ personas
2212
+ } : {},
2213
+ ...definition.defaultPersona !== void 0 ? {
2214
+ defaultPersona: definition.defaultPersona
2215
+ } : {}
2216
+ };
2217
+ });
2007
2218
  }
2008
2219
  };
2009
2220
  _ts_decorate5([
@@ -2273,6 +2484,9 @@ var ChatController = class {
2273
2484
  ...body.agent !== void 0 ? {
2274
2485
  agentName: body.agent
2275
2486
  } : {},
2487
+ ...typeof body.persona === "string" && body.persona.length > 0 ? {
2488
+ personaId: body.persona
2489
+ } : {},
2276
2490
  ...attachments.length > 0 ? {
2277
2491
  attachments
2278
2492
  } : {},
@@ -3272,6 +3486,9 @@ var ThreadsController = class {
3272
3486
  } : {},
3273
3487
  ..."model" in body ? {
3274
3488
  model: body.model ?? null
3489
+ } : {},
3490
+ ..."persona" in body ? {
3491
+ persona: body.persona ?? null
3275
3492
  } : {}
3276
3493
  });
3277
3494
  return {
@@ -3785,6 +4002,7 @@ var AgentDiscoveryService = class _AgentDiscoveryService {
3785
4002
  }
3786
4003
  this.collectContributors(instance);
3787
4004
  }
4005
+ this.assertPersonas(this.registry.list());
3788
4006
  }
3789
4007
  /** Build the internal {@link AgentDefinition} from an `@Agent` instance and its options. */
3790
4008
  definitionFrom(instance, meta) {
@@ -3828,10 +4046,48 @@ var AgentDiscoveryService = class _AgentDiscoveryService {
3828
4046
  } : {},
3829
4047
  ...meta.handoff !== void 0 ? {
3830
4048
  delegatesTo: this.handoffEdges(meta)
4049
+ } : {},
4050
+ ...meta.personas !== void 0 ? {
4051
+ personas: meta.personas
4052
+ } : {},
4053
+ ...meta.defaultPersona !== void 0 ? {
4054
+ defaultPersona: meta.defaultPersona
3831
4055
  } : {}
3832
4056
  };
3833
4057
  }
3834
4058
  /**
4059
+ * Refuse to boot on a persona declaration that could only fail later, per request: a default that
4060
+ * names no persona, an id declared twice, an alias two personas claim, or an alias that is a real
4061
+ * agent's name (which would never be reached — a registered agent always wins).
4062
+ */
4063
+ assertPersonas(definitions) {
4064
+ const agentNames = new Set(definitions.map((definition) => definition.name));
4065
+ const aliasOwner = /* @__PURE__ */ new Map();
4066
+ for (const definition of definitions) {
4067
+ const ids = /* @__PURE__ */ new Set();
4068
+ for (const persona of definition.personas ?? []) {
4069
+ if (ids.has(persona.id)) {
4070
+ throw new Error(`Agent "${definition.name}" declares persona "${persona.id}" twice.`);
4071
+ }
4072
+ ids.add(persona.id);
4073
+ for (const alias of persona.aliases ?? []) {
4074
+ const owner = `${definition.name}/${persona.id}`;
4075
+ if (agentNames.has(alias)) {
4076
+ throw new Error(`Persona ${owner} claims the alias "${alias}", which is a registered agent's name.`);
4077
+ }
4078
+ const previous = aliasOwner.get(alias);
4079
+ if (previous !== void 0) {
4080
+ throw new Error(`Personas ${previous} and ${owner} both claim the alias "${alias}".`);
4081
+ }
4082
+ aliasOwner.set(alias, owner);
4083
+ }
4084
+ }
4085
+ if (definition.defaultPersona !== void 0 && !ids.has(definition.defaultPersona)) {
4086
+ throw new Error(`Agent "${definition.name}" names "${definition.defaultPersona}" as its default persona, but declares no persona with that id.`);
4087
+ }
4088
+ }
4089
+ }
4090
+ /**
3835
4091
  * Resolve each handoff entry to an {@link AgentDelegation} — its target's registered name, plus
3836
4092
  * whether that edge detaches (skipping non-agent classes). A bare name is kept for a plain edge
3837
4093
  * rather than an always-object form, so a definition an older host built by hand still reads.
@@ -5425,6 +5681,9 @@ var AgUiRunHandler = class {
5425
5681
  ...forwarded.agent !== void 0 ? {
5426
5682
  agentName: forwarded.agent
5427
5683
  } : {},
5684
+ ...forwarded.persona !== void 0 ? {
5685
+ personaId: forwarded.persona
5686
+ } : {},
5428
5687
  ...forwarded.model !== void 0 ? {
5429
5688
  model: forwarded.model
5430
5689
  } : {},
@@ -5740,6 +5999,7 @@ __reExport(index_exports, require("@dudousxd/nestjs-agent-core"), module.exports
5740
5999
  requestUserActorResolver,
5741
6000
  resolveSkillsConfig,
5742
6001
  skillScope,
6002
+ threadPersona,
5743
6003
  toolNameFromClass,
5744
6004
  utcDay,
5745
6005
  ...require("@dudousxd/nestjs-agent-core")