@opengeni/core 0.21.10 → 0.28.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.
Files changed (132) hide show
  1. package/dist/access/index.d.ts +1 -1
  2. package/dist/application/new-session-drafts.d.ts +1 -1
  3. package/dist/application/session-commands.d.ts +2 -2
  4. package/dist/billing/limits.d.ts +1 -1
  5. package/dist/canonical-human-identities.d.ts +12 -0
  6. package/dist/canonical-human-identities.js +23 -0
  7. package/dist/canonical-human-identities.js.map +1 -0
  8. package/dist/chunk-6WKPWE5S.js +2380 -0
  9. package/dist/chunk-6WKPWE5S.js.map +1 -0
  10. package/dist/chunk-IBOEYG6N.js +34 -0
  11. package/dist/chunk-IBOEYG6N.js.map +1 -0
  12. package/dist/chunk-JJU6I5XD.js +2713 -0
  13. package/dist/chunk-JJU6I5XD.js.map +1 -0
  14. package/dist/chunk-NYPMQ7NO.js +2270 -0
  15. package/dist/chunk-NYPMQ7NO.js.map +1 -0
  16. package/dist/dependencies.d.ts +30 -6
  17. package/dist/domain/capabilities.d.ts +28 -3
  18. package/dist/domain/company-profile-durable-learning-adapter.d.ts +39 -0
  19. package/dist/domain/conversation-integrations.d.ts +225 -0
  20. package/dist/domain/durable-learning-slack-publication.d.ts +72 -0
  21. package/dist/domain/editable-artifacts/agent-application.d.ts +192 -0
  22. package/dist/domain/editable-artifacts/durable-export-postgres.d.ts +4 -0
  23. package/dist/domain/editable-artifacts/durable-export.d.ts +212 -0
  24. package/dist/domain/editable-artifacts/errors.d.ts +47 -0
  25. package/dist/domain/editable-artifacts/hash.d.ts +11 -0
  26. package/dist/domain/editable-artifacts/in-memory.d.ts +99 -0
  27. package/dist/domain/editable-artifacts/index.d.ts +14 -0
  28. package/dist/domain/editable-artifacts/ports.d.ts +341 -0
  29. package/dist/domain/editable-artifacts/postgres-adapters.d.ts +13 -0
  30. package/dist/domain/editable-artifacts/service.d.ts +105 -0
  31. package/dist/domain/editable-artifacts/snapshot-compaction.d.ts +44 -0
  32. package/dist/domain/editable-artifacts/snapshot-verifier-genesis.d.ts +50 -0
  33. package/dist/domain/editable-artifacts/snapshot-verifier-materialization.d.ts +21 -0
  34. package/dist/domain/editable-artifacts/snapshot-verifier.d.ts +118 -0
  35. package/dist/domain/editable-artifacts/types.d.ts +410 -0
  36. package/dist/domain/fiken.d.ts +35 -0
  37. package/dist/domain/memory-slack-delivery.d.ts +19 -0
  38. package/dist/domain/packs.d.ts +29 -1
  39. package/dist/domain/personal-connection-delegations.d.ts +9 -1
  40. package/dist/domain/scheduled-tasks.d.ts +2 -2
  41. package/dist/domain/session-tool-policy.d.ts +2 -2
  42. package/dist/domain/sessions.d.ts +24 -8
  43. package/dist/domain/skill-imports.d.ts +32 -0
  44. package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
  45. package/dist/domain/timeline-annotations.d.ts +5 -0
  46. package/dist/domain/video-generation-capabilities.d.ts +10 -0
  47. package/dist/domain/video-generation.d.ts +47 -0
  48. package/dist/editable-artifact-live/application.d.ts +69 -0
  49. package/dist/editable-artifact-live/errors.d.ts +1 -0
  50. package/dist/editable-artifact-live/index.d.ts +8 -0
  51. package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
  52. package/dist/editable-artifact-live/ports.d.ts +100 -0
  53. package/dist/editable-artifact-live/server.d.ts +71 -0
  54. package/dist/editable-artifact-live/ticket.d.ts +40 -0
  55. package/dist/editable-artifact-live/types.d.ts +213 -0
  56. package/dist/editable-artifact-live/wire.d.ts +34 -0
  57. package/dist/editable-artifact-live.d.ts +2 -0
  58. package/dist/editable-artifact-live.js +58 -0
  59. package/dist/editable-artifact-live.js.map +1 -0
  60. package/dist/editable-artifacts.d.ts +2 -0
  61. package/dist/editable-artifacts.js +179 -0
  62. package/dist/editable-artifacts.js.map +1 -0
  63. package/dist/index.d.ts +37 -26
  64. package/dist/index.js +4145 -959
  65. package/dist/index.js.map +1 -1
  66. package/dist/managed-session.d.ts +6 -2
  67. package/dist/rigs/index.d.ts +1 -1
  68. package/dist/rigs/provider-images.d.ts +31 -0
  69. package/dist/sandbox/fleet.d.ts +8 -5
  70. package/dist/sandbox/routing.d.ts +1 -0
  71. package/dist/session-authorization.d.ts +4 -2
  72. package/dist/transcription.d.ts +5 -0
  73. package/dist/workflow-wake-contract.d.ts +7 -0
  74. package/package.json +22 -10
  75. package/src/access/index.ts +14 -2
  76. package/src/application/new-session-drafts.ts +2 -0
  77. package/src/application/session-commands.ts +188 -107
  78. package/src/canonical-human-identities.ts +27 -0
  79. package/src/dependencies.ts +34 -2
  80. package/src/domain/capabilities.ts +729 -408
  81. package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
  82. package/src/domain/conversation-integrations.ts +1238 -0
  83. package/src/domain/durable-learning-slack-publication.ts +202 -0
  84. package/src/domain/editable-artifacts/agent-application.ts +687 -0
  85. package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
  86. package/src/domain/editable-artifacts/durable-export.ts +734 -0
  87. package/src/domain/editable-artifacts/errors.ts +115 -0
  88. package/src/domain/editable-artifacts/hash.ts +131 -0
  89. package/src/domain/editable-artifacts/in-memory.ts +1662 -0
  90. package/src/domain/editable-artifacts/index.ts +14 -0
  91. package/src/domain/editable-artifacts/ports.ts +462 -0
  92. package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
  93. package/src/domain/editable-artifacts/service.ts +2402 -0
  94. package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
  95. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
  96. package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
  97. package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
  98. package/src/domain/editable-artifacts/types.ts +835 -0
  99. package/src/domain/fiken.ts +88 -0
  100. package/src/domain/insights.ts +163 -50
  101. package/src/domain/memory-slack-delivery.ts +204 -0
  102. package/src/domain/memory-slack-publication.ts +5 -2
  103. package/src/domain/packs.ts +266 -5
  104. package/src/domain/personal-connection-delegations.ts +97 -6
  105. package/src/domain/scheduled-tasks.ts +171 -29
  106. package/src/domain/session-tool-policy.ts +8 -2
  107. package/src/domain/sessions.ts +395 -148
  108. package/src/domain/skill-imports.ts +350 -0
  109. package/src/domain/slack-publication-secret-safety.ts +42 -0
  110. package/src/domain/timeline-annotations.ts +204 -0
  111. package/src/domain/video-generation-capabilities.ts +104 -0
  112. package/src/domain/video-generation.ts +249 -0
  113. package/src/editable-artifact-live/application.ts +182 -0
  114. package/src/editable-artifact-live/errors.ts +4 -0
  115. package/src/editable-artifact-live/index.ts +8 -0
  116. package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
  117. package/src/editable-artifact-live/ports.ts +139 -0
  118. package/src/editable-artifact-live/server.ts +1707 -0
  119. package/src/editable-artifact-live/ticket.ts +217 -0
  120. package/src/editable-artifact-live/types.ts +286 -0
  121. package/src/editable-artifact-live/wire.ts +95 -0
  122. package/src/editable-artifact-live.ts +2 -0
  123. package/src/editable-artifacts.ts +2 -0
  124. package/src/index.ts +15 -0
  125. package/src/managed-session.ts +19 -2
  126. package/src/rigs/index.ts +26 -19
  127. package/src/rigs/provider-images.ts +110 -0
  128. package/src/sandbox/fleet.ts +72 -66
  129. package/src/sandbox/routing.ts +6 -3
  130. package/src/session-authorization.ts +187 -52
  131. package/src/transcription.ts +5 -0
  132. package/src/workflow-wake-contract.ts +8 -0
