@dudousxd/nestjs-agent-dashboard 0.7.0 → 0.8.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/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 type {\n ActorResolver,\n CurrentModelPrice,\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 SpendOverview,\n type ThreadActivityRowWithLabel,\n type ThreadSpendRowWithLabel,\n} from './dashboard.service.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@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 /** 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 /** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */\n @Get('approvals')\n approvals(@Query('limit') limit?: string): Promise<PendingApprovalRow[]> {\n return this.dashboard.pendingApprovals(parseLimit(limit, 50));\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 /** 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 * 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 AgentGovernanceQueries,\n AgentPricingStore,\n CurrentModelPrice,\n GovernanceRange,\n ModelSpendRow,\n PendingApprovalRow,\n RecentRunRow,\n RunAgentBreakdownRow,\n RunErrorBreakdownRow,\n RunMetrics,\n RunTrendPoint,\n ThreadActivityRow,\n ThreadSpendRow,\n ToolCallActivityRow,\n ToolStatRow,\n UsageTrendPoint,\n} from '@dudousxd/nestjs-agent-core';\nimport { channelName } from '@dudousxd/nestjs-diagnostics';\nimport { Inject, Injectable, NotImplementedException, Optional } 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/** 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\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/** 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/** The `aviary:agent:*` events the Live feed tails. Mirrors the telescope watcher's subscription. */\nconst AGENT_EVENTS = [\n 'run.started',\n 'message',\n 'tool-call',\n 'quota.exceeded',\n 'run.finished',\n 'delegated',\n] as const;\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] = await Promise.all([\n this.queries.spendByModel(range),\n this.queries.spendByActor(range),\n this.queries.usageTrend(range),\n ]);\n const byActor = await this.withActorLabels(byActorRaw);\n return { byModel, byActor, trend };\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 /** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */\n pendingApprovals(limit: number): Promise<PendingApprovalRow[]> {\n return this.queries.pendingApprovals(limit);\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 * 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 * 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()` (mirrors `AGENT_QUOTA_STORE`'s factory in\n * `@dudousxd/nestjs-agent`).\n */\nexport const DASHBOARD_APPROVAL_ACTOR_REF = Symbol.for(\n '@dudousxd/nestjs-agent-dashboard:approval-actor-ref',\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 'reflect-metadata';\nimport { type CanActivate, type DynamicModule, Module, type Type } 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 * `@nestjs/common`'s own `GUARDS_METADATA` key, INLINED rather than deep-imported from\n * '@nestjs/common/constants' — that subpath has no extension and a strict ESM resolver (which the\n * built dual ESM/CJS output of this package is loaded under) 404s on it. A drift spec imports the\n * real constant (via the resolvable `'@nestjs/common/constants.js'` subpath) and asserts this literal\n * stays byte-identical to it.\n */\nconst GUARDS_METADATA = '__guards__';\n\n/**\n * `TReq` mirrors core's `ActorResolver<TReq>` convention: the transport request type\n * {@link AgentDashboardOptions.approvalActorRef} receives. Defaults to `unknown` so untyped usage\n * compiles unchanged; a host may narrow it (e.g. `forRoot<Request>({ approvalActorRef: ... })`)\n * instead of writing its own `unknown`-narrowing type guard.\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 * Guard classes fronting BOTH dashboard controllers (the SPA at `basePath` and its JSON API at\n * `apiBasePath`). Stamped onto each controller via `@nestjs/common`'s own `@UseGuards` metadata key\n * — REPLACE semantics, so a second `forRoot(...)` call overwrites (not appends to) whatever a prior\n * call stamped, same as re-applying `@UseGuards` by hand. Omit to leave the routes unguarded (the\n * host fronts them another way, e.g. a global guard or reverse-proxy auth).\n *\n * A guard's own DEPENDENCIES resolve from this module's `imports` (see {@link imports}) — the\n * dashboard module has no application context of its own to pull them from otherwise.\n */\n guards?: Type<CanActivate>[];\n /**\n * Extra `imports` merged into the dashboard's dynamic module — the DI resolution path for a class\n * passed to {@link guards} (or any other provider the controllers need reachable). Typically the\n * host's own auth module, e.g. `imports: [AuthModule]` alongside `guards: [JwtAuthGuard]`.\n */\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 * console auth differs from chat auth (e.g. the console sits behind a separate SSO whose principal\n * the chat resolver can't read); returning `undefined` leaves `executedByRef` unset — the explicit\n * 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/** Stamp (or clear) `@UseGuards`-equivalent metadata on the dashboard controllers — REPLACE, not append. */\nfunction stampGuards(guards: Type<CanActivate>[] | undefined, ...controllers: Type[]): void {\n for (const controller of controllers) {\n Reflect.defineMetadata(GUARDS_METADATA, guards ?? [], controller);\n }\n}\n\n/**\n * Holds the JSON API + SSE controller and its read service, mounted on its own path by `forRoot`.\n * Dynamic: guards are DI-instantiated by the CONTROLLER's host module, so this module — not the\n * outer wrapper — must carry the guard classes as providers plus the host's `imports` that resolve\n * their dependencies. A static module here made `guards: [SomeGuardWithDeps]` fail at boot with\n * \"Nest can't resolve dependencies ... in the AgentApiModule context\" even when the host passed\n * the right `imports` to `forRoot`.\n */\n@Module({})\nexport class AgentApiModule {\n static register<TReq = unknown>(options: {\n imports?: DynamicModule['imports'];\n guards?: Type<CanActivate>[];\n approvalActorRef?: (req: TReq) => string | undefined;\n }): DynamicModule {\n return {\n module: AgentApiModule,\n imports: [...(options.imports ?? [])],\n controllers: [AgentApiController],\n providers: [\n DashboardService,\n ...(options.guards ?? []),\n // `useValue` even when `options.approvalActorRef` is `undefined` — AgentApiController\n // injects this WITHOUT `@Optional()` (same pattern as `AGENT_QUOTA_STORE`'s factory).\n { provide: DASHBOARD_APPROVAL_ACTOR_REF, useValue: options.approvalActorRef },\n ],\n exports: [DashboardService],\n };\n }\n}\n\n/**\n * Mounts the AI-gateway governance console: the bundled React SPA at `basePath` and its JSON + SSE\n * API at `apiBasePath` (default `<basePath>/api`).\n *\n * Import via `AgentDashboardModule.forRoot(...)` alongside your `@dudousxd/nestjs-agent` module\n * (global), which must provide `AGENT_GOVERNANCE_QUERIES` (bound by a store adapter). Front the\n * routes with the first-class `guards` option (plus `imports` for the guards' own dependencies) —\n * see {@link AgentDashboardOptions.guards}.\n *\n * Inertia hosts: the console is a full-page app, not an Inertia page. An in-app `<Link>` visit to\n * `basePath` (an XHR carrying `X-Inertia`) is bounced with the protocol's own external-redirect\n * mechanism — `409 Conflict` + `X-Inertia-Location: <the visited URL>` — so the Inertia client\n * performs a full `window.location` load and the console renders normally. In-app links to the\n * console therefore just work; no host-side special-casing needed.\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 stampGuards(options.guards, AgentApiController, AgentUiController);\n return {\n module: AgentDashboardModule,\n imports: [\n ...(options.imports ?? []),\n // Guards + host imports must reach the API controller's HOST module — enhancers resolve\n // from their controller's own module, never from a parent (see AgentApiModule.register).\n // Spread-only-when-set: exactOptionalPropertyTypes rejects an explicit `undefined`.\n AgentApiModule.register({\n ...(options.imports ? { imports: options.imports } : {}),\n ...(options.guards ? { guards: options.guards } : {}),\n ...(options.approvalActorRef ? { approvalActorRef: options.approvalActorRef } : {}),\n }),\n RouterModule.register([\n { path: basePath, module: AgentDashboardModule }, // the UI controller below\n { path: apiBasePath, module: AgentApiModule },\n ]),\n ],\n controllers: [AgentUiController],\n providers: [\n { provide: DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: DASHBOARD_API_PATH, useValue: apiBasePath },\n // AgentUiController is hosted HERE, so its guards DI-instantiate from this module.\n ...(options.guards ?? []),\n ],\n // Re-export the API module so its DashboardService reaches importers (e.g. the host's own controllers).\n exports: [AgentApiModule],\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 {\n Controller,\n Get,\n Header,\n Inject,\n NotFoundException,\n Param,\n Req,\n Res,\n StreamableFile,\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@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","AGENT_EVENTS","isAgentEnvelope","message","event","DashboardService","queries","actorDirectory","pricingStore","approvalPort","spend","range","byModel","byActorRaw","trend","Promise","all","spendByModel","spendByActor","usageTrend","byActor","withActorLabels","topThreads","limit","rows","spendByThread","reliability","metrics","byAgent","errors","runMetrics","runsByAgent","runErrors","runTrend","recentRuns","recentToolCalls","pendingApprovals","toolStats","decideApproval","toolCallId","body","executedByRef","undefined","NotImplementedException","decision","parseApprovalDecision","opts","approved","approve","reject","reason","recentThreads","length","map","row","actorLabel","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","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","toolCalls","recentToolCalls","approvals","pendingApprovals","decideApproval","toolCallId","body","req","deciderRef","resolve","id","tools","toolStats","threads","recentThreads","listPrices","upsertPrice","stream","streamEvents","normalizeDashboardPath","path","replace","unprefixed","path","replace","agentDashboardMountPaths","options","basePath","normalizeDashboardPath","apiBasePath","base","api","import_common","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","GUARDS_METADATA","normalize","path","normalizeDashboardPath","stampGuards","guards","controllers","controller","Reflect","defineMetadata","AgentApiModule","register","options","module","imports","AgentApiController","providers","DashboardService","provide","DASHBOARD_APPROVAL_ACTOR_REF","useValue","approvalActorRef","exports","AgentDashboardModule","forRoot","basePath","apiBasePath","AgentUiController","RouterModule","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"],"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 type {\n ActorResolver,\n CurrentModelPrice,\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 SpendOverview,\n type ThreadActivityRowWithLabel,\n type ThreadSpendRowWithLabel,\n} from './dashboard.service.js';\nimport {\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@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 /** 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 /** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */\n @Get('approvals')\n approvals(@Query('limit') limit?: string): Promise<PendingApprovalRow[]> {\n return this.dashboard.pendingApprovals(parseLimit(limit, 50));\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 /** 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 * 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 AgentGovernanceQueries,\n AgentPricingStore,\n CurrentModelPrice,\n GovernancePage,\n GovernancePageQuery,\n GovernanceRange,\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 { Inject, Injectable, NotImplementedException, Optional } 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/** 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\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/** 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/** The `aviary:agent:*` events the Live feed tails. Mirrors the telescope watcher's subscription. */\nconst AGENT_EVENTS = [\n 'run.started',\n 'message',\n 'tool-call',\n 'quota.exceeded',\n 'run.finished',\n 'delegated',\n] as const;\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] = await Promise.all([\n this.queries.spendByModel(range),\n this.queries.spendByActor(range),\n this.queries.usageTrend(range),\n ]);\n const byActor = await this.withActorLabels(byActorRaw);\n return { byModel, byActor, trend };\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 /** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */\n pendingApprovals(limit: number): Promise<PendingApprovalRow[]> {\n return this.queries.pendingApprovals(limit);\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 * 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()` (mirrors `AGENT_QUOTA_STORE`'s factory in\n * `@dudousxd/nestjs-agent`).\n */\nexport const DASHBOARD_APPROVAL_ACTOR_REF = Symbol.for(\n '@dudousxd/nestjs-agent-dashboard:approval-actor-ref',\n);\n","import type { RunWhere, ThreadWhere, ToolCallWhere } 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\nconst RUN_WHERE_FIELDS = ['agentName', 'status', 'errorCode', 'fromDay', 'toDay'] 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.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 'reflect-metadata';\nimport { type CanActivate, type DynamicModule, Module, type Type } 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 * `@nestjs/common`'s own `GUARDS_METADATA` key, INLINED rather than deep-imported from\n * '@nestjs/common/constants' — that subpath has no extension and a strict ESM resolver (which the\n * built dual ESM/CJS output of this package is loaded under) 404s on it. A drift spec imports the\n * real constant (via the resolvable `'@nestjs/common/constants.js'` subpath) and asserts this literal\n * stays byte-identical to it.\n */\nconst GUARDS_METADATA = '__guards__';\n\n/**\n * `TReq` mirrors core's `ActorResolver<TReq>` convention: the transport request type\n * {@link AgentDashboardOptions.approvalActorRef} receives. Defaults to `unknown` so untyped usage\n * compiles unchanged; a host may narrow it (e.g. `forRoot<Request>({ approvalActorRef: ... })`)\n * instead of writing its own `unknown`-narrowing type guard.\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 * Guard classes fronting BOTH dashboard controllers (the SPA at `basePath` and its JSON API at\n * `apiBasePath`). Stamped onto each controller via `@nestjs/common`'s own `@UseGuards` metadata key\n * — REPLACE semantics, so a second `forRoot(...)` call overwrites (not appends to) whatever a prior\n * call stamped, same as re-applying `@UseGuards` by hand. Omit to leave the routes unguarded (the\n * host fronts them another way, e.g. a global guard or reverse-proxy auth).\n *\n * A guard's own DEPENDENCIES resolve from this module's `imports` (see {@link imports}) — the\n * dashboard module has no application context of its own to pull them from otherwise.\n */\n guards?: Type<CanActivate>[];\n /**\n * Extra `imports` merged into the dashboard's dynamic module — the DI resolution path for a class\n * passed to {@link guards} (or any other provider the controllers need reachable). Typically the\n * host's own auth module, e.g. `imports: [AuthModule]` alongside `guards: [JwtAuthGuard]`.\n */\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 * console auth differs from chat auth (e.g. the console sits behind a separate SSO whose principal\n * the chat resolver can't read); returning `undefined` leaves `executedByRef` unset — the explicit\n * 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/** Stamp (or clear) `@UseGuards`-equivalent metadata on the dashboard controllers — REPLACE, not append. */\nfunction stampGuards(guards: Type<CanActivate>[] | undefined, ...controllers: Type[]): void {\n for (const controller of controllers) {\n Reflect.defineMetadata(GUARDS_METADATA, guards ?? [], controller);\n }\n}\n\n/**\n * Holds the JSON API + SSE controller and its read service, mounted on its own path by `forRoot`.\n * Dynamic: guards are DI-instantiated by the CONTROLLER's host module, so this module — not the\n * outer wrapper — must carry the guard classes as providers plus the host's `imports` that resolve\n * their dependencies. A static module here made `guards: [SomeGuardWithDeps]` fail at boot with\n * \"Nest can't resolve dependencies ... in the AgentApiModule context\" even when the host passed\n * the right `imports` to `forRoot`.\n */\n@Module({})\nexport class AgentApiModule {\n static register<TReq = unknown>(options: {\n imports?: DynamicModule['imports'];\n guards?: Type<CanActivate>[];\n approvalActorRef?: (req: TReq) => string | undefined;\n }): DynamicModule {\n return {\n module: AgentApiModule,\n imports: [...(options.imports ?? [])],\n controllers: [AgentApiController],\n providers: [\n DashboardService,\n ...(options.guards ?? []),\n // `useValue` even when `options.approvalActorRef` is `undefined` — AgentApiController\n // injects this WITHOUT `@Optional()` (same pattern as `AGENT_QUOTA_STORE`'s factory).\n { provide: DASHBOARD_APPROVAL_ACTOR_REF, useValue: options.approvalActorRef },\n ],\n exports: [DashboardService],\n };\n }\n}\n\n/**\n * Mounts the AI-gateway governance console: the bundled React SPA at `basePath` and its JSON + SSE\n * API at `apiBasePath` (default `<basePath>/api`).\n *\n * Import via `AgentDashboardModule.forRoot(...)` alongside your `@dudousxd/nestjs-agent` module\n * (global), which must provide `AGENT_GOVERNANCE_QUERIES` (bound by a store adapter). Front the\n * routes with the first-class `guards` option (plus `imports` for the guards' own dependencies) —\n * see {@link AgentDashboardOptions.guards}.\n *\n * Inertia hosts: the console is a full-page app, not an Inertia page. An in-app `<Link>` visit to\n * `basePath` (an XHR carrying `X-Inertia`) is bounced with the protocol's own external-redirect\n * mechanism — `409 Conflict` + `X-Inertia-Location: <the visited URL>` — so the Inertia client\n * performs a full `window.location` load and the console renders normally. In-app links to the\n * console therefore just work; no host-side special-casing needed.\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 stampGuards(options.guards, AgentApiController, AgentUiController);\n return {\n module: AgentDashboardModule,\n imports: [\n ...(options.imports ?? []),\n // Guards + host imports must reach the API controller's HOST module — enhancers resolve\n // from their controller's own module, never from a parent (see AgentApiModule.register).\n // Spread-only-when-set: exactOptionalPropertyTypes rejects an explicit `undefined`.\n AgentApiModule.register({\n ...(options.imports ? { imports: options.imports } : {}),\n ...(options.guards ? { guards: options.guards } : {}),\n ...(options.approvalActorRef ? { approvalActorRef: options.approvalActorRef } : {}),\n }),\n RouterModule.register([\n { path: basePath, module: AgentDashboardModule }, // the UI controller below\n { path: apiBasePath, module: AgentApiModule },\n ]),\n ],\n controllers: [AgentUiController],\n providers: [\n { provide: DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: DASHBOARD_API_PATH, useValue: apiBasePath },\n // AgentUiController is hosted HERE, so its guards DI-instantiate from this module.\n ...(options.guards ?? []),\n ],\n // Re-export the API module so its DashboardService reaches importers (e.g. the host's own controllers).\n exports: [AgentApiModule],\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 {\n Controller,\n Get,\n Header,\n Inject,\n NotFoundException,\n Param,\n Req,\n Res,\n StreamableFile,\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@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","AGENT_EVENTS","isAgentEnvelope","message","event","DashboardService","queries","actorDirectory","pricingStore","approvalPort","spend","range","byModel","byActorRaw","trend","Promise","all","spendByModel","spendByActor","usageTrend","byActor","withActorLabels","topThreads","limit","rows","spendByThread","reliability","metrics","byAgent","errors","runMetrics","runsByAgent","runErrors","runTrend","recentRuns","recentToolCalls","toolCallsPage","query","runsPage","pendingApprovals","toolStats","decideApproval","toolCallId","body","executedByRef","undefined","NotImplementedException","decision","parseApprovalDecision","opts","approved","approve","reject","reason","recentThreads","threadsPage","page","length","map","row","actorLabel","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","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","toolCalls","recentToolCalls","toolCallsPage","parseToolCallWhere","approvals","pendingApprovals","decideApproval","toolCallId","body","req","deciderRef","resolve","id","tools","toolStats","threads","recentThreads","threadsPage","parseThreadWhere","listPrices","upsertPrice","stream","streamEvents","normalizeDashboardPath","path","replace","unprefixed","path","replace","agentDashboardMountPaths","options","basePath","normalizeDashboardPath","apiBasePath","base","api","import_common","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","GUARDS_METADATA","normalize","path","normalizeDashboardPath","stampGuards","guards","controllers","controller","Reflect","defineMetadata","AgentApiModule","register","options","module","imports","AgentApiController","providers","DashboardService","provide","DASHBOARD_APPROVAL_ACTOR_REF","useValue","approvalActorRef","exports","AgentDashboardModule","forRoot","basePath","apiBasePath","AgentUiController","RouterModule","DASHBOARD_BASE_PATH","DASHBOARD_API_PATH"]}
@@ -1,4 +1,4 @@
1
- import { ActorSpendRow, AgentGovernanceQueries, ActorDirectory, AgentPricingStore, AgentApprovalPort, GovernanceRange, ModelSpendRow, UsageTrendPoint, ThreadSpendRow, RunMetrics, RunAgentBreakdownRow, RunErrorBreakdownRow, RunTrendPoint, RecentRunRow, ToolCallActivityRow, PendingApprovalRow, ToolStatRow, ThreadActivityRow, CurrentModelPrice, ActorResolver, ModelPriceInput } from '@dudousxd/nestjs-agent-core';
1
+ import { ActorSpendRow, AgentGovernanceQueries, ActorDirectory, AgentPricingStore, AgentApprovalPort, GovernanceRange, ModelSpendRow, UsageTrendPoint, ThreadSpendRow, RunMetrics, RunAgentBreakdownRow, RunErrorBreakdownRow, RunTrendPoint, RecentRunRow, ToolCallActivityRow, GovernancePageQuery, ToolCallWhere, GovernancePage, RunWhere, PendingApprovalRow, ToolStatRow, ThreadActivityRow, ThreadWhere, CurrentModelPrice, ActorResolver, ModelPriceInput } from '@dudousxd/nestjs-agent-core';
2
2
  export { ActorDirectory } from '@dudousxd/nestjs-agent-core';
3
3
  import { Observable } from 'rxjs';
4
4
  import { DynamicModule, Type, CanActivate, StreamableFile } from '@nestjs/common';
@@ -61,6 +61,10 @@ declare class DashboardService {
61
61
  recentRuns(limit: number): Promise<RecentRunRow[]>;
62
62
  /** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */
63
63
  recentToolCalls(limit: number): Promise<ToolCallActivityRow[]>;
64
+ /** Paged, filterable tool calls for the Runs & tools activity table. */
65
+ toolCallsPage(query: GovernancePageQuery<ToolCallWhere>): Promise<GovernancePage<ToolCallActivityRow>>;
66
+ /** Paged, filterable runs for the Reliability recent-runs table. */
67
+ runsPage(query: GovernancePageQuery<RunWhere>): Promise<GovernancePage<RecentRunRow>>;
64
68
  /** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */
65
69
  pendingApprovals(limit: number): Promise<PendingApprovalRow[]>;
66
70
  /** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */
@@ -76,6 +80,11 @@ declare class DashboardService {
76
80
  decideApproval(toolCallId: string, body: unknown, executedByRef: string | undefined): Promise<void>;
77
81
  /** Most recent threads with rolled-up message/token counts. */
78
82
  recentThreads(limit: number): Promise<ThreadActivityRowWithLabel[]>;
83
+ /**
84
+ * Paged, filterable threads for the Runs & tools threads table — actor-labeled the same way
85
+ * {@link recentThreads} is.
86
+ */
87
+ threadsPage(query: GovernancePageQuery<ThreadWhere>): Promise<GovernancePage<ThreadActivityRowWithLabel>>;
79
88
  /**
80
89
  * Decorate actor-scoped rows with `actorLabel`, batching the distinct `actorRef`s into ONE
81
90
  * {@link ActorDirectory.resolveDisplay} call per response. `null` for every row when no directory
@@ -117,8 +126,20 @@ declare class AgentApiController {
117
126
  reliability(from?: string, to?: string): Promise<ReliabilityOverview>;
118
127
  /** Most recent runs (default 50, max 200) for the Reliability recent-runs table. */
119
128
  runs(limit?: string): Promise<RecentRunRow[]>;
129
+ /**
130
+ * Paged, filterable runs for the Reliability recent-runs table. `page` (default 1), `limit`
131
+ * (default 25, max 200), and `where[agentName]`/`where[status]`/`where[errorCode]`/
132
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
133
+ */
134
+ runsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<RecentRunRow>>;
120
135
  /** Most recent tool calls (default 50, max 200) for the activity feed. */
121
136
  toolCalls(limit?: string): Promise<ToolCallActivityRow[]>;
137
+ /**
138
+ * Paged, filterable tool calls for the Runs & tools activity table. `page` (default 1), `limit`
139
+ * (default 25, max 200), and `where[toolName]`/`where[toolType]`/`where[status]`/`where[threadId]`/
140
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
141
+ */
142
+ toolCallsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<ToolCallActivityRow>>;
122
143
  /** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */
123
144
  approvals(limit?: string): Promise<PendingApprovalRow[]>;
124
145
  /**
@@ -139,6 +160,12 @@ declare class AgentApiController {
139
160
  tools(from?: string, to?: string): Promise<ToolStatRow[]>;
140
161
  /** Most recent threads (default 50, max 200) with rolled-up counts. */
141
162
  threads(limit?: string): Promise<ThreadActivityRowWithLabel[]>;
163
+ /**
164
+ * Paged, filterable threads for the Runs & tools threads table. `page` (default 1), `limit`
165
+ * (default 25, max 200), and `where[actorRef]`/`where[title]` (case-insensitive substring)/
166
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
167
+ */
168
+ threadsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<ThreadActivityRowWithLabel>>;
142
169
  /**
143
170
  * Current price row per model, for the pricing tab. 501s (via `DashboardService.listPrices`) when
144
171
  * no `AGENT_PRICING_STORE` is bound.
@@ -1,4 +1,4 @@
1
- import { ActorSpendRow, AgentGovernanceQueries, ActorDirectory, AgentPricingStore, AgentApprovalPort, GovernanceRange, ModelSpendRow, UsageTrendPoint, ThreadSpendRow, RunMetrics, RunAgentBreakdownRow, RunErrorBreakdownRow, RunTrendPoint, RecentRunRow, ToolCallActivityRow, PendingApprovalRow, ToolStatRow, ThreadActivityRow, CurrentModelPrice, ActorResolver, ModelPriceInput } from '@dudousxd/nestjs-agent-core';
1
+ import { ActorSpendRow, AgentGovernanceQueries, ActorDirectory, AgentPricingStore, AgentApprovalPort, GovernanceRange, ModelSpendRow, UsageTrendPoint, ThreadSpendRow, RunMetrics, RunAgentBreakdownRow, RunErrorBreakdownRow, RunTrendPoint, RecentRunRow, ToolCallActivityRow, GovernancePageQuery, ToolCallWhere, GovernancePage, RunWhere, PendingApprovalRow, ToolStatRow, ThreadActivityRow, ThreadWhere, CurrentModelPrice, ActorResolver, ModelPriceInput } from '@dudousxd/nestjs-agent-core';
2
2
  export { ActorDirectory } from '@dudousxd/nestjs-agent-core';
3
3
  import { Observable } from 'rxjs';
4
4
  import { DynamicModule, Type, CanActivate, StreamableFile } from '@nestjs/common';
@@ -61,6 +61,10 @@ declare class DashboardService {
61
61
  recentRuns(limit: number): Promise<RecentRunRow[]>;
62
62
  /** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */
63
63
  recentToolCalls(limit: number): Promise<ToolCallActivityRow[]>;
64
+ /** Paged, filterable tool calls for the Runs & tools activity table. */
65
+ toolCallsPage(query: GovernancePageQuery<ToolCallWhere>): Promise<GovernancePage<ToolCallActivityRow>>;
66
+ /** Paged, filterable runs for the Reliability recent-runs table. */
67
+ runsPage(query: GovernancePageQuery<RunWhere>): Promise<GovernancePage<RecentRunRow>>;
64
68
  /** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */
65
69
  pendingApprovals(limit: number): Promise<PendingApprovalRow[]>;
66
70
  /** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */
@@ -76,6 +80,11 @@ declare class DashboardService {
76
80
  decideApproval(toolCallId: string, body: unknown, executedByRef: string | undefined): Promise<void>;
77
81
  /** Most recent threads with rolled-up message/token counts. */
78
82
  recentThreads(limit: number): Promise<ThreadActivityRowWithLabel[]>;
83
+ /**
84
+ * Paged, filterable threads for the Runs & tools threads table — actor-labeled the same way
85
+ * {@link recentThreads} is.
86
+ */
87
+ threadsPage(query: GovernancePageQuery<ThreadWhere>): Promise<GovernancePage<ThreadActivityRowWithLabel>>;
79
88
  /**
80
89
  * Decorate actor-scoped rows with `actorLabel`, batching the distinct `actorRef`s into ONE
81
90
  * {@link ActorDirectory.resolveDisplay} call per response. `null` for every row when no directory
@@ -117,8 +126,20 @@ declare class AgentApiController {
117
126
  reliability(from?: string, to?: string): Promise<ReliabilityOverview>;
118
127
  /** Most recent runs (default 50, max 200) for the Reliability recent-runs table. */
119
128
  runs(limit?: string): Promise<RecentRunRow[]>;
129
+ /**
130
+ * Paged, filterable runs for the Reliability recent-runs table. `page` (default 1), `limit`
131
+ * (default 25, max 200), and `where[agentName]`/`where[status]`/`where[errorCode]`/
132
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
133
+ */
134
+ runsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<RecentRunRow>>;
120
135
  /** Most recent tool calls (default 50, max 200) for the activity feed. */
121
136
  toolCalls(limit?: string): Promise<ToolCallActivityRow[]>;
137
+ /**
138
+ * Paged, filterable tool calls for the Runs & tools activity table. `page` (default 1), `limit`
139
+ * (default 25, max 200), and `where[toolName]`/`where[toolType]`/`where[status]`/`where[threadId]`/
140
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
141
+ */
142
+ toolCallsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<ToolCallActivityRow>>;
122
143
  /** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */
123
144
  approvals(limit?: string): Promise<PendingApprovalRow[]>;
124
145
  /**
@@ -139,6 +160,12 @@ declare class AgentApiController {
139
160
  tools(from?: string, to?: string): Promise<ToolStatRow[]>;
140
161
  /** Most recent threads (default 50, max 200) with rolled-up counts. */
141
162
  threads(limit?: string): Promise<ThreadActivityRowWithLabel[]>;
163
+ /**
164
+ * Paged, filterable threads for the Runs & tools threads table. `page` (default 1), `limit`
165
+ * (default 25, max 200), and `where[actorRef]`/`where[title]` (case-insensitive substring)/
166
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
167
+ */
168
+ threadsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<ThreadActivityRowWithLabel>>;
142
169
  /**
143
170
  * Current price row per model, for the pricing tab. 501s (via `DashboardService.listPrices`) when
144
171
  * no `AGENT_PRICING_STORE` is bound.