@opengeni/core 0.12.7 → 0.14.3

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.
Files changed (39) hide show
  1. package/dist/access/index.d.ts +22 -0
  2. package/dist/application/new-session-drafts.d.ts +14 -0
  3. package/dist/application/session-commands.d.ts +107 -0
  4. package/dist/billing/limits.d.ts +29 -0
  5. package/dist/dependencies.d.ts +137 -0
  6. package/dist/domain/capabilities.d.ts +62 -0
  7. package/dist/domain/environments.d.ts +33 -0
  8. package/dist/domain/insights.d.ts +11 -0
  9. package/dist/domain/packs.d.ts +27 -0
  10. package/dist/domain/resources.d.ts +32 -0
  11. package/dist/domain/scheduled-tasks.d.ts +72 -0
  12. package/dist/domain/session-tool-policy.d.ts +31 -0
  13. package/dist/domain/sessions.d.ts +256 -0
  14. package/dist/domain/slack-bot.d.ts +19 -0
  15. package/dist/domain/workspace-members.d.ts +34 -0
  16. package/dist/index.d.ts +23 -1189
  17. package/dist/index.js +758 -115
  18. package/dist/index.js.map +1 -1
  19. package/dist/managed-auth-type.d.ts +2 -0
  20. package/dist/rigs/index.d.ts +57 -0
  21. package/dist/sandbox/fleet.d.ts +197 -0
  22. package/dist/sandbox/routing.d.ts +55 -0
  23. package/dist/sandbox-types.d.ts +52 -0
  24. package/dist/session-authorization.d.ts +36 -0
  25. package/dist/transcription.d.ts +71 -0
  26. package/dist/workflow-wake-contract.d.ts +4 -0
  27. package/package.json +11 -11
  28. package/src/access/index.ts +73 -2
  29. package/src/application/new-session-drafts.ts +3 -0
  30. package/src/application/session-commands.ts +22 -9
  31. package/src/dependencies.ts +5 -0
  32. package/src/domain/insights.ts +480 -0
  33. package/src/domain/session-tool-policy.ts +22 -39
  34. package/src/domain/sessions.ts +140 -72
  35. package/src/domain/slack-bot.ts +2 -4
  36. package/src/index.ts +2 -0
  37. package/src/sandbox/fleet.ts +96 -33
  38. package/src/sandbox/routing.ts +29 -7
  39. package/src/transcription.ts +142 -0