@@ -0,0 +1,88 @@
1
+ import {
2
+ FIKEN_CREDENTIAL_LABEL,
3
+ FIKEN_CREDENTIAL_ROLE,
4
+ FIKEN_PROVIDER_DOMAIN,
5
+ FikenConnectionMetadata,
6
+ type ConnectionMetadata,
7
+ type FikenCompanySummary,
8
+ type FikenConnectionMetadata as FikenMetadata,
9
+ } from "@opengeni/contracts";
10
+
11
+ export function fikenConnectionMetadata(metadata: Record<string, unknown>): FikenMetadata | null {
12
+ const parsed = FikenConnectionMetadata.safeParse(metadata);
13
+ return parsed.success ? parsed.data : null;
14
+ }
15
+
16
+ /**
17
+ * A workspace-shared Fiken connection created through one of the verified
18
+ * install paths: the pasted personal API token (`api_key`) or the Fiken OAuth
19
+ * app flow (`oauth2`). Both are deliberately workspace-owned; personal
20
+ * ("Connect only for me") ownership needs the delegation-snapshot lane and is
21
+ * not yet wired for the first-party fiken tools.
22
+ */
23
+ export function isFikenConnection(connection: ConnectionMetadata): boolean {
24
+ return (
25
+ connection.subjectId === null &&
26
+ connection.providerDomain === FIKEN_PROVIDER_DOMAIN &&
27
+ (connection.kind === "api_key" || connection.kind === "oauth2") &&
28
+ fikenConnectionMetadata(connection.metadata)?.credentialRole === FIKEN_CREDENTIAL_ROLE
29
+ );
30
+ }
31
+
32
+ /**
33
+ * The Fiken credential role is reserved for the verified install routes.
34
+ * Generic connection create/update must reject it so a caller cannot forge a
35
+ * "verified" Fiken row (with attacker-chosen companies metadata) that the
36
+ * first-party fiken tools would then bind to. Mirrors the Slack bot guard.
37
+ */
38
+ export function hasReservedFikenMetadata(
39
+ metadata: Record<string, unknown> | null | undefined,
40
+ ): boolean {
41
+ return (
42
+ metadata?.credentialRole === FIKEN_CREDENTIAL_ROLE ||
43
+ metadata?.credentialLabel === FIKEN_CREDENTIAL_LABEL
44
+ );
45
+ }
46
+
47
+ /**
48
+ * One default-company rule for both verified lanes: an explicit request wins
49
+ * when the credential can access it, then a still-accessible previous default
50
+ * survives reconnect, then a single-company credential auto-selects. Returns
51
+ * null (caller decides whether that is an error) when a requested slug is not
52
+ * among the verified companies.
53
+ */
54
+ export function resolveFikenDefaultCompanySlug(input: {
55
+ requested: string | null;
56
+ previous: string | null;
57
+ companies: readonly FikenCompanySummary[];
58
+ }): string | null {
59
+ const accessible = (slug: string | null): slug is string =>
60
+ slug !== null && input.companies.some((company) => company.slug === slug);
61
+ if (input.requested !== null) {
62
+ return accessible(input.requested) ? input.requested : null;
63
+ }
64
+ if (accessible(input.previous)) {
65
+ return input.previous;
66
+ }
67
+ return input.companies.length === 1 ? input.companies[0]!.slug : null;
68
+ }
69
+
70
+ /**
71
+ * The row a capability tile or tool default should bind to when several Fiken
72
+ * connections exist: usable status first, then newest update, then immutable
73
+ * UUID descending as the stable tie-breaker.
74
+ */
75
+ export function preferredFikenConnection<
76
+ T extends Pick<ConnectionMetadata, "status" | "updatedAt" | "id">,
77
+ >(connections: readonly T[]): T | null {
78
+ const statusRank = (status: ConnectionMetadata["status"]): number =>
79
+ status === "active" ? 0 : status === "needs_reauth" ? 1 : 2;
80
+ return (
81
+ [...connections].sort(
82
+ (left, right) =>
83
+ statusRank(left.status) - statusRank(right.status) ||
84
+ right.updatedAt.localeCompare(left.updatedAt) ||
85
+ right.id.localeCompare(left.id),
86
+ )[0] ?? null
87
+ );
88
+ }
@@ -7,6 +7,7 @@ import {
7
7
  import {
8
8
  aggregateModelCallFacts,
9
9
  aggregateModelCallFactsByDay,
10
+ aggregateModelCallFactsByHour,
10
11
  aggregateRootSessionDrivers,
11
12
  aggregateScheduleFacts,
12
13
  aggregateSessionDepth,
@@ -15,13 +16,16 @@ import {
15
16
  countScheduledTaskFires,
16
17
  countSessionsAttachedToGroups,
17
18
  enumerateUtcDays,
19
+ enumerateUtcHours,
18
20
  listFloorSessions,
19
21
  listLiveWarmLeases,
20
22
  listModelCallFacets,
23
+ listRecentModelCalls,
21
24
  listScheduledTasks,
22
25
  requireWorkspace,
23
26
  sumUsageQuantity,
24
27
  sumUsageQuantityByDay,
28
+ sumUsageQuantityByHour,
25
29
  sumUsageQuantityInRange,
26
30
  type Database,
27
31
  } from "@opengeni/db";
@@ -37,7 +41,7 @@ export type GetWorkspaceInsightsInput = {
37
41
  };
38
42
 
39
43
  function microsToUsd(micros: number): number {
40
- return Math.round((micros / 1_000_000) * 100) / 100;
44
+ return micros / 1_000_000;
41
45
  }
42
46
 
43
47
  function cacheHitPct(cached: number, input: number): number {
@@ -81,29 +85,29 @@ function resolveRangeWindow(
81
85
  since = startOfUtcDay(now);
82
86
  rangeLabel = "Today (UTC)";
83
87
  priorLabel = "Prior equal window";
84
- seriesLabel = "Credit $ (UTC day)";
85
- cacheSeriesLabel = "Cache hit %";
88
+ seriesLabel = "Credit $ / UTC hour";
89
+ cacheSeriesLabel = "Cache hit % / UTC hour";
86
90
  break;
87
91
  case "week":
88
92
  since = new Date(startOfUtcDay(now).getTime() - 6 * 24 * 60 * 60 * 1000);
89
93
  rangeLabel = "Last 7 days (UTC)";
90
94
  priorLabel = "Prior 7 days";
91
- seriesLabel = "Credit $ / day";
92
- cacheSeriesLabel = "Cache hit % / day";
95
+ seriesLabel = "Credit $ / UTC day";
96
+ cacheSeriesLabel = "Cache hit % / UTC day";
93
97
  break;
94
98
  case "month":
95
99
  since = startOfUtcMonth(now);
96
100
  rangeLabel = "This month (UTC)";
97
101
  priorLabel = "Prior equal window";
98
- seriesLabel = "Credit $ / day";
99
- cacheSeriesLabel = "Cache hit % / day";
102
+ seriesLabel = "Credit $ / UTC day";
103
+ cacheSeriesLabel = "Cache hit % / UTC day";
100
104
  break;
101
105
  case "ytd":
102
106
  since = startOfUtcYear(now);
103
107
  rangeLabel = "Year to date (UTC)";
104
108
  priorLabel = "Prior equal window";
105
- seriesLabel = "Credit $ / day";
106
- cacheSeriesLabel = "Cache hit % / day";
109
+ seriesLabel = "Credit $ / UTC day";
110
+ cacheSeriesLabel = "Cache hit % / UTC day";
107
111
  break;
108
112
  default: {
109
113
  const _exhaustive: never = range;
@@ -163,6 +167,12 @@ function billingPathOf(value: string): "opengeni_credits" | "external" {
163
167
  return value === "external" ? "external" : "opengeni_credits";
164
168
  }
165
169
 
170
+ function pricingSourceOf(
171
+ value: string | null,
172
+ ): "configured_list_price" | "gateway_reported" | null {
173
+ return value === "configured_list_price" || value === "gateway_reported" ? value : null;
174
+ }
175
+
166
176
  export async function getWorkspaceInsights(
167
177
  db: Database,
168
178
  settings: Settings,
@@ -175,6 +185,10 @@ export async function getWorkspaceInsights(
175
185
  const model = input.model?.trim() || null;
176
186
  const modelFilterActive = Boolean(provider || model);
177
187
  const filter = { provider, model };
188
+ const aggregateFactsForSeries =
189
+ input.range === "today" ? aggregateModelCallFactsByHour : aggregateModelCallFactsByDay;
190
+ const sumUsageForSeries =
191
+ input.range === "today" ? sumUsageQuantityByHour : sumUsageQuantityByDay;
178
192
 
179
193
  const [
180
194
  workspaceCreditMicros,
@@ -197,6 +211,7 @@ export async function getWorkspaceInsights(
197
211
  billableTokensUsed,
198
212
  agentRunsUsed,
199
213
  facets,
214
+ recentCalls,
200
215
  ] = await Promise.all([
201
216
  sumUsageQuantityInRange(db, {
202
217
  workspaceId: input.workspaceId,
@@ -234,19 +249,19 @@ export async function getWorkspaceInsights(
234
249
  until: window.priorUntil,
235
250
  ...filter,
236
251
  }),
237
- aggregateModelCallFactsByDay(db, {
252
+ aggregateFactsForSeries(db, {
238
253
  workspaceId: input.workspaceId,
239
254
  since: window.since,
240
255
  until: window.until,
241
256
  ...filter,
242
257
  }),
243
- sumUsageQuantityByDay(db, {
258
+ sumUsageForSeries(db, {
244
259
  workspaceId: input.workspaceId,
245
260
  eventType: "sandbox.warm_seconds",
246
261
  since: window.since,
247
262
  until: window.until,
248
263
  }),
249
- sumUsageQuantityByDay(db, {
264
+ sumUsageForSeries(db, {
250
265
  workspaceId: input.workspaceId,
251
266
  eventType: "model.cost",
252
267
  since: window.since,
@@ -276,7 +291,7 @@ export async function getWorkspaceInsights(
276
291
  aggregateSessionDepth(db, input.workspaceId),
277
292
  listFloorSessions(db, input.workspaceId, 24),
278
293
  settings.sandboxSelfhostedEnabled
279
- ? countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS)
294
+ ? countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS, now)
280
295
  : Promise.resolve(0),
281
296
  sumUsageQuantity(db, {
282
297
  workspaceId: input.workspaceId,
@@ -293,23 +308,37 @@ export async function getWorkspaceInsights(
293
308
  since: window.since,
294
309
  until: window.until,
295
310
  }),
311
+ listRecentModelCalls(db, {
312
+ workspaceId: input.workspaceId,
313
+ since: window.since,
314
+ until: window.until,
315
+ ...filter,
316
+ limit: 50,
317
+ }),
296
318
  ]);
297
319
 
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
- );
320
+ const [priorRootDrivers, attached, fireCounts] = await Promise.all([
321
+ // Exact prior costs for the current top drivers — never a separate top-N page that
322
+ // drops roots and invents +$full as "new" spend.
323
+ aggregateRootSessionDrivers(db, {
324
+ workspaceId: input.workspaceId,
325
+ since: window.priorSince,
326
+ until: window.priorUntil,
327
+ ...filter,
328
+ rootSessionIds: rootDrivers.map((row) => row.rootSessionId),
329
+ }),
330
+ countSessionsAttachedToGroups(
331
+ db,
332
+ input.workspaceId,
333
+ warmGroups.map((group) => group.groupId),
334
+ ),
335
+ countScheduledTaskFires(db, {
336
+ workspaceId: input.workspaceId,
337
+ since: window.since,
338
+ until: window.until,
339
+ taskIds: tasks.map((task) => task.id),
340
+ }),
341
+ ]);
313
342
  const backendByGroup = new Map(liveWarm.map((lease) => [lease.groupId, lease.backend]));
314
343
  const warmSecondsByGroup = new Map(warmGroups.map((group) => [group.groupId, group.warmSeconds]));
315
344
 
@@ -323,36 +352,82 @@ export async function getWorkspaceInsights(
323
352
  inputTokens: row.inputTokens,
324
353
  outputTokens: row.outputTokens,
325
354
  cachedTokens: row.cachedTokens,
355
+ cacheInputTokens: row.cacheInputTokens,
326
356
  cacheWriteTokens: row.cacheWriteTokens,
327
357
  reasoningTokens: row.reasoningTokens,
358
+ totalTokens: row.totalTokens,
359
+ tokenKnownCalls: row.tokenKnownCalls,
360
+ cacheKnownCalls: row.cacheKnownCalls,
328
361
  creditUsd: microsToUsd(row.pricedCostMicros),
362
+ estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
363
+ estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
329
364
  }))
330
- .sort((a, b) => b.inputTokens - a.inputTokens);
365
+ .sort((a, b) => b.totalTokens - a.totalTokens);
331
366
 
332
367
  const creditMicros = modelRows.reduce((sum, row) => sum + row.pricedCostMicros, 0);
333
368
  const priorCreditMicros = priorModelRows.reduce((sum, row) => sum + row.pricedCostMicros, 0);
369
+ const estimatedProviderCostMicros = modelRows.reduce(
370
+ (sum, row) => sum + row.estimatedProviderCostMicros,
371
+ 0,
372
+ );
373
+ const priorEstimatedProviderCostMicros = priorModelRows.reduce(
374
+ (sum, row) => sum + row.estimatedProviderCostMicros,
375
+ 0,
376
+ );
377
+ const estimatedProviderCostKnownCalls = modelRows.reduce(
378
+ (sum, row) => sum + row.estimatedProviderCostKnownCalls,
379
+ 0,
380
+ );
381
+ const priorEstimatedProviderCostKnownCalls = priorModelRows.reduce(
382
+ (sum, row) => sum + row.estimatedProviderCostKnownCalls,
383
+ 0,
384
+ );
385
+ const modelCalls = modelRows.reduce((sum, row) => sum + row.calls, 0);
334
386
  const priorInputTokens = priorModelRows.reduce((sum, row) => sum + row.inputTokens, 0);
387
+ const priorTotalTokens = priorModelRows.reduce((sum, row) => sum + row.totalTokens, 0);
335
388
  const priorCachedTokens = priorModelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
389
+ const priorCacheInputTokens = priorModelRows.reduce((sum, row) => sum + row.cacheInputTokens, 0);
336
390
  const priorCalls = priorModelRows.reduce((sum, row) => sum + row.calls, 0);
337
391
 
338
- const days = enumerateUtcDays(window.since, window.until);
339
- const series = days.map((day) => {
340
- const facts = factDays.get(day) ?? {
392
+ const buckets =
393
+ input.range === "today"
394
+ ? enumerateUtcHours(window.since, window.until)
395
+ : enumerateUtcDays(window.since, window.until);
396
+ const series = buckets.map((bucket) => {
397
+ const facts = factDays.get(bucket) ?? {
341
398
  costMicros: 0,
399
+ estimatedProviderCostMicros: 0,
400
+ estimatedProviderCostKnownCalls: 0,
342
401
  inputTokens: 0,
402
+ outputTokens: 0,
343
403
  cachedTokens: 0,
404
+ cacheInputTokens: 0,
405
+ cacheWriteTokens: 0,
406
+ reasoningTokens: 0,
407
+ totalTokens: 0,
408
+ tokenKnownCalls: 0,
409
+ cacheKnownCalls: 0,
344
410
  calls: 0,
345
411
  };
346
412
  const modelCostMicros = modelFilterActive
347
413
  ? facts.costMicros
348
- : (costDays.get(day) ?? facts.costMicros);
414
+ : (costDays.get(bucket) ?? facts.costMicros);
349
415
  return {
350
- label: day.slice(5),
416
+ label: input.range === "today" ? bucket.slice(11) : bucket.slice(5),
351
417
  modelCostUsd: microsToUsd(modelCostMicros),
352
- warmSeconds: warmDays.get(day) ?? 0,
418
+ estimatedProviderUsd: microsToUsd(facts.estimatedProviderCostMicros),
419
+ estimatedProviderCostKnownCalls: facts.estimatedProviderCostKnownCalls,
420
+ warmSeconds: warmDays.get(bucket) ?? 0,
353
421
  inputTokens: facts.inputTokens,
422
+ outputTokens: facts.outputTokens,
354
423
  cachedTokens: facts.cachedTokens,
355
- cacheHitPct: cacheHitPct(facts.cachedTokens, facts.inputTokens),
424
+ cacheInputTokens: facts.cacheInputTokens,
425
+ cacheWriteTokens: facts.cacheWriteTokens,
426
+ reasoningTokens: facts.reasoningTokens,
427
+ totalTokens: facts.totalTokens,
428
+ tokenKnownCalls: facts.tokenKnownCalls,
429
+ cacheKnownCalls: facts.cacheKnownCalls,
430
+ cacheHitPct: cacheHitPct(facts.cachedTokens, facts.cacheInputTokens),
356
431
  calls: facts.calls,
357
432
  };
358
433
  });
@@ -360,7 +435,10 @@ export async function getWorkspaceInsights(
360
435
  const priorDriverByRoot = new Map(
361
436
  priorRootDrivers.map((row) => [row.rootSessionId, row.pricedCostMicros]),
362
437
  );
363
- const creditUsdForPct = Math.max(microsToUsd(creditMicros), 0.01);
438
+ const totalTokensForPct = Math.max(
439
+ rootDrivers.reduce((sum, row) => sum + row.totalTokens, 0),
440
+ 1,
441
+ );
364
442
  const drivers = rootDrivers.map((row) => {
365
443
  const creditUsd = microsToUsd(row.pricedCostMicros);
366
444
  const priorUsd = microsToUsd(priorDriverByRoot.get(row.rootSessionId) ?? 0);
@@ -369,19 +447,19 @@ export async function getWorkspaceInsights(
369
447
  groupBy: "root_session" as const,
370
448
  label: row.title?.trim() || row.rootSessionId.slice(0, 8),
371
449
  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,
450
+ estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
451
+ estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
452
+ tokens: row.totalTokens,
453
+ cacheHitPct: cacheHitPct(row.cachedTokens, row.cacheInputTokens),
454
+ pctOfCreditUsd:
455
+ creditMicros > 0
456
+ ? Math.min(100, Math.round((row.pricedCostMicros / creditMicros) * 100))
457
+ : 0,
458
+ pctOfTokens: Math.min(100, Math.round((row.totalTokens / totalTokensForPct) * 100)),
459
+ deltaUsdVsPrior: creditUsd - priorUsd,
376
460
  };
377
461
  });
378
462
 
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
463
  const scheduleFactById = new Map(scheduleFacts.map((row) => [row.scheduledTaskId, row] as const));
386
464
  const schedules = tasks.map((task) => {
387
465
  const fact = scheduleFactById.get(task.id);
@@ -390,8 +468,10 @@ export async function getWorkspaceInsights(
390
468
  name: task.name,
391
469
  fires: fireCounts.get(task.id) ?? 0,
392
470
  creditUsd: fact ? microsToUsd(fact.pricedCostMicros) : null,
393
- tokens: fact ? fact.inputTokens : null,
394
- cacheHitPct: fact ? cacheHitPct(fact.cachedTokens, fact.inputTokens) : null,
471
+ estimatedProviderUsd: fact ? microsToUsd(fact.estimatedProviderCostMicros) : null,
472
+ estimatedProviderCostKnownCalls: fact ? fact.estimatedProviderCostKnownCalls : null,
473
+ tokens: fact ? fact.totalTokens : null,
474
+ cacheHitPct: fact ? cacheHitPct(fact.cachedTokens, fact.cacheInputTokens) : null,
395
475
  billing: fact ? billingPathOf(fact.billingPath) : null,
396
476
  };
397
477
  });
@@ -422,6 +502,9 @@ export async function getWorkspaceInsights(
422
502
  priorLabel: window.priorLabel,
423
503
  seriesLabel: window.seriesLabel,
424
504
  cacheSeriesLabel: window.cacheSeriesLabel,
505
+ windowStart: window.since.toISOString(),
506
+ windowEnd: window.until.toISOString(),
507
+ generatedAt: now.toISOString(),
425
508
  timezone: "UTC",
426
509
  models,
427
510
  facets,
@@ -432,6 +515,30 @@ export async function getWorkspaceInsights(
432
515
  })),
433
516
  drivers,
434
517
  schedules,
518
+ recentCalls: recentCalls.map((row) => ({
519
+ id: row.id,
520
+ occurredAt: row.occurredAt.toISOString(),
521
+ recordedAt: row.recordedAt.toISOString(),
522
+ sessionId: row.sessionId,
523
+ sessionTitle: row.sessionTitle?.trim() || "Untitled session",
524
+ turnId: row.turnId,
525
+ provider: row.provider,
526
+ providerApi: row.providerApi,
527
+ model: row.model,
528
+ billing: billingPathOf(row.billingPath),
529
+ inputTokens: row.inputTokens,
530
+ outputTokens: row.outputTokens,
531
+ cachedTokens: row.cachedTokens,
532
+ cacheWriteTokens: row.cacheWriteTokens,
533
+ reasoningTokens: row.reasoningTokens,
534
+ totalTokens: row.totalTokens,
535
+ creditUsd: microsToUsd(row.pricedCostMicros),
536
+ estimatedProviderUsd:
537
+ row.estimatedProviderCostMicros == null
538
+ ? null
539
+ : microsToUsd(row.estimatedProviderCostMicros),
540
+ pricingSource: pricingSourceOf(row.pricingSource),
541
+ })),
435
542
  warmSeconds,
436
543
  priorWarmSeconds,
437
544
  warmGroups: warmGroups.map((group) => ({
@@ -458,8 +565,14 @@ export async function getWorkspaceInsights(
458
565
  priorWorkspaceCreditUsd: microsToUsd(priorWorkspaceCreditMicros),
459
566
  creditUsd: microsToUsd(creditMicros),
460
567
  priorCreditUsd: microsToUsd(priorCreditMicros),
568
+ estimatedProviderUsd: microsToUsd(estimatedProviderCostMicros),
569
+ priorEstimatedProviderUsd: microsToUsd(priorEstimatedProviderCostMicros),
570
+ estimatedProviderCostKnownCalls,
571
+ priorEstimatedProviderCostKnownCalls,
572
+ modelCalls,
461
573
  priorInputTokens,
462
- priorCacheHitPct: cacheHitPct(priorCachedTokens, priorInputTokens),
574
+ priorTotalTokens,
575
+ priorCacheHitPct: cacheHitPct(priorCachedTokens, priorCacheInputTokens),
463
576
  priorCalls,
464
577
  goalsActive: depth.goalsActive,
465
578
  goalsCompleted: depth.goalsCompleted,
@@ -0,0 +1,204 @@
1
+ import type { MemorySlackPublicationDistribution } from "@opengeni/contracts";
2
+ import {
3
+ correctWorkspaceMemory,
4
+ enqueueMemorySlackPublication,
5
+ getCurrentMemorySlackPublicationConfiguration,
6
+ getWorkspaceMemorySlackPublicationSnapshot,
7
+ saveWorkspaceMemory,
8
+ withWorkspaceRls,
9
+ type CorrectWorkspaceMemoryInput,
10
+ type CorrectWorkspaceMemoryResult,
11
+ type Database,
12
+ type EnqueueMemorySlackPublicationResult,
13
+ type MemoryEmbedder,
14
+ type MemorySlackPublicationActor,
15
+ type SaveWorkspaceMemoryInput,
16
+ type SaveWorkspaceMemoryResult,
17
+ type WorkspaceMemorySlackPublicationSnapshot,
18
+ } from "@opengeni/db";
19
+ import {
20
+ evaluateMemorySlackPublication,
21
+ type MemorySlackOrigin,
22
+ type MemorySlackPublicationDecision,
23
+ } from "./memory-slack-publication";
24
+
25
+ export type MemorySlackPublicationCommitRequest = {
26
+ distribution: MemorySlackPublicationDistribution;
27
+ actor: MemorySlackPublicationActor;
28
+ ownerLabel?: string | null;
29
+ origin?: MemorySlackOrigin;
30
+ };
31
+
32
+ export type MemorySlackPublicationCommitResult = {
33
+ decision: MemorySlackPublicationDecision | null;
34
+ enqueue: EnqueueMemorySlackPublicationResult | null;
35
+ };
36
+
37
+ export async function saveWorkspaceMemoryWithSlackPublication(
38
+ db: Database,
39
+ input: SaveWorkspaceMemoryInput,
40
+ publication: MemorySlackPublicationCommitRequest | null,
41
+ embedder?: MemoryEmbedder,
42
+ ): Promise<SaveWorkspaceMemoryResult & { slackPublication: MemorySlackPublicationCommitResult }> {
43
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
44
+ const result = await saveWorkspaceMemory(scopedDb, input, embedder);
45
+ const slackPublication = publication
46
+ ? await publishSavedMemoryMutation(scopedDb, input, result, publication)
47
+ : { decision: null, enqueue: null };
48
+ return { ...result, slackPublication };
49
+ });
50
+ }
51
+
52
+ export async function correctWorkspaceMemoryWithSlackPublication(
53
+ db: Database,
54
+ input: CorrectWorkspaceMemoryInput,
55
+ publication: MemorySlackPublicationCommitRequest | null,
56
+ embedder?: MemoryEmbedder,
57
+ ): Promise<
58
+ CorrectWorkspaceMemoryResult & { slackPublication: MemorySlackPublicationCommitResult }
59
+ > {
60
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
61
+ const result = await correctWorkspaceMemory(scopedDb, input, embedder);
62
+ if (!publication || result.action !== "superseded" || !result.replacement) {
63
+ return { ...result, slackPublication: { decision: null, enqueue: null } };
64
+ }
65
+ const replacement = await requiredSnapshot(scopedDb, input.workspaceId, result.replacement.id);
66
+ const slackPublication = await evaluateAndEnqueue(scopedDb, {
67
+ accountId: input.accountId,
68
+ workspaceId: input.workspaceId,
69
+ snapshot: replacement,
70
+ changeKind: "corrected",
71
+ relatedMemoryId: result.memory.id,
72
+ occurredAt: result.replacement.updatedAt,
73
+ publication,
74
+ });
75
+ return { ...result, slackPublication };
76
+ });
77
+ }
78
+
79
+ async function publishSavedMemoryMutation(
80
+ db: Database,
81
+ input: SaveWorkspaceMemoryInput,
82
+ result: SaveWorkspaceMemoryResult,
83
+ publication: MemorySlackPublicationCommitRequest,
84
+ ): Promise<MemorySlackPublicationCommitResult> {
85
+ if (result.updated || (result.deduped && !result.superseded)) {
86
+ return { decision: null, enqueue: null };
87
+ }
88
+
89
+ if (result.superseded) {
90
+ const replacement = await requiredSnapshot(db, input.workspaceId, result.memory.id);
91
+ if (replacement.supersedesId === result.superseded.id) {
92
+ return await evaluateAndEnqueue(db, {
93
+ accountId: input.accountId,
94
+ workspaceId: input.workspaceId,
95
+ snapshot: replacement,
96
+ changeKind: "corrected",
97
+ relatedMemoryId: result.superseded.id,
98
+ occurredAt: result.memory.updatedAt,
99
+ publication,
100
+ });
101
+ }
102
+ const retired = await requiredSnapshot(db, input.workspaceId, result.superseded.id);
103
+ return await evaluateAndEnqueue(db, {
104
+ accountId: input.accountId,
105
+ workspaceId: input.workspaceId,
106
+ snapshot: retired,
107
+ changeKind: "superseded",
108
+ relatedMemoryId: result.memory.id,
109
+ occurredAt: result.superseded.updatedAt,
110
+ publication,
111
+ });
112
+ }
113
+
114
+ const created = await requiredSnapshot(db, input.workspaceId, result.memory.id);
115
+ return await evaluateAndEnqueue(db, {
116
+ accountId: input.accountId,
117
+ workspaceId: input.workspaceId,
118
+ snapshot: created,
119
+ changeKind: "created",
120
+ relatedMemoryId: null,
121
+ occurredAt: result.memory.createdAt,
122
+ publication,
123
+ });
124
+ }
125
+
126
+ async function evaluateAndEnqueue(
127
+ db: Database,
128
+ input: {
129
+ accountId: string;
130
+ workspaceId: string;
131
+ snapshot: WorkspaceMemorySlackPublicationSnapshot;
132
+ changeKind: "created" | "corrected" | "superseded";
133
+ relatedMemoryId: string | null;
134
+ occurredAt: string;
135
+ publication: MemorySlackPublicationCommitRequest;
136
+ },
137
+ ): Promise<MemorySlackPublicationCommitResult> {
138
+ const configuration = await getCurrentMemorySlackPublicationConfiguration(db, input.workspaceId);
139
+ const decision = evaluateMemorySlackPublication({
140
+ context: {
141
+ accountId: input.accountId,
142
+ workspaceId: input.workspaceId,
143
+ now: input.occurredAt,
144
+ },
145
+ policy: configuration
146
+ ? {
147
+ enabled: configuration.enabled,
148
+ autoImportances: configuration.autoImportances,
149
+ reviewImportances: configuration.reviewImportances,
150
+ }
151
+ : undefined,
152
+ memory: {
153
+ sourceType: "workspace_memory",
154
+ accountId: input.snapshot.accountId,
155
+ workspaceId: input.snapshot.workspaceId,
156
+ id: input.snapshot.id,
157
+ version: input.snapshot.version,
158
+ status: input.snapshot.status as Parameters<
159
+ typeof evaluateMemorySlackPublication
160
+ >[0]["memory"]["status"],
161
+ kind: input.snapshot.kind as Parameters<
162
+ typeof evaluateMemorySlackPublication
163
+ >[0]["memory"]["kind"],
164
+ scopeType: input.snapshot.scopeType as Parameters<
165
+ typeof evaluateMemorySlackPublication
166
+ >[0]["memory"]["scopeType"],
167
+ namespace: input.snapshot.namespace,
168
+ labels: input.snapshot.labels,
169
+ validFrom: input.snapshot.validFrom,
170
+ validUntil: input.snapshot.validUntil,
171
+ supersedesId: input.snapshot.supersedesId,
172
+ supersededById: input.snapshot.supersededById,
173
+ },
174
+ change: {
175
+ kind: input.changeKind,
176
+ relatedMemoryId: input.relatedMemoryId,
177
+ occurredAt: input.occurredAt,
178
+ origin: input.publication.origin ?? "native",
179
+ ownerLabel: input.publication.ownerLabel ?? null,
180
+ },
181
+ distribution: input.publication.distribution,
182
+ });
183
+ if (!decision.eligible) return { decision, enqueue: null };
184
+
185
+ const enqueue = await enqueueMemorySlackPublication(db, {
186
+ accountId: input.accountId,
187
+ workspaceId: input.workspaceId,
188
+ sourceType: "workspace_memory",
189
+ sourceId: input.snapshot.id,
190
+ sourceVersion: String(input.snapshot.version),
191
+ sourceIdempotencyKey: decision.idempotencyKey,
192
+ projection: decision.projection,
193
+ importance: decision.projection.importance,
194
+ deliveryMode: decision.deliveryMode,
195
+ actor: input.publication.actor,
196
+ });
197
+ return { decision, enqueue };
198
+ }
199
+
200
+ async function requiredSnapshot(db: Database, workspaceId: string, memoryId: string) {
201
+ const snapshot = await getWorkspaceMemorySlackPublicationSnapshot(db, workspaceId, memoryId);
202
+ if (!snapshot) throw new Error("Memory Slack publication source disappeared before commit");
203
+ return snapshot;
204
+ }