@dudousxd/nestjs-agent 1.28.0 → 1.30.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.
@@ -228,6 +228,28 @@ function invalid(code, message) {
228
228
  });
229
229
  }
230
230
  __name(invalid, "invalid");
231
+ function bodyField(body, key) {
232
+ if (body[key] !== void 0) return body[key];
233
+ return isRecord(body.metadata) ? body.metadata[key] : void 0;
234
+ }
235
+ __name(bodyField, "bodyField");
236
+ function readDataModel(raw, maxBytes) {
237
+ if (raw === void 0) return void 0;
238
+ if (!isRecord(raw) || !isRecord(raw.surfaces)) {
239
+ return "a2uiClientDataModel carries { version, surfaces }";
240
+ }
241
+ let size;
242
+ try {
243
+ size = new TextEncoder().encode(JSON.stringify(raw.surfaces)).length;
244
+ } catch {
245
+ return "a2uiClientDataModel must be JSON";
246
+ }
247
+ if (size > maxBytes) {
248
+ return `a2uiClientDataModel may be at most ${maxBytes} bytes (this one is ${size})`;
249
+ }
250
+ return Object.keys(raw.surfaces).length > 0 ? raw.surfaces : void 0;
251
+ }
252
+ __name(readDataModel, "readDataModel");
231
253
  var A2uiRunHandler = class {
232
254
  static {
233
255
  __name(this, "A2uiRunHandler");
@@ -237,17 +259,43 @@ var A2uiRunHandler = class {
237
259
  projection;
238
260
  quietMs;
239
261
  maxActionBytes;
240
- constructor(agent, actorResolver, projection, quietMs, maxActionBytes) {
262
+ maxDataModelBytes;
263
+ constructor(agent, actorResolver, projection, quietMs, maxActionBytes, maxDataModelBytes = void 0) {
241
264
  this.agent = agent;
242
265
  this.actorResolver = actorResolver;
243
266
  this.projection = projection;
244
267
  this.quietMs = quietMs;
245
268
  this.maxActionBytes = maxActionBytes;
269
+ this.maxDataModelBytes = maxDataModelBytes;
270
+ }
271
+ /**
272
+ * `GET <path>/a2ui/threads/:threadId`: a stored thread as `{ threadId, entries }`
273
+ * (`a2uiThreadReplay`) — what a client draws again after a reload. Owner-scoped; a thread nobody
274
+ * has answers `{ entries: [] }`. `?catalog=<id>` names the client's basic catalog id.
275
+ */
276
+ async replay(req, threadId, catalog) {
277
+ const actor = await this.actorResolver.resolve(req);
278
+ const owner = await this.agent.threadOwner(threadId);
279
+ if (owner === null) return {
280
+ threadId,
281
+ entries: []
282
+ };
283
+ const thread = await this.agent.getThread(actor, threadId);
284
+ const supported = typeof catalog === "string" ? [
285
+ catalog
286
+ ] : Array.isArray(catalog) ? catalog.filter((id) => typeof id === "string") : void 0;
287
+ return {
288
+ threadId,
289
+ entries: (0, import_a2ui.a2uiThreadReplay)(thread?.messages ?? [], (0, import_a2ui.negotiateA2uiCatalog)(this.projection, supported))
290
+ };
246
291
  }
247
292
  async handle(req, res, raw) {
248
293
  const body = isRecord(raw) ? raw : {};
249
294
  const agentName = typeof body.agent === "string" && body.agent.length > 0 ? body.agent : void 0;
250
295
  const actor = await this.actorResolver.resolve(req);
296
+ const projection = (0, import_a2ui.negotiateA2uiCatalog)(this.projection, (0, import_a2ui.readA2uiClientCapabilities)(bodyField(body, "a2uiClientCapabilities")));
297
+ const dataModel = readDataModel(bodyField(body, "a2uiClientDataModel"), this.maxDataModelBytes ?? 32768);
298
+ if (typeof dataModel === "string") throw invalid("invalid_data_model", dataModel);
251
299
  let action;
252
300
  if (body.action !== void 0) {
253
301
  const read = isRecord(body.action) ? (0, import_a2ui.readA2uiAction)(body.action, {
@@ -275,7 +323,7 @@ var A2uiRunHandler = class {
275
323
  this.writeHead(res, {
276
324
  "X-Agent-Thread-Id": address.threadId
277
325
  });
278
- const projector = new import_a2ui.A2uiProjector(this.projection);
326
+ const projector = new import_a2ui.A2uiProjector(projection);
279
327
  const messageId = `proposal-${address.proposalId}`;
280
328
  for (const event of [
281
329
  {
@@ -310,7 +358,8 @@ var A2uiRunHandler = class {
310
358
  skip: address.position,
311
359
  answered: [
312
360
  address.toolCallId
313
- ]
361
+ ],
362
+ projection
314
363
  });
315
364
  return;
316
365
  }
@@ -345,10 +394,16 @@ var A2uiRunHandler = class {
345
394
  } : {},
346
395
  ...uiCapabilities !== void 0 ? {
347
396
  uiCapabilities
397
+ } : {},
398
+ ...dataModel !== void 0 ? {
399
+ pageContext: {
400
+ a2uiDataModel: dataModel
401
+ }
348
402
  } : {}
349
403
  });
350
404
  await this.stream(res, started.runId, {
351
- threadId: started.threadId
405
+ threadId: started.threadId,
406
+ projection
352
407
  });
353
408
  }
354
409
  /** Open the JSON Lines response. */
@@ -369,7 +424,7 @@ var A2uiRunHandler = class {
369
424
  "X-Agent-Thread-Id": options.threadId
370
425
  } : {}
371
426
  });
372
- const projector = new import_a2ui.A2uiProjector(this.projection);
427
+ const projector = new import_a2ui.A2uiProjector(options.projection ?? this.projection);
373
428
  const write = /* @__PURE__ */ __name((messages) => {
374
429
  for (const message of messages) res.write(`${JSON.stringify(message)}
375
430
  `);
@@ -406,7 +461,7 @@ var A2uiRunHandler = class {
406
461
  };
407
462
  function a2uiAdapter(options = {}) {
408
463
  const path = (options.path ?? "a2ui").replace(/^\/+|\/+$/g, "");
409
- const { path: _path, quietMs, maxActionBytes, ...projection } = options;
464
+ const { path: _path, quietMs, maxActionBytes, maxDataModelBytes, ...projection } = options;
410
465
  let A2uiController = class A2uiController extends A2uiRunHandler {
411
466
  static {
412
467
  __name(this, "A2uiController");
@@ -415,12 +470,28 @@ function a2uiAdapter(options = {}) {
415
470
  super(agent, actorResolver, projection.catalog === void 0 && catalog !== void 0 ? {
416
471
  ...projection,
417
472
  catalog
418
- } : projection, quietMs, maxActionBytes);
473
+ } : projection, quietMs, maxActionBytes, maxDataModelBytes);
474
+ }
475
+ thread(req, threadId, catalog) {
476
+ return this.replay(req, threadId, catalog);
419
477
  }
420
478
  run(req, res, body) {
421
479
  return this.handle(req, res, body);
422
480
  }
423
481
  };
482
+ _ts_decorate2([
483
+ (0, import_common2.Get)(`${path}/threads/:threadId`),
484
+ _ts_param(0, (0, import_common2.Req)()),
485
+ _ts_param(1, (0, import_common2.Param)("threadId")),
486
+ _ts_param(2, (0, import_common2.Query)("catalog")),
487
+ _ts_metadata("design:type", Function),
488
+ _ts_metadata("design:paramtypes", [
489
+ typeof Request === "undefined" ? Object : Request,
490
+ String,
491
+ Object
492
+ ]),
493
+ _ts_metadata("design:returntype", typeof ReturnType === "undefined" ? Object : ReturnType)
494
+ ], A2uiController.prototype, "thread", null);
424
495
  _ts_decorate2([
425
496
  (0, import_common2.Post)(path),
426
497
  (0, import_common2.HttpCode)(200),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/a2ui/index.ts","../../src/a2ui/a2ui.adapter.ts","../../src/agent-service.token.ts","../../src/genui/agent-genui.module.ts","../../src/functional-tool.ts"],"sourcesContent":["/**\n * `@dudousxd/nestjs-agent/a2ui` — A2UI (https://a2ui.org, v0.9) for the agent: the `a2uiAdapter()`\n * route (JSON Lines of A2UI messages), and the framework-free pieces it is built from (re-exported\n * from `@dudousxd/nestjs-agent-core/a2ui`) — the `ui` frame → A2UI converter, the builtin →\n * basic-catalog mappings, the catalog export, the inbound action reader. For A2UI over AG-UI, pass\n * `a2ui: true` to `agUiAdapter()`.\n */\n\nexport * from '@dudousxd/nestjs-agent-core/a2ui';\nexport { type A2uiAdapterOptions, A2uiRunHandler, a2uiAdapter } from './a2ui.adapter.js';\n","import { AGENT_ACTOR_RESOLVER, type ActorResolver } from '@dudousxd/nestjs-agent-core';\nimport {\n A2UI_APPROVE_ACTION,\n A2UI_REJECT_ACTION,\n A2uiProjector,\n type A2uiServerMessage,\n type A2uiStreamOptions,\n readA2uiAction,\n} from '@dudousxd/nestjs-agent-core/a2ui';\nimport {\n agUiEvents,\n agUiFramesFromNdjson,\n decodeInterruptId,\n} from '@dudousxd/nestjs-agent-core/ag-ui';\nimport {\n type Catalog,\n type UiAction,\n type UiCapabilities,\n uiActionText,\n validateUiCapabilities,\n} from '@dudousxd/nestjs-agent-core/genui';\nimport {\n BadRequestException,\n Body,\n Controller,\n ForbiddenException,\n HttpCode,\n Inject,\n Optional,\n Post,\n Req,\n Res,\n} from '@nestjs/common';\nimport type { Request, Response } from 'express';\nimport { AGENT_SERVICE } from '../agent-service.token.js';\nimport type { AgentService } from '../agent.service.js';\nimport { GENUI_CATALOG } from '../genui/agent-genui.module.js';\nimport type { AgentProtocolAdapter } from '../protocol-adapter.js';\n\nexport interface A2uiAdapterOptions extends A2uiStreamOptions {\n /** Where the route mounts, under the agent's path. Default `'a2ui'` → `POST <path>/a2ui`. */\n path?: string;\n /** As `agUiAdapter`'s: how long a run waiting on a person may stay silent before the stream ends. */\n quietMs?: number;\n /** Largest action accepted, as JSON, in bytes. Default 8192. */\n maxActionBytes?: number;\n}\n\n/** The surface an A2UI decision is recorded as having come through. */\nconst A2UI_VIA = 'a2ui';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalid(code: string, message: string): BadRequestException {\n return new BadRequestException({ statusCode: 400, code, message });\n}\n\n/**\n * Serves the agent as an A2UI agent. Not mounted directly — {@link a2uiAdapter} mounts a subclass at\n * the configured path — but exported so a host can extend it under a route of its own.\n */\nexport class A2uiRunHandler {\n constructor(\n protected readonly agent: AgentService,\n protected readonly actorResolver: ActorResolver,\n protected readonly projection: A2uiStreamOptions,\n protected readonly quietMs: number | undefined,\n protected readonly maxActionBytes: number | undefined,\n ) {}\n\n async handle(req: Request, res: Response, raw: unknown): Promise<void> {\n const body = isRecord(raw) ? raw : {};\n const agentName =\n typeof body.agent === 'string' && body.agent.length > 0 ? body.agent : undefined;\n const actor = await this.actorResolver.resolve(req);\n\n let action: UiAction | undefined;\n if (body.action !== undefined) {\n const read = isRecord(body.action)\n ? readA2uiAction(body.action, {\n ...(this.maxActionBytes !== undefined ? { maxBytes: this.maxActionBytes } : {}),\n })\n : 'action must be an A2UI action message';\n if (typeof read === 'string') throw invalid('invalid_action', read);\n action = read;\n }\n\n // Deciding the approval a stream ended on: settle it, then stream the rest of the run.\n if (action?.name === A2UI_APPROVE_ACTION || action?.name === A2UI_REJECT_ACTION) {\n const interruptId = action.context.interruptId;\n const address = typeof interruptId === 'string' ? decodeInterruptId(interruptId) : undefined;\n if (address === undefined || address === null || address.kind === 'elicitation') {\n throw invalid('invalid_action', 'an approval action carries the interruptId it decides');\n }\n // An independent proposal: decided through the proposal service (its own run is not\n // waiting), as an AG-UI resume decides it, and answered with the configured reply.\n if (address.kind === 'proposal') {\n if (address.proposalId === undefined || address.threadId === undefined) {\n throw invalid('invalid_action', 'the interruptId names no proposal');\n }\n const decided = await this.agent.decideActionProposal(\n actor,\n address.threadId,\n address.proposalId,\n {\n decision: action.name === A2UI_APPROVE_ACTION ? 'approved' : 'rejected',\n via: A2UI_VIA,\n },\n );\n this.writeHead(res, { 'X-Agent-Thread-Id': address.threadId });\n const projector = new A2uiProjector(this.projection);\n const messageId = `proposal-${address.proposalId}`;\n for (const event of [\n { type: 'TEXT_MESSAGE_START' as const, messageId, role: 'assistant' as const },\n { type: 'TEXT_MESSAGE_CONTENT' as const, messageId, delta: decided.text },\n ]) {\n for (const message of projector.project(event)) res.write(`${JSON.stringify(message)}\\n`);\n }\n res.end();\n return;\n }\n // The same checks an AG-UI resume makes: the run is the actor's and still waiting, and the\n // actor may decide this call.\n await this.agent.assertResumable(actor, address.stream);\n await this.agent.checkDecision(actor, address.toolCallId);\n if (action.name === A2UI_APPROVE_ACTION) {\n await this.agent.approve(actor, address.toolCallId, { via: A2UI_VIA });\n } else {\n await this.agent.reject(actor, address.toolCallId, undefined, { via: A2UI_VIA });\n }\n await this.stream(res, address.stream, {\n skip: address.position,\n answered: [address.toolCallId],\n });\n return;\n }\n\n const message = typeof body.message === 'string' ? body.message : '';\n const text = action !== undefined ? uiActionText(action) : message;\n if (text.trim().length === 0) {\n throw invalid('no_user_message', 'send a message or an action');\n }\n const threadId =\n typeof body.threadId === 'string' && body.threadId.length > 0 ? body.threadId : undefined;\n const owner = threadId !== undefined ? await this.agent.threadOwner(threadId) : null;\n if (owner !== null && owner !== actor.id) {\n throw new ForbiddenException('thread belongs to another actor');\n }\n let uiCapabilities: UiCapabilities | undefined;\n if (Object.hasOwn(body, 'uiCapabilities')) {\n try {\n uiCapabilities = validateUiCapabilities(body.uiCapabilities);\n } catch (error) {\n throw invalid('invalid_input', (error as Error).message);\n }\n }\n const started = await this.agent.chat({\n actor,\n message: text,\n ...(threadId === undefined ? {} : owner !== null ? { threadId } : { newThreadId: threadId }),\n ...(agentName !== undefined ? { agentName } : {}),\n ...(uiCapabilities !== undefined ? { uiCapabilities } : {}),\n });\n await this.stream(res, started.runId, { threadId: started.threadId });\n }\n\n /** Open the JSON Lines response. */\n protected writeHead(res: Response, extra: Record<string, string>): void {\n res.status(200);\n res.setHeader('Content-Type', 'application/jsonl; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n for (const [name, value] of Object.entries(extra)) res.setHeader(name, value);\n res.flushHeaders();\n }\n\n /** Write one run as A2UI JSON Lines, until it ends or stops to ask. */\n protected async stream(\n res: Response,\n runId: string,\n options: { threadId?: string; skip?: number; answered?: string[] },\n ): Promise<void> {\n this.writeHead(res, {\n 'X-Agent-Run-Id': runId,\n ...(options.threadId !== undefined ? { 'X-Agent-Thread-Id': options.threadId } : {}),\n });\n const projector = new A2uiProjector(this.projection);\n const write = (messages: A2uiServerMessage[]) => {\n for (const message of messages) res.write(`${JSON.stringify(message)}\\n`);\n };\n try {\n const frames = agUiFramesFromNdjson(this.agent.subscribe(runId));\n for await (const event of agUiEvents(frames, {\n threadId: options.threadId ?? runId,\n runId,\n streamRunId: runId,\n ...(options.threadId !== undefined ? { streamThreadId: options.threadId } : {}),\n ...(options.skip !== undefined ? { skip: options.skip } : {}),\n ...(options.answered !== undefined ? { answered: options.answered } : {}),\n ...(this.quietMs !== undefined ? { quietMs: this.quietMs } : {}),\n })) {\n write(projector.project(event));\n }\n } catch {\n write(projector.project({ type: 'RUN_ERROR', message: 'The run could not be followed.' }));\n }\n res.end();\n }\n}\n\n/**\n * Serve the agent as an A2UI (https://a2ui.org, v0.9) agent: `POST <path>/a2ui` answers with the\n * run as a JSON Lines stream of A2UI server-to-client messages — `createSurface`,\n * `updateComponents`, `updateDataModel`, `deleteSurface` — that any A2UI renderer draws.\n *\n * ```ts\n * import { a2uiAdapter } from '@dudousxd/nestjs-agent/a2ui'\n *\n * AgentModule.forRoot({ model, adapters: [a2uiAdapter()] })\n * ```\n *\n * The body is `{ threadId?, message?, action?, uiCapabilities?, agent? }`:\n * - `message` — the user's text;\n * - `action` — an A2UI client message (`{ version: 'v0.9', action: { name, surfaceId,\n * sourceComponentId, timestamp, context } }`, or v0.8's `{ userAction }`): it becomes the turn\n * (`uiActionText`). `agora.approve` / `agora.reject` with `context.interruptId` decide the\n * approval a previous stream ended on, and stream the rest of that run (an independent\n * proposal is decided through the proposal service and answered with its reply);\n * - `threadId` — continue that thread (its owner is checked), or start one under that id.\n *\n * Response headers name the library's ids: `X-Agent-Thread-Id`, `X-Agent-Run-Id`. Authenticated by\n * the module's `actorResolver`, guarded by its `guards` and owner-scoped exactly as `chat` is.\n * Components nothing maps are drawn as their text, from the `AgentGenuiModule` catalog when there\n * is one (or `catalog` here).\n */\nexport function a2uiAdapter(options: A2uiAdapterOptions = {}): AgentProtocolAdapter {\n const path = (options.path ?? 'a2ui').replace(/^\\/+|\\/+$/g, '');\n const { path: _path, quietMs, maxActionBytes, ...projection } = options;\n\n @Controller()\n class A2uiController extends A2uiRunHandler {\n constructor(\n // Tokens, never classes: this entry is its own bundle (see AGENT_SERVICE).\n @Inject(AGENT_SERVICE) agent: AgentService,\n @Inject(AGENT_ACTOR_RESOLVER) actorResolver: ActorResolver,\n @Optional() @Inject(GENUI_CATALOG) catalog?: Catalog,\n ) {\n super(\n agent,\n actorResolver,\n projection.catalog === undefined && catalog !== undefined\n ? { ...projection, catalog }\n : projection,\n quietMs,\n maxActionBytes,\n );\n }\n\n @Post(path)\n @HttpCode(200)\n run(@Req() req: Request, @Res() res: Response, @Body() body: unknown): Promise<void> {\n return this.handle(req, res, body);\n }\n }\n\n return { name: 'a2ui', controllers: [A2uiController] };\n}\n","/**\n * `AgentService`, as the controllers of a secondary entry (`@dudousxd/nestjs-agent/a2ui`) ask for it.\n *\n * A token rather than the class: every entry is its own bundle with its own copy of every class it\n * imports, so the class used as an injection token there is not the one `AgentModule` provides.\n * `Symbol.for` is the same value in every bundle.\n */\nexport const AGENT_SERVICE = Symbol.for('@dudousxd/nestjs-agent:agent-service');\n","import type { ToolPresentation } from '@dudousxd/nestjs-agent-core';\nimport type {\n Catalog,\n GenuiCatalogScope,\n GenuiStreaming,\n GenuiToolsOptions,\n TreeLimits,\n TreeSchemaMode,\n} from '@dudousxd/nestjs-agent-core/genui';\nimport {\n type DefineSandboxOptions,\n defineCatalog,\n defineSandbox,\n genuiTools,\n} from '@dudousxd/nestjs-agent-core/genui';\nimport {\n type DynamicModule,\n type FactoryProvider,\n Inject,\n Module,\n type ModuleMetadata,\n type Provider,\n type Type,\n} from '@nestjs/common';\nimport { provideAgentTools } from '../functional-tool.js';\n\n/** The app's genui catalog — `@InjectGenuiCatalog()` it anywhere; `overrideProvider` it in tests. */\nexport const GENUI_CATALOG = Symbol.for('@dudousxd/nestjs-agent:genui-catalog');\n\n/** The resolved {@link AgentGenuiOptions} (without the resolver). */\nexport const GENUI_OPTIONS = Symbol.for('@dudousxd/nestjs-agent:genui-options');\n\n/** Inject the app's genui catalog ({@link GENUI_CATALOG}). */\nexport const InjectGenuiCatalog = (): ParameterDecorator & PropertyDecorator =>\n Inject(GENUI_CATALOG);\n\n/**\n * Picks the catalog for ONE request — a tenant's own, versioned components. Consulted when a genui\n * call is validated and when the turn's tool list is described to the model, so components can\n * change without rebuilding tools at boot. Cache in here if resolving costs a round trip: it runs\n * once per turn per genui tool, and once per call.\n *\n * Provide it through `AgentGenuiModule.forRoot({ resolver })` (a class, resolved with DI, or an\n * instance); it is then injectable, and replaceable in a test with\n * `overrideProvider(GenuiCatalogResolver)`.\n */\nexport abstract class GenuiCatalogResolver {\n abstract resolve(scope: GenuiCatalogScope): Catalog | Promise<Catalog>;\n}\n\n/** What {@link AgentGenuiModule} builds tools from. */\nexport interface AgentGenuiOptions {\n /**\n * The catalog. With a {@link GenuiCatalogResolver} it is the boot-time one — it names the\n * per-component tools — and may be empty. Default: an empty catalog.\n */\n catalog?: Catalog;\n /**\n * `tree` (default): one `ui__render` tool taking a composed tree (a single node is pushed as that\n * component). `per-component`: one `ui__show_<snake>` tool per component.\n */\n mode?: 'per-component' | 'tree';\n /** `ui__render`'s input schema: `'strict'` (default, a `$defs` union per component) or `'loose'`. */\n treeSchema?: TreeSchemaMode;\n /**\n * Tree mode: `'complete'` (default) draws the tree once the call has run; `'partial'` streams it\n * as `partial` ui frames while the model writes it. A component's own `streaming` overrides it.\n */\n streaming?: GenuiStreaming;\n /** Least time between two partial tree frames of one call, in ms. Default 100. */\n streamingThrottleMs?: number;\n /** End the model's turn once a genui call succeeds (no narrating follow-up call). */\n terminal?: boolean;\n /** Tool name in `tree` mode. Default `ui__render`. */\n treeToolName?: string;\n /** Text prepended to the tree tool's description. */\n treeInstructions?: string;\n treeLimits?: TreeLimits;\n /** Tool-name prefix in `per-component` mode. Default `ui__show_`. */\n namePrefix?: string;\n /**\n * Also register ONE generic tool taking `{ component, props }`, validated against the request's\n * catalog — how a model reaches a tenant's own components, which no boot-time tool names. `true`\n * names it `ui__show`.\n */\n showTool?: boolean | string;\n /** Text prepended to the show tool's description. */\n showInstructions?: string;\n /** Roles allowed to call the tools. Undefined → the roles policy default. */\n roles?: string[];\n /** Override the generated presentation per component (or per tree / show tool name). */\n presentation?: (component: string) => ToolPresentation | undefined;\n /**\n * Add the sandbox component (`Sandbox`): the model may write HTML, CSS and JS for a one-off\n * interactive answer when no catalog component fits. `true` for the defaults (no network), or\n * the {@link DefineSandboxOptions} (a policy listing origins, extra instructions). Off by default.\n * The browser's catalog should carry the same definition (`defineSandbox(...)` in the shared\n * catalog file) so the renderer enforces the same policy.\n */\n sandbox?: boolean | DefineSandboxOptions;\n}\n\ntype ResolverOption = Type<GenuiCatalogResolver> | GenuiCatalogResolver;\n\nexport interface AgentGenuiModuleOptions extends AgentGenuiOptions {\n /** Per-request catalogs. A class is instantiated with DI (its dependencies must be global or exported to the root). */\n resolver?: ResolverOption;\n}\n\nexport interface AgentGenuiModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {\n inject?: FactoryProvider['inject'];\n useFactory: (...deps: any[]) => AgentGenuiOptions | Promise<AgentGenuiOptions>;\n /** Per-request catalogs. A class resolves its dependencies from `imports`. */\n resolver?: ResolverOption;\n}\n\nfunction resolverProviders(resolver: ResolverOption | undefined): Provider[] {\n if (resolver === undefined) return [];\n return [\n typeof resolver === 'function'\n ? { provide: GenuiCatalogResolver, useClass: resolver }\n : { provide: GenuiCatalogResolver, useValue: resolver },\n ];\n}\n\nfunction toolOptions(\n options: AgentGenuiOptions,\n resolver: GenuiCatalogResolver | undefined,\n): GenuiToolsOptions {\n const { catalog: _catalog, sandbox: _sandbox, ...rest } = options;\n return {\n ...rest,\n ...(resolver !== undefined\n ? { resolveCatalog: (scope: GenuiCatalogScope) => resolver.resolve(scope) }\n : {}),\n };\n}\n\n/** The configured catalog, with the sandbox component added when `sandbox` asks for it. */\nfunction withSandbox(options: AgentGenuiOptions): Catalog {\n const base = options.catalog ?? defineCatalog([]);\n const { sandbox } = options;\n return sandbox === undefined || sandbox === false\n ? base\n : base.extend([defineSandbox(sandbox === true ? {} : sandbox)]);\n}\n\nfunction coreProviders(resolver: ResolverOption | undefined): Provider[] {\n return [\n {\n provide: GENUI_CATALOG,\n useFactory: (options: AgentGenuiOptions) => withSandbox(options),\n inject: [GENUI_OPTIONS],\n },\n ...resolverProviders(resolver),\n // The generated tools, registered by the agent module's discovery like any `provideAgentTool`.\n // Built from the injected catalog, so `overrideProvider(GENUI_CATALOG)` changes them too.\n provideAgentTools(\n (options: AgentGenuiOptions, catalog: Catalog, perRequest?: GenuiCatalogResolver) =>\n genuiTools(catalog, toolOptions(options, perRequest)),\n [GENUI_OPTIONS, GENUI_CATALOG, { token: GenuiCatalogResolver, optional: true }],\n ),\n ];\n}\n\nconst EXPORTS = [GENUI_CATALOG, GENUI_OPTIONS];\n\n/**\n * Generative UI for the agent: registers the tools that let the model push catalog components\n * (one `ui__render` tree tool, or `ui__show_<component>` per component, and/or the generic\n * `ui__show`), and makes the catalog\n * injectable. Global, like `AgentModule`; import it once, next to it.\n *\n * ```ts\n * AgentGenuiModule.forRoot({ catalog, mode: 'tree', terminal: true })\n * ```\n */\n@Module({})\nexport class AgentGenuiModule {\n static forRoot(options: AgentGenuiModuleOptions = {}): DynamicModule {\n const { resolver, ...rest } = options;\n return {\n module: AgentGenuiModule,\n global: true,\n providers: [{ provide: GENUI_OPTIONS, useValue: rest }, ...coreProviders(resolver)],\n exports: resolver === undefined ? EXPORTS : [...EXPORTS, GenuiCatalogResolver],\n };\n }\n\n static forRootAsync(options: AgentGenuiModuleAsyncOptions): DynamicModule {\n return {\n module: AgentGenuiModule,\n global: true,\n imports: options.imports ?? [],\n providers: [\n { provide: GENUI_OPTIONS, useFactory: options.useFactory, inject: options.inject ?? [] },\n ...coreProviders(options.resolver),\n ],\n exports: options.resolver === undefined ? EXPORTS : [...EXPORTS, GenuiCatalogResolver],\n };\n }\n}\n","import {\n type FunctionalToolDefinition,\n type ToolHandler,\n type ToolSpec,\n createFunctionalTool,\n} from '@dudousxd/nestjs-agent-core';\nimport type { FactoryProvider, Provider } from '@nestjs/common';\n\n/**\n * Brand stamped onto a functional-tool provider instance so `AiToolDiscoveryService` picks it up\n * when it walks every provider at boot — the declarative alternative to a manual `registry.register`.\n */\nexport const AGENT_TOOL_BRAND = Symbol.for('@dudousxd/nestjs-agent:functional-tool');\n\n/** A tool expressed as data + handler (e.g. from `createExecuteSqlTool`), not an `@AiTool` class. */\nexport interface FunctionalTool {\n spec: ToolSpec;\n handler: ToolHandler;\n}\n\n/** A {@link FunctionalTool} carrying {@link AGENT_TOOL_BRAND} — what a provider resolves to. */\nexport interface BrandedFunctionalTool extends FunctionalTool {\n readonly [AGENT_TOOL_BRAND]: true;\n}\n\nfunction brand(tool: FunctionalTool): BrandedFunctionalTool {\n return { [AGENT_TOOL_BRAND]: true, spec: tool.spec, handler: tool.handler };\n}\n\n/** Narrows a provider instance to a branded functional tool for boot-time registration. */\nexport function isBrandedFunctionalTool(value: object): value is BrandedFunctionalTool {\n return AGENT_TOOL_BRAND in value && 'spec' in value && 'handler' in value;\n}\n\n/**\n * Registers a functional tool declaratively — drop the returned provider in a module's `providers`\n * and `AiToolDiscoveryService` auto-registers it at boot. Each call uses a fresh, unique provide\n * token so the same tool can be provided many times without collisions.\n *\n * Two forms: a static tool (`useValue`) or a factory with `inject` for DI-resolved dependencies\n * (`useFactory`). Either way the resolved instance is branded with {@link AGENT_TOOL_BRAND}.\n */\nexport function provideAgentTool(tool: FunctionalTool): Provider;\nexport function provideAgentTool(\n factory: (...deps: never[]) => FunctionalTool,\n inject?: FactoryProvider['inject'],\n): Provider;\nexport function provideAgentTool(\n factoryOrTool: FunctionalTool | ((...deps: never[]) => FunctionalTool),\n inject?: FactoryProvider['inject'],\n): Provider {\n const provide = Symbol('nestjs-agent:functional-tool-instance');\n if (typeof factoryOrTool === 'function') {\n return {\n provide,\n useFactory: (...deps: never[]) => brand(factoryOrTool(...deps)),\n inject: inject ?? [],\n };\n }\n return { provide, useValue: brand(factoryOrTool) };\n}\n\n/**\n * Registers a LIST of functional tools from one factory — for a producer whose tool count is only\n * known once its dependencies resolve (a catalog loaded from config, one tool per entry). The\n * factory's result is branded element by element; `AiToolDiscoveryService` registers each.\n */\nexport function provideAgentTools(\n factory: (...deps: never[]) => readonly FunctionalTool[] | Promise<readonly FunctionalTool[]>,\n inject?: FactoryProvider['inject'],\n): Provider {\n return {\n provide: Symbol('nestjs-agent:functional-tool-list'),\n useFactory: async (...deps: never[]) => (await factory(...deps)).map(brand),\n inject: inject ?? [],\n };\n}\n\n/** Author a functional tool with inferred Standard Schema input and domain output. */\nexport const defineTool = createFunctionalTool;\nexport type { FunctionalToolDefinition };\n"],"mappings":"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","names":["import_nestjs_agent_core","import_genui","import_common","AGENT_SERVICE","Symbol","for","AGENT_TOOL_BRAND","Symbol","for","brand","tool","spec","handler","provideAgentTools","factory","inject","provide","Symbol","useFactory","deps","map","brand","GENUI_CATALOG","Symbol","for","GENUI_OPTIONS","GenuiCatalogResolver","resolverProviders","resolver","undefined","provide","useClass","useValue","toolOptions","options","catalog","_catalog","sandbox","_sandbox","rest","resolveCatalog","scope","resolve","withSandbox","base","defineCatalog","extend","defineSandbox","coreProviders","GENUI_CATALOG","useFactory","inject","GENUI_OPTIONS","provideAgentTools","perRequest","genuiTools","token","optional","EXPORTS","AgentGenuiModule","forRoot","module","global","providers","exports","forRootAsync","imports","A2UI_VIA","isRecord","value","Array","isArray","invalid","code","message","BadRequestException","statusCode","A2uiRunHandler","agent","actorResolver","projection","quietMs","maxActionBytes","handle","req","res","raw","body","agentName","length","undefined","actor","resolve","action","read","readA2uiAction","maxBytes","name","A2UI_APPROVE_ACTION","A2UI_REJECT_ACTION","interruptId","context","address","decodeInterruptId","kind","proposalId","threadId","decided","decideActionProposal","decision","via","writeHead","projector","A2uiProjector","messageId","event","type","role","delta","text","project","write","JSON","stringify","end","assertResumable","stream","checkDecision","toolCallId","approve","reject","skip","position","answered","uiActionText","trim","owner","threadOwner","id","ForbiddenException","uiCapabilities","Object","hasOwn","validateUiCapabilities","error","started","chat","newThreadId","runId","extra","status","setHeader","entries","flushHeaders","options","messages","frames","agUiFramesFromNdjson","subscribe","agUiEvents","streamRunId","streamThreadId","a2uiAdapter","path","replace","_path","A2uiController","catalog","run","controllers"]}
1
+ {"version":3,"sources":["../../src/a2ui/index.ts","../../src/a2ui/a2ui.adapter.ts","../../src/agent-service.token.ts","../../src/genui/agent-genui.module.ts","../../src/functional-tool.ts"],"sourcesContent":["/**\n * `@dudousxd/nestjs-agent/a2ui` — A2UI (https://a2ui.org, v0.9) for the agent: the `a2uiAdapter()`\n * route (JSON Lines of A2UI messages), and the framework-free pieces it is built from (re-exported\n * from `@dudousxd/nestjs-agent-core/a2ui`) — the `ui` frame → A2UI converter, the builtin →\n * basic-catalog mappings, the catalog export, the inbound action reader. For A2UI over AG-UI, pass\n * `a2ui: true` to `agUiAdapter()`.\n */\n\nexport * from '@dudousxd/nestjs-agent-core/a2ui';\nexport { type A2uiAdapterOptions, A2uiRunHandler, a2uiAdapter } from './a2ui.adapter.js';\n","import { AGENT_ACTOR_RESOLVER, type ActorResolver } from '@dudousxd/nestjs-agent-core';\nimport {\n A2UI_APPROVE_ACTION,\n A2UI_REJECT_ACTION,\n A2uiProjector,\n type A2uiServerMessage,\n type A2uiStreamOptions,\n a2uiThreadReplay,\n negotiateA2uiCatalog,\n readA2uiAction,\n readA2uiClientCapabilities,\n} from '@dudousxd/nestjs-agent-core/a2ui';\nimport {\n agUiEvents,\n agUiFramesFromNdjson,\n decodeInterruptId,\n} from '@dudousxd/nestjs-agent-core/ag-ui';\nimport {\n type Catalog,\n type UiAction,\n type UiCapabilities,\n uiActionText,\n validateUiCapabilities,\n} from '@dudousxd/nestjs-agent-core/genui';\nimport {\n BadRequestException,\n Body,\n Controller,\n ForbiddenException,\n Get,\n HttpCode,\n Inject,\n Optional,\n Param,\n Post,\n Query,\n Req,\n Res,\n} from '@nestjs/common';\nimport type { Request, Response } from 'express';\nimport { AGENT_SERVICE } from '../agent-service.token.js';\nimport type { AgentService } from '../agent.service.js';\nimport { GENUI_CATALOG } from '../genui/agent-genui.module.js';\nimport type { AgentProtocolAdapter } from '../protocol-adapter.js';\n\nexport interface A2uiAdapterOptions extends A2uiStreamOptions {\n /** Where the route mounts, under the agent's path. Default `'a2ui'` → `POST <path>/a2ui`. */\n path?: string;\n /** As `agUiAdapter`'s: how long a run waiting on a person may stay silent before the stream ends. */\n quietMs?: number;\n /** Largest action accepted, as JSON, in bytes. Default 8192. */\n maxActionBytes?: number;\n /** Largest `a2uiClientDataModel` accepted, as JSON, in bytes. Default 32768. */\n maxDataModelBytes?: number;\n}\n\n/** The surface an A2UI decision is recorded as having come through. */\nconst A2UI_VIA = 'a2ui';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalid(code: string, message: string): BadRequestException {\n return new BadRequestException({ statusCode: 400, code, message });\n}\n\n/** A field of the body, or of its A2A-style `metadata` (where A2UI puts client capabilities). */\nfunction bodyField(body: Record<string, unknown>, key: string): unknown {\n if (body[key] !== undefined) return body[key];\n return isRecord(body.metadata) ? body.metadata[key] : undefined;\n}\n\n/** `a2uiClientDataModel` (`{ version, surfaces: { <surfaceId>: model } }`): its surfaces, or why not. */\nfunction readDataModel(\n raw: unknown,\n maxBytes: number,\n): Record<string, unknown> | undefined | string {\n if (raw === undefined) return undefined;\n if (!isRecord(raw) || !isRecord(raw.surfaces)) {\n return 'a2uiClientDataModel carries { version, surfaces }';\n }\n let size: number;\n try {\n size = new TextEncoder().encode(JSON.stringify(raw.surfaces)).length;\n } catch {\n return 'a2uiClientDataModel must be JSON';\n }\n if (size > maxBytes) {\n return `a2uiClientDataModel may be at most ${maxBytes} bytes (this one is ${size})`;\n }\n return Object.keys(raw.surfaces).length > 0 ? raw.surfaces : undefined;\n}\n\n/**\n * Serves the agent as an A2UI agent. Not mounted directly — {@link a2uiAdapter} mounts a subclass at\n * the configured path — but exported so a host can extend it under a route of its own.\n */\nexport class A2uiRunHandler {\n constructor(\n protected readonly agent: AgentService,\n protected readonly actorResolver: ActorResolver,\n protected readonly projection: A2uiStreamOptions,\n protected readonly quietMs: number | undefined,\n protected readonly maxActionBytes: number | undefined,\n protected readonly maxDataModelBytes: number | undefined = undefined,\n ) {}\n\n /**\n * `GET <path>/a2ui/threads/:threadId`: a stored thread as `{ threadId, entries }`\n * (`a2uiThreadReplay`) — what a client draws again after a reload. Owner-scoped; a thread nobody\n * has answers `{ entries: [] }`. `?catalog=<id>` names the client's basic catalog id.\n */\n async replay(\n req: Request,\n threadId: string,\n catalog: unknown,\n ): Promise<{ threadId: string; entries: ReturnType<typeof a2uiThreadReplay> }> {\n const actor = await this.actorResolver.resolve(req);\n const owner = await this.agent.threadOwner(threadId);\n if (owner === null) return { threadId, entries: [] };\n const thread = await this.agent.getThread(actor, threadId);\n const supported =\n typeof catalog === 'string'\n ? [catalog]\n : Array.isArray(catalog)\n ? catalog.filter((id): id is string => typeof id === 'string')\n : undefined;\n return {\n threadId,\n entries: a2uiThreadReplay(\n thread?.messages ?? [],\n negotiateA2uiCatalog(this.projection, supported),\n ),\n };\n }\n\n async handle(req: Request, res: Response, raw: unknown): Promise<void> {\n const body = isRecord(raw) ? raw : {};\n const agentName =\n typeof body.agent === 'string' && body.agent.length > 0 ? body.agent : undefined;\n const actor = await this.actorResolver.resolve(req);\n // The basic catalog under the id this client advertises (`a2uiClientCapabilities`).\n const projection = negotiateA2uiCatalog(\n this.projection,\n readA2uiClientCapabilities(bodyField(body, 'a2uiClientCapabilities')),\n );\n const dataModel = readDataModel(\n bodyField(body, 'a2uiClientDataModel'),\n this.maxDataModelBytes ?? 32768,\n );\n if (typeof dataModel === 'string') throw invalid('invalid_data_model', dataModel);\n\n let action: UiAction | undefined;\n if (body.action !== undefined) {\n const read = isRecord(body.action)\n ? readA2uiAction(body.action, {\n ...(this.maxActionBytes !== undefined ? { maxBytes: this.maxActionBytes } : {}),\n })\n : 'action must be an A2UI action message';\n if (typeof read === 'string') throw invalid('invalid_action', read);\n action = read;\n }\n\n // Deciding the approval a stream ended on: settle it, then stream the rest of the run.\n if (action?.name === A2UI_APPROVE_ACTION || action?.name === A2UI_REJECT_ACTION) {\n const interruptId = action.context.interruptId;\n const address = typeof interruptId === 'string' ? decodeInterruptId(interruptId) : undefined;\n if (address === undefined || address === null || address.kind === 'elicitation') {\n throw invalid('invalid_action', 'an approval action carries the interruptId it decides');\n }\n // An independent proposal: decided through the proposal service (its own run is not\n // waiting), as an AG-UI resume decides it, and answered with the configured reply.\n if (address.kind === 'proposal') {\n if (address.proposalId === undefined || address.threadId === undefined) {\n throw invalid('invalid_action', 'the interruptId names no proposal');\n }\n const decided = await this.agent.decideActionProposal(\n actor,\n address.threadId,\n address.proposalId,\n {\n decision: action.name === A2UI_APPROVE_ACTION ? 'approved' : 'rejected',\n via: A2UI_VIA,\n },\n );\n this.writeHead(res, { 'X-Agent-Thread-Id': address.threadId });\n const projector = new A2uiProjector(projection);\n const messageId = `proposal-${address.proposalId}`;\n for (const event of [\n { type: 'TEXT_MESSAGE_START' as const, messageId, role: 'assistant' as const },\n { type: 'TEXT_MESSAGE_CONTENT' as const, messageId, delta: decided.text },\n ]) {\n for (const message of projector.project(event)) res.write(`${JSON.stringify(message)}\\n`);\n }\n res.end();\n return;\n }\n // The same checks an AG-UI resume makes: the run is the actor's and still waiting, and the\n // actor may decide this call.\n await this.agent.assertResumable(actor, address.stream);\n await this.agent.checkDecision(actor, address.toolCallId);\n if (action.name === A2UI_APPROVE_ACTION) {\n await this.agent.approve(actor, address.toolCallId, { via: A2UI_VIA });\n } else {\n await this.agent.reject(actor, address.toolCallId, undefined, { via: A2UI_VIA });\n }\n await this.stream(res, address.stream, {\n skip: address.position,\n answered: [address.toolCallId],\n projection,\n });\n return;\n }\n\n const message = typeof body.message === 'string' ? body.message : '';\n const text = action !== undefined ? uiActionText(action) : message;\n if (text.trim().length === 0) {\n throw invalid('no_user_message', 'send a message or an action');\n }\n const threadId =\n typeof body.threadId === 'string' && body.threadId.length > 0 ? body.threadId : undefined;\n const owner = threadId !== undefined ? await this.agent.threadOwner(threadId) : null;\n if (owner !== null && owner !== actor.id) {\n throw new ForbiddenException('thread belongs to another actor');\n }\n let uiCapabilities: UiCapabilities | undefined;\n if (Object.hasOwn(body, 'uiCapabilities')) {\n try {\n uiCapabilities = validateUiCapabilities(body.uiCapabilities);\n } catch (error) {\n throw invalid('invalid_input', (error as Error).message);\n }\n }\n const started = await this.agent.chat({\n actor,\n message: text,\n ...(threadId === undefined ? {} : owner !== null ? { threadId } : { newThreadId: threadId }),\n ...(agentName !== undefined ? { agentName } : {}),\n ...(uiCapabilities !== undefined ? { uiCapabilities } : {}),\n ...(dataModel !== undefined ? { pageContext: { a2uiDataModel: dataModel } } : {}),\n });\n await this.stream(res, started.runId, { threadId: started.threadId, projection });\n }\n\n /** Open the JSON Lines response. */\n protected writeHead(res: Response, extra: Record<string, string>): void {\n res.status(200);\n res.setHeader('Content-Type', 'application/jsonl; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n for (const [name, value] of Object.entries(extra)) res.setHeader(name, value);\n res.flushHeaders();\n }\n\n /** Write one run as A2UI JSON Lines, until it ends or stops to ask. */\n protected async stream(\n res: Response,\n runId: string,\n options: {\n threadId?: string;\n skip?: number;\n answered?: string[];\n projection?: A2uiStreamOptions;\n },\n ): Promise<void> {\n this.writeHead(res, {\n 'X-Agent-Run-Id': runId,\n ...(options.threadId !== undefined ? { 'X-Agent-Thread-Id': options.threadId } : {}),\n });\n const projector = new A2uiProjector(options.projection ?? this.projection);\n const write = (messages: A2uiServerMessage[]) => {\n for (const message of messages) res.write(`${JSON.stringify(message)}\\n`);\n };\n try {\n const frames = agUiFramesFromNdjson(this.agent.subscribe(runId));\n for await (const event of agUiEvents(frames, {\n threadId: options.threadId ?? runId,\n runId,\n streamRunId: runId,\n ...(options.threadId !== undefined ? { streamThreadId: options.threadId } : {}),\n ...(options.skip !== undefined ? { skip: options.skip } : {}),\n ...(options.answered !== undefined ? { answered: options.answered } : {}),\n ...(this.quietMs !== undefined ? { quietMs: this.quietMs } : {}),\n })) {\n write(projector.project(event));\n }\n } catch {\n write(projector.project({ type: 'RUN_ERROR', message: 'The run could not be followed.' }));\n }\n res.end();\n }\n}\n\n/**\n * Serve the agent as an A2UI (https://a2ui.org, v0.9) agent: `POST <path>/a2ui` answers with the\n * run as a JSON Lines stream of A2UI server-to-client messages — `createSurface`,\n * `updateComponents`, `updateDataModel`, `deleteSurface` — that any A2UI renderer draws.\n *\n * ```ts\n * import { a2uiAdapter } from '@dudousxd/nestjs-agent/a2ui'\n *\n * AgentModule.forRoot({ model, adapters: [a2uiAdapter()] })\n * ```\n *\n * The body is `{ threadId?, message?, action?, uiCapabilities?, a2uiClientCapabilities?,\n * a2uiClientDataModel?, agent? }` (the two A2UI fields also read from `metadata`, A2A-style):\n * - `message` — the user's text;\n * - `action` — an A2UI client message (`{ version: 'v0.9', action: { name, surfaceId,\n * sourceComponentId, timestamp, context } }`, or v0.8's `{ userAction }`): it becomes the turn\n * (`uiActionText`). `agora.approve` / `agora.reject` with `context.interruptId` decide the\n * approval a previous stream ended on, and stream the rest of that run (an independent\n * proposal is decided through the proposal service and answered with its reply);\n * - `threadId` — continue that thread (its owner is checked), or start one under that id;\n * - `a2uiClientCapabilities` — the catalogs the client draws (`{ 'v0.9': { supportedCatalogIds } }`,\n * what `MessageProcessor.getRendererCapabilities()` builds): the basic catalog is sent under the\n * id the client lists for it;\n * - `a2uiClientDataModel` — the data models of surfaces created with `sendDataModel`, handed to the\n * prompt builder as `pageContext.a2uiDataModel`.\n *\n * `GET <path>/a2ui/threads/:threadId` answers a stored thread as `{ threadId, entries }`\n * (`a2uiThreadReplay`): what a client draws again after a reload.\n *\n * Response headers name the library's ids: `X-Agent-Thread-Id`, `X-Agent-Run-Id`. Authenticated by\n * the module's `actorResolver`, guarded by its `guards` and owner-scoped exactly as `chat` is.\n * Components nothing maps are drawn as their text, from the `AgentGenuiModule` catalog when there\n * is one (or `catalog` here).\n */\nexport function a2uiAdapter(options: A2uiAdapterOptions = {}): AgentProtocolAdapter {\n const path = (options.path ?? 'a2ui').replace(/^\\/+|\\/+$/g, '');\n const { path: _path, quietMs, maxActionBytes, maxDataModelBytes, ...projection } = options;\n\n @Controller()\n class A2uiController extends A2uiRunHandler {\n constructor(\n // Tokens, never classes: this entry is its own bundle (see AGENT_SERVICE).\n @Inject(AGENT_SERVICE) agent: AgentService,\n @Inject(AGENT_ACTOR_RESOLVER) actorResolver: ActorResolver,\n @Optional() @Inject(GENUI_CATALOG) catalog?: Catalog,\n ) {\n super(\n agent,\n actorResolver,\n projection.catalog === undefined && catalog !== undefined\n ? { ...projection, catalog }\n : projection,\n quietMs,\n maxActionBytes,\n maxDataModelBytes,\n );\n }\n\n @Get(`${path}/threads/:threadId`)\n thread(\n @Req() req: Request,\n @Param('threadId') threadId: string,\n @Query('catalog') catalog: unknown,\n ): ReturnType<A2uiRunHandler['replay']> {\n return this.replay(req, threadId, catalog);\n }\n\n @Post(path)\n @HttpCode(200)\n run(@Req() req: Request, @Res() res: Response, @Body() body: unknown): Promise<void> {\n return this.handle(req, res, body);\n }\n }\n\n return { name: 'a2ui', controllers: [A2uiController] };\n}\n","/**\n * `AgentService`, as the controllers of a secondary entry (`@dudousxd/nestjs-agent/a2ui`) ask for it.\n *\n * A token rather than the class: every entry is its own bundle with its own copy of every class it\n * imports, so the class used as an injection token there is not the one `AgentModule` provides.\n * `Symbol.for` is the same value in every bundle.\n */\nexport const AGENT_SERVICE = Symbol.for('@dudousxd/nestjs-agent:agent-service');\n","import type { ToolPresentation } from '@dudousxd/nestjs-agent-core';\nimport type {\n Catalog,\n GenuiCatalogScope,\n GenuiStreaming,\n GenuiToolsOptions,\n TreeLimits,\n TreeSchemaMode,\n} from '@dudousxd/nestjs-agent-core/genui';\nimport {\n type DefineSandboxOptions,\n defineCatalog,\n defineSandbox,\n genuiTools,\n} from '@dudousxd/nestjs-agent-core/genui';\nimport {\n type DynamicModule,\n type FactoryProvider,\n Inject,\n Module,\n type ModuleMetadata,\n type Provider,\n type Type,\n} from '@nestjs/common';\nimport { provideAgentTools } from '../functional-tool.js';\n\n/** The app's genui catalog — `@InjectGenuiCatalog()` it anywhere; `overrideProvider` it in tests. */\nexport const GENUI_CATALOG = Symbol.for('@dudousxd/nestjs-agent:genui-catalog');\n\n/** The resolved {@link AgentGenuiOptions} (without the resolver). */\nexport const GENUI_OPTIONS = Symbol.for('@dudousxd/nestjs-agent:genui-options');\n\n/** Inject the app's genui catalog ({@link GENUI_CATALOG}). */\nexport const InjectGenuiCatalog = (): ParameterDecorator & PropertyDecorator =>\n Inject(GENUI_CATALOG);\n\n/**\n * Picks the catalog for ONE request — a tenant's own, versioned components. Consulted when a genui\n * call is validated and when the turn's tool list is described to the model, so components can\n * change without rebuilding tools at boot. Cache in here if resolving costs a round trip: it runs\n * once per turn per genui tool, and once per call.\n *\n * Provide it through `AgentGenuiModule.forRoot({ resolver })` (a class, resolved with DI, or an\n * instance); it is then injectable, and replaceable in a test with\n * `overrideProvider(GenuiCatalogResolver)`.\n */\nexport abstract class GenuiCatalogResolver {\n abstract resolve(scope: GenuiCatalogScope): Catalog | Promise<Catalog>;\n}\n\n/** What {@link AgentGenuiModule} builds tools from. */\nexport interface AgentGenuiOptions {\n /**\n * The catalog. With a {@link GenuiCatalogResolver} it is the boot-time one — it names the\n * per-component tools — and may be empty. Default: an empty catalog.\n */\n catalog?: Catalog;\n /**\n * `tree` (default): one `ui__render` tool taking a composed tree (a single node is pushed as that\n * component). `per-component`: one `ui__show_<snake>` tool per component.\n */\n mode?: 'per-component' | 'tree';\n /** `ui__render`'s input schema: `'strict'` (default, a `$defs` union per component) or `'loose'`. */\n treeSchema?: TreeSchemaMode;\n /**\n * Tree mode: `'complete'` (default) draws the tree once the call has run; `'partial'` streams it\n * as `partial` ui frames while the model writes it. A component's own `streaming` overrides it.\n */\n streaming?: GenuiStreaming;\n /** Least time between two partial tree frames of one call, in ms. Default 100. */\n streamingThrottleMs?: number;\n /** End the model's turn once a genui call succeeds (no narrating follow-up call). */\n terminal?: boolean;\n /** Tool name in `tree` mode. Default `ui__render`. */\n treeToolName?: string;\n /** Text prepended to the tree tool's description. */\n treeInstructions?: string;\n treeLimits?: TreeLimits;\n /**\n * Tree mode: components that ALSO get a flat tool whose input IS their props —\n * `componentTools: ['Sandbox']` adds `ui__sandbox`, so a model writing a big component on its own\n * has no `{ type, props }` envelope to leave out.\n */\n componentTools?: readonly string[];\n /** Tool-name prefix for `componentTools`. Default `ui__` (`Sandbox` → `ui__sandbox`). */\n componentToolPrefix?: string;\n /** Tool-name prefix in `per-component` mode. Default `ui__show_`. */\n namePrefix?: string;\n /**\n * Also register ONE generic tool taking `{ component, props }`, validated against the request's\n * catalog — how a model reaches a tenant's own components, which no boot-time tool names. `true`\n * names it `ui__show`.\n */\n showTool?: boolean | string;\n /** Text prepended to the show tool's description. */\n showInstructions?: string;\n /** Roles allowed to call the tools. Undefined → the roles policy default. */\n roles?: string[];\n /** Override the generated presentation per component (or per tree / show tool name). */\n presentation?: (component: string) => ToolPresentation | undefined;\n /**\n * Add the sandbox component (`Sandbox`): the model may write HTML, CSS and JS for a one-off\n * interactive answer when no catalog component fits. `true` for the defaults (no network), or\n * the {@link DefineSandboxOptions} (a policy listing origins, extra instructions). Off by default.\n * The browser's catalog should carry the same definition (`defineSandbox(...)` in the shared\n * catalog file) so the renderer enforces the same policy.\n */\n sandbox?: boolean | DefineSandboxOptions;\n}\n\ntype ResolverOption = Type<GenuiCatalogResolver> | GenuiCatalogResolver;\n\nexport interface AgentGenuiModuleOptions extends AgentGenuiOptions {\n /** Per-request catalogs. A class is instantiated with DI (its dependencies must be global or exported to the root). */\n resolver?: ResolverOption;\n}\n\nexport interface AgentGenuiModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {\n inject?: FactoryProvider['inject'];\n useFactory: (...deps: any[]) => AgentGenuiOptions | Promise<AgentGenuiOptions>;\n /** Per-request catalogs. A class resolves its dependencies from `imports`. */\n resolver?: ResolverOption;\n}\n\nfunction resolverProviders(resolver: ResolverOption | undefined): Provider[] {\n if (resolver === undefined) return [];\n return [\n typeof resolver === 'function'\n ? { provide: GenuiCatalogResolver, useClass: resolver }\n : { provide: GenuiCatalogResolver, useValue: resolver },\n ];\n}\n\nfunction toolOptions(\n options: AgentGenuiOptions,\n resolver: GenuiCatalogResolver | undefined,\n): GenuiToolsOptions {\n const { catalog: _catalog, sandbox: _sandbox, ...rest } = options;\n return {\n ...rest,\n ...(resolver !== undefined\n ? { resolveCatalog: (scope: GenuiCatalogScope) => resolver.resolve(scope) }\n : {}),\n };\n}\n\n/** The configured catalog, with the sandbox component added when `sandbox` asks for it. */\nfunction withSandbox(options: AgentGenuiOptions): Catalog {\n const base = options.catalog ?? defineCatalog([]);\n const { sandbox } = options;\n return sandbox === undefined || sandbox === false\n ? base\n : base.extend([defineSandbox(sandbox === true ? {} : sandbox)]);\n}\n\nfunction coreProviders(resolver: ResolverOption | undefined): Provider[] {\n return [\n {\n provide: GENUI_CATALOG,\n useFactory: (options: AgentGenuiOptions) => withSandbox(options),\n inject: [GENUI_OPTIONS],\n },\n ...resolverProviders(resolver),\n // The generated tools, registered by the agent module's discovery like any `provideAgentTool`.\n // Built from the injected catalog, so `overrideProvider(GENUI_CATALOG)` changes them too.\n provideAgentTools(\n (options: AgentGenuiOptions, catalog: Catalog, perRequest?: GenuiCatalogResolver) =>\n genuiTools(catalog, toolOptions(options, perRequest)),\n [GENUI_OPTIONS, GENUI_CATALOG, { token: GenuiCatalogResolver, optional: true }],\n ),\n ];\n}\n\nconst EXPORTS = [GENUI_CATALOG, GENUI_OPTIONS];\n\n/**\n * Generative UI for the agent: registers the tools that let the model push catalog components\n * (one `ui__render` tree tool, or `ui__show_<component>` per component, and/or the generic\n * `ui__show`), and makes the catalog\n * injectable. Global, like `AgentModule`; import it once, next to it.\n *\n * ```ts\n * AgentGenuiModule.forRoot({ catalog, mode: 'tree', terminal: true })\n * ```\n */\n@Module({})\nexport class AgentGenuiModule {\n static forRoot(options: AgentGenuiModuleOptions = {}): DynamicModule {\n const { resolver, ...rest } = options;\n return {\n module: AgentGenuiModule,\n global: true,\n providers: [{ provide: GENUI_OPTIONS, useValue: rest }, ...coreProviders(resolver)],\n exports: resolver === undefined ? EXPORTS : [...EXPORTS, GenuiCatalogResolver],\n };\n }\n\n static forRootAsync(options: AgentGenuiModuleAsyncOptions): DynamicModule {\n return {\n module: AgentGenuiModule,\n global: true,\n imports: options.imports ?? [],\n providers: [\n { provide: GENUI_OPTIONS, useFactory: options.useFactory, inject: options.inject ?? [] },\n ...coreProviders(options.resolver),\n ],\n exports: options.resolver === undefined ? EXPORTS : [...EXPORTS, GenuiCatalogResolver],\n };\n }\n}\n","import {\n type FunctionalToolDefinition,\n type ToolHandler,\n type ToolSpec,\n createFunctionalTool,\n} from '@dudousxd/nestjs-agent-core';\nimport type { FactoryProvider, Provider } from '@nestjs/common';\n\n/**\n * Brand stamped onto a functional-tool provider instance so `AiToolDiscoveryService` picks it up\n * when it walks every provider at boot — the declarative alternative to a manual `registry.register`.\n */\nexport const AGENT_TOOL_BRAND = Symbol.for('@dudousxd/nestjs-agent:functional-tool');\n\n/** A tool expressed as data + handler (e.g. from `createExecuteSqlTool`), not an `@AiTool` class. */\nexport interface FunctionalTool {\n spec: ToolSpec;\n handler: ToolHandler;\n}\n\n/** A {@link FunctionalTool} carrying {@link AGENT_TOOL_BRAND} — what a provider resolves to. */\nexport interface BrandedFunctionalTool extends FunctionalTool {\n readonly [AGENT_TOOL_BRAND]: true;\n}\n\nfunction brand(tool: FunctionalTool): BrandedFunctionalTool {\n return { [AGENT_TOOL_BRAND]: true, spec: tool.spec, handler: tool.handler };\n}\n\n/** Narrows a provider instance to a branded functional tool for boot-time registration. */\nexport function isBrandedFunctionalTool(value: object): value is BrandedFunctionalTool {\n return AGENT_TOOL_BRAND in value && 'spec' in value && 'handler' in value;\n}\n\n/**\n * Registers a functional tool declaratively — drop the returned provider in a module's `providers`\n * and `AiToolDiscoveryService` auto-registers it at boot. Each call uses a fresh, unique provide\n * token so the same tool can be provided many times without collisions.\n *\n * Two forms: a static tool (`useValue`) or a factory with `inject` for DI-resolved dependencies\n * (`useFactory`). Either way the resolved instance is branded with {@link AGENT_TOOL_BRAND}.\n */\nexport function provideAgentTool(tool: FunctionalTool): Provider;\nexport function provideAgentTool(\n factory: (...deps: never[]) => FunctionalTool,\n inject?: FactoryProvider['inject'],\n): Provider;\nexport function provideAgentTool(\n factoryOrTool: FunctionalTool | ((...deps: never[]) => FunctionalTool),\n inject?: FactoryProvider['inject'],\n): Provider {\n const provide = Symbol('nestjs-agent:functional-tool-instance');\n if (typeof factoryOrTool === 'function') {\n return {\n provide,\n useFactory: (...deps: never[]) => brand(factoryOrTool(...deps)),\n inject: inject ?? [],\n };\n }\n return { provide, useValue: brand(factoryOrTool) };\n}\n\n/**\n * Registers a LIST of functional tools from one factory — for a producer whose tool count is only\n * known once its dependencies resolve (a catalog loaded from config, one tool per entry). The\n * factory's result is branded element by element; `AiToolDiscoveryService` registers each.\n */\nexport function provideAgentTools(\n factory: (...deps: never[]) => readonly FunctionalTool[] | Promise<readonly FunctionalTool[]>,\n inject?: FactoryProvider['inject'],\n): Provider {\n return {\n provide: Symbol('nestjs-agent:functional-tool-list'),\n useFactory: async (...deps: never[]) => (await factory(...deps)).map(brand),\n inject: inject ?? [],\n };\n}\n\n/** Author a functional tool with inferred Standard Schema input and domain output. */\nexport const defineTool = createFunctionalTool;\nexport type { FunctionalToolDefinition };\n"],"mappings":"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","names":["import_nestjs_agent_core","import_genui","import_common","AGENT_SERVICE","Symbol","for","AGENT_TOOL_BRAND","Symbol","for","brand","tool","spec","handler","provideAgentTools","factory","inject","provide","Symbol","useFactory","deps","map","brand","GENUI_CATALOG","Symbol","for","GENUI_OPTIONS","GenuiCatalogResolver","resolverProviders","resolver","undefined","provide","useClass","useValue","toolOptions","options","catalog","_catalog","sandbox","_sandbox","rest","resolveCatalog","scope","resolve","withSandbox","base","defineCatalog","extend","defineSandbox","coreProviders","GENUI_CATALOG","useFactory","inject","GENUI_OPTIONS","provideAgentTools","perRequest","genuiTools","token","optional","EXPORTS","AgentGenuiModule","forRoot","module","global","providers","exports","forRootAsync","imports","A2UI_VIA","isRecord","value","Array","isArray","invalid","code","message","BadRequestException","statusCode","bodyField","body","key","undefined","metadata","readDataModel","raw","maxBytes","surfaces","size","TextEncoder","encode","JSON","stringify","length","Object","keys","A2uiRunHandler","agent","actorResolver","projection","quietMs","maxActionBytes","maxDataModelBytes","replay","req","threadId","catalog","actor","resolve","owner","threadOwner","entries","thread","getThread","supported","filter","id","a2uiThreadReplay","messages","negotiateA2uiCatalog","handle","res","agentName","readA2uiClientCapabilities","dataModel","action","read","readA2uiAction","name","A2UI_APPROVE_ACTION","A2UI_REJECT_ACTION","interruptId","context","address","decodeInterruptId","kind","proposalId","decided","decideActionProposal","decision","via","writeHead","projector","A2uiProjector","messageId","event","type","role","delta","text","project","write","end","assertResumable","stream","checkDecision","toolCallId","approve","reject","skip","position","answered","uiActionText","trim","ForbiddenException","uiCapabilities","hasOwn","validateUiCapabilities","error","started","chat","newThreadId","pageContext","a2uiDataModel","runId","extra","status","setHeader","flushHeaders","options","frames","agUiFramesFromNdjson","subscribe","agUiEvents","streamRunId","streamThreadId","a2uiAdapter","path","replace","_path","A2uiController","run","controllers"]}
@@ -1,5 +1,5 @@
1
1
  export * from '@dudousxd/nestjs-agent-core/a2ui';
2
- export { b as A2uiAdapterOptions, c as A2uiRunHandler, i as a2uiAdapter } from '../index-C-Zo8rTL.cjs';
2
+ export { b as A2uiAdapterOptions, c as A2uiRunHandler, i as a2uiAdapter } from '../index-D9EM7ivO.cjs';
3
3
  import '@dudousxd/nestjs-agent-core';
4
4
  import 'express';
5
5
  import '@dudousxd/nestjs-agent-core/genui';
@@ -1,5 +1,5 @@
1
1
  export * from '@dudousxd/nestjs-agent-core/a2ui';
2
- export { b as A2uiAdapterOptions, c as A2uiRunHandler, i as a2uiAdapter } from '../index-CBO7egb7.js';
2
+ export { b as A2uiAdapterOptions, c as A2uiRunHandler, i as a2uiAdapter } from '../index-APh8WN3z.js';
3
3
  import '@dudousxd/nestjs-agent-core';
4
4
  import 'express';
5
5
  import '@dudousxd/nestjs-agent-core/genui';
@@ -6,10 +6,10 @@ export * from "@dudousxd/nestjs-agent-core/a2ui";
6
6
 
7
7
  // src/a2ui/a2ui.adapter.ts
8
8
  import { AGENT_ACTOR_RESOLVER } from "@dudousxd/nestjs-agent-core";
9
- import { A2UI_APPROVE_ACTION, A2UI_REJECT_ACTION, A2uiProjector, readA2uiAction } from "@dudousxd/nestjs-agent-core/a2ui";
9
+ import { A2UI_APPROVE_ACTION, A2UI_REJECT_ACTION, A2uiProjector, a2uiThreadReplay, negotiateA2uiCatalog, readA2uiAction, readA2uiClientCapabilities } from "@dudousxd/nestjs-agent-core/a2ui";
10
10
  import { agUiEvents, agUiFramesFromNdjson, decodeInterruptId } from "@dudousxd/nestjs-agent-core/ag-ui";
11
11
  import { uiActionText, validateUiCapabilities } from "@dudousxd/nestjs-agent-core/genui";
12
- import { BadRequestException, Body, Controller, ForbiddenException, HttpCode, Inject as Inject2, Optional, Post, Req, Res } from "@nestjs/common";
12
+ import { BadRequestException, Body, Controller, ForbiddenException, Get, HttpCode, Inject as Inject2, Optional, Param, Post, Query, Req, Res } from "@nestjs/common";
13
13
 
14
14
  // src/agent-service.token.ts
15
15
  var AGENT_SERVICE = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:agent-service");
@@ -204,6 +204,28 @@ function invalid(code, message) {
204
204
  });
205
205
  }
206
206
  __name(invalid, "invalid");
207
+ function bodyField(body, key) {
208
+ if (body[key] !== void 0) return body[key];
209
+ return isRecord(body.metadata) ? body.metadata[key] : void 0;
210
+ }
211
+ __name(bodyField, "bodyField");
212
+ function readDataModel(raw, maxBytes) {
213
+ if (raw === void 0) return void 0;
214
+ if (!isRecord(raw) || !isRecord(raw.surfaces)) {
215
+ return "a2uiClientDataModel carries { version, surfaces }";
216
+ }
217
+ let size;
218
+ try {
219
+ size = new TextEncoder().encode(JSON.stringify(raw.surfaces)).length;
220
+ } catch {
221
+ return "a2uiClientDataModel must be JSON";
222
+ }
223
+ if (size > maxBytes) {
224
+ return `a2uiClientDataModel may be at most ${maxBytes} bytes (this one is ${size})`;
225
+ }
226
+ return Object.keys(raw.surfaces).length > 0 ? raw.surfaces : void 0;
227
+ }
228
+ __name(readDataModel, "readDataModel");
207
229
  var A2uiRunHandler = class {
208
230
  static {
209
231
  __name(this, "A2uiRunHandler");
@@ -213,17 +235,43 @@ var A2uiRunHandler = class {
213
235
  projection;
214
236
  quietMs;
215
237
  maxActionBytes;
216
- constructor(agent, actorResolver, projection, quietMs, maxActionBytes) {
238
+ maxDataModelBytes;
239
+ constructor(agent, actorResolver, projection, quietMs, maxActionBytes, maxDataModelBytes = void 0) {
217
240
  this.agent = agent;
218
241
  this.actorResolver = actorResolver;
219
242
  this.projection = projection;
220
243
  this.quietMs = quietMs;
221
244
  this.maxActionBytes = maxActionBytes;
245
+ this.maxDataModelBytes = maxDataModelBytes;
246
+ }
247
+ /**
248
+ * `GET <path>/a2ui/threads/:threadId`: a stored thread as `{ threadId, entries }`
249
+ * (`a2uiThreadReplay`) — what a client draws again after a reload. Owner-scoped; a thread nobody
250
+ * has answers `{ entries: [] }`. `?catalog=<id>` names the client's basic catalog id.
251
+ */
252
+ async replay(req, threadId, catalog) {
253
+ const actor = await this.actorResolver.resolve(req);
254
+ const owner = await this.agent.threadOwner(threadId);
255
+ if (owner === null) return {
256
+ threadId,
257
+ entries: []
258
+ };
259
+ const thread = await this.agent.getThread(actor, threadId);
260
+ const supported = typeof catalog === "string" ? [
261
+ catalog
262
+ ] : Array.isArray(catalog) ? catalog.filter((id) => typeof id === "string") : void 0;
263
+ return {
264
+ threadId,
265
+ entries: a2uiThreadReplay(thread?.messages ?? [], negotiateA2uiCatalog(this.projection, supported))
266
+ };
222
267
  }
223
268
  async handle(req, res, raw) {
224
269
  const body = isRecord(raw) ? raw : {};
225
270
  const agentName = typeof body.agent === "string" && body.agent.length > 0 ? body.agent : void 0;
226
271
  const actor = await this.actorResolver.resolve(req);
272
+ const projection = negotiateA2uiCatalog(this.projection, readA2uiClientCapabilities(bodyField(body, "a2uiClientCapabilities")));
273
+ const dataModel = readDataModel(bodyField(body, "a2uiClientDataModel"), this.maxDataModelBytes ?? 32768);
274
+ if (typeof dataModel === "string") throw invalid("invalid_data_model", dataModel);
227
275
  let action;
228
276
  if (body.action !== void 0) {
229
277
  const read = isRecord(body.action) ? readA2uiAction(body.action, {
@@ -251,7 +299,7 @@ var A2uiRunHandler = class {
251
299
  this.writeHead(res, {
252
300
  "X-Agent-Thread-Id": address.threadId
253
301
  });
254
- const projector = new A2uiProjector(this.projection);
302
+ const projector = new A2uiProjector(projection);
255
303
  const messageId = `proposal-${address.proposalId}`;
256
304
  for (const event of [
257
305
  {
@@ -286,7 +334,8 @@ var A2uiRunHandler = class {
286
334
  skip: address.position,
287
335
  answered: [
288
336
  address.toolCallId
289
- ]
337
+ ],
338
+ projection
290
339
  });
291
340
  return;
292
341
  }
@@ -321,10 +370,16 @@ var A2uiRunHandler = class {
321
370
  } : {},
322
371
  ...uiCapabilities !== void 0 ? {
323
372
  uiCapabilities
373
+ } : {},
374
+ ...dataModel !== void 0 ? {
375
+ pageContext: {
376
+ a2uiDataModel: dataModel
377
+ }
324
378
  } : {}
325
379
  });
326
380
  await this.stream(res, started.runId, {
327
- threadId: started.threadId
381
+ threadId: started.threadId,
382
+ projection
328
383
  });
329
384
  }
330
385
  /** Open the JSON Lines response. */
@@ -345,7 +400,7 @@ var A2uiRunHandler = class {
345
400
  "X-Agent-Thread-Id": options.threadId
346
401
  } : {}
347
402
  });
348
- const projector = new A2uiProjector(this.projection);
403
+ const projector = new A2uiProjector(options.projection ?? this.projection);
349
404
  const write = /* @__PURE__ */ __name((messages) => {
350
405
  for (const message of messages) res.write(`${JSON.stringify(message)}
351
406
  `);
@@ -382,7 +437,7 @@ var A2uiRunHandler = class {
382
437
  };
383
438
  function a2uiAdapter(options = {}) {
384
439
  const path = (options.path ?? "a2ui").replace(/^\/+|\/+$/g, "");
385
- const { path: _path, quietMs, maxActionBytes, ...projection } = options;
440
+ const { path: _path, quietMs, maxActionBytes, maxDataModelBytes, ...projection } = options;
386
441
  let A2uiController = class A2uiController extends A2uiRunHandler {
387
442
  static {
388
443
  __name(this, "A2uiController");
@@ -391,12 +446,28 @@ function a2uiAdapter(options = {}) {
391
446
  super(agent, actorResolver, projection.catalog === void 0 && catalog !== void 0 ? {
392
447
  ...projection,
393
448
  catalog
394
- } : projection, quietMs, maxActionBytes);
449
+ } : projection, quietMs, maxActionBytes, maxDataModelBytes);
450
+ }
451
+ thread(req, threadId, catalog) {
452
+ return this.replay(req, threadId, catalog);
395
453
  }
396
454
  run(req, res, body) {
397
455
  return this.handle(req, res, body);
398
456
  }
399
457
  };
458
+ _ts_decorate2([
459
+ Get(`${path}/threads/:threadId`),
460
+ _ts_param(0, Req()),
461
+ _ts_param(1, Param("threadId")),
462
+ _ts_param(2, Query("catalog")),
463
+ _ts_metadata("design:type", Function),
464
+ _ts_metadata("design:paramtypes", [
465
+ typeof Request === "undefined" ? Object : Request,
466
+ String,
467
+ Object
468
+ ]),
469
+ _ts_metadata("design:returntype", typeof ReturnType === "undefined" ? Object : ReturnType)
470
+ ], A2uiController.prototype, "thread", null);
400
471
  _ts_decorate2([
401
472
  Post(path),
402
473
  HttpCode(200),