@dudousxd/nestjs-agent-dashboard 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/agent-client.d.ts +40 -0
- package/dist/client/agent-client.d.ts.map +1 -1
- package/dist/client/agent-client.js +23 -0
- package/dist/client/agent-client.js.map +1 -1
- package/dist/server/index.cjs +235 -63
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +105 -8
- package/dist/server/index.d.ts +105 -8
- package/dist/server/index.js +188 -19
- package/dist/server/index.js.map +1 -1
- package/dist/spa/assets/index-BjIcXlBO.js +1 -0
- package/dist/spa/assets/index-CES4RqI4.js +49 -0
- package/dist/spa/assets/index-CVcgbps5.css +1 -0
- package/dist/spa/assets/preview-A3jHlGra.js +1 -0
- package/dist/spa/index.html +3 -3
- package/dist/spa/preview.html +3 -3
- package/package.json +2 -2
- package/dist/spa/assets/index-BiQzj2IF.js +0 -49
- package/dist/spa/assets/index-C-8Bnj9D.css +0 -1
- package/dist/spa/assets/index-CHzfsa2R.js +0 -1
- package/dist/spa/assets/preview-Dh9sCp0L.js +0 -1
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActorSpendRow, AgentGovernanceQueries, ActorDirectory, AgentPricingStore, GovernanceRange, ModelSpendRow, UsageTrendPoint, ThreadSpendRow, RunMetrics, RunAgentBreakdownRow, RunErrorBreakdownRow, RunTrendPoint, RecentRunRow, ToolCallActivityRow, ThreadActivityRow, CurrentModelPrice, ModelPriceInput } from '@dudousxd/nestjs-agent-core';
|
|
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';
|
|
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';
|
|
@@ -49,7 +49,8 @@ declare class DashboardService {
|
|
|
49
49
|
private readonly queries;
|
|
50
50
|
private readonly actorDirectory?;
|
|
51
51
|
private readonly pricingStore?;
|
|
52
|
-
|
|
52
|
+
private readonly approvalPort?;
|
|
53
|
+
constructor(queries: AgentGovernanceQueries, actorDirectory?: ActorDirectory | undefined, pricingStore?: AgentPricingStore | undefined, approvalPort?: AgentApprovalPort | undefined);
|
|
53
54
|
/** Spend/usage overview for a day range: by-model + by-actor spend and the daily trend, in parallel. */
|
|
54
55
|
spend(range: GovernanceRange): Promise<SpendOverview>;
|
|
55
56
|
/** Top threads by cost for a day range (default 10, highest cost first). */
|
|
@@ -60,6 +61,19 @@ declare class DashboardService {
|
|
|
60
61
|
recentRuns(limit: number): Promise<RecentRunRow[]>;
|
|
61
62
|
/** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */
|
|
62
63
|
recentToolCalls(limit: number): Promise<ToolCallActivityRow[]>;
|
|
64
|
+
/** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */
|
|
65
|
+
pendingApprovals(limit: number): Promise<PendingApprovalRow[]>;
|
|
66
|
+
/** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */
|
|
67
|
+
toolStats(range: GovernanceRange): Promise<ToolStatRow[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Decide a pending HITL tool call from the console. Routes through the OPTIONAL
|
|
70
|
+
* {@link AGENT_APPROVAL_PORT} — bound by `@dudousxd/nestjs-agent` to the SAME signal path chat
|
|
71
|
+
* approvals use — so a 501 here (checked BEFORE body validation, same ordering as
|
|
72
|
+
* {@link upsertPrice}) means "no approval port bound", not "your body was invalid". `executedByRef`
|
|
73
|
+
* is an OPAQUE decider ref the caller resolved from the live request (see
|
|
74
|
+
* `AgentDashboardOptions.approvalActorRef`); omitted when the host didn't configure one.
|
|
75
|
+
*/
|
|
76
|
+
decideApproval(toolCallId: string, body: unknown, executedByRef: string | undefined): Promise<void>;
|
|
63
77
|
/** Most recent threads with rolled-up message/token counts. */
|
|
64
78
|
recentThreads(limit: number): Promise<ThreadActivityRowWithLabel[]>;
|
|
65
79
|
/**
|
|
@@ -92,7 +106,9 @@ declare class DashboardService {
|
|
|
92
106
|
*/
|
|
93
107
|
declare class AgentApiController {
|
|
94
108
|
private readonly dashboard;
|
|
95
|
-
|
|
109
|
+
private readonly approvalActorRef;
|
|
110
|
+
private readonly actorResolver?;
|
|
111
|
+
constructor(dashboard: DashboardService, approvalActorRef: ((req: unknown) => string | undefined) | undefined, actorResolver?: ActorResolver | undefined);
|
|
96
112
|
/** `{ byModel, byActor, trend }` for a day range (defaults to the last 30 days). */
|
|
97
113
|
spend(from?: string, to?: string): Promise<SpendOverview>;
|
|
98
114
|
/** Top threads by cost (default 10, max 200) for a day range (defaults to the last 30 days). */
|
|
@@ -103,6 +119,24 @@ declare class AgentApiController {
|
|
|
103
119
|
runs(limit?: string): Promise<RecentRunRow[]>;
|
|
104
120
|
/** Most recent tool calls (default 50, max 200) for the activity feed. */
|
|
105
121
|
toolCalls(limit?: string): Promise<ToolCallActivityRow[]>;
|
|
122
|
+
/** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */
|
|
123
|
+
approvals(limit?: string): Promise<PendingApprovalRow[]>;
|
|
124
|
+
/**
|
|
125
|
+
* Decide a pending HITL tool call. Body `{ approved: boolean; reason?: string }`. 501s (via
|
|
126
|
+
* `DashboardService.decideApproval`) when no `AGENT_APPROVAL_PORT` is bound. `executedByRef`
|
|
127
|
+
* comes from {@link deciderRef} run against the live request.
|
|
128
|
+
*/
|
|
129
|
+
decideApproval(toolCallId: string, body: unknown, req: unknown): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* WHO decided, as an opaque ref: an explicit `approvalActorRef` override wins outright (no
|
|
132
|
+
* resolver fallback, even when it returns `undefined`); otherwise the AgentModule-configured
|
|
133
|
+
* actor resolver — the same identity seam chat requests use. A resolver that throws is an
|
|
134
|
+
* unauthenticated/unreadable request, not an error to surface: the decision itself was already
|
|
135
|
+
* authorized by the dashboard's guards, so the ref is simply omitted.
|
|
136
|
+
*/
|
|
137
|
+
private deciderRef;
|
|
138
|
+
/** Per-tool call/failure/rejection/latency rollup for a day range (defaults to the last 30 days). */
|
|
139
|
+
tools(from?: string, to?: string): Promise<ToolStatRow[]>;
|
|
106
140
|
/** Most recent threads (default 50, max 200) with rolled-up counts. */
|
|
107
141
|
threads(limit?: string): Promise<ThreadActivityRowWithLabel[]>;
|
|
108
142
|
/**
|
|
@@ -153,7 +187,13 @@ interface AgentDashboardMountPathsOptions {
|
|
|
153
187
|
*/
|
|
154
188
|
declare function agentDashboardMountPaths(options?: AgentDashboardMountPathsOptions): string[];
|
|
155
189
|
|
|
156
|
-
|
|
190
|
+
/**
|
|
191
|
+
* `TReq` mirrors core's `ActorResolver<TReq>` convention: the transport request type
|
|
192
|
+
* {@link AgentDashboardOptions.approvalActorRef} receives. Defaults to `unknown` so untyped usage
|
|
193
|
+
* compiles unchanged; a host may narrow it (e.g. `forRoot<Request>({ approvalActorRef: ... })`)
|
|
194
|
+
* instead of writing its own `unknown`-narrowing type guard.
|
|
195
|
+
*/
|
|
196
|
+
interface AgentDashboardOptions<TReq = unknown> {
|
|
157
197
|
/**
|
|
158
198
|
* Where the SPA (UI) is served. Default `/ai-gateway`. This is a page route — keep it out of an
|
|
159
199
|
* `/api` prefix so it reads as a UI, not an endpoint.
|
|
@@ -182,6 +222,18 @@ interface AgentDashboardOptions {
|
|
|
182
222
|
* host's own auth module, e.g. `imports: [AuthModule]` alongside `guards: [JwtAuthGuard]`.
|
|
183
223
|
*/
|
|
184
224
|
imports?: DynamicModule['imports'];
|
|
225
|
+
/**
|
|
226
|
+
* OVERRIDE for WHO is deciding a HITL approval — invoked on the incoming
|
|
227
|
+
* `POST <api>/approvals/:toolCallId` request to stamp `AgentApprovalPort`'s `opts.executedByRef`.
|
|
228
|
+
*
|
|
229
|
+
* DEFAULT (option omitted): the AgentModule-configured actor resolver (`AGENT_ACTOR_RESOLVER`) is
|
|
230
|
+
* consulted — the same identity seam chat requests use — and its actor id becomes the decider ref
|
|
231
|
+
* (a throwing resolver, i.e. an unauthenticated request, just omits the ref). Set this only when
|
|
232
|
+
* console auth differs from chat auth (e.g. the console sits behind a separate SSO whose principal
|
|
233
|
+
* the chat resolver can't read); returning `undefined` leaves `executedByRef` unset — the explicit
|
|
234
|
+
* override wins outright, with no resolver fallback.
|
|
235
|
+
*/
|
|
236
|
+
approvalActorRef?: (req: TReq) => string | undefined;
|
|
185
237
|
}
|
|
186
238
|
/**
|
|
187
239
|
* Holds the JSON API + SSE controller and its read service, mounted on its own path by `forRoot`.
|
|
@@ -192,9 +244,10 @@ interface AgentDashboardOptions {
|
|
|
192
244
|
* the right `imports` to `forRoot`.
|
|
193
245
|
*/
|
|
194
246
|
declare class AgentApiModule {
|
|
195
|
-
static register(options: {
|
|
247
|
+
static register<TReq = unknown>(options: {
|
|
196
248
|
imports?: DynamicModule['imports'];
|
|
197
249
|
guards?: Type<CanActivate>[];
|
|
250
|
+
approvalActorRef?: (req: TReq) => string | undefined;
|
|
198
251
|
}): DynamicModule;
|
|
199
252
|
}
|
|
200
253
|
/**
|
|
@@ -205,11 +258,31 @@ declare class AgentApiModule {
|
|
|
205
258
|
* (global), which must provide `AGENT_GOVERNANCE_QUERIES` (bound by a store adapter). Front the
|
|
206
259
|
* routes with the first-class `guards` option (plus `imports` for the guards' own dependencies) —
|
|
207
260
|
* see {@link AgentDashboardOptions.guards}.
|
|
261
|
+
*
|
|
262
|
+
* Inertia hosts: the console is a full-page app, not an Inertia page. An in-app `<Link>` visit to
|
|
263
|
+
* `basePath` (an XHR carrying `X-Inertia`) is bounced with the protocol's own external-redirect
|
|
264
|
+
* mechanism — `409 Conflict` + `X-Inertia-Location: <the visited URL>` — so the Inertia client
|
|
265
|
+
* performs a full `window.location` load and the console renders normally. In-app links to the
|
|
266
|
+
* console therefore just work; no host-side special-casing needed.
|
|
208
267
|
*/
|
|
209
268
|
declare class AgentDashboardModule {
|
|
210
|
-
static forRoot(options?: AgentDashboardOptions): DynamicModule;
|
|
269
|
+
static forRoot<TReq = unknown>(options?: AgentDashboardOptions<TReq>): DynamicModule;
|
|
211
270
|
}
|
|
212
271
|
|
|
272
|
+
/**
|
|
273
|
+
* The slice of the express request the Inertia bounce below reads — structural, so the package
|
|
274
|
+
* needs no express dependency (the API controller similarly takes `@Req() req: unknown`).
|
|
275
|
+
*/
|
|
276
|
+
interface UiPageRequest {
|
|
277
|
+
headers: Record<string, string | string[] | undefined>;
|
|
278
|
+
/** Path + query as received (express `originalUrl`) — what the full-page visit must reload. */
|
|
279
|
+
originalUrl: string;
|
|
280
|
+
}
|
|
281
|
+
/** The slice of the express response the Inertia bounce writes (passthrough mode — Nest still sends). */
|
|
282
|
+
interface UiPageResponse {
|
|
283
|
+
status(code: number): unknown;
|
|
284
|
+
setHeader(name: string, value: string): unknown;
|
|
285
|
+
}
|
|
213
286
|
/**
|
|
214
287
|
* Serves the bundled AI-gateway console SPA at the configured base (+ hashed assets at
|
|
215
288
|
* `<base>/assets`). The path comes from `RouterModule` (set by
|
|
@@ -220,7 +293,7 @@ declare class AgentUiController {
|
|
|
220
293
|
private readonly apiBasePath;
|
|
221
294
|
private readonly dir;
|
|
222
295
|
constructor(basePath: string, apiBasePath: string);
|
|
223
|
-
index(): string;
|
|
296
|
+
index(req: UiPageRequest, res: UiPageResponse): string;
|
|
224
297
|
asset(file: string): StreamableFile;
|
|
225
298
|
}
|
|
226
299
|
|
|
@@ -260,9 +333,33 @@ declare const AGENT_ACTOR_DIRECTORY: unique symbol;
|
|
|
260
333
|
* tab/endpoints 501 with a clear message when nothing is bound.
|
|
261
334
|
*/
|
|
262
335
|
declare const AGENT_PRICING_STORE: unique symbol;
|
|
336
|
+
/**
|
|
337
|
+
* Console-side HITL decisions (`AgentApprovalPort`), owned by `@dudousxd/nestjs-agent-core`.
|
|
338
|
+
* Re-declared here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional:
|
|
339
|
+
* bound by `@dudousxd/nestjs-agent` (which adapts it to the same signal path chat approvals use) —
|
|
340
|
+
* absent, `POST <api>/approvals/:toolCallId` 501s and the approvals inbox renders read-only.
|
|
341
|
+
*/
|
|
342
|
+
declare const AGENT_APPROVAL_PORT: unique symbol;
|
|
343
|
+
/**
|
|
344
|
+
* The app's request-identity seam (`ActorResolver`), owned by `@dudousxd/nestjs-agent-core` and
|
|
345
|
+
* bound + exported by the (global) `AgentModule` from the host's `actorResolver` option. Re-declared
|
|
346
|
+
* here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional: the default
|
|
347
|
+
* decider attribution for `POST <api>/approvals/:toolCallId` — absent (or throwing, i.e.
|
|
348
|
+
* unauthenticated), `executedByRef` is simply omitted.
|
|
349
|
+
*/
|
|
350
|
+
declare const AGENT_ACTOR_RESOLVER: unique symbol;
|
|
263
351
|
/** DI token carrying the UI mount base (e.g. `/ai-gateway`). */
|
|
264
352
|
declare const DASHBOARD_BASE_PATH: unique symbol;
|
|
265
353
|
/** DI token carrying the JSON API base the SPA fetches from (e.g. `/ai-gateway/api`). */
|
|
266
354
|
declare const DASHBOARD_API_PATH: unique symbol;
|
|
355
|
+
/**
|
|
356
|
+
* DI token carrying the host-provided {@link AgentDashboardOptions.approvalActorRef} extractor (or
|
|
357
|
+
* `undefined` when the host didn't set one — the controller then falls back to
|
|
358
|
+
* {@link AGENT_ACTOR_RESOLVER} for decider attribution). Threaded to `AgentApiModule` (where the API
|
|
359
|
+
* controller actually lives) same as the pattern above — `useValue` even when `undefined`, so
|
|
360
|
+
* injecting it needs no `@Optional()` (mirrors `AGENT_QUOTA_STORE`'s factory in
|
|
361
|
+
* `@dudousxd/nestjs-agent`).
|
|
362
|
+
*/
|
|
363
|
+
declare const DASHBOARD_APPROVAL_ACTOR_REF: unique symbol;
|
|
267
364
|
|
|
268
|
-
export { AGENT_ACTOR_DIRECTORY, AGENT_GOVERNANCE_QUERIES, AGENT_PRICING_STORE, type ActorSpendRowWithLabel, AgentApiController, AgentApiModule, AgentDashboardModule, type AgentDashboardMountPathsOptions, type AgentDashboardOptions, AgentUiController, DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, DashboardService, type LiveAgentEvent, type ReliabilityOverview, type SpendOverview, type ThreadActivityRowWithLabel, type ThreadSpendRowWithLabel, type WithActorLabel, agentDashboardMountPaths, parsePriceInput };
|
|
365
|
+
export { AGENT_ACTOR_DIRECTORY, AGENT_ACTOR_RESOLVER, AGENT_APPROVAL_PORT, AGENT_GOVERNANCE_QUERIES, AGENT_PRICING_STORE, type ActorSpendRowWithLabel, AgentApiController, AgentApiModule, AgentDashboardModule, type AgentDashboardMountPathsOptions, type AgentDashboardOptions, AgentUiController, DASHBOARD_API_PATH, DASHBOARD_APPROVAL_ACTOR_REF, DASHBOARD_BASE_PATH, DashboardService, type LiveAgentEvent, type ReliabilityOverview, type SpendOverview, type ThreadActivityRowWithLabel, type ThreadSpendRowWithLabel, type WithActorLabel, agentDashboardMountPaths, parsePriceInput };
|
package/dist/server/index.js
CHANGED
|
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
4
|
// src/server/agent-api.controller.ts
|
|
5
|
-
import { Body, Controller, Get, Post, Query, Sse } from "@nestjs/common";
|
|
5
|
+
import { Body, Controller, Get, HttpCode, Inject as Inject2, Optional as Optional2, Param, Post, Query, Req, Sse } from "@nestjs/common";
|
|
6
6
|
|
|
7
7
|
// src/server/dashboard.service.ts
|
|
8
8
|
import { subscribe, unsubscribe } from "node:diagnostics_channel";
|
|
@@ -10,27 +10,49 @@ import { channelName } from "@dudousxd/nestjs-diagnostics";
|
|
|
10
10
|
import { Inject, Injectable, NotImplementedException, Optional } from "@nestjs/common";
|
|
11
11
|
import { Observable as Observable2 } from "rxjs";
|
|
12
12
|
|
|
13
|
-
// src/server/parse-
|
|
13
|
+
// src/server/parse-approval-decision.ts
|
|
14
14
|
import { BadRequestException } from "@nestjs/common";
|
|
15
|
-
function
|
|
15
|
+
function parseApprovalDecision(body) {
|
|
16
16
|
if (typeof body !== "object" || body === null) {
|
|
17
17
|
throw new BadRequestException("Expected a JSON object body.");
|
|
18
18
|
}
|
|
19
|
+
const { approved, reason } = body;
|
|
20
|
+
if (typeof approved !== "boolean") {
|
|
21
|
+
throw new BadRequestException('"approved" must be a boolean.');
|
|
22
|
+
}
|
|
23
|
+
if (reason !== void 0 && typeof reason !== "string") {
|
|
24
|
+
throw new BadRequestException('"reason" must be a string when present.');
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
approved,
|
|
28
|
+
...reason !== void 0 ? {
|
|
29
|
+
reason
|
|
30
|
+
} : {}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
__name(parseApprovalDecision, "parseApprovalDecision");
|
|
34
|
+
|
|
35
|
+
// src/server/parse-price-input.ts
|
|
36
|
+
import { BadRequestException as BadRequestException2 } from "@nestjs/common";
|
|
37
|
+
function parsePriceInput(body) {
|
|
38
|
+
if (typeof body !== "object" || body === null) {
|
|
39
|
+
throw new BadRequestException2("Expected a JSON object body.");
|
|
40
|
+
}
|
|
19
41
|
const { modelId, inputPricePer1m, outputPricePer1m, cacheWritePricePer1m, cacheReadPricePer1m } = body;
|
|
20
42
|
if (typeof modelId !== "string" || modelId.trim().length === 0) {
|
|
21
|
-
throw new
|
|
43
|
+
throw new BadRequestException2('"modelId" must be a non-empty string.');
|
|
22
44
|
}
|
|
23
45
|
if (!isFiniteNonNegative(inputPricePer1m)) {
|
|
24
|
-
throw new
|
|
46
|
+
throw new BadRequestException2('"inputPricePer1m" must be a non-negative number.');
|
|
25
47
|
}
|
|
26
48
|
if (!isFiniteNonNegative(outputPricePer1m)) {
|
|
27
|
-
throw new
|
|
49
|
+
throw new BadRequestException2('"outputPricePer1m" must be a non-negative number.');
|
|
28
50
|
}
|
|
29
51
|
if (cacheWritePricePer1m !== void 0 && !isFiniteNonNegative(cacheWritePricePer1m)) {
|
|
30
|
-
throw new
|
|
52
|
+
throw new BadRequestException2('"cacheWritePricePer1m" must be a non-negative number when present.');
|
|
31
53
|
}
|
|
32
54
|
if (cacheReadPricePer1m !== void 0 && !isFiniteNonNegative(cacheReadPricePer1m)) {
|
|
33
|
-
throw new
|
|
55
|
+
throw new BadRequestException2('"cacheReadPricePer1m" must be a non-negative number when present.');
|
|
34
56
|
}
|
|
35
57
|
return {
|
|
36
58
|
modelId,
|
|
@@ -54,8 +76,11 @@ __name(isFiniteNonNegative, "isFiniteNonNegative");
|
|
|
54
76
|
var AGENT_GOVERNANCE_QUERIES = Symbol.for("@dudousxd/nestjs-agent:governance-queries");
|
|
55
77
|
var AGENT_ACTOR_DIRECTORY = Symbol.for("@dudousxd/nestjs-agent:actor-directory");
|
|
56
78
|
var AGENT_PRICING_STORE = Symbol.for("@dudousxd/nestjs-agent:pricing-store");
|
|
79
|
+
var AGENT_APPROVAL_PORT = Symbol.for("@dudousxd/nestjs-agent:approval-port");
|
|
80
|
+
var AGENT_ACTOR_RESOLVER = Symbol.for("@dudousxd/nestjs-agent:actor-resolver");
|
|
57
81
|
var DASHBOARD_BASE_PATH = Symbol.for("@dudousxd/nestjs-agent-dashboard:base-path");
|
|
58
82
|
var DASHBOARD_API_PATH = Symbol.for("@dudousxd/nestjs-agent-dashboard:api-path");
|
|
83
|
+
var DASHBOARD_APPROVAL_ACTOR_REF = Symbol.for("@dudousxd/nestjs-agent-dashboard:approval-actor-ref");
|
|
59
84
|
|
|
60
85
|
// src/server/dashboard.service.ts
|
|
61
86
|
function _ts_decorate(decorators, target, key, desc) {
|
|
@@ -76,6 +101,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
76
101
|
}
|
|
77
102
|
__name(_ts_param, "_ts_param");
|
|
78
103
|
var PRICING_STORE_UNBOUND_MESSAGE = "Pricing CRUD is unavailable: no AGENT_PRICING_STORE is bound. Bind a pricing store (e.g. MikroOrmPricingStore from @dudousxd/nestjs-agent-store-mikro-orm) to enable it.";
|
|
104
|
+
var APPROVAL_PORT_UNBOUND_MESSAGE = "Approve/reject is unavailable: no AGENT_APPROVAL_PORT is bound. Import AgentModule from @dudousxd/nestjs-agent alongside this dashboard to enable it \u2014 the approvals inbox stays read-only until then.";
|
|
79
105
|
var AGENT_EVENTS = [
|
|
80
106
|
"run.started",
|
|
81
107
|
"message",
|
|
@@ -95,10 +121,12 @@ var DashboardService = class {
|
|
|
95
121
|
queries;
|
|
96
122
|
actorDirectory;
|
|
97
123
|
pricingStore;
|
|
98
|
-
|
|
124
|
+
approvalPort;
|
|
125
|
+
constructor(queries, actorDirectory, pricingStore, approvalPort) {
|
|
99
126
|
this.queries = queries;
|
|
100
127
|
this.actorDirectory = actorDirectory;
|
|
101
128
|
this.pricingStore = pricingStore;
|
|
129
|
+
this.approvalPort = approvalPort;
|
|
102
130
|
}
|
|
103
131
|
/** Spend/usage overview for a day range: by-model + by-actor spend and the daily trend, in parallel. */
|
|
104
132
|
async spend(range) {
|
|
@@ -142,6 +170,41 @@ var DashboardService = class {
|
|
|
142
170
|
recentToolCalls(limit) {
|
|
143
171
|
return this.queries.recentToolCalls(limit);
|
|
144
172
|
}
|
|
173
|
+
/** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */
|
|
174
|
+
pendingApprovals(limit) {
|
|
175
|
+
return this.queries.pendingApprovals(limit);
|
|
176
|
+
}
|
|
177
|
+
/** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */
|
|
178
|
+
toolStats(range) {
|
|
179
|
+
return this.queries.toolStats(range);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Decide a pending HITL tool call from the console. Routes through the OPTIONAL
|
|
183
|
+
* {@link AGENT_APPROVAL_PORT} — bound by `@dudousxd/nestjs-agent` to the SAME signal path chat
|
|
184
|
+
* approvals use — so a 501 here (checked BEFORE body validation, same ordering as
|
|
185
|
+
* {@link upsertPrice}) means "no approval port bound", not "your body was invalid". `executedByRef`
|
|
186
|
+
* is an OPAQUE decider ref the caller resolved from the live request (see
|
|
187
|
+
* `AgentDashboardOptions.approvalActorRef`); omitted when the host didn't configure one.
|
|
188
|
+
*/
|
|
189
|
+
async decideApproval(toolCallId, body, executedByRef) {
|
|
190
|
+
if (this.approvalPort === void 0) {
|
|
191
|
+
throw new NotImplementedException(APPROVAL_PORT_UNBOUND_MESSAGE);
|
|
192
|
+
}
|
|
193
|
+
const decision = parseApprovalDecision(body);
|
|
194
|
+
const opts = executedByRef !== void 0 ? {
|
|
195
|
+
executedByRef
|
|
196
|
+
} : {};
|
|
197
|
+
if (decision.approved) {
|
|
198
|
+
await this.approvalPort.approve(toolCallId, opts);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
await this.approvalPort.reject(toolCallId, {
|
|
202
|
+
...opts,
|
|
203
|
+
...decision.reason !== void 0 ? {
|
|
204
|
+
reason: decision.reason
|
|
205
|
+
} : {}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
145
208
|
/** Most recent threads with rolled-up message/token counts. */
|
|
146
209
|
async recentThreads(limit) {
|
|
147
210
|
const rows = await this.queries.recentThreads(limit);
|
|
@@ -227,11 +290,14 @@ DashboardService = _ts_decorate([
|
|
|
227
290
|
_ts_param(1, Inject(AGENT_ACTOR_DIRECTORY)),
|
|
228
291
|
_ts_param(2, Optional()),
|
|
229
292
|
_ts_param(2, Inject(AGENT_PRICING_STORE)),
|
|
293
|
+
_ts_param(3, Optional()),
|
|
294
|
+
_ts_param(3, Inject(AGENT_APPROVAL_PORT)),
|
|
230
295
|
_ts_metadata("design:type", Function),
|
|
231
296
|
_ts_metadata("design:paramtypes", [
|
|
232
297
|
typeof AgentGovernanceQueries === "undefined" ? Object : AgentGovernanceQueries,
|
|
233
298
|
typeof ActorDirectory === "undefined" ? Object : ActorDirectory,
|
|
234
|
-
typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore
|
|
299
|
+
typeof AgentPricingStore === "undefined" ? Object : AgentPricingStore,
|
|
300
|
+
typeof AgentApprovalPort === "undefined" ? Object : AgentApprovalPort
|
|
235
301
|
])
|
|
236
302
|
], DashboardService);
|
|
237
303
|
|
|
@@ -281,8 +347,12 @@ var AgentApiController = class {
|
|
|
281
347
|
__name(this, "AgentApiController");
|
|
282
348
|
}
|
|
283
349
|
dashboard;
|
|
284
|
-
|
|
350
|
+
approvalActorRef;
|
|
351
|
+
actorResolver;
|
|
352
|
+
constructor(dashboard, approvalActorRef, actorResolver) {
|
|
285
353
|
this.dashboard = dashboard;
|
|
354
|
+
this.approvalActorRef = approvalActorRef;
|
|
355
|
+
this.actorResolver = actorResolver;
|
|
286
356
|
}
|
|
287
357
|
/** `{ byModel, byActor, trend }` for a day range (defaults to the last 30 days). */
|
|
288
358
|
spend(from, to) {
|
|
@@ -304,6 +374,42 @@ var AgentApiController = class {
|
|
|
304
374
|
toolCalls(limit) {
|
|
305
375
|
return this.dashboard.recentToolCalls(parseLimit(limit, 50));
|
|
306
376
|
}
|
|
377
|
+
/** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */
|
|
378
|
+
approvals(limit) {
|
|
379
|
+
return this.dashboard.pendingApprovals(parseLimit(limit, 50));
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Decide a pending HITL tool call. Body `{ approved: boolean; reason?: string }`. 501s (via
|
|
383
|
+
* `DashboardService.decideApproval`) when no `AGENT_APPROVAL_PORT` is bound. `executedByRef`
|
|
384
|
+
* comes from {@link deciderRef} run against the live request.
|
|
385
|
+
*/
|
|
386
|
+
async decideApproval(toolCallId, body, req) {
|
|
387
|
+
await this.dashboard.decideApproval(toolCallId, body, await this.deciderRef(req));
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* WHO decided, as an opaque ref: an explicit `approvalActorRef` override wins outright (no
|
|
391
|
+
* resolver fallback, even when it returns `undefined`); otherwise the AgentModule-configured
|
|
392
|
+
* actor resolver — the same identity seam chat requests use. A resolver that throws is an
|
|
393
|
+
* unauthenticated/unreadable request, not an error to surface: the decision itself was already
|
|
394
|
+
* authorized by the dashboard's guards, so the ref is simply omitted.
|
|
395
|
+
*/
|
|
396
|
+
async deciderRef(req) {
|
|
397
|
+
if (this.approvalActorRef !== void 0) {
|
|
398
|
+
return this.approvalActorRef(req);
|
|
399
|
+
}
|
|
400
|
+
if (this.actorResolver === void 0) {
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
return (await this.actorResolver.resolve(req)).id;
|
|
405
|
+
} catch {
|
|
406
|
+
return void 0;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/** Per-tool call/failure/rejection/latency rollup for a day range (defaults to the last 30 days). */
|
|
410
|
+
tools(from, to) {
|
|
411
|
+
return this.dashboard.toolStats(resolveRange(from, to));
|
|
412
|
+
}
|
|
307
413
|
/** Most recent threads (default 50, max 200) with rolled-up counts. */
|
|
308
414
|
threads(limit) {
|
|
309
415
|
return this.dashboard.recentThreads(parseLimit(limit, 50));
|
|
@@ -381,6 +487,40 @@ _ts_decorate2([
|
|
|
381
487
|
]),
|
|
382
488
|
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
383
489
|
], AgentApiController.prototype, "toolCalls", null);
|
|
490
|
+
_ts_decorate2([
|
|
491
|
+
Get("approvals"),
|
|
492
|
+
_ts_param2(0, Query("limit")),
|
|
493
|
+
_ts_metadata2("design:type", Function),
|
|
494
|
+
_ts_metadata2("design:paramtypes", [
|
|
495
|
+
String
|
|
496
|
+
]),
|
|
497
|
+
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
498
|
+
], AgentApiController.prototype, "approvals", null);
|
|
499
|
+
_ts_decorate2([
|
|
500
|
+
Post("approvals/:toolCallId"),
|
|
501
|
+
HttpCode(204),
|
|
502
|
+
_ts_param2(0, Param("toolCallId")),
|
|
503
|
+
_ts_param2(1, Body()),
|
|
504
|
+
_ts_param2(2, Req()),
|
|
505
|
+
_ts_metadata2("design:type", Function),
|
|
506
|
+
_ts_metadata2("design:paramtypes", [
|
|
507
|
+
String,
|
|
508
|
+
Object,
|
|
509
|
+
Object
|
|
510
|
+
]),
|
|
511
|
+
_ts_metadata2("design:returntype", Promise)
|
|
512
|
+
], AgentApiController.prototype, "decideApproval", null);
|
|
513
|
+
_ts_decorate2([
|
|
514
|
+
Get("tools"),
|
|
515
|
+
_ts_param2(0, Query("from")),
|
|
516
|
+
_ts_param2(1, Query("to")),
|
|
517
|
+
_ts_metadata2("design:type", Function),
|
|
518
|
+
_ts_metadata2("design:paramtypes", [
|
|
519
|
+
String,
|
|
520
|
+
String
|
|
521
|
+
]),
|
|
522
|
+
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
523
|
+
], AgentApiController.prototype, "tools", null);
|
|
384
524
|
_ts_decorate2([
|
|
385
525
|
Get("threads"),
|
|
386
526
|
_ts_param2(0, Query("limit")),
|
|
@@ -413,9 +553,14 @@ _ts_decorate2([
|
|
|
413
553
|
], AgentApiController.prototype, "stream", null);
|
|
414
554
|
AgentApiController = _ts_decorate2([
|
|
415
555
|
Controller(),
|
|
556
|
+
_ts_param2(1, Inject2(DASHBOARD_APPROVAL_ACTOR_REF)),
|
|
557
|
+
_ts_param2(2, Optional2()),
|
|
558
|
+
_ts_param2(2, Inject2(AGENT_ACTOR_RESOLVER)),
|
|
416
559
|
_ts_metadata2("design:type", Function),
|
|
417
560
|
_ts_metadata2("design:paramtypes", [
|
|
418
|
-
typeof DashboardService === "undefined" ? Object : DashboardService
|
|
561
|
+
typeof DashboardService === "undefined" ? Object : DashboardService,
|
|
562
|
+
Object,
|
|
563
|
+
typeof ActorResolver === "undefined" ? Object : ActorResolver
|
|
419
564
|
])
|
|
420
565
|
], AgentApiController);
|
|
421
566
|
|
|
@@ -453,7 +598,7 @@ import { RouterModule } from "@nestjs/core";
|
|
|
453
598
|
import { existsSync, readFileSync } from "node:fs";
|
|
454
599
|
import { basename, extname, join, resolve, sep } from "node:path";
|
|
455
600
|
import { fileURLToPath } from "node:url";
|
|
456
|
-
import { Controller as Controller2, Get as Get2, Header, Inject as
|
|
601
|
+
import { Controller as Controller2, Get as Get2, Header, Inject as Inject3, NotFoundException, Param as Param2, Req as Req2, Res, StreamableFile } from "@nestjs/common";
|
|
457
602
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
458
603
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
459
604
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -498,7 +643,12 @@ var AgentUiController = class {
|
|
|
498
643
|
}
|
|
499
644
|
// index.html references hash-named bundles, so it MUST NOT be cached (stale bundle = the classic
|
|
500
645
|
// "stuck loading after a deploy"). The hashed assets below are immutable.
|
|
501
|
-
index() {
|
|
646
|
+
index(req, res) {
|
|
647
|
+
if (req.headers["x-inertia"] !== void 0) {
|
|
648
|
+
res.status(409);
|
|
649
|
+
res.setHeader("X-Inertia-Location", req.originalUrl);
|
|
650
|
+
return "";
|
|
651
|
+
}
|
|
502
652
|
const indexPath = join(this.dir, "index.html");
|
|
503
653
|
if (!existsSync(indexPath)) {
|
|
504
654
|
throw new NotFoundException("Dashboard is not built. Run the package build.");
|
|
@@ -525,14 +675,21 @@ _ts_decorate3([
|
|
|
525
675
|
Get2(),
|
|
526
676
|
Header("Content-Type", "text/html; charset=utf-8"),
|
|
527
677
|
Header("Cache-Control", "no-store, must-revalidate"),
|
|
678
|
+
_ts_param3(0, Req2()),
|
|
679
|
+
_ts_param3(1, Res({
|
|
680
|
+
passthrough: true
|
|
681
|
+
})),
|
|
528
682
|
_ts_metadata3("design:type", Function),
|
|
529
|
-
_ts_metadata3("design:paramtypes", [
|
|
683
|
+
_ts_metadata3("design:paramtypes", [
|
|
684
|
+
typeof UiPageRequest === "undefined" ? Object : UiPageRequest,
|
|
685
|
+
typeof UiPageResponse === "undefined" ? Object : UiPageResponse
|
|
686
|
+
]),
|
|
530
687
|
_ts_metadata3("design:returntype", String)
|
|
531
688
|
], AgentUiController.prototype, "index", null);
|
|
532
689
|
_ts_decorate3([
|
|
533
690
|
Get2("assets/:file"),
|
|
534
691
|
Header("Cache-Control", "public, max-age=31536000, immutable"),
|
|
535
|
-
_ts_param3(0,
|
|
692
|
+
_ts_param3(0, Param2("file")),
|
|
536
693
|
_ts_metadata3("design:type", Function),
|
|
537
694
|
_ts_metadata3("design:paramtypes", [
|
|
538
695
|
String
|
|
@@ -541,8 +698,8 @@ _ts_decorate3([
|
|
|
541
698
|
], AgentUiController.prototype, "asset", null);
|
|
542
699
|
AgentUiController = _ts_decorate3([
|
|
543
700
|
Controller2(),
|
|
544
|
-
_ts_param3(0,
|
|
545
|
-
_ts_param3(1,
|
|
701
|
+
_ts_param3(0, Inject3(DASHBOARD_BASE_PATH)),
|
|
702
|
+
_ts_param3(1, Inject3(DASHBOARD_API_PATH)),
|
|
546
703
|
_ts_metadata3("design:type", Function),
|
|
547
704
|
_ts_metadata3("design:paramtypes", [
|
|
548
705
|
String,
|
|
@@ -584,7 +741,13 @@ var AgentApiModule = class _AgentApiModule {
|
|
|
584
741
|
],
|
|
585
742
|
providers: [
|
|
586
743
|
DashboardService,
|
|
587
|
-
...options.guards ?? []
|
|
744
|
+
...options.guards ?? [],
|
|
745
|
+
// `useValue` even when `options.approvalActorRef` is `undefined` — AgentApiController
|
|
746
|
+
// injects this WITHOUT `@Optional()` (same pattern as `AGENT_QUOTA_STORE`'s factory).
|
|
747
|
+
{
|
|
748
|
+
provide: DASHBOARD_APPROVAL_ACTOR_REF,
|
|
749
|
+
useValue: options.approvalActorRef
|
|
750
|
+
}
|
|
588
751
|
],
|
|
589
752
|
exports: [
|
|
590
753
|
DashboardService
|
|
@@ -616,6 +779,9 @@ var AgentDashboardModule = class _AgentDashboardModule {
|
|
|
616
779
|
} : {},
|
|
617
780
|
...options.guards ? {
|
|
618
781
|
guards: options.guards
|
|
782
|
+
} : {},
|
|
783
|
+
...options.approvalActorRef ? {
|
|
784
|
+
approvalActorRef: options.approvalActorRef
|
|
619
785
|
} : {}
|
|
620
786
|
}),
|
|
621
787
|
RouterModule.register([
|
|
@@ -656,6 +822,8 @@ AgentDashboardModule = _ts_decorate4([
|
|
|
656
822
|
], AgentDashboardModule);
|
|
657
823
|
export {
|
|
658
824
|
AGENT_ACTOR_DIRECTORY,
|
|
825
|
+
AGENT_ACTOR_RESOLVER,
|
|
826
|
+
AGENT_APPROVAL_PORT,
|
|
659
827
|
AGENT_GOVERNANCE_QUERIES,
|
|
660
828
|
AGENT_PRICING_STORE,
|
|
661
829
|
AgentApiController,
|
|
@@ -663,6 +831,7 @@ export {
|
|
|
663
831
|
AgentDashboardModule,
|
|
664
832
|
AgentUiController,
|
|
665
833
|
DASHBOARD_API_PATH,
|
|
834
|
+
DASHBOARD_APPROVAL_ACTOR_REF,
|
|
666
835
|
DASHBOARD_BASE_PATH,
|
|
667
836
|
DashboardService,
|
|
668
837
|
agentDashboardMountPaths,
|