@@ -7,6 +7,7 @@ import type {
7
7
  AccessGrant,
8
8
  SessionAuthorizationOperation,
9
9
  SessionAuthorizationPort,
10
+ SessionAuthorizationSurface,
10
11
  SessionCommandReceipt,
11
12
  SessionControlRequest,
12
13
  SessionControlResponse,
@@ -15,7 +16,7 @@ import type {
15
16
  WorkspaceInferenceControlRequest,
16
17
  WorkspaceInferenceControlResponse,
17
18
  } from "@opengeni/contracts";
18
- import { reasoningEffortForMetadata } from "@opengeni/contracts";
19
+ import { latencyModeForMetadata, reasoningEffortForMetadata } from "@opengeni/contracts";
19
20
  import {
20
21
  deleteSessionQueueItemInTransaction,
21
22
  editQueuedTurnInTransaction,
@@ -55,6 +56,8 @@ export type HumanSessionCommandContext = {
55
56
  workspaceId: string;
56
57
  sessionId: string;
57
58
  subjectId: string;
59
+ /** See AgentSessionCommandContext.authorizationSurface. */
60
+ authorizationSurface?: SessionAuthorizationSurface;
58
61
  };
59
62
 
60
63
  export type AgentSessionCommandContext = {
@@ -65,6 +68,13 @@ export type AgentSessionCommandContext = {
65
68
  callerTurnId: string;
66
69
  callerAttemptId: string;
67
70
  callerExecutionGeneration: number;
71
+ /**
72
+ * The trusted adapter surface that owns this command's one authorization
73
+ * decision. Direct core callers omit it and retain the canonical `core`
74
+ * surface; adapters that delegate the complete command set it explicitly so
75
+ * they do not authorize once at the edge and then repeat the host call here.
76
+ */
77
+ authorizationSurface?: SessionAuthorizationSurface;
68
78
  };
69
79
 
70
80
  type SessionAuthorizationCommandDeps = {
@@ -104,7 +114,7 @@ async function authorizeHumanSessionCommand(
104
114
  return await requireSessionAuthorization(deps, humanAccessGrant(context), {
105
115
  sessionId: context.sessionId,
106
116
  operation,
107
- surface: "core",
117
+ surface: context.authorizationSurface ?? "core",
108
118
  });
109
119
  }
110
120
 
@@ -117,7 +127,7 @@ async function authorizeAgentSessionCommand(
117
127
  return await requireSessionAuthorization(deps, agentAccessGrant(context), {
118
128
  sessionId: targetSessionId,
119
129
  operation,
120
- surface: "core",
130
+ surface: context.authorizationSurface ?? "core",
121
131
  });
122
132
  }
123
133
 
@@ -339,7 +349,12 @@ export async function controlAgentSessionWorkstream(
339
349
  reason?: string | null;
340
350
  },
341
351
  ) {
342
- await authorizeAgentSessionCommand(deps, context, input.targetSessionId, "session.control");
352
+ const authorization = await authorizeAgentSessionCommand(
353
+ deps,
354
+ context,
355
+ input.targetSessionId,
356
+ "session.control",
357
+ );
343
358
  const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
344
359
  scoped.transaction((tx) =>
345
360
  mutateSessionControlInTransaction(tx as unknown as Database, {
@@ -358,7 +373,7 @@ export async function controlAgentSessionWorkstream(
358
373
  ]);
359
374
  await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
360
375
  await requestControlWakeDispatch(deps, result.wakeCount);
361
- return result;
376
+ return { ...result, authorization };
362
377
  }
363
378
 
364
379
  function receipt(row: SessionCommandReceiptRow): SessionCommandReceipt {
@@ -384,10 +399,9 @@ function composerDraft(
384
399
  revision: row.revision,
385
400
  text: row.text,
386
401
  resources: row.resources as ComposerDraft["resources"],
387
- tools: row.tools as ComposerDraft["tools"],
388
- toolsProvided: row.toolsProvided,
389
402
  model: row.model,
390
403
  reasoningEffort: row.reasoningEffort as ComposerDraft["reasoningEffort"],
404
+ latencyMode: row.latencyMode as ComposerDraft["latencyMode"],
391
405
  sourceTurnId: row.sourceTurnId,
392
406
  sourceTurnVersion: row.sourceTurnVersion,
393
407
  updatedAt: row.updatedAt.toISOString(),
@@ -648,10 +662,9 @@ export async function getHumanComposerDraft(
648
662
  revision: 0,
649
663
  text: "",
650
664
  resources: [],
651
- tools: [],
652
- toolsProvided: false,
653
665
  model: session.model,
654
666
  reasoningEffort: reasoningEffortForMetadata(session.metadata, "medium"),
667
+ latencyMode: latencyModeForMetadata(session.metadata, "standard"),
655
668
  sourceTurnId: null,
656
669
  sourceTurnVersion: null,
657
670
  updatedAt: null,
@@ -14,6 +14,7 @@ import type { Observability } from "@opengeni/observability";
14
14
  import type { createObjectStorage } from "@opengeni/storage";
15
15
  import type { ManagedAuth } from "./managed-auth-type";
16
16
  import type { ApiSandboxClient, ResumeBoxByIdInput, ResumedSandboxSession } from "./sandbox-types";
17
+ import type { TranscriptionService } from "./transcription";
17
18
 
18
19
  export type SessionWorkflowClient = {
19
20
  signalUserMessage: (input: {
@@ -110,6 +111,10 @@ export type AppDependencies = {
110
111
  codexFetch?: typeof fetch;
111
112
  /** Injectable Slack Web API transport for deterministic bot-connection tests. */
112
113
  slackFetch?: typeof fetch;
114
+ /** Injectable Google OAuth/Drive transport for deterministic connector tests. */
115
+ googleDriveFetch?: typeof fetch;
116
+ /** Optional host-owned voice-input transcription service. */
117
+ transcription?: TranscriptionService | null;
113
118
  // The API process's OWN agent-loop-free sandbox client (constructed from
114
119
  // settings via @opengeni/runtime/sandbox). Undefined when sandboxBackend=none.
115
120
  // This is the foundation of the API-direct control plane: the API resumes
@@ -0,0 +1,480 @@
1
+ import { configuredStaticUsageLimits, type Settings } from "@opengeni/config";
2
+ import {
3
+ WorkspaceInsightsSnapshot,
4
+ type InsightsRange,
5
+ type WorkspaceInsightsResponse,
6
+ } from "@opengeni/contracts";
7
+ import {
8
+ aggregateModelCallFacts,
9
+ aggregateModelCallFactsByDay,
10
+ aggregateRootSessionDrivers,
11
+ aggregateScheduleFacts,
12
+ aggregateSessionDepth,
13
+ aggregateWarmSecondsByGroup,
14
+ countOnlineMachines,
15
+ countScheduledTaskFires,
16
+ countSessionsAttachedToGroups,
17
+ enumerateUtcDays,
18
+ listFloorSessions,
19
+ listLiveWarmLeases,
20
+ listModelCallFacets,
21
+ listScheduledTasks,
22
+ requireWorkspace,
23
+ sumUsageQuantity,
24
+ sumUsageQuantityByDay,
25
+ sumUsageQuantityInRange,
26
+ type Database,
27
+ } from "@opengeni/db";
28
+
29
+ const MACHINE_HEARTBEAT_FRESH_MS = 120_000;
30
+
31
+ export type GetWorkspaceInsightsInput = {
32
+ workspaceId: string;
33
+ range: InsightsRange;
34
+ provider?: string | null;
35
+ model?: string | null;
36
+ now?: Date;
37
+ };
38
+
39
+ function microsToUsd(micros: number): number {
40
+ return Math.round((micros / 1_000_000) * 100) / 100;
41
+ }
42
+
43
+ function cacheHitPct(cached: number, input: number): number {
44
+ if (input <= 0) return 0;
45
+ return Math.min(100, Math.max(0, Math.round((cached / input) * 100)));
46
+ }
47
+
48
+ function startOfUtcDay(date: Date): Date {
49
+ return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
50
+ }
51
+
52
+ function startOfUtcMonth(date: Date): Date {
53
+ return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
54
+ }
55
+
56
+ function startOfUtcYear(date: Date): Date {
57
+ return new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
58
+ }
59
+
60
+ function resolveRangeWindow(
61
+ range: InsightsRange,
62
+ now: Date,
63
+ ): {
64
+ since: Date;
65
+ until: Date;
66
+ priorSince: Date;
67
+ priorUntil: Date;
68
+ rangeLabel: string;
69
+ priorLabel: string;
70
+ seriesLabel: string;
71
+ cacheSeriesLabel: string;
72
+ } {
73
+ const until = now;
74
+ let since: Date;
75
+ let rangeLabel: string;
76
+ let priorLabel: string;
77
+ let seriesLabel: string;
78
+ let cacheSeriesLabel: string;
79
+ switch (range) {
80
+ case "today":
81
+ since = startOfUtcDay(now);
82
+ rangeLabel = "Today (UTC)";
83
+ priorLabel = "Prior equal window";
84
+ seriesLabel = "Credit $ (UTC day)";
85
+ cacheSeriesLabel = "Cache hit %";
86
+ break;
87
+ case "week":
88
+ since = new Date(startOfUtcDay(now).getTime() - 6 * 24 * 60 * 60 * 1000);
89
+ rangeLabel = "Last 7 days (UTC)";
90
+ priorLabel = "Prior 7 days";
91
+ seriesLabel = "Credit $ / day";
92
+ cacheSeriesLabel = "Cache hit % / day";
93
+ break;
94
+ case "month":
95
+ since = startOfUtcMonth(now);
96
+ rangeLabel = "This month (UTC)";
97
+ priorLabel = "Prior equal window";
98
+ seriesLabel = "Credit $ / day";
99
+ cacheSeriesLabel = "Cache hit % / day";
100
+ break;
101
+ case "ytd":
102
+ since = startOfUtcYear(now);
103
+ rangeLabel = "Year to date (UTC)";
104
+ priorLabel = "Prior equal window";
105
+ seriesLabel = "Credit $ / day";
106
+ cacheSeriesLabel = "Cache hit % / day";
107
+ break;
108
+ default: {
109
+ const _exhaustive: never = range;
110
+ throw new Error(`Unknown insights range: ${_exhaustive}`);
111
+ }
112
+ }
113
+ const durationMs = until.getTime() - since.getTime();
114
+ const priorUntil = since;
115
+ const priorSince = new Date(priorUntil.getTime() - Math.max(durationMs, 1));
116
+ return {
117
+ since,
118
+ until,
119
+ priorSince,
120
+ priorUntil,
121
+ rangeLabel,
122
+ priorLabel,
123
+ seriesLabel,
124
+ cacheSeriesLabel,
125
+ };
126
+ }
127
+
128
+ function ageLabel(updatedAt: Date, now: Date): string {
129
+ const ms = Math.max(0, now.getTime() - updatedAt.getTime());
130
+ const minutes = Math.floor(ms / 60_000);
131
+ if (minutes < 1) return "just now";
132
+ if (minutes < 60) return `${minutes}m`;
133
+ const hours = Math.floor(minutes / 60);
134
+ if (hours < 48) return `${hours}h`;
135
+ return `${Math.floor(hours / 24)}d`;
136
+ }
137
+
138
+ function floorState(input: {
139
+ status: string;
140
+ directControlState: string;
141
+ }): "running" | "paused" | "failed" | "idle" | "compacting" | "waiting" {
142
+ if (input.directControlState === "paused") return "paused";
143
+ switch (input.status) {
144
+ case "running":
145
+ return "running";
146
+ case "failed":
147
+ return "failed";
148
+ case "requires_action":
149
+ case "waiting":
150
+ return "waiting";
151
+ case "compacting":
152
+ return "compacting";
153
+ case "completed":
154
+ case "idle":
155
+ case "queued":
156
+ return "idle";
157
+ default:
158
+ return "idle";
159
+ }
160
+ }
161
+
162
+ function billingPathOf(value: string): "opengeni_credits" | "external" {
163
+ return value === "external" ? "external" : "opengeni_credits";
164
+ }
165
+
166
+ export async function getWorkspaceInsights(
167
+ db: Database,
168
+ settings: Settings,
169
+ input: GetWorkspaceInsightsInput,
170
+ ): Promise<WorkspaceInsightsResponse> {
171
+ await requireWorkspace(db, input.workspaceId);
172
+ const now = input.now ?? new Date();
173
+ const window = resolveRangeWindow(input.range, now);
174
+ const provider = input.provider?.trim() || null;
175
+ const model = input.model?.trim() || null;
176
+ const modelFilterActive = Boolean(provider || model);
177
+ const filter = { provider, model };
178
+
179
+ const [
180
+ workspaceCreditMicros,
181
+ priorWorkspaceCreditMicros,
182
+ warmSeconds,
183
+ priorWarmSeconds,
184
+ modelRows,
185
+ priorModelRows,
186
+ factDays,
187
+ warmDays,
188
+ costDays,
189
+ warmGroups,
190
+ liveWarm,
191
+ rootDrivers,
192
+ scheduleFacts,
193
+ tasks,
194
+ depth,
195
+ floorRows,
196
+ machinesOnline,
197
+ billableTokensUsed,
198
+ agentRunsUsed,
199
+ facets,
200
+ ] = await Promise.all([
201
+ sumUsageQuantityInRange(db, {
202
+ workspaceId: input.workspaceId,
203
+ eventType: "model.cost",
204
+ since: window.since,
205
+ until: window.until,
206
+ }),
207
+ sumUsageQuantityInRange(db, {
208
+ workspaceId: input.workspaceId,
209
+ eventType: "model.cost",
210
+ since: window.priorSince,
211
+ until: window.priorUntil,
212
+ }),
213
+ sumUsageQuantityInRange(db, {
214
+ workspaceId: input.workspaceId,
215
+ eventType: "sandbox.warm_seconds",
216
+ since: window.since,
217
+ until: window.until,
218
+ }),
219
+ sumUsageQuantityInRange(db, {
220
+ workspaceId: input.workspaceId,
221
+ eventType: "sandbox.warm_seconds",
222
+ since: window.priorSince,
223
+ until: window.priorUntil,
224
+ }),
225
+ aggregateModelCallFacts(db, {
226
+ workspaceId: input.workspaceId,
227
+ since: window.since,
228
+ until: window.until,
229
+ ...filter,
230
+ }),
231
+ aggregateModelCallFacts(db, {
232
+ workspaceId: input.workspaceId,
233
+ since: window.priorSince,
234
+ until: window.priorUntil,
235
+ ...filter,
236
+ }),
237
+ aggregateModelCallFactsByDay(db, {
238
+ workspaceId: input.workspaceId,
239
+ since: window.since,
240
+ until: window.until,
241
+ ...filter,
242
+ }),
243
+ sumUsageQuantityByDay(db, {
244
+ workspaceId: input.workspaceId,
245
+ eventType: "sandbox.warm_seconds",
246
+ since: window.since,
247
+ until: window.until,
248
+ }),
249
+ sumUsageQuantityByDay(db, {
250
+ workspaceId: input.workspaceId,
251
+ eventType: "model.cost",
252
+ since: window.since,
253
+ until: window.until,
254
+ }),
255
+ aggregateWarmSecondsByGroup(db, {
256
+ workspaceId: input.workspaceId,
257
+ since: window.since,
258
+ until: window.until,
259
+ limit: 24,
260
+ }),
261
+ listLiveWarmLeases(db, input.workspaceId),
262
+ aggregateRootSessionDrivers(db, {
263
+ workspaceId: input.workspaceId,
264
+ since: window.since,
265
+ until: window.until,
266
+ ...filter,
267
+ limit: 8,
268
+ }),
269
+ aggregateScheduleFacts(db, {
270
+ workspaceId: input.workspaceId,
271
+ since: window.since,
272
+ until: window.until,
273
+ ...filter,
274
+ }),
275
+ listScheduledTasks(db, input.workspaceId, 100),
276
+ aggregateSessionDepth(db, input.workspaceId),
277
+ listFloorSessions(db, input.workspaceId, 24),
278
+ settings.sandboxSelfhostedEnabled
279
+ ? countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS)
280
+ : Promise.resolve(0),
281
+ sumUsageQuantity(db, {
282
+ workspaceId: input.workspaceId,
283
+ eventType: "model.tokens",
284
+ since: startOfUtcMonth(now),
285
+ }),
286
+ sumUsageQuantity(db, {
287
+ workspaceId: input.workspaceId,
288
+ eventType: "agent_run.created",
289
+ since: startOfUtcMonth(now),
290
+ }),
291
+ listModelCallFacets(db, {
292
+ workspaceId: input.workspaceId,
293
+ since: window.since,
294
+ until: window.until,
295
+ }),
296
+ ]);
297
+
298
+ // Exact prior costs for the current top drivers — never a separate top-N page that
299
+ // drops roots and invents +$full as "new" spend.
300
+ const priorRootDrivers = await aggregateRootSessionDrivers(db, {
301
+ workspaceId: input.workspaceId,
302
+ since: window.priorSince,
303
+ until: window.priorUntil,
304
+ ...filter,
305
+ rootSessionIds: rootDrivers.map((row) => row.rootSessionId),
306
+ });
307
+
308
+ const attached = await countSessionsAttachedToGroups(
309
+ db,
310
+ input.workspaceId,
311
+ warmGroups.map((group) => group.groupId),
312
+ );
313
+ const backendByGroup = new Map(liveWarm.map((lease) => [lease.groupId, lease.backend]));
314
+ const warmSecondsByGroup = new Map(warmGroups.map((group) => [group.groupId, group.warmSeconds]));
315
+
316
+ const models = modelRows
317
+ .map((row) => ({
318
+ id: `${row.provider}:${row.model}:${row.billingPath}`,
319
+ model: row.model,
320
+ provider: row.provider,
321
+ billing: billingPathOf(row.billingPath),
322
+ calls: row.calls,
323
+ inputTokens: row.inputTokens,
324
+ outputTokens: row.outputTokens,
325
+ cachedTokens: row.cachedTokens,
326
+ cacheWriteTokens: row.cacheWriteTokens,
327
+ reasoningTokens: row.reasoningTokens,
328
+ creditUsd: microsToUsd(row.pricedCostMicros),
329
+ }))
330
+ .sort((a, b) => b.inputTokens - a.inputTokens);
331
+
332
+ const creditMicros = modelRows.reduce((sum, row) => sum + row.pricedCostMicros, 0);
333
+ const priorCreditMicros = priorModelRows.reduce((sum, row) => sum + row.pricedCostMicros, 0);
334
+ const priorInputTokens = priorModelRows.reduce((sum, row) => sum + row.inputTokens, 0);
335
+ const priorCachedTokens = priorModelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
336
+ const priorCalls = priorModelRows.reduce((sum, row) => sum + row.calls, 0);
337
+
338
+ const days = enumerateUtcDays(window.since, window.until);
339
+ const series = days.map((day) => {
340
+ const facts = factDays.get(day) ?? {
341
+ costMicros: 0,
342
+ inputTokens: 0,
343
+ cachedTokens: 0,
344
+ calls: 0,
345
+ };
346
+ const modelCostMicros = modelFilterActive
347
+ ? facts.costMicros
348
+ : (costDays.get(day) ?? facts.costMicros);
349
+ return {
350
+ label: day.slice(5),
351
+ modelCostUsd: microsToUsd(modelCostMicros),
352
+ warmSeconds: warmDays.get(day) ?? 0,
353
+ inputTokens: facts.inputTokens,
354
+ cachedTokens: facts.cachedTokens,
355
+ cacheHitPct: cacheHitPct(facts.cachedTokens, facts.inputTokens),
356
+ calls: facts.calls,
357
+ };
358
+ });
359
+
360
+ const priorDriverByRoot = new Map(
361
+ priorRootDrivers.map((row) => [row.rootSessionId, row.pricedCostMicros]),
362
+ );
363
+ const creditUsdForPct = Math.max(microsToUsd(creditMicros), 0.01);
364
+ const drivers = rootDrivers.map((row) => {
365
+ const creditUsd = microsToUsd(row.pricedCostMicros);
366
+ const priorUsd = microsToUsd(priorDriverByRoot.get(row.rootSessionId) ?? 0);
367
+ return {
368
+ id: `root:${row.rootSessionId}`,
369
+ groupBy: "root_session" as const,
370
+ label: row.title?.trim() || row.rootSessionId.slice(0, 8),
371
+ creditUsd,
372
+ tokens: row.inputTokens,
373
+ cacheHitPct: cacheHitPct(row.cachedTokens, row.inputTokens),
374
+ pctOfCreditUsd: Math.min(100, Math.round((creditUsd / creditUsdForPct) * 100)),
375
+ deltaUsdVsPrior: Math.round((creditUsd - priorUsd) * 100) / 100,
376
+ };
377
+ });
378
+
379
+ const fireCounts = await countScheduledTaskFires(db, {
380
+ workspaceId: input.workspaceId,
381
+ since: window.since,
382
+ until: window.until,
383
+ taskIds: tasks.map((task) => task.id),
384
+ });
385
+ const scheduleFactById = new Map(scheduleFacts.map((row) => [row.scheduledTaskId, row] as const));
386
+ const schedules = tasks.map((task) => {
387
+ const fact = scheduleFactById.get(task.id);
388
+ return {
389
+ id: task.id,
390
+ name: task.name,
391
+ fires: fireCounts.get(task.id) ?? 0,
392
+ creditUsd: fact ? microsToUsd(fact.pricedCostMicros) : null,
393
+ tokens: fact ? fact.inputTokens : null,
394
+ cacheHitPct: fact ? cacheHitPct(fact.cachedTokens, fact.inputTokens) : null,
395
+ billing: fact ? billingPathOf(fact.billingPath) : null,
396
+ };
397
+ });
398
+
399
+ const limits = configuredStaticUsageLimits(settings);
400
+ // Floor sessions only carry product model, not provider. Filter when an exact
401
+ // model is selected; otherwise leave the list workspace-wide (captioned in UI).
402
+ const floor = floorRows
403
+ .filter((row) => {
404
+ if (!model) return true;
405
+ return row.model === model;
406
+ })
407
+ .map((row) => ({
408
+ id: row.id,
409
+ title: row.title?.trim() || "Untitled session",
410
+ state: floorState(row),
411
+ depth: row.nestedAgentDepth,
412
+ model: row.model,
413
+ provider: null,
414
+ ageLabel: ageLabel(row.updatedAt, now),
415
+ cacheHitPct: null,
416
+ route: row.sandboxBackend,
417
+ }));
418
+
419
+ const snapshot = WorkspaceInsightsSnapshot.parse({
420
+ range: input.range,
421
+ rangeLabel: window.rangeLabel,
422
+ priorLabel: window.priorLabel,
423
+ seriesLabel: window.seriesLabel,
424
+ cacheSeriesLabel: window.cacheSeriesLabel,
425
+ timezone: "UTC",
426
+ models,
427
+ facets,
428
+ series,
429
+ depth: depth.buckets.map((bucket) => ({
430
+ depth: bucket.depth,
431
+ sessions: bucket.sessions,
432
+ })),
433
+ drivers,
434
+ schedules,
435
+ warmSeconds,
436
+ priorWarmSeconds,
437
+ warmGroups: warmGroups.map((group) => ({
438
+ id: group.groupId,
439
+ groupId: group.groupId,
440
+ label: group.groupId.slice(0, 8),
441
+ backend: backendByGroup.get(group.groupId) ?? null,
442
+ warmSeconds: group.warmSeconds,
443
+ sessionsAttached: attached.get(group.groupId) ?? 0,
444
+ })),
445
+ liveWarm: liveWarm.map((lease) => ({
446
+ id: lease.id,
447
+ groupId: lease.groupId,
448
+ backend: lease.backend,
449
+ turnHolders: lease.turnHolders,
450
+ viewerHolders: lease.viewerHolders,
451
+ warmForLabel: lease.turnHolders > 0 ? "in use" : "idle warm",
452
+ warmSeconds: warmSecondsByGroup.get(lease.groupId) ?? 0,
453
+ })),
454
+ floor,
455
+ selfhostedEnabled: settings.sandboxSelfhostedEnabled,
456
+ machinesOnline,
457
+ workspaceCreditUsd: microsToUsd(workspaceCreditMicros),
458
+ priorWorkspaceCreditUsd: microsToUsd(priorWorkspaceCreditMicros),
459
+ creditUsd: microsToUsd(creditMicros),
460
+ priorCreditUsd: microsToUsd(priorCreditMicros),
461
+ priorInputTokens,
462
+ priorCacheHitPct: cacheHitPct(priorCachedTokens, priorInputTokens),
463
+ priorCalls,
464
+ goalsActive: depth.goalsActive,
465
+ goalsCompleted: depth.goalsCompleted,
466
+ sessionsTouched: depth.sessionsTouched,
467
+ rootSessions: depth.rootSessions,
468
+ deepestDepth: depth.deepestDepth,
469
+ deepestSessionTitle: depth.deepestSessionTitle || "",
470
+ avgDepth: Math.round(depth.avgDepth * 10) / 10,
471
+ warmIdleNow: liveWarm.filter((lease) => lease.turnHolders === 0).length,
472
+ billableTokensUsed,
473
+ billableTokenCap: limits.maxMonthlyTokensPerWorkspace ?? null,
474
+ agentRunsUsed,
475
+ agentRunCap: limits.maxMonthlyAgentRunsPerWorkspace ?? null,
476
+ modelFilterActive,
477
+ });
478
+
479
+ return { snapshot };
480
+ }