@dudousxd/nestjs-agent-dashboard 0.19.0 → 0.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/index.ts","../../src/server/agent-api.controller.ts","../../src/server/dashboard.service.ts","../../src/server/parse-approval-decision.ts","../../src/server/parse-price-input.ts","../../src/server/tokens.ts","../../src/server/parse-page-query.ts","../../src/server/normalize-path.ts","../../src/server/agent-dashboard-mount-paths.ts","../../src/server/agent-dashboard.module.ts","../../src/server/agent-ui.controller.ts"],"sourcesContent":["export * from './actor-directory.js';\nexport * from './agent-api.controller.js';\nexport * from './agent-dashboard-mount-paths.js';\nexport * from './agent-dashboard.module.js';\nexport * from './agent-ui.controller.js';\nexport * from './dashboard.service.js';\nexport * from './parse-price-input.js';\nexport * from './tokens.js';\n","import { CanAnyone } from '@dudousxd/nestjs-agent-core';\nimport type {\n ActorResolver,\n CurrentModelPrice,\n GenuiStats,\n GovernancePage,\n PendingApprovalRow,\n RecentRunRow,\n ToolCallActivityRow,\n ToolStatRow,\n} from '@dudousxd/nestjs-agent-core';\nimport {\n Body,\n Controller,\n Get,\n HttpCode,\n Inject,\n Optional,\n Param,\n Post,\n Query,\n Req,\n Sse,\n UseGuards,\n} from '@nestjs/common';\nimport type { Observable } from 'rxjs';\nimport {\n DashboardService,\n type LiveAgentEvent,\n type ReliabilityOverview,\n type RunDetailWithLabel,\n type SpendOverview,\n type ThreadActivityRowWithLabel,\n type ThreadDetailWithLabel,\n type ThreadSpendRowWithLabel,\n} from './dashboard.service.js';\nimport {\n parseApprovalWhere,\n parsePageNumber,\n parseRunWhere,\n parseThreadWhere,\n parseToolCallWhere,\n} from './parse-page-query.js';\nimport { AGENT_ACTOR_RESOLVER, DASHBOARD_APPROVAL_ACTOR_REF } from './tokens.js';\n\nconst DAY_MS = 86_400_000;\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** A `YYYY-MM-DD` UTC day string `daysAgo` days before now (0 = today). */\nfunction utcDay(daysAgo: number): string {\n return new Date(Date.now() - daysAgo * DAY_MS).toISOString().slice(0, 10);\n}\n\n/** Accept a client-supplied `YYYY-MM-DD`, or fall back to `fallback`; guards against junk input. */\nfunction dayOr(value: string | undefined, fallback: string): string {\n return value !== undefined && ISO_DAY.test(value) ? value : fallback;\n}\n\n/** Resolve the `from`/`to` query params into a validated range, defaulting to the last 30 days. */\nfunction resolveRange(\n from: string | undefined,\n to: string | undefined,\n): {\n fromDay: string;\n toDay: string;\n} {\n return { fromDay: dayOr(from, utcDay(29)), toDay: dayOr(to, utcDay(0)) };\n}\n\n/** Parse a `limit` query param, clamped to a sane window; falls back to `fallback` when absent/junk. */\nfunction parseLimit(value: string | undefined, fallback: number): number {\n const parsed = value === undefined ? Number.NaN : Number.parseInt(value, 10);\n if (!Number.isFinite(parsed)) return fallback;\n return Math.max(1, Math.min(200, parsed));\n}\n\n/**\n * JSON + SSE API consumed by the AI-gateway console SPA. Mounted at `apiBasePath` (set by\n * `RouterModule` in {@link AgentDashboardModule.forRoot}), so the controller routes are relative.\n *\n * Who reaches it is the app's: `decorators` on `AgentDashboardModule.forRoot`, or the app mounts it\n * itself (`routes: false`) — with `@nestjs/authentication` / `@nestjs/authorization`.\n */\n@CanAnyone()\n@Controller()\nexport class AgentApiController {\n constructor(\n private readonly dashboard: DashboardService,\n // `useValue` even when the host omitted `approvalActorRef` — no `@Optional()` needed (the token\n // is always bound; see DASHBOARD_APPROVAL_ACTOR_REF's TSDoc).\n @Inject(DASHBOARD_APPROVAL_ACTOR_REF)\n private readonly approvalActorRef: ((req: unknown) => string | undefined) | undefined,\n // The app's identity seam, bound + exported by the (global) AgentModule — the DEFAULT decider\n // attribution when no `approvalActorRef` override is configured. Genuinely `@Optional()`: the\n // dashboard can be mounted without AgentModule (read-model only), where nothing binds it.\n @Optional()\n @Inject(AGENT_ACTOR_RESOLVER)\n private readonly actorResolver?: ActorResolver,\n ) {}\n\n /** `{ byModel, byActor, trend }` for a day range (defaults to the last 30 days). */\n @Get('spend')\n spend(@Query('from') from?: string, @Query('to') to?: string): Promise<SpendOverview> {\n return this.dashboard.spend(resolveRange(from, to));\n }\n\n /** Top threads by cost (default 10, max 200) for a day range (defaults to the last 30 days). */\n @Get('top-threads')\n topThreads(\n @Query('from') from?: string,\n @Query('to') to?: string,\n @Query('limit') limit?: string,\n ): Promise<ThreadSpendRowWithLabel[]> {\n return this.dashboard.topThreads(resolveRange(from, to), parseLimit(limit, 10));\n }\n\n /** `{ metrics, byAgent, errors, trend }` for a day range (defaults to the last 30 days). */\n @Get('reliability')\n reliability(\n @Query('from') from?: string,\n @Query('to') to?: string,\n ): Promise<ReliabilityOverview> {\n return this.dashboard.reliability(resolveRange(from, to));\n }\n\n /** Most recent runs (default 50, max 200) for the Reliability recent-runs table. */\n @Get('runs')\n runs(@Query('limit') limit?: string): Promise<RecentRunRow[]> {\n return this.dashboard.recentRuns(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable runs for the Reliability recent-runs table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[agentName]`/`where[status]`/`where[errorCode]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('runs-page')\n runsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<RecentRunRow>> {\n const parsedWhere = parseRunWhere(where);\n return this.dashboard.runsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * One run drill-down: the run, its owning thread's headline (actor-labeled) and its tool calls, in\n * ONE round trip. 404s on an unknown run id.\n *\n * Declared AFTER `runs-page` so the literal route wins over this parameterised one regardless of\n * how the router orders same-prefix paths.\n */\n @Get('runs/:runId')\n runDetail(@Param('runId') runId: string): Promise<RunDetailWithLabel> {\n return this.dashboard.runDetail(runId);\n }\n\n /** Most recent tool calls (default 50, max 200) for the activity feed. */\n @Get('tool-calls')\n toolCalls(@Query('limit') limit?: string): Promise<ToolCallActivityRow[]> {\n return this.dashboard.recentToolCalls(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable tool calls for the Runs & tools activity table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[toolName]`/`where[toolType]`/`where[status]`/`where[threadId]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('tool-calls-page')\n toolCallsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<ToolCallActivityRow>> {\n const parsedWhere = parseToolCallWhere(where);\n return this.dashboard.toolCallsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox.\n *\n * Capped with NO total, so a backlog past `limit` is invisible from the response alone. Kept\n * because the SPA still calls it; anything new should call `approvals-page` instead.\n */\n @Get('approvals')\n approvals(@Query('limit') limit?: string): Promise<PendingApprovalRow[]> {\n return this.dashboard.pendingApprovals(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable approvals inbox, oldest first. `page` (default 1), `limit` (default 25, max\n * 200), and `where[toolName]`/`where[threadId]`/`where[actorRef]`/`where[agentName]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n *\n * Unlike `GET approvals` this reports `total`, so the console can say \"showing N of M\" instead of\n * silently hiding pending decisions behind a row cap.\n */\n @Get('approvals-page')\n approvalsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<PendingApprovalRow>> {\n const parsedWhere = parseApprovalWhere(where);\n return this.dashboard.approvalsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * Decide a pending HITL tool call. Body `{ approved: boolean; reason?: string }`. 501s (via\n * `DashboardService.decideApproval`) when no `AGENT_APPROVAL_PORT` is bound. `executedByRef`\n * comes from {@link deciderRef} run against the live request.\n */\n @Post('approvals/:toolCallId')\n @HttpCode(204)\n async decideApproval(\n @Param('toolCallId') toolCallId: string,\n @Body() body: unknown,\n @Req() req: unknown,\n ): Promise<void> {\n await this.dashboard.decideApproval(toolCallId, body, await this.deciderRef(req));\n }\n\n /**\n * WHO decided, as an opaque ref: an explicit `approvalActorRef` override wins outright (no\n * resolver fallback, even when it returns `undefined`); otherwise the AgentModule-configured\n * actor resolver — the same identity seam chat requests use. A resolver that throws is an\n * unauthenticated/unreadable request, not an error to surface: the decision itself was already\n * authorized by the dashboard's guards, so the ref is simply omitted.\n */\n private async deciderRef(req: unknown): Promise<string | undefined> {\n if (this.approvalActorRef !== undefined) {\n return this.approvalActorRef(req);\n }\n if (this.actorResolver === undefined) {\n return undefined;\n }\n try {\n return (await this.actorResolver.resolve(req)).id;\n } catch {\n return undefined;\n }\n }\n\n /** Per-tool call/failure/rejection/latency rollup for a day range (defaults to the last 30 days). */\n @Get('tools')\n tools(@Query('from') from?: string, @Query('to') to?: string): Promise<ToolStatRow[]> {\n return this.dashboard.toolStats(resolveRange(from, to));\n }\n\n /**\n * Generative-UI telemetry for a day range (defaults to the last 30 days): `{ totals, byToolModel,\n * errors, byDay, costPerTurn, latency }`. 501s (via `DashboardService.genuiStats`) when the bound\n * store does not implement `genuiStats`.\n */\n @Get('genui')\n genui(@Query('from') from?: string, @Query('to') to?: string): Promise<GenuiStats> {\n return this.dashboard.genuiStats(resolveRange(from, to));\n }\n\n /** Most recent threads (default 50, max 200) with rolled-up counts. */\n @Get('threads')\n threads(@Query('limit') limit?: string): Promise<ThreadActivityRowWithLabel[]> {\n return this.dashboard.recentThreads(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable threads for the Runs & tools threads table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[actorRef]`/`where[title]` (case-insensitive substring)/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('threads-page')\n threadsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<ThreadActivityRowWithLabel>> {\n const parsedWhere = parseThreadWhere(where);\n return this.dashboard.threadsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * One thread drill-down: the thread (actor-labeled), its lifetime token/cost rollup, its newest\n * runs and its newest messages, in ONE round trip. `messages` (default 50, max 200) and `runs`\n * (default 25, max 200) cap the two lists; `runTotal` and `thread.messageCount` report the true\n * sizes so the console can say what the caps hid. 404s on an unknown thread id.\n *\n * A soft-deleted thread is still returned, flagged `deleted: true` — an audit needs the thread it\n * just lost, and the alternative is a drill-down that 404s on exactly the interesting case.\n */\n @Get('threads/:threadId')\n threadDetail(\n @Param('threadId') threadId: string,\n @Query('messages') messages?: string,\n @Query('runs') runs?: string,\n ): Promise<ThreadDetailWithLabel> {\n return this.dashboard.threadDetail({\n threadId,\n messageLimit: parseLimit(messages, 50),\n runLimit: parseLimit(runs, 25),\n });\n }\n\n /**\n * Current price row per model, for the pricing tab. 501s (via `DashboardService.listPrices`) when\n * no `AGENT_PRICING_STORE` is bound.\n */\n @Get('pricing')\n listPrices(): Promise<CurrentModelPrice[]> {\n return this.dashboard.listPrices();\n }\n\n /**\n * Set a model's current price. Body shape mirrors core's `ModelPriceInput`\n * (`{ modelId, inputPricePer1m, outputPricePer1m, cacheWritePricePer1m?, cacheReadPricePer1m? }`).\n * 501s when no `AGENT_PRICING_STORE` is bound; 400s on a malformed body.\n */\n @Post('pricing')\n upsertPrice(@Body() body: unknown): Promise<void> {\n return this.dashboard.upsertPrice(body);\n }\n\n /** Server-Sent Events stream of live `aviary:agent:*` events — the Live feed tails it. */\n @Sse('stream')\n stream(): Observable<{ data: LiveAgentEvent }> {\n return this.dashboard.streamEvents();\n }\n}\n","import { subscribe, unsubscribe } from 'node:diagnostics_channel';\nimport type {\n ActorSpendRow,\n AgentApprovalPort,\n AgentDiagnosticEvent,\n AgentGovernanceQueries,\n AgentPricingStore,\n ApprovalWhere,\n CurrentModelPrice,\n FeatureSpendRow,\n GenuiStats,\n GovernancePage,\n GovernancePageQuery,\n GovernanceRange,\n GovernanceRunDetail,\n GovernanceThreadDetail,\n GovernanceThreadDetailQuery,\n ModelSpendRow,\n PendingApprovalRow,\n RecentRunRow,\n RunAgentBreakdownRow,\n RunErrorBreakdownRow,\n RunMetrics,\n RunTrendPoint,\n RunWhere,\n ThreadActivityRow,\n ThreadSpendRow,\n ThreadWhere,\n ToolCallActivityRow,\n ToolCallWhere,\n ToolStatRow,\n UsageTrendPoint,\n} from '@dudousxd/nestjs-agent-core';\nimport { channelName } from '@dudousxd/nestjs-diagnostics';\nimport {\n Inject,\n Injectable,\n NotFoundException,\n NotImplementedException,\n Optional,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport type { ActorDirectory } from './actor-directory.js';\nimport { parseApprovalDecision } from './parse-approval-decision.js';\nimport { parsePriceInput } from './parse-price-input.js';\nimport {\n AGENT_ACTOR_DIRECTORY,\n AGENT_APPROVAL_PORT,\n AGENT_GOVERNANCE_QUERIES,\n AGENT_PRICING_STORE,\n} from './tokens.js';\n\n/** An actor-scoped row decorated with its resolved display label — `null` when unbound or unresolved. */\nexport interface WithActorLabel {\n actorLabel: string | null;\n}\n\nexport type ActorSpendRowWithLabel = ActorSpendRow & WithActorLabel;\nexport type ThreadSpendRowWithLabel = ThreadSpendRow & WithActorLabel;\nexport type ThreadActivityRowWithLabel = ThreadActivityRow & WithActorLabel;\n\n/**\n * `GET <api>/runs/:runId` — a run drill-down with the owning thread's `actorLabel` resolved, the\n * same way {@link threadsPage} labels its rows. The run row itself stays unlabeled, matching every\n * other run surface in this console.\n */\nexport type RunDetailWithLabel = Omit<GovernanceRunDetail, 'thread'> & {\n thread: GovernanceRunDetail['thread'] & WithActorLabel;\n};\n\n/** `GET <api>/threads/:threadId` — a thread drill-down, actor-labeled like {@link threadsPage}. */\nexport type ThreadDetailWithLabel = Omit<GovernanceThreadDetail, 'thread'> & {\n thread: ThreadActivityRowWithLabel;\n};\n\n/** The spend/usage overview the SPA renders on its headline section (`GET <api>/spend`). */\nexport interface SpendOverview {\n byModel: ModelSpendRow[];\n byActor: ActorSpendRowWithLabel[];\n trend: UsageTrendPoint[];\n /**\n * Spend per feature — agent turns and usage recorded outside them side by side. Absent when the\n * bound governance adapter has no `spendByFeature`.\n */\n byFeature?: FeatureSpendRow[];\n}\n\n/** The run-reliability overview the SPA renders on its Reliability section (`GET <api>/reliability`). */\nexport interface ReliabilityOverview {\n metrics: RunMetrics;\n byAgent: RunAgentBreakdownRow[];\n errors: RunErrorBreakdownRow[];\n trend: RunTrendPoint[];\n}\n\n/** The message returned as a 501 when no `AGENT_PRICING_STORE` is bound. */\nconst PRICING_STORE_UNBOUND_MESSAGE =\n 'Pricing CRUD is unavailable: no AGENT_PRICING_STORE is bound. Bind a pricing store (e.g. ' +\n 'MikroOrmPricingStore from @dudousxd/nestjs-agent-store-mikro-orm) to enable it.';\n\n/** The message returned as a 501 when no `AGENT_APPROVAL_PORT` is bound. */\nconst APPROVAL_PORT_UNBOUND_MESSAGE =\n 'Approve/reject is unavailable: no AGENT_APPROVAL_PORT is bound. Import AgentModule from ' +\n '@dudousxd/nestjs-agent alongside this dashboard to enable it — the approvals inbox stays ' +\n 'read-only until then.';\n\n/** The message returned as a 501 when the bound read-model does not implement `genuiStats`. */\nconst GENUI_STATS_UNSUPPORTED_MESSAGE =\n 'Generative UI telemetry is unavailable: the bound AGENT_GOVERNANCE_QUERIES store does not ' +\n 'implement genuiStats. Use a store adapter that records generative-UI calls (recordGenuiCalls) ' +\n 'to enable it.';\n\n/** One live agent event forwarded over SSE, flattened from the `aviary:agent:*` diagnostics envelope. */\nexport interface LiveAgentEvent {\n /** The event name, e.g. `run.started` / `tool-call` / `quota.exceeded`. */\n event: string;\n /** Epoch millis the event was emitted. */\n ts: number;\n /** The library-defined payload (see the `Agent*Event` shapes in core's diagnostics). */\n payload: Record<string, unknown>;\n}\n\n/**\n * The `aviary:agent:*` events the Live feed tails — a curated subset of core's\n * `AGENT_DIAGNOSTIC_EVENTS`, typed against it so a renamed event is a compile error here. Every run\n * TERMINAL is in it (finished, failed, cancelled): a feed that showed runs starting but never failing\n * would read as \"all fine\" exactly when it is not. The per-turn bookkeeping events (skills, memory,\n * retrieval, tool retries) are left to the telescope.\n */\nconst AGENT_EVENTS: readonly AgentDiagnosticEvent[] = [\n 'run.started',\n 'message',\n 'tool-call',\n 'quota.exceeded',\n 'run.finished',\n 'run.failed',\n 'run.cancelled',\n 'delegated',\n];\n\n/** The `node:diagnostics_channel` envelope `emit()` publishes (see `@dudousxd/nestjs-diagnostics`). */\ninterface AgentDiagnosticEnvelope {\n event: string;\n ts?: number;\n payload?: Record<string, unknown>;\n}\n\n/** Narrow the untyped diagnostics-channel message to the envelope we forward. */\nfunction isAgentEnvelope(message: unknown): message is AgentDiagnosticEnvelope {\n return (\n typeof message === 'object' &&\n message !== null &&\n 'event' in message &&\n typeof (message as { event: unknown }).event === 'string'\n );\n}\n\n/**\n * Read-model + live bridge backing the AI-gateway console.\n *\n * - Historical, restart-surviving spend/usage/threads come from the injected\n * {@link AGENT_GOVERNANCE_QUERIES} read-model (backed by a store adapter). The host must provide\n * that token — bind it via your `@dudousxd/nestjs-agent` module (global) alongside this dashboard.\n * - Live activity comes off the `aviary:agent:*` diagnostics channel, subscribed per SSE client and\n * unsubscribed when the client disconnects.\n * - `actorLabel` on actor-scoped rows comes from the OPTIONAL {@link AGENT_ACTOR_DIRECTORY} — `null`\n * on every row when nothing is bound, so the console degrades to raw `actorRef`s instead of failing.\n * - Pricing CRUD (`listPrices`/`upsertPrice`) reads/writes the OPTIONAL {@link AGENT_PRICING_STORE} —\n * a 501 with a clear message when nothing is bound.\n */\n@Injectable()\nexport class DashboardService {\n constructor(\n @Inject(AGENT_GOVERNANCE_QUERIES) private readonly queries: AgentGovernanceQueries,\n @Optional()\n @Inject(AGENT_ACTOR_DIRECTORY)\n private readonly actorDirectory?: ActorDirectory,\n @Optional()\n @Inject(AGENT_PRICING_STORE)\n private readonly pricingStore?: AgentPricingStore,\n @Optional()\n @Inject(AGENT_APPROVAL_PORT)\n private readonly approvalPort?: AgentApprovalPort,\n ) {}\n\n /** Spend/usage overview for a day range: by-model + by-actor spend and the daily trend, in parallel. */\n async spend(range: GovernanceRange): Promise<SpendOverview> {\n const [byModel, byActorRaw, trend, byFeature] = await Promise.all([\n this.queries.spendByModel(range),\n this.queries.spendByActor(range),\n this.queries.usageTrend(range),\n this.queries.spendByFeature?.(range),\n ]);\n const byActor = await this.withActorLabels(byActorRaw);\n return { byModel, byActor, trend, ...(byFeature !== undefined ? { byFeature } : {}) };\n }\n\n /** Top threads by cost for a day range (default 10, highest cost first). */\n async topThreads(range: GovernanceRange, limit = 10): Promise<ThreadSpendRowWithLabel[]> {\n const rows = await this.queries.spendByThread(range, limit);\n return this.withActorLabels(rows);\n }\n\n /** Run reliability for a day range: metrics, by-agent/by-error breakdowns and the trend, in parallel. */\n async reliability(range: GovernanceRange): Promise<ReliabilityOverview> {\n const [metrics, byAgent, errors, trend] = await Promise.all([\n this.queries.runMetrics(range),\n this.queries.runsByAgent(range),\n this.queries.runErrors(range),\n this.queries.runTrend(range),\n ]);\n return { metrics, byAgent, errors, trend };\n }\n\n /** Most recent runs (status/agent/duration/error) for the Reliability recent-runs table. */\n recentRuns(limit: number): Promise<RecentRunRow[]> {\n return this.queries.recentRuns(limit);\n }\n\n /** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */\n recentToolCalls(limit: number): Promise<ToolCallActivityRow[]> {\n return this.queries.recentToolCalls(limit);\n }\n\n /** Paged, filterable tool calls for the Runs & tools activity table. */\n toolCallsPage(\n query: GovernancePageQuery<ToolCallWhere>,\n ): Promise<GovernancePage<ToolCallActivityRow>> {\n return this.queries.toolCallsPage(query);\n }\n\n /** Paged, filterable runs for the Reliability recent-runs table. */\n runsPage(query: GovernancePageQuery<RunWhere>): Promise<GovernancePage<RecentRunRow>> {\n return this.queries.runsPage(query);\n }\n\n /**\n * Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox.\n * Capped and totalless — kept because the SPA still calls `GET approvals`; new callers want\n * {@link approvalsPage}, which reports how much of the backlog the cap hid.\n */\n pendingApprovals(limit: number): Promise<PendingApprovalRow[]> {\n return this.queries.pendingApprovals(limit);\n }\n\n /** Paged, filterable approvals inbox with a total — the backlog-safe read of the HITL queue. */\n approvalsPage(\n query: GovernancePageQuery<ApprovalWhere>,\n ): Promise<GovernancePage<PendingApprovalRow>> {\n return this.queries.approvalsPage(query);\n }\n\n /**\n * One run with its thread and its tool calls. 404s on an unknown id rather than returning an empty\n * detail: \"this run does not exist\" and \"this run did nothing\" are different answers, and a console\n * that conflates them sends an operator looking for a bug that isn't there.\n */\n async runDetail(runId: string): Promise<RunDetailWithLabel> {\n const detail = await this.queries.runDetail(runId);\n if (detail === null) {\n throw new NotFoundException(`No run \"${runId}\".`);\n }\n const [thread] = await this.withActorLabels([detail.thread]);\n // `withActorLabels` returns one row per input; the array is never empty here.\n return { ...detail, thread: thread ?? { ...detail.thread, actorLabel: null } };\n }\n\n /** One thread with its usage rollup, runs and messages. 404s on an unknown id, like {@link runDetail}. */\n async threadDetail(query: GovernanceThreadDetailQuery): Promise<ThreadDetailWithLabel> {\n const detail = await this.queries.threadDetail(query);\n if (detail === null) {\n throw new NotFoundException(`No thread \"${query.threadId}\".`);\n }\n const [thread] = await this.withActorLabels([detail.thread]);\n return { ...detail, thread: thread ?? { ...detail.thread, actorLabel: null } };\n }\n\n /** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */\n toolStats(range: GovernanceRange): Promise<ToolStatRow[]> {\n return this.queries.toolStats(range);\n }\n\n /**\n * Generative-UI telemetry for a day range — first-try success per tool/model, common errors, UI\n * cost per day/turn and time-to-frame. `genuiStats` is OPTIONAL on the read-model (a store that\n * does not record UI calls leaves it out), so an unsupported store 501s with a clear message, the\n * same posture as an unbound pricing store — the console renders that as its own state.\n */\n async genuiStats(range: GovernanceRange): Promise<GenuiStats> {\n if (this.queries.genuiStats === undefined) {\n throw new NotImplementedException(GENUI_STATS_UNSUPPORTED_MESSAGE);\n }\n return this.queries.genuiStats(range);\n }\n\n /**\n * Decide a pending HITL tool call from the console. Routes through the OPTIONAL\n * {@link AGENT_APPROVAL_PORT} — bound by `@dudousxd/nestjs-agent` to the SAME signal path chat\n * approvals use — so a 501 here (checked BEFORE body validation, same ordering as\n * {@link upsertPrice}) means \"no approval port bound\", not \"your body was invalid\". `executedByRef`\n * is an OPAQUE decider ref the caller resolved from the live request (see\n * `AgentDashboardOptions.approvalActorRef`); omitted when the host didn't configure one.\n */\n async decideApproval(\n toolCallId: string,\n body: unknown,\n executedByRef: string | undefined,\n ): Promise<void> {\n if (this.approvalPort === undefined) {\n throw new NotImplementedException(APPROVAL_PORT_UNBOUND_MESSAGE);\n }\n const decision = parseApprovalDecision(body);\n const opts = executedByRef !== undefined ? { executedByRef } : {};\n if (decision.approved) {\n await this.approvalPort.approve(toolCallId, opts);\n return;\n }\n await this.approvalPort.reject(toolCallId, {\n ...opts,\n ...(decision.reason !== undefined ? { reason: decision.reason } : {}),\n });\n }\n\n /** Most recent threads with rolled-up message/token counts. */\n async recentThreads(limit: number): Promise<ThreadActivityRowWithLabel[]> {\n const rows = await this.queries.recentThreads(limit);\n return this.withActorLabels(rows);\n }\n\n /**\n * Paged, filterable threads for the Runs & tools threads table — actor-labeled the same way\n * {@link recentThreads} is.\n */\n async threadsPage(\n query: GovernancePageQuery<ThreadWhere>,\n ): Promise<GovernancePage<ThreadActivityRowWithLabel>> {\n const page = await this.queries.threadsPage(query);\n const rows = await this.withActorLabels(page.rows);\n return { ...page, rows };\n }\n\n /**\n * Decorate actor-scoped rows with `actorLabel`, batching the distinct `actorRef`s into ONE\n * {@link ActorDirectory.resolveDisplay} call per response. `null` for every row when no directory\n * is bound, or for a ref the directory didn't resolve.\n */\n private async withActorLabels<Row extends { actorRef: string }>(\n rows: Row[],\n ): Promise<(Row & WithActorLabel)[]> {\n if (rows.length === 0) {\n return [];\n }\n if (this.actorDirectory === undefined) {\n return rows.map((row) => ({ ...row, actorLabel: null }));\n }\n const refs = [...new Set(rows.map((row) => row.actorRef))];\n const resolved = await this.actorDirectory.resolveDisplay(refs);\n return rows.map((row) => ({ ...row, actorLabel: resolved[row.actorRef] ?? null }));\n }\n\n /** Current price row per model, for the pricing tab. 501s when no `AGENT_PRICING_STORE` is bound. */\n async listPrices(): Promise<CurrentModelPrice[]> {\n if (this.pricingStore === undefined) {\n throw new NotImplementedException(PRICING_STORE_UNBOUND_MESSAGE);\n }\n return this.pricingStore.listCurrentPrices();\n }\n\n /**\n * Set a model's current price (`POST <api>/pricing` body). 501s when no `AGENT_PRICING_STORE` is\n * bound (checked BEFORE body validation, so an unbound store always reports as unimplemented rather\n * than as a validation error); otherwise the body is minimally validated via {@link parsePriceInput}.\n */\n async upsertPrice(body: unknown): Promise<void> {\n if (this.pricingStore === undefined) {\n throw new NotImplementedException(PRICING_STORE_UNBOUND_MESSAGE);\n }\n await this.pricingStore.upsertModelPrice(parsePriceInput(body));\n }\n\n /**\n * Live SSE stream of `aviary:agent:*` diagnostics events. One subscription per SSE client:\n * subscribing wires a handler onto each agent channel; the returned teardown removes them all when\n * the client disconnects (or the observable is otherwise unsubscribed).\n */\n streamEvents(): Observable<{ data: LiveAgentEvent }> {\n return new Observable<{ data: LiveAgentEvent }>((subscriber) => {\n const bindings = AGENT_EVENTS.map((event) => {\n const name = channelName('agent', event);\n const handler = (message: unknown): void => {\n if (!isAgentEnvelope(message)) return;\n subscriber.next({\n data: {\n event: message.event,\n ts: message.ts ?? Date.now(),\n payload: message.payload ?? {},\n },\n });\n };\n subscribe(name, handler);\n return { name, handler };\n });\n return () => {\n for (const binding of bindings) unsubscribe(binding.name, binding.handler);\n };\n });\n }\n}\n","import { BadRequestException } from '@nestjs/common';\n\n/** The parsed, minimally-validated `POST <api>/approvals/:toolCallId` body. */\nexport interface ApprovalDecisionInput {\n approved: boolean;\n reason?: string;\n}\n\n/**\n * Minimal shape guard for a `POST <api>/approvals/:toolCallId` body — rejects junk before it reaches\n * `AgentApprovalPort.approve`/`reject`. Mirrors `parsePriceInput`'s role for the pricing endpoint.\n */\nexport function parseApprovalDecision(body: unknown): ApprovalDecisionInput {\n if (typeof body !== 'object' || body === null) {\n throw new BadRequestException('Expected a JSON object body.');\n }\n const { approved, reason } = body as Record<string, unknown>;\n\n if (typeof approved !== 'boolean') {\n throw new BadRequestException('\"approved\" must be a boolean.');\n }\n if (reason !== undefined && typeof reason !== 'string') {\n throw new BadRequestException('\"reason\" must be a string when present.');\n }\n\n return { approved, ...(reason !== undefined ? { reason } : {}) };\n}\n","import type { ModelPriceInput } from '@dudousxd/nestjs-agent-core';\nimport { BadRequestException } from '@nestjs/common';\n\n/**\n * Minimal shape guard for a `POST <api>/pricing` body — rejects junk before it reaches\n * `AgentPricingStore.upsertModelPrice`. Not a full schema validator (the store adapter owns real\n * constraints, e.g. uniqueness); this only checks the wire shape core's `ModelPriceInput` requires.\n */\nexport function parsePriceInput(body: unknown): ModelPriceInput {\n if (typeof body !== 'object' || body === null) {\n throw new BadRequestException('Expected a JSON object body.');\n }\n const { modelId, inputPricePer1m, outputPricePer1m, cacheWritePricePer1m, cacheReadPricePer1m } =\n body as Record<string, unknown>;\n\n if (typeof modelId !== 'string' || modelId.trim().length === 0) {\n throw new BadRequestException('\"modelId\" must be a non-empty string.');\n }\n if (!isFiniteNonNegative(inputPricePer1m)) {\n throw new BadRequestException('\"inputPricePer1m\" must be a non-negative number.');\n }\n if (!isFiniteNonNegative(outputPricePer1m)) {\n throw new BadRequestException('\"outputPricePer1m\" must be a non-negative number.');\n }\n if (cacheWritePricePer1m !== undefined && !isFiniteNonNegative(cacheWritePricePer1m)) {\n throw new BadRequestException(\n '\"cacheWritePricePer1m\" must be a non-negative number when present.',\n );\n }\n if (cacheReadPricePer1m !== undefined && !isFiniteNonNegative(cacheReadPricePer1m)) {\n throw new BadRequestException(\n '\"cacheReadPricePer1m\" must be a non-negative number when present.',\n );\n }\n\n return {\n modelId,\n inputPricePer1m,\n outputPricePer1m,\n ...(cacheWritePricePer1m !== undefined ? { cacheWritePricePer1m } : {}),\n ...(cacheReadPricePer1m !== undefined ? { cacheReadPricePer1m } : {}),\n };\n}\n\nfunction isFiniteNonNegative(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0;\n}\n","/**\n * DI tokens for the standalone AI-gateway dashboard.\n *\n * All use `Symbol.for(...)` (the global symbol registry) on purpose: pnpm peer multiplexing + dual\n * ESM/CJS can load a package more than once, and a plain `Symbol()` would mint a distinct token per\n * copy and break DI across the ESM/CJS split. A registered symbol collapses every copy onto the same\n * token.\n */\n\n/**\n * The governance read-model, owned by `@dudousxd/nestjs-agent-core`. We re-declare it here BY VALUE\n * (not by import) so DI does not depend on a runtime value-import of core resolving — `Symbol.for`\n * with the identical key resolves to the SAME symbol instance as core's own\n * `packages/core/src/tokens.ts` export. The key MUST stay byte-identical with that export.\n */\nexport const AGENT_GOVERNANCE_QUERIES = Symbol.for('@dudousxd/nestjs-agent:governance-queries');\n\n/**\n * Optional actor→label resolver (see {@link ActorDirectory} in `./actor-directory.js`), owned by\n * `@dudousxd/nestjs-agent-core`. Re-declared here BY VALUE for the same reason as\n * {@link AGENT_GOVERNANCE_QUERIES} above — the key MUST stay byte-identical with core's own\n * `AGENT_ACTOR_DIRECTORY` export so both copies collapse onto the same registered symbol. Optional:\n * the dashboard works with actorRef-only rows when nothing is bound.\n */\nexport const AGENT_ACTOR_DIRECTORY = Symbol.for('@dudousxd/nestjs-agent:actor-directory');\n\n/**\n * The pricing WRITE side (`AgentPricingStore`), owned by `@dudousxd/nestjs-agent-core`. Re-declared\n * here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional: the pricing\n * tab/endpoints 501 with a clear message when nothing is bound.\n */\nexport const AGENT_PRICING_STORE = Symbol.for('@dudousxd/nestjs-agent:pricing-store');\n\n/**\n * Console-side HITL decisions (`AgentApprovalPort`), owned by `@dudousxd/nestjs-agent-core`.\n * Re-declared here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional:\n * bound by `@dudousxd/nestjs-agent` (which adapts it to the same signal path chat approvals use) —\n * absent, `POST <api>/approvals/:toolCallId` 501s and the approvals inbox renders read-only.\n */\nexport const AGENT_APPROVAL_PORT = Symbol.for('@dudousxd/nestjs-agent:approval-port');\n\n/**\n * The app's request-identity seam (`ActorResolver`), owned by `@dudousxd/nestjs-agent-core` and\n * bound + exported by the (global) `AgentModule` from the host's `actorResolver` option. Re-declared\n * here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional: the default\n * decider attribution for `POST <api>/approvals/:toolCallId` — absent (or throwing, i.e.\n * unauthenticated), `executedByRef` is simply omitted.\n */\nexport const AGENT_ACTOR_RESOLVER = Symbol.for('@dudousxd/nestjs-agent:actor-resolver');\n\n/** DI token carrying the UI mount base (e.g. `/ai-gateway`). */\nexport const DASHBOARD_BASE_PATH = Symbol.for('@dudousxd/nestjs-agent-dashboard:base-path');\n\n/** DI token carrying the JSON API base the SPA fetches from (e.g. `/ai-gateway/api`). */\nexport const DASHBOARD_API_PATH = Symbol.for('@dudousxd/nestjs-agent-dashboard:api-path');\n\n/**\n * DI token carrying the host-provided {@link AgentDashboardOptions.approvalActorRef} extractor (or\n * `undefined` when the host didn't set one — the controller then falls back to\n * {@link AGENT_ACTOR_RESOLVER} for decider attribution). Threaded to `AgentApiModule` (where the API\n * controller actually lives) same as the pattern above — `useValue` even when `undefined`, so\n * injecting it needs no `@Optional()`.\n */\nexport const DASHBOARD_APPROVAL_ACTOR_REF = Symbol.for(\n '@dudousxd/nestjs-agent-dashboard:approval-actor-ref',\n);\n","import type {\n ApprovalWhere,\n RunWhere,\n ThreadWhere,\n ToolCallWhere,\n} from '@dudousxd/nestjs-agent-core';\nimport { BadRequestException } from '@nestjs/common';\n\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Parse a `page` query param (1-based); falls back to 1 when absent or not a positive integer. */\nexport function parsePageNumber(value: string | undefined): number {\n const parsed = value === undefined ? Number.NaN : Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 1) return 1;\n return parsed;\n}\n\n/** Rejects a `where[<field>]` whose field name isn't in `allowed`, naming the offending field. */\nfunction assertKnownFields(raw: Record<string, string>, allowed: readonly string[]): void {\n for (const field of Object.keys(raw)) {\n if (!allowed.includes(field)) {\n throw new BadRequestException(`Unknown where field \"${field}\".`);\n }\n }\n}\n\n/** Rejects a day-bound `where[<field>]` that isn't `YYYY-MM-DD`, mirroring the range endpoints' check. */\nfunction assertDayFields(raw: Record<string, string>, dayFields: readonly string[]): void {\n for (const field of dayFields) {\n const value = raw[field];\n if (value !== undefined && !ISO_DAY.test(value)) {\n throw new BadRequestException(`\"where[${field}]\" must be YYYY-MM-DD.`);\n }\n }\n}\n\nconst DAY_FIELDS = ['fromDay', 'toDay'] as const;\nconst TOOL_CALL_WHERE_FIELDS = [\n 'toolName',\n 'toolType',\n 'status',\n 'threadId',\n 'fromDay',\n 'toDay',\n] as const;\n\n/**\n * Parse `GET tool-calls-page`'s `where[...]` query params into a `ToolCallWhere`. `undefined` (no\n * `where` params at all) means no filter. Throws `BadRequestException` on an unknown field name or a\n * malformed day bound.\n */\nexport function parseToolCallWhere(\n raw: Record<string, string> | undefined,\n): ToolCallWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, TOOL_CALL_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.toolName !== undefined ? { toolName: raw.toolName } : {}),\n ...(raw.toolType !== undefined ? { toolType: raw.toolType } : {}),\n ...(raw.status !== undefined ? { status: raw.status } : {}),\n ...(raw.threadId !== undefined ? { threadId: raw.threadId } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\nconst THREAD_WHERE_FIELDS = ['actorRef', 'title', 'fromDay', 'toDay'] as const;\n\n/**\n * Parse `GET threads-page`'s `where[...]` query params into a `ThreadWhere`. `undefined` means no\n * filter. Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseThreadWhere(raw: Record<string, string> | undefined): ThreadWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, THREAD_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.actorRef !== undefined ? { actorRef: raw.actorRef } : {}),\n ...(raw.title !== undefined ? { title: raw.title } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\n// `threadId` was supported by every adapter's `RunWhere` but never accepted here, so \"show me this\n// thread's runs\" — the drill-down's most obvious follow-up query — 400'd with \"Unknown where field\".\nconst RUN_WHERE_FIELDS = [\n 'agentName',\n 'status',\n 'errorCode',\n 'threadId',\n 'fromDay',\n 'toDay',\n] as const;\n\n/**\n * Parse `GET runs-page`'s `where[...]` query params into a `RunWhere`. `undefined` means no filter.\n * Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseRunWhere(raw: Record<string, string> | undefined): RunWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, RUN_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.agentName !== undefined ? { agentName: raw.agentName } : {}),\n ...(raw.status !== undefined ? { status: raw.status } : {}),\n ...(raw.errorCode !== undefined ? { errorCode: raw.errorCode } : {}),\n ...(raw.threadId !== undefined ? { threadId: raw.threadId } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\nconst APPROVAL_WHERE_FIELDS = [\n 'toolName',\n 'threadId',\n 'actorRef',\n 'agentName',\n 'fromDay',\n 'toDay',\n] as const;\n\n/**\n * Parse `GET approvals-page`'s `where[...]` query params into an `ApprovalWhere`. `undefined` means\n * no filter. Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseApprovalWhere(\n raw: Record<string, string> | undefined,\n): ApprovalWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, APPROVAL_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.toolName !== undefined ? { toolName: raw.toolName } : {}),\n ...(raw.threadId !== undefined ? { threadId: raw.threadId } : {}),\n ...(raw.actorRef !== undefined ? { actorRef: raw.actorRef } : {}),\n ...(raw.agentName !== undefined ? { agentName: raw.agentName } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n","/**\n * Leading slash, no trailing slash (`'ai-gateway/'` -> `'/ai-gateway'`). Shared by\n * {@link AgentDashboardModule.forRoot} and {@link agentDashboardMountPaths} so the mount-path math\n * behind the module and the pure helper that mirrors it can never drift apart.\n */\nexport function normalizeDashboardPath(path: string): string {\n return `/${path.replace(/^\\/+|\\/+$/g, '')}`;\n}\n","import { normalizeDashboardPath } from './normalize-path.js';\n\n/** Same shape {@link AgentDashboardModule.forRoot} accepts — kept local so this stays a pure, DI-free helper. */\nexport interface AgentDashboardMountPathsOptions {\n basePath?: string;\n apiBasePath?: string;\n}\n\n/** Strip the leading slash `normalizeDashboardPath` adds — `setGlobalPrefix`'s `exclude` roots are unprefixed. */\nfunction unprefixed(path: string): string {\n return path.replace(/^\\/+/, '');\n}\n\n/**\n * Route roots a host must EXCLUDE from a global prefix (`setGlobalPrefix('api', { exclude })`) so\n * the AI-gateway dashboard's SPA and JSON API keep resolving at their configured mount paths instead\n * of being shifted under the prefix.\n *\n * Unlike a single-surface dashboard (e.g. `telescopeMountPaths()`), this one mounts TWO route roots —\n * the UI at `basePath` and its JSON API at `apiBasePath` — so excluding only one leaves the other\n * shadowed. `options` mirrors {@link AgentDashboardOptions} and resolves through the exact same\n * defaulting (`apiBasePath` falls back to `<basePath>/api`) as {@link AgentDashboardModule.forRoot},\n * so the excluded roots always agree with what actually got mounted.\n *\n * @example\n * ```ts\n * // Raw defaults (basePath `/ai-gateway`, apiBasePath `/ai-gateway/api`):\n * app.setGlobalPrefix('api', { exclude: agentDashboardMountPaths() });\n * // -> ['ai-gateway', 'ai-gateway/{*splat}', 'ai-gateway/api', 'ai-gateway/api/{*splat}']\n *\n * // The recommended pattern — apiBasePath nested under the app's own `/api` prefix — MUST pass the\n * // same options given to `forRoot(...)`:\n * const dashboardOptions = { apiBasePath: '/api/ai-gateway' };\n * app.setGlobalPrefix('api', { exclude: agentDashboardMountPaths(dashboardOptions) });\n * // -> ['ai-gateway', 'ai-gateway/{*splat}', 'api/ai-gateway', 'api/ai-gateway/{*splat}']\n * ```\n */\nexport function agentDashboardMountPaths(options?: AgentDashboardMountPathsOptions): string[] {\n const basePath = normalizeDashboardPath(options?.basePath ?? '/ai-gateway');\n const apiBasePath = normalizeDashboardPath(options?.apiBasePath ?? `${basePath}/api`);\n const base = unprefixed(basePath);\n const api = unprefixed(apiBasePath);\n return [base, `${base}/{*splat}`, api, `${api}/{*splat}`];\n}\n","import { withControllerDecorators } from '@dudousxd/nestjs-agent-core';\nimport 'reflect-metadata';\nimport { type DynamicModule, Module } from '@nestjs/common';\nimport { RouterModule } from '@nestjs/core';\nimport { AgentApiController } from './agent-api.controller.js';\nimport { AgentUiController } from './agent-ui.controller.js';\nimport { DashboardService } from './dashboard.service.js';\nimport { normalizeDashboardPath } from './normalize-path.js';\nimport { DASHBOARD_API_PATH, DASHBOARD_APPROVAL_ACTOR_REF, DASHBOARD_BASE_PATH } from './tokens.js';\n\n/**\n * `TReq` mirrors core's `ActorResolver<TReq>` convention: the transport request type\n * {@link AgentDashboardOptions.approvalActorRef} receives. Defaults to `unknown`.\n */\nexport interface AgentDashboardOptions<TReq = unknown> {\n /**\n * Where the SPA (UI) is served. Default `/ai-gateway`. This is a page route — keep it out of an\n * `/api` prefix so it reads as a UI, not an endpoint.\n */\n basePath?: string;\n /**\n * Where the JSON API is mounted (what the SPA fetches). Default `<basePath>/api`. Set it under\n * your app's `/api` prefix — e.g. `/api/ai-gateway` — so the API inherits the app's auth/proxy\n * rules while the UI stays at `basePath`.\n */\n apiBasePath?: string;\n /**\n * Who reaches the console: class decorators applied to every controller this module mounts — the\n * ecosystem's convention, like `@nestjs/mcp`'s `decorators` — with the official tools, e.g.\n * `[UseGuards(AuthenticationGuard), Can(ConsolePolicy, 'view')]` from `@nestjs/authentication` /\n * `@nestjs/authorization`, or a function as a guard instance, `UseGuards({ canActivate })`.\n * Applied to a per-registration subclass, so nothing sticks to the shared classes.\n *\n * In production (`NODE_ENV=production`) the console refuses to boot without them, unless the app\n * mounts the routes itself (`routes: false`): a governance console is never served open there by\n * default. Outside production the routes are open (and to `@nestjs/authorization`'s\n * deny-by-default guard: `CanAnyone()`). Static wiring.\n */\n decorators?: ClassDecorator[];\n /**\n * `false` mounts none of the console's routes, while its services still run — the way NestJS's\n * own packages leave routes to you: serve them from controllers of your own, protected with the\n * official tools, by extending the exported `AgentApiController` / `AgentUiController` under your\n * own path and decorators (the module exports what they inject). Static wiring.\n */\n routes?: boolean;\n /** Extra `imports` merged into the console's modules — what a decorator's guard injects. */\n imports?: DynamicModule['imports'];\n /**\n * OVERRIDE for WHO is deciding a HITL approval — invoked on the incoming\n * `POST <api>/approvals/:toolCallId` request to stamp `AgentApprovalPort`'s `opts.executedByRef`.\n *\n * DEFAULT (option omitted): the AgentModule-configured actor resolver (`AGENT_ACTOR_RESOLVER`) is\n * consulted — the same identity seam chat requests use — and its actor id becomes the decider ref\n * (a throwing resolver, i.e. an unauthenticated request, just omits the ref). Set this only when\n * the console's caller is identified differently from chat's; returning `undefined` leaves\n * `executedByRef` unset — the explicit override wins outright, with no resolver fallback.\n */\n approvalActorRef?: (req: TReq) => string | undefined;\n}\n\n/** Leading slash, no trailing slash. */\nfunction normalize(path: string): string {\n return normalizeDashboardPath(path);\n}\n\n/**\n * Refuse to serve a governance console open in production: either decorators state who reaches it,\n * or the app mounts the routes itself.\n */\nfunction assertProtected(options: AgentDashboardOptions<unknown>): void {\n if (process.env.NODE_ENV !== 'production') return;\n if (options.routes === false) return;\n if ((options.decorators ?? []).length > 0) return;\n throw new Error(\n 'AgentDashboardModule: the AI-gateway console would be served open in production. Say who reaches it, with ' +\n \"`decorators: [UseGuards(AuthenticationGuard), Can(ConsolePolicy, 'view')]`, or mount its routes yourself \" +\n 'with `routes: false` and extend `AgentApiController` / `AgentUiController` under your own guards.',\n );\n}\n\n/** Holds the JSON API + SSE controller and its read service, mounted on its own path. */\n@Module({})\nexport class AgentApiModule {\n static register<TReq = unknown>(options: {\n imports?: DynamicModule['imports'];\n decorators?: ClassDecorator[];\n routes?: boolean;\n approvalActorRef?: (req: TReq) => string | undefined;\n }): DynamicModule {\n return {\n module: AgentApiModule,\n imports: [...(options.imports ?? [])],\n controllers:\n options.routes === false\n ? []\n : withControllerDecorators([AgentApiController], options.decorators),\n providers: [\n DashboardService,\n // `useValue` even when `options.approvalActorRef` is `undefined` — AgentApiController\n // injects this WITHOUT `@Optional()`.\n { provide: DASHBOARD_APPROVAL_ACTOR_REF, useValue: options.approvalActorRef },\n ],\n // What a self-mounted `AgentApiController` subclass injects, too (`routes: false`).\n exports: [DashboardService, DASHBOARD_APPROVAL_ACTOR_REF],\n };\n }\n}\n\n/**\n * The AI-gateway console: the SPA at `basePath` and its JSON API at `apiBasePath`. Who reaches them\n * is the app's — `decorators`, or the app mounts them itself (`routes: false`) — with\n * `@nestjs/authentication` / `@nestjs/authorization`.\n */\n@Module({})\nexport class AgentDashboardModule {\n static forRoot<TReq = unknown>(options: AgentDashboardOptions<TReq> = {}): DynamicModule {\n assertProtected(options as AgentDashboardOptions<unknown>);\n const basePath = normalize(options.basePath ?? '/ai-gateway');\n const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);\n return {\n module: AgentDashboardModule,\n imports: [\n ...(options.imports ?? []),\n // Spread-only-when-set: exactOptionalPropertyTypes rejects an explicit `undefined`.\n AgentApiModule.register({\n ...(options.imports ? { imports: options.imports } : {}),\n ...(options.decorators ? { decorators: options.decorators } : {}),\n ...(options.routes === false ? { routes: false } : {}),\n ...(options.approvalActorRef ? { approvalActorRef: options.approvalActorRef } : {}),\n }),\n ...(options.routes === false\n ? []\n : [\n RouterModule.register([\n { path: basePath, module: AgentDashboardModule },\n { path: apiBasePath, module: AgentApiModule },\n ]),\n ]),\n ],\n controllers:\n options.routes === false\n ? []\n : withControllerDecorators([AgentUiController], options.decorators),\n providers: [\n { provide: DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: DASHBOARD_API_PATH, useValue: apiBasePath },\n ],\n // Re-export the API module so its DashboardService reaches importers (e.g. the host's own\n // controllers), and the paths a self-mounted `AgentUiController` subclass injects.\n exports: [AgentApiModule, DASHBOARD_BASE_PATH, DASHBOARD_API_PATH],\n };\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { basename, extname, join, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { CanAnyone } from '@dudousxd/nestjs-agent-core';\nimport {\n Controller,\n Get,\n Header,\n Inject,\n NotFoundException,\n Param,\n Req,\n Res,\n StreamableFile,\n UseFilters,\n UseGuards,\n} from '@nestjs/common';\nimport { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH } from './tokens.js';\n\n/**\n * The slice of the express request the Inertia bounce below reads — structural, so the package\n * needs no express dependency (the API controller similarly takes `@Req() req: unknown`).\n */\ninterface UiPageRequest {\n headers: Record<string, string | string[] | undefined>;\n /** Path + query as received (express `originalUrl`) — what the full-page visit must reload. */\n originalUrl: string;\n}\n\n/** The slice of the express response the Inertia bounce writes (passthrough mode — Nest still sends). */\ninterface UiPageResponse {\n status(code: number): unknown;\n setHeader(name: string, value: string): unknown;\n}\n\n/** The base the SPA bundle was built with (Vite `base`); rewritten to the configured base at serve time. */\nconst BUILD_BASE = '/ai-gateway';\n\n/** dist/server/agent-ui.controller.js -> ../spa (the Vite build output). */\nfunction spaDir(): string {\n return fileURLToPath(new URL('../spa', import.meta.url));\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.json': 'application/json; charset=utf-8',\n '.woff2': 'font/woff2',\n '.ico': 'image/x-icon',\n};\n\n/**\n * Serves the bundled AI-gateway console SPA at the configured base (+ hashed assets at\n * `<base>/assets`). The path comes from `RouterModule` (set by\n * {@link AgentDashboardModule.forRoot}({ basePath })), so the controller routes are relative.\n *\n * Who reaches it is the app's: `decorators` on `AgentDashboardModule.forRoot`, or the app mounts it\n * itself (`routes: false`) — with `@nestjs/authentication` / `@nestjs/authorization`.\n */\n@CanAnyone()\n@Controller()\nexport class AgentUiController {\n private readonly dir = spaDir();\n\n constructor(\n @Inject(DASHBOARD_BASE_PATH) private readonly basePath: string,\n @Inject(DASHBOARD_API_PATH) private readonly apiBasePath: string,\n ) {}\n\n // index.html references hash-named bundles, so it MUST NOT be cached (stale bundle = the classic\n // \"stuck loading after a deploy\"). The hashed assets below are immutable.\n @Get()\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store, must-revalidate')\n index(@Req() req: UiPageRequest, @Res({ passthrough: true }) res: UiPageResponse): string {\n // An Inertia <Link> in the host app visits this page route as an XHR expecting an Inertia JSON\n // page object; serving the SPA's HTML instead lands it in the Inertia client's `srcdoc` error\n // modal, where the page's relative asset URLs die on CORS (origin null). The protocol's own\n // escape hatch for \"this URL is not an Inertia page\" is a 409 Conflict carrying\n // `X-Inertia-Location`: the client responds with a full `window.location` visit, which renders\n // the console normally. Only HTML page routes need this — asset binaries are never fetched with\n // the header.\n if (req.headers['x-inertia'] !== undefined) {\n res.status(409);\n res.setHeader('X-Inertia-Location', req.originalUrl);\n return '';\n }\n const indexPath = join(this.dir, 'index.html');\n if (!existsSync(indexPath)) {\n throw new NotFoundException('Dashboard is not built. Run the package build.');\n }\n // The bundle was built with Vite base `/ai-gateway/`; rewrite asset URLs to the configured base\n // so the SPA loads from `<base>/assets` wherever it's mounted, and tell the client its API base.\n const html = readFileSync(indexPath, 'utf8').replaceAll(\n `=\"${BUILD_BASE}/`,\n `=\"${this.basePath}/`,\n );\n // __AGENT_BASE__ = where assets load; __AGENT_API__ = where the SPA fetches the JSON API.\n const inject = `<script>window.__AGENT_BASE__='${this.basePath}';window.__AGENT_API__='${this.apiBasePath}';</script>`;\n return html.includes('</head>') ? html.replace('</head>', `${inject}</head>`) : inject + html;\n }\n\n @Get('assets/:file')\n @Header('Cache-Control', 'public, max-age=31536000, immutable')\n asset(@Param('file') file: string): StreamableFile {\n const safe = basename(file);\n if (safe !== file) throw new NotFoundException();\n const root = resolve(this.dir, 'assets');\n const assetPath = resolve(root, safe);\n if (!assetPath.startsWith(root + sep) || !existsSync(assetPath)) {\n throw new NotFoundException();\n }\n const type = CONTENT_TYPES[extname(safe)] ?? 'application/octet-stream';\n return new StreamableFile(readFileSync(assetPath), { type });\n }\n}\n"],"mappings":"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","names":["import_common","import_common","parseApprovalDecision","body","BadRequestException","approved","reason","undefined","import_common","parsePriceInput","body","BadRequestException","modelId","inputPricePer1m","outputPricePer1m","cacheWritePricePer1m","cacheReadPricePer1m","trim","length","isFiniteNonNegative","undefined","value","Number","isFinite","AGENT_GOVERNANCE_QUERIES","Symbol","for","AGENT_ACTOR_DIRECTORY","AGENT_PRICING_STORE","AGENT_APPROVAL_PORT","AGENT_ACTOR_RESOLVER","DASHBOARD_BASE_PATH","DASHBOARD_API_PATH","DASHBOARD_APPROVAL_ACTOR_REF","PRICING_STORE_UNBOUND_MESSAGE","APPROVAL_PORT_UNBOUND_MESSAGE","GENUI_STATS_UNSUPPORTED_MESSAGE","AGENT_EVENTS","isAgentEnvelope","message","event","DashboardService","queries","actorDirectory","pricingStore","approvalPort","spend","range","byModel","byActorRaw","trend","byFeature","Promise","all","spendByModel","spendByActor","usageTrend","spendByFeature","byActor","withActorLabels","undefined","topThreads","limit","rows","spendByThread","reliability","metrics","byAgent","errors","runMetrics","runsByAgent","runErrors","runTrend","recentRuns","recentToolCalls","toolCallsPage","query","runsPage","pendingApprovals","approvalsPage","runDetail","runId","detail","NotFoundException","thread","actorLabel","threadDetail","threadId","toolStats","genuiStats","NotImplementedException","decideApproval","toolCallId","body","executedByRef","decision","parseApprovalDecision","opts","approved","approve","reject","reason","recentThreads","threadsPage","page","length","map","row","refs","Set","actorRef","resolved","resolveDisplay","listPrices","listCurrentPrices","upsertPrice","upsertModelPrice","parsePriceInput","streamEvents","Observable","subscriber","bindings","name","channelName","handler","next","data","ts","Date","now","payload","subscribe","binding","unsubscribe","import_common","ISO_DAY","parsePageNumber","value","parsed","undefined","Number","NaN","parseInt","isFinite","assertKnownFields","raw","allowed","field","Object","keys","includes","BadRequestException","assertDayFields","dayFields","test","DAY_FIELDS","TOOL_CALL_WHERE_FIELDS","parseToolCallWhere","length","toolName","toolType","status","threadId","fromDay","toDay","THREAD_WHERE_FIELDS","parseThreadWhere","actorRef","title","RUN_WHERE_FIELDS","parseRunWhere","agentName","errorCode","APPROVAL_WHERE_FIELDS","parseApprovalWhere","DAY_MS","ISO_DAY","utcDay","daysAgo","Date","now","toISOString","slice","dayOr","value","fallback","undefined","test","resolveRange","from","to","fromDay","toDay","parseLimit","parsed","Number","NaN","parseInt","isFinite","Math","max","min","AgentApiController","dashboard","approvalActorRef","actorResolver","spend","topThreads","limit","reliability","runs","recentRuns","runsPage","page","where","parsedWhere","parseRunWhere","parsePageNumber","pageSize","runDetail","runId","toolCalls","recentToolCalls","toolCallsPage","parseToolCallWhere","approvals","pendingApprovals","approvalsPage","parseApprovalWhere","decideApproval","toolCallId","body","req","deciderRef","resolve","id","tools","toolStats","genui","genuiStats","threads","recentThreads","threadsPage","parseThreadWhere","threadDetail","threadId","messages","messageLimit","runLimit","listPrices","upsertPrice","stream","streamEvents","normalizeDashboardPath","path","replace","unprefixed","path","replace","agentDashboardMountPaths","options","basePath","normalizeDashboardPath","apiBasePath","base","api","import_nestjs_agent_core","import_common","import_nestjs_agent_core","import_common","BUILD_BASE","spaDir","fileURLToPath","URL","CONTENT_TYPES","AgentUiController","dir","basePath","apiBasePath","index","req","res","headers","undefined","status","setHeader","originalUrl","indexPath","join","existsSync","NotFoundException","html","readFileSync","replaceAll","inject","includes","replace","asset","file","safe","basename","root","resolve","assetPath","startsWith","sep","type","extname","StreamableFile","passthrough","normalize","path","normalizeDashboardPath","assertProtected","options","process","env","NODE_ENV","routes","decorators","length","Error","AgentApiModule","register","module","imports","controllers","withControllerDecorators","AgentApiController","providers","DashboardService","provide","DASHBOARD_APPROVAL_ACTOR_REF","useValue","approvalActorRef","exports","AgentDashboardModule","forRoot","basePath","apiBasePath","RouterModule","AgentUiController","DASHBOARD_BASE_PATH","DASHBOARD_API_PATH"]}
1
+ {"version":3,"sources":["../../src/server/index.ts","../../src/server/agent-api.controller.ts","../../src/server/dashboard.service.ts","../../src/server/parse-approval-decision.ts","../../src/server/parse-price-input.ts","../../src/server/tokens.ts","../../src/server/parse-page-query.ts","../../src/server/normalize-path.ts","../../src/server/agent-dashboard-mount-paths.ts","../../src/server/agent-dashboard.module.ts","../../src/server/agent-ui.controller.ts","../../src/server/console-authorize.guard.ts","../../src/server/console-authorize.ts"],"sourcesContent":["export * from './actor-directory.js';\nexport * from './agent-api.controller.js';\nexport * from './agent-dashboard-mount-paths.js';\nexport * from './agent-dashboard.module.js';\nexport * from './agent-ui.controller.js';\nexport {\n CONSOLE_PAGE_METADATA,\n ConsoleAccessDenied,\n ConsoleAuthorizeFilter,\n ConsoleAuthorizeGuard,\n ConsolePage,\n DashboardConsoleAccess,\n UseConsoleAuthorize,\n} from './console-authorize.guard.js';\nexport {\n CONSOLE_AUTHORIZE_MISSING_WARNING,\n CONSOLE_GATE,\n authorizeByToken,\n normalizeConsoleResult,\n resolveConsoleAuthorize,\n type ConsoleAuthorize,\n type ConsoleAuthorizeResult,\n type ConsoleAuthorizer,\n type ConsoleAuthorizerClass,\n type ConsoleDecide,\n type ConsoleDecision,\n type ConsoleGate,\n type ConsolePolicyCheck,\n} from './console-authorize.js';\nexport * from './dashboard.service.js';\nexport * from './parse-price-input.js';\nexport * from './tokens.js';\n","import { CanAnyone } from '@dudousxd/nestjs-agent-core';\nimport type {\n ActorResolver,\n CurrentModelPrice,\n GenuiStats,\n GovernancePage,\n PendingApprovalRow,\n RecentRunRow,\n ToolCallActivityRow,\n ToolStatRow,\n} from '@dudousxd/nestjs-agent-core';\nimport {\n Body,\n Controller,\n Get,\n HttpCode,\n Inject,\n Optional,\n Param,\n Post,\n Query,\n Req,\n Sse,\n} from '@nestjs/common';\nimport type { Observable } from 'rxjs';\nimport {\n DashboardService,\n type LiveAgentEvent,\n type ReliabilityOverview,\n type RunDetailWithLabel,\n type SpendOverview,\n type ThreadActivityRowWithLabel,\n type ThreadDetailWithLabel,\n type ThreadSpendRowWithLabel,\n} from './dashboard.service.js';\nimport {\n parseApprovalWhere,\n parsePageNumber,\n parseRunWhere,\n parseThreadWhere,\n parseToolCallWhere,\n} from './parse-page-query.js';\nimport { AGENT_ACTOR_RESOLVER, DASHBOARD_APPROVAL_ACTOR_REF } from './tokens.js';\n\nconst DAY_MS = 86_400_000;\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** A `YYYY-MM-DD` UTC day string `daysAgo` days before now (0 = today). */\nfunction utcDay(daysAgo: number): string {\n return new Date(Date.now() - daysAgo * DAY_MS).toISOString().slice(0, 10);\n}\n\n/** Accept a client-supplied `YYYY-MM-DD`, or fall back to `fallback`; guards against junk input. */\nfunction dayOr(value: string | undefined, fallback: string): string {\n return value !== undefined && ISO_DAY.test(value) ? value : fallback;\n}\n\n/** Resolve the `from`/`to` query params into a validated range, defaulting to the last 30 days. */\nfunction resolveRange(\n from: string | undefined,\n to: string | undefined,\n): {\n fromDay: string;\n toDay: string;\n} {\n return { fromDay: dayOr(from, utcDay(29)), toDay: dayOr(to, utcDay(0)) };\n}\n\n/** Parse a `limit` query param, clamped to a sane window; falls back to `fallback` when absent/junk. */\nfunction parseLimit(value: string | undefined, fallback: number): number {\n const parsed = value === undefined ? Number.NaN : Number.parseInt(value, 10);\n if (!Number.isFinite(parsed)) return fallback;\n return Math.max(1, Math.min(200, parsed));\n}\n\n/**\n * JSON + SSE API consumed by the AI-gateway console SPA. Mounted at `apiBasePath` (set by\n * `RouterModule` in {@link AgentDashboardModule.forRoot}), so the controller routes are relative.\n *\n * Who reaches it: `authorize` on `AgentDashboardModule.forRoot` (checked by `ConsoleAuthorizeGuard`,\n * after the app's `decorators`). Mounted by the app itself (`routes: false`), apply\n * `UseConsoleAuthorize()` to your subclass.\n */\n@CanAnyone()\n@Controller()\nexport class AgentApiController {\n constructor(\n private readonly dashboard: DashboardService,\n // `useValue` even when the host omitted `approvalActorRef` — no `@Optional()` needed (the token\n // is always bound; see DASHBOARD_APPROVAL_ACTOR_REF's TSDoc).\n @Inject(DASHBOARD_APPROVAL_ACTOR_REF)\n private readonly approvalActorRef: ((req: unknown) => string | undefined) | undefined,\n // The app's identity seam, bound + exported by the (global) AgentModule — the DEFAULT decider\n // attribution when no `approvalActorRef` override is configured. Genuinely `@Optional()`: the\n // dashboard can be mounted without AgentModule (read-model only), where nothing binds it.\n @Optional()\n @Inject(AGENT_ACTOR_RESOLVER)\n private readonly actorResolver?: ActorResolver,\n ) {}\n\n /** `{ byModel, byActor, trend }` for a day range (defaults to the last 30 days). */\n @Get('spend')\n spend(@Query('from') from?: string, @Query('to') to?: string): Promise<SpendOverview> {\n return this.dashboard.spend(resolveRange(from, to));\n }\n\n /** Top threads by cost (default 10, max 200) for a day range (defaults to the last 30 days). */\n @Get('top-threads')\n topThreads(\n @Query('from') from?: string,\n @Query('to') to?: string,\n @Query('limit') limit?: string,\n ): Promise<ThreadSpendRowWithLabel[]> {\n return this.dashboard.topThreads(resolveRange(from, to), parseLimit(limit, 10));\n }\n\n /** `{ metrics, byAgent, errors, trend }` for a day range (defaults to the last 30 days). */\n @Get('reliability')\n reliability(\n @Query('from') from?: string,\n @Query('to') to?: string,\n ): Promise<ReliabilityOverview> {\n return this.dashboard.reliability(resolveRange(from, to));\n }\n\n /** Most recent runs (default 50, max 200) for the Reliability recent-runs table. */\n @Get('runs')\n runs(@Query('limit') limit?: string): Promise<RecentRunRow[]> {\n return this.dashboard.recentRuns(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable runs for the Reliability recent-runs table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[agentName]`/`where[status]`/`where[errorCode]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('runs-page')\n runsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<RecentRunRow>> {\n const parsedWhere = parseRunWhere(where);\n return this.dashboard.runsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * One run drill-down: the run, its owning thread's headline (actor-labeled) and its tool calls, in\n * ONE round trip. 404s on an unknown run id.\n *\n * Declared AFTER `runs-page` so the literal route wins over this parameterised one regardless of\n * how the router orders same-prefix paths.\n */\n @Get('runs/:runId')\n runDetail(@Param('runId') runId: string): Promise<RunDetailWithLabel> {\n return this.dashboard.runDetail(runId);\n }\n\n /** Most recent tool calls (default 50, max 200) for the activity feed. */\n @Get('tool-calls')\n toolCalls(@Query('limit') limit?: string): Promise<ToolCallActivityRow[]> {\n return this.dashboard.recentToolCalls(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable tool calls for the Runs & tools activity table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[toolName]`/`where[toolType]`/`where[status]`/`where[threadId]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('tool-calls-page')\n toolCallsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<ToolCallActivityRow>> {\n const parsedWhere = parseToolCallWhere(where);\n return this.dashboard.toolCallsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox.\n *\n * Capped with NO total, so a backlog past `limit` is invisible from the response alone. Kept\n * because the SPA still calls it; anything new should call `approvals-page` instead.\n */\n @Get('approvals')\n approvals(@Query('limit') limit?: string): Promise<PendingApprovalRow[]> {\n return this.dashboard.pendingApprovals(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable approvals inbox, oldest first. `page` (default 1), `limit` (default 25, max\n * 200), and `where[toolName]`/`where[threadId]`/`where[actorRef]`/`where[agentName]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n *\n * Unlike `GET approvals` this reports `total`, so the console can say \"showing N of M\" instead of\n * silently hiding pending decisions behind a row cap.\n */\n @Get('approvals-page')\n approvalsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<PendingApprovalRow>> {\n const parsedWhere = parseApprovalWhere(where);\n return this.dashboard.approvalsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * Decide a pending HITL tool call. Body `{ approved: boolean; reason?: string }`. 501s (via\n * `DashboardService.decideApproval`) when no `AGENT_APPROVAL_PORT` is bound. `executedByRef`\n * comes from {@link deciderRef} run against the live request.\n */\n @Post('approvals/:toolCallId')\n @HttpCode(204)\n async decideApproval(\n @Param('toolCallId') toolCallId: string,\n @Body() body: unknown,\n @Req() req: unknown,\n ): Promise<void> {\n await this.dashboard.decideApproval(toolCallId, body, await this.deciderRef(req));\n }\n\n /**\n * WHO decided, as an opaque ref: an explicit `approvalActorRef` override wins outright (no\n * resolver fallback, even when it returns `undefined`); otherwise the AgentModule-configured\n * actor resolver — the same identity seam chat requests use. A resolver that throws is an\n * unauthenticated/unreadable request, not an error to surface: the decision itself was already\n * authorized by the dashboard's guards, so the ref is simply omitted.\n */\n private async deciderRef(req: unknown): Promise<string | undefined> {\n if (this.approvalActorRef !== undefined) {\n return this.approvalActorRef(req);\n }\n if (this.actorResolver === undefined) {\n return undefined;\n }\n try {\n return (await this.actorResolver.resolve(req)).id;\n } catch {\n return undefined;\n }\n }\n\n /** Per-tool call/failure/rejection/latency rollup for a day range (defaults to the last 30 days). */\n @Get('tools')\n tools(@Query('from') from?: string, @Query('to') to?: string): Promise<ToolStatRow[]> {\n return this.dashboard.toolStats(resolveRange(from, to));\n }\n\n /**\n * Generative-UI telemetry for a day range (defaults to the last 30 days): `{ totals, byToolModel,\n * errors, byDay, costPerTurn, latency }`. 501s (via `DashboardService.genuiStats`) when the bound\n * store does not implement `genuiStats`.\n */\n @Get('genui')\n genui(@Query('from') from?: string, @Query('to') to?: string): Promise<GenuiStats> {\n return this.dashboard.genuiStats(resolveRange(from, to));\n }\n\n /** Most recent threads (default 50, max 200) with rolled-up counts. */\n @Get('threads')\n threads(@Query('limit') limit?: string): Promise<ThreadActivityRowWithLabel[]> {\n return this.dashboard.recentThreads(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable threads for the Runs & tools threads table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[actorRef]`/`where[title]` (case-insensitive substring)/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('threads-page')\n threadsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<ThreadActivityRowWithLabel>> {\n const parsedWhere = parseThreadWhere(where);\n return this.dashboard.threadsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * One thread drill-down: the thread (actor-labeled), its lifetime token/cost rollup, its newest\n * runs and its newest messages, in ONE round trip. `messages` (default 50, max 200) and `runs`\n * (default 25, max 200) cap the two lists; `runTotal` and `thread.messageCount` report the true\n * sizes so the console can say what the caps hid. 404s on an unknown thread id.\n *\n * A soft-deleted thread is still returned, flagged `deleted: true` — an audit needs the thread it\n * just lost, and the alternative is a drill-down that 404s on exactly the interesting case.\n */\n @Get('threads/:threadId')\n threadDetail(\n @Param('threadId') threadId: string,\n @Query('messages') messages?: string,\n @Query('runs') runs?: string,\n ): Promise<ThreadDetailWithLabel> {\n return this.dashboard.threadDetail({\n threadId,\n messageLimit: parseLimit(messages, 50),\n runLimit: parseLimit(runs, 25),\n });\n }\n\n /**\n * Current price row per model, for the pricing tab. 501s (via `DashboardService.listPrices`) when\n * no `AGENT_PRICING_STORE` is bound.\n */\n @Get('pricing')\n listPrices(): Promise<CurrentModelPrice[]> {\n return this.dashboard.listPrices();\n }\n\n /**\n * Set a model's current price. Body shape mirrors core's `ModelPriceInput`\n * (`{ modelId, inputPricePer1m, outputPricePer1m, cacheWritePricePer1m?, cacheReadPricePer1m? }`).\n * 501s when no `AGENT_PRICING_STORE` is bound; 400s on a malformed body.\n */\n @Post('pricing')\n upsertPrice(@Body() body: unknown): Promise<void> {\n return this.dashboard.upsertPrice(body);\n }\n\n /** Server-Sent Events stream of live `aviary:agent:*` events — the Live feed tails it. */\n @Sse('stream')\n stream(): Observable<{ data: LiveAgentEvent }> {\n return this.dashboard.streamEvents();\n }\n}\n","import { subscribe, unsubscribe } from 'node:diagnostics_channel';\nimport type {\n ActorSpendRow,\n AgentApprovalPort,\n AgentDiagnosticEvent,\n AgentGovernanceQueries,\n AgentPricingStore,\n ApprovalWhere,\n CurrentModelPrice,\n FeatureSpendRow,\n GenuiStats,\n GovernancePage,\n GovernancePageQuery,\n GovernanceRange,\n GovernanceRunDetail,\n GovernanceThreadDetail,\n GovernanceThreadDetailQuery,\n ModelSpendRow,\n PendingApprovalRow,\n RecentRunRow,\n RunAgentBreakdownRow,\n RunErrorBreakdownRow,\n RunMetrics,\n RunTrendPoint,\n RunWhere,\n ThreadActivityRow,\n ThreadSpendRow,\n ThreadWhere,\n ToolCallActivityRow,\n ToolCallWhere,\n ToolStatRow,\n UsageTrendPoint,\n} from '@dudousxd/nestjs-agent-core';\nimport { channelName } from '@dudousxd/nestjs-diagnostics';\nimport {\n Inject,\n Injectable,\n NotFoundException,\n NotImplementedException,\n Optional,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport type { ActorDirectory } from './actor-directory.js';\nimport { parseApprovalDecision } from './parse-approval-decision.js';\nimport { parsePriceInput } from './parse-price-input.js';\nimport {\n AGENT_ACTOR_DIRECTORY,\n AGENT_APPROVAL_PORT,\n AGENT_GOVERNANCE_QUERIES,\n AGENT_PRICING_STORE,\n} from './tokens.js';\n\n/** An actor-scoped row decorated with its resolved display label — `null` when unbound or unresolved. */\nexport interface WithActorLabel {\n actorLabel: string | null;\n}\n\nexport type ActorSpendRowWithLabel = ActorSpendRow & WithActorLabel;\nexport type ThreadSpendRowWithLabel = ThreadSpendRow & WithActorLabel;\nexport type ThreadActivityRowWithLabel = ThreadActivityRow & WithActorLabel;\n\n/**\n * `GET <api>/runs/:runId` — a run drill-down with the owning thread's `actorLabel` resolved, the\n * same way {@link threadsPage} labels its rows. The run row itself stays unlabeled, matching every\n * other run surface in this console.\n */\nexport type RunDetailWithLabel = Omit<GovernanceRunDetail, 'thread'> & {\n thread: GovernanceRunDetail['thread'] & WithActorLabel;\n};\n\n/** `GET <api>/threads/:threadId` — a thread drill-down, actor-labeled like {@link threadsPage}. */\nexport type ThreadDetailWithLabel = Omit<GovernanceThreadDetail, 'thread'> & {\n thread: ThreadActivityRowWithLabel;\n};\n\n/** The spend/usage overview the SPA renders on its headline section (`GET <api>/spend`). */\nexport interface SpendOverview {\n byModel: ModelSpendRow[];\n byActor: ActorSpendRowWithLabel[];\n trend: UsageTrendPoint[];\n /**\n * Spend per feature — agent turns and usage recorded outside them side by side. Absent when the\n * bound governance adapter has no `spendByFeature`.\n */\n byFeature?: FeatureSpendRow[];\n}\n\n/** The run-reliability overview the SPA renders on its Reliability section (`GET <api>/reliability`). */\nexport interface ReliabilityOverview {\n metrics: RunMetrics;\n byAgent: RunAgentBreakdownRow[];\n errors: RunErrorBreakdownRow[];\n trend: RunTrendPoint[];\n}\n\n/** The message returned as a 501 when no `AGENT_PRICING_STORE` is bound. */\nconst PRICING_STORE_UNBOUND_MESSAGE =\n 'Pricing CRUD is unavailable: no AGENT_PRICING_STORE is bound. Bind a pricing store (e.g. ' +\n 'MikroOrmPricingStore from @dudousxd/nestjs-agent-store-mikro-orm) to enable it.';\n\n/** The message returned as a 501 when no `AGENT_APPROVAL_PORT` is bound. */\nconst APPROVAL_PORT_UNBOUND_MESSAGE =\n 'Approve/reject is unavailable: no AGENT_APPROVAL_PORT is bound. Import AgentModule from ' +\n '@dudousxd/nestjs-agent alongside this dashboard to enable it — the approvals inbox stays ' +\n 'read-only until then.';\n\n/** The message returned as a 501 when the bound read-model does not implement `genuiStats`. */\nconst GENUI_STATS_UNSUPPORTED_MESSAGE =\n 'Generative UI telemetry is unavailable: the bound AGENT_GOVERNANCE_QUERIES store does not ' +\n 'implement genuiStats. Use a store adapter that records generative-UI calls (recordGenuiCalls) ' +\n 'to enable it.';\n\n/** One live agent event forwarded over SSE, flattened from the `aviary:agent:*` diagnostics envelope. */\nexport interface LiveAgentEvent {\n /** The event name, e.g. `run.started` / `tool-call` / `quota.exceeded`. */\n event: string;\n /** Epoch millis the event was emitted. */\n ts: number;\n /** The library-defined payload (see the `Agent*Event` shapes in core's diagnostics). */\n payload: Record<string, unknown>;\n}\n\n/**\n * The `aviary:agent:*` events the Live feed tails — a curated subset of core's\n * `AGENT_DIAGNOSTIC_EVENTS`, typed against it so a renamed event is a compile error here. Every run\n * TERMINAL is in it (finished, failed, cancelled): a feed that showed runs starting but never failing\n * would read as \"all fine\" exactly when it is not. The per-turn bookkeeping events (skills, memory,\n * retrieval, tool retries) are left to the telescope.\n */\nconst AGENT_EVENTS: readonly AgentDiagnosticEvent[] = [\n 'run.started',\n 'message',\n 'tool-call',\n 'quota.exceeded',\n 'run.finished',\n 'run.failed',\n 'run.cancelled',\n 'delegated',\n];\n\n/** The `node:diagnostics_channel` envelope `emit()` publishes (see `@dudousxd/nestjs-diagnostics`). */\ninterface AgentDiagnosticEnvelope {\n event: string;\n ts?: number;\n payload?: Record<string, unknown>;\n}\n\n/** Narrow the untyped diagnostics-channel message to the envelope we forward. */\nfunction isAgentEnvelope(message: unknown): message is AgentDiagnosticEnvelope {\n return (\n typeof message === 'object' &&\n message !== null &&\n 'event' in message &&\n typeof (message as { event: unknown }).event === 'string'\n );\n}\n\n/**\n * Read-model + live bridge backing the AI-gateway console.\n *\n * - Historical, restart-surviving spend/usage/threads come from the injected\n * {@link AGENT_GOVERNANCE_QUERIES} read-model (backed by a store adapter). The host must provide\n * that token — bind it via your `@dudousxd/nestjs-agent` module (global) alongside this dashboard.\n * - Live activity comes off the `aviary:agent:*` diagnostics channel, subscribed per SSE client and\n * unsubscribed when the client disconnects.\n * - `actorLabel` on actor-scoped rows comes from the OPTIONAL {@link AGENT_ACTOR_DIRECTORY} — `null`\n * on every row when nothing is bound, so the console degrades to raw `actorRef`s instead of failing.\n * - Pricing CRUD (`listPrices`/`upsertPrice`) reads/writes the OPTIONAL {@link AGENT_PRICING_STORE} —\n * a 501 with a clear message when nothing is bound.\n */\n@Injectable()\nexport class DashboardService {\n constructor(\n @Inject(AGENT_GOVERNANCE_QUERIES) private readonly queries: AgentGovernanceQueries,\n @Optional()\n @Inject(AGENT_ACTOR_DIRECTORY)\n private readonly actorDirectory?: ActorDirectory,\n @Optional()\n @Inject(AGENT_PRICING_STORE)\n private readonly pricingStore?: AgentPricingStore,\n @Optional()\n @Inject(AGENT_APPROVAL_PORT)\n private readonly approvalPort?: AgentApprovalPort,\n ) {}\n\n /** Spend/usage overview for a day range: by-model + by-actor spend and the daily trend, in parallel. */\n async spend(range: GovernanceRange): Promise<SpendOverview> {\n const [byModel, byActorRaw, trend, byFeature] = await Promise.all([\n this.queries.spendByModel(range),\n this.queries.spendByActor(range),\n this.queries.usageTrend(range),\n this.queries.spendByFeature?.(range),\n ]);\n const byActor = await this.withActorLabels(byActorRaw);\n return { byModel, byActor, trend, ...(byFeature !== undefined ? { byFeature } : {}) };\n }\n\n /** Top threads by cost for a day range (default 10, highest cost first). */\n async topThreads(range: GovernanceRange, limit = 10): Promise<ThreadSpendRowWithLabel[]> {\n const rows = await this.queries.spendByThread(range, limit);\n return this.withActorLabels(rows);\n }\n\n /** Run reliability for a day range: metrics, by-agent/by-error breakdowns and the trend, in parallel. */\n async reliability(range: GovernanceRange): Promise<ReliabilityOverview> {\n const [metrics, byAgent, errors, trend] = await Promise.all([\n this.queries.runMetrics(range),\n this.queries.runsByAgent(range),\n this.queries.runErrors(range),\n this.queries.runTrend(range),\n ]);\n return { metrics, byAgent, errors, trend };\n }\n\n /** Most recent runs (status/agent/duration/error) for the Reliability recent-runs table. */\n recentRuns(limit: number): Promise<RecentRunRow[]> {\n return this.queries.recentRuns(limit);\n }\n\n /** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */\n recentToolCalls(limit: number): Promise<ToolCallActivityRow[]> {\n return this.queries.recentToolCalls(limit);\n }\n\n /** Paged, filterable tool calls for the Runs & tools activity table. */\n toolCallsPage(\n query: GovernancePageQuery<ToolCallWhere>,\n ): Promise<GovernancePage<ToolCallActivityRow>> {\n return this.queries.toolCallsPage(query);\n }\n\n /** Paged, filterable runs for the Reliability recent-runs table. */\n runsPage(query: GovernancePageQuery<RunWhere>): Promise<GovernancePage<RecentRunRow>> {\n return this.queries.runsPage(query);\n }\n\n /**\n * Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox.\n * Capped and totalless — kept because the SPA still calls `GET approvals`; new callers want\n * {@link approvalsPage}, which reports how much of the backlog the cap hid.\n */\n pendingApprovals(limit: number): Promise<PendingApprovalRow[]> {\n return this.queries.pendingApprovals(limit);\n }\n\n /** Paged, filterable approvals inbox with a total — the backlog-safe read of the HITL queue. */\n approvalsPage(\n query: GovernancePageQuery<ApprovalWhere>,\n ): Promise<GovernancePage<PendingApprovalRow>> {\n return this.queries.approvalsPage(query);\n }\n\n /**\n * One run with its thread and its tool calls. 404s on an unknown id rather than returning an empty\n * detail: \"this run does not exist\" and \"this run did nothing\" are different answers, and a console\n * that conflates them sends an operator looking for a bug that isn't there.\n */\n async runDetail(runId: string): Promise<RunDetailWithLabel> {\n const detail = await this.queries.runDetail(runId);\n if (detail === null) {\n throw new NotFoundException(`No run \"${runId}\".`);\n }\n const [thread] = await this.withActorLabels([detail.thread]);\n // `withActorLabels` returns one row per input; the array is never empty here.\n return { ...detail, thread: thread ?? { ...detail.thread, actorLabel: null } };\n }\n\n /** One thread with its usage rollup, runs and messages. 404s on an unknown id, like {@link runDetail}. */\n async threadDetail(query: GovernanceThreadDetailQuery): Promise<ThreadDetailWithLabel> {\n const detail = await this.queries.threadDetail(query);\n if (detail === null) {\n throw new NotFoundException(`No thread \"${query.threadId}\".`);\n }\n const [thread] = await this.withActorLabels([detail.thread]);\n return { ...detail, thread: thread ?? { ...detail.thread, actorLabel: null } };\n }\n\n /** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */\n toolStats(range: GovernanceRange): Promise<ToolStatRow[]> {\n return this.queries.toolStats(range);\n }\n\n /**\n * Generative-UI telemetry for a day range — first-try success per tool/model, common errors, UI\n * cost per day/turn and time-to-frame. `genuiStats` is OPTIONAL on the read-model (a store that\n * does not record UI calls leaves it out), so an unsupported store 501s with a clear message, the\n * same posture as an unbound pricing store — the console renders that as its own state.\n */\n async genuiStats(range: GovernanceRange): Promise<GenuiStats> {\n if (this.queries.genuiStats === undefined) {\n throw new NotImplementedException(GENUI_STATS_UNSUPPORTED_MESSAGE);\n }\n return this.queries.genuiStats(range);\n }\n\n /**\n * Decide a pending HITL tool call from the console. Routes through the OPTIONAL\n * {@link AGENT_APPROVAL_PORT} — bound by `@dudousxd/nestjs-agent` to the SAME signal path chat\n * approvals use — so a 501 here (checked BEFORE body validation, same ordering as\n * {@link upsertPrice}) means \"no approval port bound\", not \"your body was invalid\". `executedByRef`\n * is an OPAQUE decider ref the caller resolved from the live request (see\n * `AgentDashboardOptions.approvalActorRef`); omitted when the host didn't configure one.\n */\n async decideApproval(\n toolCallId: string,\n body: unknown,\n executedByRef: string | undefined,\n ): Promise<void> {\n if (this.approvalPort === undefined) {\n throw new NotImplementedException(APPROVAL_PORT_UNBOUND_MESSAGE);\n }\n const decision = parseApprovalDecision(body);\n const opts = executedByRef !== undefined ? { executedByRef } : {};\n if (decision.approved) {\n await this.approvalPort.approve(toolCallId, opts);\n return;\n }\n await this.approvalPort.reject(toolCallId, {\n ...opts,\n ...(decision.reason !== undefined ? { reason: decision.reason } : {}),\n });\n }\n\n /** Most recent threads with rolled-up message/token counts. */\n async recentThreads(limit: number): Promise<ThreadActivityRowWithLabel[]> {\n const rows = await this.queries.recentThreads(limit);\n return this.withActorLabels(rows);\n }\n\n /**\n * Paged, filterable threads for the Runs & tools threads table — actor-labeled the same way\n * {@link recentThreads} is.\n */\n async threadsPage(\n query: GovernancePageQuery<ThreadWhere>,\n ): Promise<GovernancePage<ThreadActivityRowWithLabel>> {\n const page = await this.queries.threadsPage(query);\n const rows = await this.withActorLabels(page.rows);\n return { ...page, rows };\n }\n\n /**\n * Decorate actor-scoped rows with `actorLabel`, batching the distinct `actorRef`s into ONE\n * {@link ActorDirectory.resolveDisplay} call per response. `null` for every row when no directory\n * is bound, or for a ref the directory didn't resolve.\n */\n private async withActorLabels<Row extends { actorRef: string }>(\n rows: Row[],\n ): Promise<(Row & WithActorLabel)[]> {\n if (rows.length === 0) {\n return [];\n }\n if (this.actorDirectory === undefined) {\n return rows.map((row) => ({ ...row, actorLabel: null }));\n }\n const refs = [...new Set(rows.map((row) => row.actorRef))];\n const resolved = await this.actorDirectory.resolveDisplay(refs);\n return rows.map((row) => ({ ...row, actorLabel: resolved[row.actorRef] ?? null }));\n }\n\n /** Current price row per model, for the pricing tab. 501s when no `AGENT_PRICING_STORE` is bound. */\n async listPrices(): Promise<CurrentModelPrice[]> {\n if (this.pricingStore === undefined) {\n throw new NotImplementedException(PRICING_STORE_UNBOUND_MESSAGE);\n }\n return this.pricingStore.listCurrentPrices();\n }\n\n /**\n * Set a model's current price (`POST <api>/pricing` body). 501s when no `AGENT_PRICING_STORE` is\n * bound (checked BEFORE body validation, so an unbound store always reports as unimplemented rather\n * than as a validation error); otherwise the body is minimally validated via {@link parsePriceInput}.\n */\n async upsertPrice(body: unknown): Promise<void> {\n if (this.pricingStore === undefined) {\n throw new NotImplementedException(PRICING_STORE_UNBOUND_MESSAGE);\n }\n await this.pricingStore.upsertModelPrice(parsePriceInput(body));\n }\n\n /**\n * Live SSE stream of `aviary:agent:*` diagnostics events. One subscription per SSE client:\n * subscribing wires a handler onto each agent channel; the returned teardown removes them all when\n * the client disconnects (or the observable is otherwise unsubscribed).\n */\n streamEvents(): Observable<{ data: LiveAgentEvent }> {\n return new Observable<{ data: LiveAgentEvent }>((subscriber) => {\n const bindings = AGENT_EVENTS.map((event) => {\n const name = channelName('agent', event);\n const handler = (message: unknown): void => {\n if (!isAgentEnvelope(message)) return;\n subscriber.next({\n data: {\n event: message.event,\n ts: message.ts ?? Date.now(),\n payload: message.payload ?? {},\n },\n });\n };\n subscribe(name, handler);\n return { name, handler };\n });\n return () => {\n for (const binding of bindings) unsubscribe(binding.name, binding.handler);\n };\n });\n }\n}\n","import { BadRequestException } from '@nestjs/common';\n\n/** The parsed, minimally-validated `POST <api>/approvals/:toolCallId` body. */\nexport interface ApprovalDecisionInput {\n approved: boolean;\n reason?: string;\n}\n\n/**\n * Minimal shape guard for a `POST <api>/approvals/:toolCallId` body — rejects junk before it reaches\n * `AgentApprovalPort.approve`/`reject`. Mirrors `parsePriceInput`'s role for the pricing endpoint.\n */\nexport function parseApprovalDecision(body: unknown): ApprovalDecisionInput {\n if (typeof body !== 'object' || body === null) {\n throw new BadRequestException('Expected a JSON object body.');\n }\n const { approved, reason } = body as Record<string, unknown>;\n\n if (typeof approved !== 'boolean') {\n throw new BadRequestException('\"approved\" must be a boolean.');\n }\n if (reason !== undefined && typeof reason !== 'string') {\n throw new BadRequestException('\"reason\" must be a string when present.');\n }\n\n return { approved, ...(reason !== undefined ? { reason } : {}) };\n}\n","import type { ModelPriceInput } from '@dudousxd/nestjs-agent-core';\nimport { BadRequestException } from '@nestjs/common';\n\n/**\n * Minimal shape guard for a `POST <api>/pricing` body — rejects junk before it reaches\n * `AgentPricingStore.upsertModelPrice`. Not a full schema validator (the store adapter owns real\n * constraints, e.g. uniqueness); this only checks the wire shape core's `ModelPriceInput` requires.\n */\nexport function parsePriceInput(body: unknown): ModelPriceInput {\n if (typeof body !== 'object' || body === null) {\n throw new BadRequestException('Expected a JSON object body.');\n }\n const { modelId, inputPricePer1m, outputPricePer1m, cacheWritePricePer1m, cacheReadPricePer1m } =\n body as Record<string, unknown>;\n\n if (typeof modelId !== 'string' || modelId.trim().length === 0) {\n throw new BadRequestException('\"modelId\" must be a non-empty string.');\n }\n if (!isFiniteNonNegative(inputPricePer1m)) {\n throw new BadRequestException('\"inputPricePer1m\" must be a non-negative number.');\n }\n if (!isFiniteNonNegative(outputPricePer1m)) {\n throw new BadRequestException('\"outputPricePer1m\" must be a non-negative number.');\n }\n if (cacheWritePricePer1m !== undefined && !isFiniteNonNegative(cacheWritePricePer1m)) {\n throw new BadRequestException(\n '\"cacheWritePricePer1m\" must be a non-negative number when present.',\n );\n }\n if (cacheReadPricePer1m !== undefined && !isFiniteNonNegative(cacheReadPricePer1m)) {\n throw new BadRequestException(\n '\"cacheReadPricePer1m\" must be a non-negative number when present.',\n );\n }\n\n return {\n modelId,\n inputPricePer1m,\n outputPricePer1m,\n ...(cacheWritePricePer1m !== undefined ? { cacheWritePricePer1m } : {}),\n ...(cacheReadPricePer1m !== undefined ? { cacheReadPricePer1m } : {}),\n };\n}\n\nfunction isFiniteNonNegative(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0;\n}\n","/**\n * DI tokens for the standalone AI-gateway dashboard.\n *\n * All use `Symbol.for(...)` (the global symbol registry) on purpose: pnpm peer multiplexing + dual\n * ESM/CJS can load a package more than once, and a plain `Symbol()` would mint a distinct token per\n * copy and break DI across the ESM/CJS split. A registered symbol collapses every copy onto the same\n * token.\n */\n\n/**\n * The governance read-model, owned by `@dudousxd/nestjs-agent-core`. We re-declare it here BY VALUE\n * (not by import) so DI does not depend on a runtime value-import of core resolving — `Symbol.for`\n * with the identical key resolves to the SAME symbol instance as core's own\n * `packages/core/src/tokens.ts` export. The key MUST stay byte-identical with that export.\n */\nexport const AGENT_GOVERNANCE_QUERIES = Symbol.for('@dudousxd/nestjs-agent:governance-queries');\n\n/**\n * Optional actor→label resolver (see {@link ActorDirectory} in `./actor-directory.js`), owned by\n * `@dudousxd/nestjs-agent-core`. Re-declared here BY VALUE for the same reason as\n * {@link AGENT_GOVERNANCE_QUERIES} above — the key MUST stay byte-identical with core's own\n * `AGENT_ACTOR_DIRECTORY` export so both copies collapse onto the same registered symbol. Optional:\n * the dashboard works with actorRef-only rows when nothing is bound.\n */\nexport const AGENT_ACTOR_DIRECTORY = Symbol.for('@dudousxd/nestjs-agent:actor-directory');\n\n/**\n * The pricing WRITE side (`AgentPricingStore`), owned by `@dudousxd/nestjs-agent-core`. Re-declared\n * here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional: the pricing\n * tab/endpoints 501 with a clear message when nothing is bound.\n */\nexport const AGENT_PRICING_STORE = Symbol.for('@dudousxd/nestjs-agent:pricing-store');\n\n/**\n * Console-side HITL decisions (`AgentApprovalPort`), owned by `@dudousxd/nestjs-agent-core`.\n * Re-declared here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional:\n * bound by `@dudousxd/nestjs-agent` (which adapts it to the same signal path chat approvals use) —\n * absent, `POST <api>/approvals/:toolCallId` 501s and the approvals inbox renders read-only.\n */\nexport const AGENT_APPROVAL_PORT = Symbol.for('@dudousxd/nestjs-agent:approval-port');\n\n/**\n * The app's request-identity seam (`ActorResolver`), owned by `@dudousxd/nestjs-agent-core` and\n * bound + exported by the (global) `AgentModule` from the host's `actorResolver` option. Re-declared\n * here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional: the default\n * decider attribution for `POST <api>/approvals/:toolCallId` — absent (or throwing, i.e.\n * unauthenticated), `executedByRef` is simply omitted.\n */\nexport const AGENT_ACTOR_RESOLVER = Symbol.for('@dudousxd/nestjs-agent:actor-resolver');\n\n/** DI token carrying the UI mount base (e.g. `/ai-gateway`). */\nexport const DASHBOARD_BASE_PATH = Symbol.for('@dudousxd/nestjs-agent-dashboard:base-path');\n\n/** DI token carrying the JSON API base the SPA fetches from (e.g. `/ai-gateway/api`). */\nexport const DASHBOARD_API_PATH = Symbol.for('@dudousxd/nestjs-agent-dashboard:api-path');\n\n/**\n * DI token carrying the host-provided {@link AgentDashboardOptions.approvalActorRef} extractor (or\n * `undefined` when the host didn't set one — the controller then falls back to\n * {@link AGENT_ACTOR_RESOLVER} for decider attribution). Threaded to `AgentApiModule` (where the API\n * controller actually lives) same as the pattern above — `useValue` even when `undefined`, so\n * injecting it needs no `@Optional()`.\n */\nexport const DASHBOARD_APPROVAL_ACTOR_REF = Symbol.for(\n '@dudousxd/nestjs-agent-dashboard:approval-actor-ref',\n);\n\n/** DI token carrying the console's `authorize` (and whether to warn when it is omitted). */\nexport const DASHBOARD_CONSOLE_OPTIONS = Symbol.for(\n '@dudousxd/nestjs-agent-dashboard:console-options',\n);\n","import type {\n ApprovalWhere,\n RunWhere,\n ThreadWhere,\n ToolCallWhere,\n} from '@dudousxd/nestjs-agent-core';\nimport { BadRequestException } from '@nestjs/common';\n\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Parse a `page` query param (1-based); falls back to 1 when absent or not a positive integer. */\nexport function parsePageNumber(value: string | undefined): number {\n const parsed = value === undefined ? Number.NaN : Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 1) return 1;\n return parsed;\n}\n\n/** Rejects a `where[<field>]` whose field name isn't in `allowed`, naming the offending field. */\nfunction assertKnownFields(raw: Record<string, string>, allowed: readonly string[]): void {\n for (const field of Object.keys(raw)) {\n if (!allowed.includes(field)) {\n throw new BadRequestException(`Unknown where field \"${field}\".`);\n }\n }\n}\n\n/** Rejects a day-bound `where[<field>]` that isn't `YYYY-MM-DD`, mirroring the range endpoints' check. */\nfunction assertDayFields(raw: Record<string, string>, dayFields: readonly string[]): void {\n for (const field of dayFields) {\n const value = raw[field];\n if (value !== undefined && !ISO_DAY.test(value)) {\n throw new BadRequestException(`\"where[${field}]\" must be YYYY-MM-DD.`);\n }\n }\n}\n\nconst DAY_FIELDS = ['fromDay', 'toDay'] as const;\nconst TOOL_CALL_WHERE_FIELDS = [\n 'toolName',\n 'toolType',\n 'status',\n 'threadId',\n 'fromDay',\n 'toDay',\n] as const;\n\n/**\n * Parse `GET tool-calls-page`'s `where[...]` query params into a `ToolCallWhere`. `undefined` (no\n * `where` params at all) means no filter. Throws `BadRequestException` on an unknown field name or a\n * malformed day bound.\n */\nexport function parseToolCallWhere(\n raw: Record<string, string> | undefined,\n): ToolCallWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, TOOL_CALL_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.toolName !== undefined ? { toolName: raw.toolName } : {}),\n ...(raw.toolType !== undefined ? { toolType: raw.toolType } : {}),\n ...(raw.status !== undefined ? { status: raw.status } : {}),\n ...(raw.threadId !== undefined ? { threadId: raw.threadId } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\nconst THREAD_WHERE_FIELDS = ['actorRef', 'title', 'fromDay', 'toDay'] as const;\n\n/**\n * Parse `GET threads-page`'s `where[...]` query params into a `ThreadWhere`. `undefined` means no\n * filter. Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseThreadWhere(raw: Record<string, string> | undefined): ThreadWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, THREAD_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.actorRef !== undefined ? { actorRef: raw.actorRef } : {}),\n ...(raw.title !== undefined ? { title: raw.title } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\n// `threadId` was supported by every adapter's `RunWhere` but never accepted here, so \"show me this\n// thread's runs\" — the drill-down's most obvious follow-up query — 400'd with \"Unknown where field\".\nconst RUN_WHERE_FIELDS = [\n 'agentName',\n 'status',\n 'errorCode',\n 'threadId',\n 'fromDay',\n 'toDay',\n] as const;\n\n/**\n * Parse `GET runs-page`'s `where[...]` query params into a `RunWhere`. `undefined` means no filter.\n * Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseRunWhere(raw: Record<string, string> | undefined): RunWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, RUN_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.agentName !== undefined ? { agentName: raw.agentName } : {}),\n ...(raw.status !== undefined ? { status: raw.status } : {}),\n ...(raw.errorCode !== undefined ? { errorCode: raw.errorCode } : {}),\n ...(raw.threadId !== undefined ? { threadId: raw.threadId } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\nconst APPROVAL_WHERE_FIELDS = [\n 'toolName',\n 'threadId',\n 'actorRef',\n 'agentName',\n 'fromDay',\n 'toDay',\n] as const;\n\n/**\n * Parse `GET approvals-page`'s `where[...]` query params into an `ApprovalWhere`. `undefined` means\n * no filter. Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseApprovalWhere(\n raw: Record<string, string> | undefined,\n): ApprovalWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, APPROVAL_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.toolName !== undefined ? { toolName: raw.toolName } : {}),\n ...(raw.threadId !== undefined ? { threadId: raw.threadId } : {}),\n ...(raw.actorRef !== undefined ? { actorRef: raw.actorRef } : {}),\n ...(raw.agentName !== undefined ? { agentName: raw.agentName } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n","/**\n * Leading slash, no trailing slash (`'ai-gateway/'` -> `'/ai-gateway'`). Shared by\n * {@link AgentDashboardModule.forRoot} and {@link agentDashboardMountPaths} so the mount-path math\n * behind the module and the pure helper that mirrors it can never drift apart.\n */\nexport function normalizeDashboardPath(path: string): string {\n return `/${path.replace(/^\\/+|\\/+$/g, '')}`;\n}\n","import { normalizeDashboardPath } from './normalize-path.js';\n\n/** Same shape {@link AgentDashboardModule.forRoot} accepts — kept local so this stays a pure, DI-free helper. */\nexport interface AgentDashboardMountPathsOptions {\n basePath?: string;\n apiBasePath?: string;\n}\n\n/** Strip the leading slash `normalizeDashboardPath` adds — `setGlobalPrefix`'s `exclude` roots are unprefixed. */\nfunction unprefixed(path: string): string {\n return path.replace(/^\\/+/, '');\n}\n\n/**\n * Route roots a host must EXCLUDE from a global prefix (`setGlobalPrefix('api', { exclude })`) so\n * the AI-gateway dashboard's SPA and JSON API keep resolving at their configured mount paths instead\n * of being shifted under the prefix.\n *\n * Unlike a single-surface dashboard (e.g. `telescopeMountPaths()`), this one mounts TWO route roots —\n * the UI at `basePath` and its JSON API at `apiBasePath` — so excluding only one leaves the other\n * shadowed. `options` mirrors {@link AgentDashboardOptions} and resolves through the exact same\n * defaulting (`apiBasePath` falls back to `<basePath>/api`) as {@link AgentDashboardModule.forRoot},\n * so the excluded roots always agree with what actually got mounted.\n *\n * @example\n * ```ts\n * // Raw defaults (basePath `/ai-gateway`, apiBasePath `/ai-gateway/api`):\n * app.setGlobalPrefix('api', { exclude: agentDashboardMountPaths() });\n * // -> ['ai-gateway', 'ai-gateway/{*splat}', 'ai-gateway/api', 'ai-gateway/api/{*splat}']\n *\n * // The recommended pattern — apiBasePath nested under the app's own `/api` prefix — MUST pass the\n * // same options given to `forRoot(...)`:\n * const dashboardOptions = { apiBasePath: '/api/ai-gateway' };\n * app.setGlobalPrefix('api', { exclude: agentDashboardMountPaths(dashboardOptions) });\n * // -> ['ai-gateway', 'ai-gateway/{*splat}', 'api/ai-gateway', 'api/ai-gateway/{*splat}']\n * ```\n */\nexport function agentDashboardMountPaths(options?: AgentDashboardMountPathsOptions): string[] {\n const basePath = normalizeDashboardPath(options?.basePath ?? '/ai-gateway');\n const apiBasePath = normalizeDashboardPath(options?.apiBasePath ?? `${basePath}/api`);\n const base = unprefixed(basePath);\n const api = unprefixed(apiBasePath);\n return [base, `${base}/{*splat}`, api, `${api}/{*splat}`];\n}\n","import { withControllerDecorators } from '@dudousxd/nestjs-agent-core';\nimport 'reflect-metadata';\nimport { type DynamicModule, Module, UseFilters, UseGuards } from '@nestjs/common';\nimport { RouterModule } from '@nestjs/core';\nimport { AgentApiController } from './agent-api.controller.js';\nimport { AgentUiController } from './agent-ui.controller.js';\nimport {\n ConsoleAuthorizeFilter,\n ConsoleAuthorizeGuard,\n DashboardConsoleAccess,\n type DashboardConsoleOptions,\n} from './console-authorize.guard.js';\nimport type { ConsoleAuthorize } from './console-authorize.js';\nimport { DashboardService } from './dashboard.service.js';\nimport { normalizeDashboardPath } from './normalize-path.js';\nimport {\n DASHBOARD_API_PATH,\n DASHBOARD_APPROVAL_ACTOR_REF,\n DASHBOARD_BASE_PATH,\n DASHBOARD_CONSOLE_OPTIONS,\n} from './tokens.js';\n\n/**\n * `TReq` mirrors core's `ActorResolver<TReq>` convention: the transport request type\n * {@link AgentDashboardOptions.approvalActorRef} receives. Defaults to `unknown`.\n */\nexport interface AgentDashboardOptions<TReq = unknown> {\n /**\n * Where the SPA (UI) is served. Default `/ai-gateway`. This is a page route — keep it out of an\n * `/api` prefix so it reads as a UI, not an endpoint.\n */\n basePath?: string;\n /**\n * Where the JSON API is mounted (what the SPA fetches). Default `<basePath>/api`. Set it under\n * your app's `/api` prefix — e.g. `/api/ai-gateway` — so the API inherits the app's auth/proxy\n * rules while the UI stays at `basePath`.\n */\n apiBasePath?: string;\n /**\n * Who reaches the console (the SPA and every API/SSE route): an inline function\n * `(ctx) => boolean | { allowed } | { redirectTo }`, an `@Injectable()` `ConsoleAuthorizer` class,\n * a lazy import of one, `{ policy, ability }` (`@nestjs/authorization`), or a gate name\n * (`@dudousxd/nestjs-authz`). `authorizeByToken({ token })` checks a bearer token.\n *\n * Omitted: open outside production; in production every request answers 403 (and a warning is\n * logged at boot).\n */\n authorize?: ConsoleAuthorize;\n /**\n * Class decorators applied to every controller this module mounts, before the console's own\n * `authorize` check — e.g. `[UseGuards(AuthenticationGuard)]` so `request.user` is set when\n * `authorize` runs. Applied to a per-registration subclass, so nothing sticks to the shared\n * classes. They do not replace `authorize`. Static wiring.\n */\n decorators?: ClassDecorator[];\n /**\n * `false` mounts none of the console's routes, while its services still run — the way NestJS's\n * own packages leave routes to you: serve them from controllers of your own, protected with the\n * official tools, by extending the exported `AgentApiController` / `AgentUiController` under your\n * own path and decorators (the module exports what they inject); `UseConsoleAuthorize()` applies\n * this module's `authorize` to them. Static wiring.\n */\n routes?: boolean;\n /** Extra `imports` merged into the console's modules — what a decorator's guard injects. */\n imports?: DynamicModule['imports'];\n /**\n * OVERRIDE for WHO is deciding a HITL approval — invoked on the incoming\n * `POST <api>/approvals/:toolCallId` request to stamp `AgentApprovalPort`'s `opts.executedByRef`.\n *\n * DEFAULT (option omitted): the AgentModule-configured actor resolver (`AGENT_ACTOR_RESOLVER`) is\n * consulted — the same identity seam chat requests use — and its actor id becomes the decider ref\n * (a throwing resolver, i.e. an unauthenticated request, just omits the ref). Set this only when\n * the console's caller is identified differently from chat's; returning `undefined` leaves\n * `executedByRef` unset — the explicit override wins outright, with no resolver fallback.\n */\n approvalActorRef?: (req: TReq) => string | undefined;\n}\n\n/** Leading slash, no trailing slash. */\nfunction normalize(path: string): string {\n return normalizeDashboardPath(path);\n}\n\n/** The decorators every mounted controller gets: the app's, then the console's access check. */\nfunction consoleDecorators(decorators: ClassDecorator[] | undefined): ClassDecorator[] {\n return [\n ...(decorators ?? []),\n UseGuards(ConsoleAuthorizeGuard),\n UseFilters(ConsoleAuthorizeFilter),\n ];\n}\n\n/** Holds the JSON API + SSE controller and its read service, mounted on its own path. */\n@Module({})\nexport class AgentApiModule {\n static register<TReq = unknown>(options: {\n imports?: DynamicModule['imports'];\n decorators?: ClassDecorator[];\n routes?: boolean;\n approvalActorRef?: (req: TReq) => string | undefined;\n authorize?: ConsoleAuthorize;\n }): DynamicModule {\n // The warning for an omitted `authorize` logs with `routes: false` too: the exported guard still\n // answers 403 in production.\n const consoleOptions: DashboardConsoleOptions = {\n ...(options.authorize !== undefined ? { authorize: options.authorize } : {}),\n };\n return {\n module: AgentApiModule,\n imports: [...(options.imports ?? [])],\n controllers:\n options.routes === false\n ? []\n : withControllerDecorators([AgentApiController], consoleDecorators(options.decorators)),\n providers: [\n DashboardService,\n // `useValue` even when `options.approvalActorRef` is `undefined` — AgentApiController\n // injects this WITHOUT `@Optional()`.\n { provide: DASHBOARD_APPROVAL_ACTOR_REF, useValue: options.approvalActorRef },\n { provide: DASHBOARD_CONSOLE_OPTIONS, useValue: consoleOptions },\n DashboardConsoleAccess,\n ],\n // What a self-mounted `AgentApiController` subclass injects, too (`routes: false`), and the\n // access check `UseConsoleAuthorize()` runs.\n exports: [DashboardService, DASHBOARD_APPROVAL_ACTOR_REF, DashboardConsoleAccess],\n };\n }\n}\n\n/**\n * The AI-gateway console: the SPA at `basePath` and its JSON API at `apiBasePath`. Who reaches them\n * is `authorize` (see {@link AgentDashboardOptions.authorize}).\n */\n@Module({})\nexport class AgentDashboardModule {\n static forRoot<TReq = unknown>(options: AgentDashboardOptions<TReq> = {}): DynamicModule {\n const basePath = normalize(options.basePath ?? '/ai-gateway');\n const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);\n return {\n module: AgentDashboardModule,\n imports: [\n ...(options.imports ?? []),\n // Spread-only-when-set: exactOptionalPropertyTypes rejects an explicit `undefined`.\n AgentApiModule.register({\n ...(options.imports ? { imports: options.imports } : {}),\n ...(options.decorators ? { decorators: options.decorators } : {}),\n ...(options.routes === false ? { routes: false } : {}),\n ...(options.approvalActorRef ? { approvalActorRef: options.approvalActorRef } : {}),\n ...(options.authorize !== undefined ? { authorize: options.authorize } : {}),\n }),\n ...(options.routes === false\n ? []\n : [\n RouterModule.register([\n { path: basePath, module: AgentDashboardModule },\n { path: apiBasePath, module: AgentApiModule },\n ]),\n ]),\n ],\n controllers:\n options.routes === false\n ? []\n : withControllerDecorators([AgentUiController], consoleDecorators(options.decorators)),\n providers: [\n { provide: DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: DASHBOARD_API_PATH, useValue: apiBasePath },\n ],\n // Re-export the API module so its DashboardService reaches importers (e.g. the host's own\n // controllers), and the paths a self-mounted `AgentUiController` subclass injects.\n exports: [AgentApiModule, DASHBOARD_BASE_PATH, DASHBOARD_API_PATH],\n };\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { basename, extname, join, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { CanAnyone } from '@dudousxd/nestjs-agent-core';\nimport {\n Controller,\n Get,\n Header,\n Inject,\n NotFoundException,\n Param,\n Req,\n Res,\n StreamableFile,\n} from '@nestjs/common';\nimport { ConsolePage } from './console-authorize.guard.js';\nimport { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH } from './tokens.js';\n\n/**\n * The slice of the express request the Inertia bounce below reads — structural, so the package\n * needs no express dependency (the API controller similarly takes `@Req() req: unknown`).\n */\ninterface UiPageRequest {\n headers: Record<string, string | string[] | undefined>;\n /** Path + query as received (express `originalUrl`) — what the full-page visit must reload. */\n originalUrl: string;\n}\n\n/** The slice of the express response the Inertia bounce writes (passthrough mode — Nest still sends). */\ninterface UiPageResponse {\n status(code: number): unknown;\n setHeader(name: string, value: string): unknown;\n}\n\n/** The base the SPA bundle was built with (Vite `base`); rewritten to the configured base at serve time. */\nconst BUILD_BASE = '/ai-gateway';\n\n/** dist/server/agent-ui.controller.js -> ../spa (the Vite build output). */\nfunction spaDir(): string {\n return fileURLToPath(new URL('../spa', import.meta.url));\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.json': 'application/json; charset=utf-8',\n '.woff2': 'font/woff2',\n '.ico': 'image/x-icon',\n};\n\n/**\n * Serves the bundled AI-gateway console SPA at the configured base (+ hashed assets at\n * `<base>/assets`). The path comes from `RouterModule` (set by\n * {@link AgentDashboardModule.forRoot}({ basePath })), so the controller routes are relative.\n *\n * Who reaches it: `authorize` on `AgentDashboardModule.forRoot` (checked by `ConsoleAuthorizeGuard`,\n * after the app's `decorators`). Mounted by the app itself (`routes: false`), apply\n * `UseConsoleAuthorize()` to your subclass.\n */\n@CanAnyone()\n@Controller()\nexport class AgentUiController {\n private readonly dir = spaDir();\n\n constructor(\n @Inject(DASHBOARD_BASE_PATH) private readonly basePath: string,\n @Inject(DASHBOARD_API_PATH) private readonly apiBasePath: string,\n ) {}\n\n // index.html references hash-named bundles, so it MUST NOT be cached (stale bundle = the classic\n // \"stuck loading after a deploy\"). The hashed assets below are immutable.\n @Get()\n @ConsolePage()\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store, must-revalidate')\n index(@Req() req: UiPageRequest, @Res({ passthrough: true }) res: UiPageResponse): string {\n // An Inertia <Link> in the host app visits this page route as an XHR expecting an Inertia JSON\n // page object; serving the SPA's HTML instead lands it in the Inertia client's `srcdoc` error\n // modal, where the page's relative asset URLs die on CORS (origin null). The protocol's own\n // escape hatch for \"this URL is not an Inertia page\" is a 409 Conflict carrying\n // `X-Inertia-Location`: the client responds with a full `window.location` visit, which renders\n // the console normally. Only HTML page routes need this — asset binaries are never fetched with\n // the header.\n if (req.headers['x-inertia'] !== undefined) {\n res.status(409);\n res.setHeader('X-Inertia-Location', req.originalUrl);\n return '';\n }\n const indexPath = join(this.dir, 'index.html');\n if (!existsSync(indexPath)) {\n throw new NotFoundException('Dashboard is not built. Run the package build.');\n }\n // The bundle was built with Vite base `/ai-gateway/`; rewrite asset URLs to the configured base\n // so the SPA loads from `<base>/assets` wherever it's mounted, and tell the client its API base.\n const html = readFileSync(indexPath, 'utf8').replaceAll(\n `=\"${BUILD_BASE}/`,\n `=\"${this.basePath}/`,\n );\n // __AGENT_BASE__ = where assets load; __AGENT_API__ = where the SPA fetches the JSON API.\n const inject = `<script>window.__AGENT_BASE__='${this.basePath}';window.__AGENT_API__='${this.apiBasePath}';</script>`;\n return html.includes('</head>') ? html.replace('</head>', `${inject}</head>`) : inject + html;\n }\n\n @Get('assets/:file')\n @Header('Cache-Control', 'public, max-age=31536000, immutable')\n asset(@Param('file') file: string): StreamableFile {\n const safe = basename(file);\n if (safe !== file) throw new NotFoundException();\n const root = resolve(this.dir, 'assets');\n const assetPath = resolve(root, safe);\n if (!assetPath.startsWith(root + sep) || !existsSync(assetPath)) {\n throw new NotFoundException();\n }\n const type = CONTENT_TYPES[extname(safe)] ?? 'application/octet-stream';\n return new StreamableFile(readFileSync(assetPath), { type });\n }\n}\n","import {\n type ArgumentsHost,\n type CanActivate,\n Catch,\n type ExceptionFilter,\n type ExecutionContext,\n HttpException,\n Inject,\n Injectable,\n type OnModuleInit,\n SetMetadata,\n UseFilters,\n UseGuards,\n applyDecorators,\n} from '@nestjs/common';\nimport { HttpAdapterHost, ModuleRef } from '@nestjs/core';\nimport {\n type ConsoleAuthorize,\n type ConsoleDecide,\n type ConsoleDecision,\n resolveConsoleAuthorize,\n} from './console-authorize.js';\nimport { DASHBOARD_CONSOLE_OPTIONS } from './tokens.js';\n\n/** What {@link DashboardConsoleAccess} is built from (bound per `forRoot`). */\nexport interface DashboardConsoleOptions {\n authorize?: ConsoleAuthorize;\n}\n\n/** Holds the console's resolved `authorize` check for one registration. */\n@Injectable()\nexport class DashboardConsoleAccess implements OnModuleInit {\n #decide: ConsoleDecide | undefined;\n\n constructor(\n @Inject(DASHBOARD_CONSOLE_OPTIONS) private readonly options: DashboardConsoleOptions,\n private readonly moduleRef: ModuleRef,\n ) {}\n\n #resolving: Promise<ConsoleDecide> | undefined;\n\n /** Resolves `authorize` at boot: a configuration mistake fails the boot, and the warning logs once. */\n async onModuleInit(): Promise<void> {\n this.#resolving ??= resolveConsoleAuthorize(this.options.authorize, this.moduleRef);\n this.#decide = await this.#resolving;\n }\n\n /** The decision for one request. */\n async check(context: ExecutionContext): Promise<ConsoleDecision> {\n if (this.#decide === undefined) await this.onModuleInit();\n return (this.#decide as ConsoleDecide)(context);\n }\n}\n\n/** Marks a controller or handler as serving pages: a denied browser navigation gets HTML or a 302. */\nexport const CONSOLE_PAGE_METADATA = '@dudousxd/nestjs-agent-dashboard:console-page';\n\n/**\n * On a controller or a handler that serves pages: a denied `GET` that accepts HTML gets a page (or a\n * 302 for `redirectTo`), not JSON.\n */\nexport const ConsolePage = (): ClassDecorator & MethodDecorator =>\n SetMetadata(CONSOLE_PAGE_METADATA, true);\n\n/** Thrown by {@link ConsoleAuthorizeGuard}; its body is the JSON answer even without the filter. */\nexport class ConsoleAccessDenied extends HttpException {\n constructor(\n readonly decision: Exclude<ConsoleDecision, { allowed: true }>,\n readonly page: boolean,\n ) {\n super(\n decision.reason === 'forbidden'\n ? { error: 'forbidden' }\n : {\n error: 'unauthenticated',\n ...(decision.redirectTo !== undefined ? { redirectTo: decision.redirectTo } : {}),\n },\n decision.reason === 'forbidden' ? 403 : 401,\n );\n }\n}\n\ninterface HttpRequestLike {\n method?: string;\n headers?: Record<string, string | string[] | undefined>;\n}\n\nfunction isPageNavigation(context: ExecutionContext): boolean {\n if (context.getType() !== 'http') return false;\n const page =\n Reflect.getMetadata(CONSOLE_PAGE_METADATA, context.getHandler()) === true ||\n Reflect.getMetadata(CONSOLE_PAGE_METADATA, context.getClass()) === true;\n if (!page) return false;\n const req = context.switchToHttp().getRequest<HttpRequestLike>();\n if ((req.method ?? 'GET').toUpperCase() !== 'GET') return false;\n const accept = req.headers?.accept;\n const value = Array.isArray(accept) ? accept.join(',') : (accept ?? '');\n return value.includes('text/html');\n}\n\n/**\n * Runs the console's `authorize` on every route of a controller it guards. Applied by the module\n * after the app's `decorators` (so authentication guards have already set `request.user`); a\n * self-mounted controller (`routes: false`) applies it with {@link UseConsoleAuthorize}.\n */\n@Injectable()\nexport class ConsoleAuthorizeGuard implements CanActivate {\n constructor(private readonly access: DashboardConsoleAccess) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const decision = await this.access.check(context);\n if (decision.allowed) return true;\n throw new ConsoleAccessDenied(decision, isPageNavigation(context));\n }\n}\n\n/** The minimal page a denied browser navigation gets. */\nfunction deniedPage(title: string, message: string): string {\n const body =\n 'font-family:system-ui,sans-serif;display:grid;place-items:center;min-height:100vh;margin:0';\n return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>${title}</title></head><body style=\"${body}\"><main style=\"text-align:center\"><h1>${title}</h1><p>${message}</p></main></body></html>`;\n}\n\nconst FORBIDDEN_PAGE = deniedPage('Access denied', 'You do not have access to this console.');\nconst UNAUTHENTICATED_PAGE = deniedPage('Sign in required', 'Sign in to open this console.');\n\n/** Writes a console denial: JSON for API calls; a 302 or an HTML page (403 / 401) for page navigations. */\n@Catch(ConsoleAccessDenied)\n@Injectable()\nexport class ConsoleAuthorizeFilter implements ExceptionFilter<ConsoleAccessDenied> {\n constructor(private readonly adapterHost: HttpAdapterHost) {}\n\n catch(exception: ConsoleAccessDenied, host: ArgumentsHost): void {\n const adapter = this.adapterHost.httpAdapter;\n const res = host.switchToHttp().getResponse<unknown>();\n const { decision, page } = exception;\n if (page && decision.reason === 'unauthenticated' && decision.redirectTo !== undefined) {\n adapter.redirect(res, 302, decision.redirectTo);\n return;\n }\n if (page) {\n const forbidden = decision.reason === 'forbidden';\n adapter.setHeader(res, 'Content-Type', 'text/html; charset=utf-8');\n adapter.reply(res, forbidden ? FORBIDDEN_PAGE : UNAUTHENTICATED_PAGE, forbidden ? 403 : 401);\n return;\n }\n adapter.reply(res, exception.getResponse(), exception.getStatus());\n }\n}\n\n/**\n * The console's access check on a controller of your own (with `routes: false`): the\n * {@link ConsoleAuthorizeGuard} plus the filter that writes its denials. Write it ABOVE your own\n * authentication decorators: class decorators apply bottom-up, so it then runs after them and\n * `request.user` is set.\n */\nexport function UseConsoleAuthorize(): ClassDecorator & MethodDecorator {\n return applyDecorators(UseGuards(ConsoleAuthorizeGuard), UseFilters(ConsoleAuthorizeFilter));\n}\n","import { createHash, timingSafeEqual } from 'node:crypto';\nimport { type ExecutionContext, Logger, type Type } from '@nestjs/common';\nimport type { ModuleRef } from '@nestjs/core';\n\n/** What an `authorize` check answers. */\nexport type ConsoleAuthorizeResult =\n | boolean\n | { allowed: true }\n | { allowed: false; reason?: 'unauthenticated' | 'forbidden' }\n | { redirectTo: string };\n\n/** Class form: an @Injectable() provider (constructor injection works). */\nexport interface ConsoleAuthorizer {\n authorize(context: ExecutionContext): ConsoleAuthorizeResult | Promise<ConsoleAuthorizeResult>;\n}\nexport type ConsoleAuthorizerClass = Type<ConsoleAuthorizer>;\n\n/** @nestjs/authorization policy check: policy[ability](user). */\nexport interface ConsolePolicyCheck {\n policy: Type<unknown>;\n ability: string;\n}\n\n/** Who reaches the console. */\nexport type ConsoleAuthorize =\n | ((context: ExecutionContext) => ConsoleAuthorizeResult | Promise<ConsoleAuthorizeResult>) // inline\n | ConsoleAuthorizerClass // class\n | (() => Promise<{ default: ConsoleAuthorizerClass }>) // lazy import\n | ConsolePolicyCheck // official policy\n | string; // registered gate name\n\n/** A normalized `authorize` answer. */\nexport type ConsoleDecision =\n | { allowed: true }\n | { allowed: false; reason: 'unauthenticated' | 'forbidden'; redirectTo?: string };\n\n/** What a resolved `authorize` is: called once per request, never throws. */\nexport type ConsoleDecide = (context: ExecutionContext) => Promise<ConsoleDecision>;\n\n/**\n * The provider that resolves gate names (`authorize: 'console.agent'`), bound globally by\n * `@dudousxd/nestjs-authz`'s `AuthzModule`. A registry symbol, so it is found without depending on\n * that package.\n */\nexport const CONSOLE_GATE = Symbol.for('@dudousxd/aviary:console-gate');\nexport interface ConsoleGate {\n allows(name: string, context: ExecutionContext): Promise<ConsoleAuthorizeResult>;\n}\n\nconst PACKAGE = '@dudousxd/nestjs-agent-dashboard';\nconst MODULE = 'AgentDashboardModule';\nconst logger = new Logger(MODULE);\n\n/** The one boot warning for a console with no `authorize`. Same wording in every Aviary console. */\nexport const CONSOLE_AUTHORIZE_MISSING_WARNING = `[${PACKAGE}] console has no \\`authorize\\` configured: it is open outside production and denies every request in production. Set \\`authorize\\` in ${MODULE}.forRoot().`;\n\n// Fresh objects on every call: a decision is never a shared, mutable singleton.\nconst allow = (): ConsoleDecision => ({ allowed: true });\nconst forbidden = (): ConsoleDecision => ({ allowed: false, reason: 'forbidden' });\nconst unauthenticated = (): ConsoleDecision => ({ allowed: false, reason: 'unauthenticated' });\n\nfunction isProduction(): boolean {\n return (process.env.NODE_ENV ?? '').trim().toLowerCase() === 'production';\n}\n\n/** Turns any {@link ConsoleAuthorizeResult} into a {@link ConsoleDecision}. */\nexport function normalizeConsoleResult(result: unknown): ConsoleDecision {\n if (result === true) return allow();\n if (result === null || typeof result !== 'object') return forbidden();\n const value = result as { allowed?: unknown; reason?: unknown; redirectTo?: unknown };\n if (typeof value.redirectTo === 'string' && value.redirectTo !== '' && value.allowed !== true) {\n return { allowed: false, reason: 'unauthenticated', redirectTo: value.redirectTo };\n }\n if (value.allowed === true) return allow();\n return value.reason === 'unauthenticated' ? unauthenticated() : forbidden();\n}\n\n/**\n * A class with `authorize()`: on the prototype, or (an instance-property arrow function,\n * `authorize = (ctx) => ...`) only on instances, so any `class`.\n */\nfunction isAuthorizerClass(value: unknown): value is ConsoleAuthorizerClass {\n if (typeof value !== 'function') return false;\n if (\n typeof (value as { prototype?: { authorize?: unknown } }).prototype?.authorize === 'function'\n ) {\n return true;\n }\n return /^class[\\s{]/.test(Function.prototype.toString.call(value));\n}\n\nfunction isPolicyCheck(value: unknown): value is ConsolePolicyCheck {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as ConsolePolicyCheck).policy === 'function' &&\n typeof (value as ConsolePolicyCheck).ability === 'string'\n );\n}\n\nfunction lazyModuleClass(value: unknown): ConsoleAuthorizerClass | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const candidate = (value as { default?: unknown }).default;\n return isAuthorizerClass(candidate) ? candidate : undefined;\n}\n\n/** A provider registered anywhere (`strict: false`), or `undefined`. */\nfunction tryGet<T>(moduleRef: ModuleRef, token: unknown): T | undefined {\n try {\n return (moduleRef.get(token as never, { strict: false }) as T | undefined) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/** One instance per class per registration: a registered provider, else created with DI. */\nfunction classInstances(moduleRef: ModuleRef) {\n const instances = new Map<ConsoleAuthorizerClass, Promise<ConsoleAuthorizer>>();\n return (Class: ConsoleAuthorizerClass): Promise<ConsoleAuthorizer> => {\n let instance = instances.get(Class);\n if (instance === undefined) {\n const registered = tryGet<ConsoleAuthorizer>(moduleRef, Class);\n instance = registered !== undefined ? Promise.resolve(registered) : moduleRef.create(Class);\n // A failed create is retried on the next request, not cached as a permanent denial.\n instance.catch(() => instances.delete(Class));\n instances.set(Class, instance);\n }\n return instance;\n };\n}\n\n/** `null`, `undefined` and `false` mean \"nobody signed in\", like `@nestjs/authorization`'s guard. */\nfunction hasUser(user: unknown): boolean {\n return user !== null && user !== undefined && user !== false;\n}\n\n/** `{ policy, ability }`: `@nestjs/authorization` (an optional peer) imported lazily, checked at boot. */\nasync function policyDecider(\n check: ConsolePolicyCheck,\n moduleRef: ModuleRef,\n): Promise<(context: ExecutionContext) => Promise<ConsoleDecision>> {\n let mod: typeof import('@nestjs/authorization');\n try {\n mod = await import('@nestjs/authorization');\n } catch {\n throw new Error(\n `${MODULE}: \\`authorize: { policy, ability }\\` needs @nestjs/authorization installed and AuthorizationModule imported; or pass a function, a class, or a gate name.`,\n );\n }\n const service = tryGet<{\n can(policy: Type<unknown>, ability: string, user: unknown): Promise<boolean>;\n }>(moduleRef, mod.AuthorizationService);\n if (service === undefined) {\n throw new Error(\n `${MODULE}: \\`authorize: { policy, ability }\\` needs AuthorizationModule.forRoot() from @nestjs/authorization imported in the app.`,\n );\n }\n const options = tryGet<{ getUser?: (context: ExecutionContext) => unknown }>(\n moduleRef,\n mod.AUTHORIZATION_MODULE_OPTIONS,\n );\n // The user the official `@Can()` sees: the module's `getUser` when one is configured, else the\n // package default (`request.user`). `undefined`, `null` and `false` all mean a guest.\n const getUser = (options?.getUser ?? mod.defaultGetUser) as (\n context: ExecutionContext,\n ) => unknown;\n return async (context) => {\n const user = await getUser(context);\n const signedIn = hasUser(user);\n if (await service.can(check.policy, check.ability, signedIn ? user : null)) return allow();\n return signedIn ? forbidden() : unauthenticated();\n };\n}\n\n/**\n * Resolves `authorize` into one function the guard calls per request. Configuration mistakes (a\n * gate name without nestjs-authz, a policy without `@nestjs/authorization`) throw here, so call it\n * at boot. The returned function never throws: an error is a `forbidden`.\n *\n * `authorize` omitted: open outside production, `forbidden` for every request in production (read\n * when this runs), and one warning logged (unless `warnIfMissing: false`).\n */\nexport async function resolveConsoleAuthorize(\n authorize: ConsoleAuthorize | undefined,\n moduleRef: ModuleRef,\n options: { logger?: Logger; warnIfMissing?: boolean } = {},\n): Promise<ConsoleDecide> {\n const log = options.logger ?? logger;\n if (authorize === undefined) {\n if (options.warnIfMissing !== false) log.warn(CONSOLE_AUTHORIZE_MISSING_WARNING);\n const production = isProduction();\n return async () => (production ? forbidden() : allow());\n }\n\n const instanceOf = classInstances(moduleRef);\n let decide: (context: ExecutionContext) => Promise<unknown>;\n if (typeof authorize === 'string') {\n const name = authorize;\n const gate = tryGet<ConsoleGate>(moduleRef, CONSOLE_GATE);\n if (gate === undefined) {\n throw new Error(\n `${MODULE}: \\`authorize: '${name}'\\` needs @dudousxd/nestjs-authz's AuthzModule (it resolves gate names); or pass a function, a class, or { policy, ability }.`,\n );\n }\n decide = (context) => gate.allows(name, context);\n } else if (isPolicyCheck(authorize)) {\n decide = await policyDecider(authorize, moduleRef);\n } else if (isAuthorizerClass(authorize)) {\n const Class = authorize;\n decide = async (context) => (await instanceOf(Class)).authorize(context);\n } else if (typeof authorize === 'function') {\n const fn = authorize as (context: ExecutionContext) => unknown;\n let lazyClass: ConsoleAuthorizerClass | undefined;\n decide = async (context) => {\n if (lazyClass !== undefined) return (await instanceOf(lazyClass)).authorize(context);\n const result = await fn(context);\n const Class = lazyModuleClass(result);\n if (Class === undefined) return result;\n // A lazy import: remember the class, so the import runs once per registration.\n lazyClass = Class;\n return (await instanceOf(Class)).authorize(context);\n };\n } else {\n throw new Error(\n `${MODULE}: \\`authorize\\` must be a function, a class with authorize(), a lazy import, { policy, ability } or a gate name.`,\n );\n }\n\n return async (context) => {\n try {\n return normalizeConsoleResult(await decide(context));\n } catch (error) {\n log.warn(\n `console \\`authorize\\` threw, denying the request: ${error instanceof Error ? error.message : String(error)}`,\n );\n return forbidden();\n }\n };\n}\n\nfunction sha256(value: string): Buffer {\n return createHash('sha256').update(value).digest();\n}\n\n/**\n * `authorize` by a bearer token: allows when `Authorization: Bearer <token>` matches (compared in\n * constant time), else `unauthenticated`. Header only. An empty token throws here, at config time.\n */\nexport function authorizeByToken(options: { token: string | undefined }): (\n context: ExecutionContext,\n) => ConsoleAuthorizeResult {\n const token = options.token;\n if (typeof token !== 'string' || token === '') {\n throw new Error('authorizeByToken: `token` is empty; set it (e.g. from an env var).');\n }\n const expected = sha256(token);\n return (context) => {\n const req = context\n .switchToHttp()\n .getRequest<{ headers?: Record<string, string | string[] | undefined> }>();\n const header = req?.headers?.authorization;\n const value = Array.isArray(header) ? header[0] : header;\n const match = typeof value === 'string' ? /^Bearer\\s+(.+)$/i.exec(value.trim()) : null;\n if (match?.[1] !== undefined && timingSafeEqual(sha256(match[1]), expected)) return true;\n return { allowed: false, reason: 'unauthenticated' };\n };\n}\n"],"mappings":"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","names":["import_common","import_common","parseApprovalDecision","body","BadRequestException","approved","reason","undefined","import_common","parsePriceInput","body","BadRequestException","modelId","inputPricePer1m","outputPricePer1m","cacheWritePricePer1m","cacheReadPricePer1m","trim","length","isFiniteNonNegative","undefined","value","Number","isFinite","AGENT_GOVERNANCE_QUERIES","Symbol","for","AGENT_ACTOR_DIRECTORY","AGENT_PRICING_STORE","AGENT_APPROVAL_PORT","AGENT_ACTOR_RESOLVER","DASHBOARD_BASE_PATH","DASHBOARD_API_PATH","DASHBOARD_APPROVAL_ACTOR_REF","DASHBOARD_CONSOLE_OPTIONS","PRICING_STORE_UNBOUND_MESSAGE","APPROVAL_PORT_UNBOUND_MESSAGE","GENUI_STATS_UNSUPPORTED_MESSAGE","AGENT_EVENTS","isAgentEnvelope","message","event","DashboardService","queries","actorDirectory","pricingStore","approvalPort","spend","range","byModel","byActorRaw","trend","byFeature","Promise","all","spendByModel","spendByActor","usageTrend","spendByFeature","byActor","withActorLabels","undefined","topThreads","limit","rows","spendByThread","reliability","metrics","byAgent","errors","runMetrics","runsByAgent","runErrors","runTrend","recentRuns","recentToolCalls","toolCallsPage","query","runsPage","pendingApprovals","approvalsPage","runDetail","runId","detail","NotFoundException","thread","actorLabel","threadDetail","threadId","toolStats","genuiStats","NotImplementedException","decideApproval","toolCallId","body","executedByRef","decision","parseApprovalDecision","opts","approved","approve","reject","reason","recentThreads","threadsPage","page","length","map","row","refs","Set","actorRef","resolved","resolveDisplay","listPrices","listCurrentPrices","upsertPrice","upsertModelPrice","parsePriceInput","streamEvents","Observable","subscriber","bindings","name","channelName","handler","next","data","ts","Date","now","payload","subscribe","binding","unsubscribe","import_common","ISO_DAY","parsePageNumber","value","parsed","undefined","Number","NaN","parseInt","isFinite","assertKnownFields","raw","allowed","field","Object","keys","includes","BadRequestException","assertDayFields","dayFields","test","DAY_FIELDS","TOOL_CALL_WHERE_FIELDS","parseToolCallWhere","length","toolName","toolType","status","threadId","fromDay","toDay","THREAD_WHERE_FIELDS","parseThreadWhere","actorRef","title","RUN_WHERE_FIELDS","parseRunWhere","agentName","errorCode","APPROVAL_WHERE_FIELDS","parseApprovalWhere","DAY_MS","ISO_DAY","utcDay","daysAgo","Date","now","toISOString","slice","dayOr","value","fallback","undefined","test","resolveRange","from","to","fromDay","toDay","parseLimit","parsed","Number","NaN","parseInt","isFinite","Math","max","min","AgentApiController","dashboard","approvalActorRef","actorResolver","spend","topThreads","limit","reliability","runs","recentRuns","runsPage","page","where","parsedWhere","parseRunWhere","parsePageNumber","pageSize","runDetail","runId","toolCalls","recentToolCalls","toolCallsPage","parseToolCallWhere","approvals","pendingApprovals","approvalsPage","parseApprovalWhere","decideApproval","toolCallId","body","req","deciderRef","resolve","id","tools","toolStats","genui","genuiStats","threads","recentThreads","threadsPage","parseThreadWhere","threadDetail","threadId","messages","messageLimit","runLimit","listPrices","upsertPrice","stream","streamEvents","normalizeDashboardPath","path","replace","unprefixed","path","replace","agentDashboardMountPaths","options","basePath","normalizeDashboardPath","apiBasePath","base","api","import_nestjs_agent_core","import_common","import_core","import_nestjs_agent_core","import_common","import_common","import_common","CONSOLE_GATE","Symbol","for","PACKAGE","MODULE","logger","Logger","CONSOLE_AUTHORIZE_MISSING_WARNING","allow","allowed","forbidden","reason","unauthenticated","isProduction","process","env","NODE_ENV","trim","toLowerCase","normalizeConsoleResult","result","value","redirectTo","isAuthorizerClass","prototype","authorize","test","Function","toString","call","isPolicyCheck","policy","ability","lazyModuleClass","undefined","candidate","default","tryGet","moduleRef","token","get","strict","classInstances","instances","Map","Class","instance","registered","Promise","resolve","create","catch","delete","set","hasUser","user","policyDecider","check","mod","Error","service","AuthorizationService","options","AUTHORIZATION_MODULE_OPTIONS","getUser","defaultGetUser","context","signedIn","can","resolveConsoleAuthorize","log","warnIfMissing","warn","production","instanceOf","decide","name","gate","allows","fn","lazyClass","error","message","String","sha256","createHash","update","digest","authorizeByToken","expected","req","switchToHttp","getRequest","header","headers","authorization","Array","isArray","match","exec","timingSafeEqual","DashboardConsoleAccess","options","moduleRef","onModuleInit","resolveConsoleAuthorize","authorize","check","context","undefined","CONSOLE_PAGE_METADATA","ConsolePage","SetMetadata","ConsoleAccessDenied","HttpException","decision","page","reason","error","redirectTo","isPageNavigation","getType","Reflect","getMetadata","getHandler","getClass","req","switchToHttp","getRequest","method","toUpperCase","accept","headers","value","Array","isArray","join","includes","ConsoleAuthorizeGuard","access","canActivate","allowed","deniedPage","title","message","body","FORBIDDEN_PAGE","UNAUTHENTICATED_PAGE","ConsoleAuthorizeFilter","adapterHost","catch","exception","host","adapter","httpAdapter","res","getResponse","redirect","forbidden","setHeader","reply","getStatus","UseConsoleAuthorize","applyDecorators","UseGuards","UseFilters","BUILD_BASE","spaDir","fileURLToPath","URL","CONTENT_TYPES","AgentUiController","dir","basePath","apiBasePath","index","req","res","headers","undefined","status","setHeader","originalUrl","indexPath","join","existsSync","NotFoundException","html","readFileSync","replaceAll","inject","includes","replace","asset","file","safe","basename","root","resolve","assetPath","startsWith","sep","type","extname","StreamableFile","passthrough","normalize","path","normalizeDashboardPath","consoleDecorators","decorators","UseGuards","ConsoleAuthorizeGuard","UseFilters","ConsoleAuthorizeFilter","AgentApiModule","register","options","consoleOptions","authorize","undefined","module","imports","controllers","routes","withControllerDecorators","AgentApiController","providers","DashboardService","provide","DASHBOARD_APPROVAL_ACTOR_REF","useValue","approvalActorRef","DASHBOARD_CONSOLE_OPTIONS","DashboardConsoleAccess","exports","AgentDashboardModule","forRoot","basePath","apiBasePath","RouterModule","AgentUiController","DASHBOARD_BASE_PATH","DASHBOARD_API_PATH"]}