@opengeni/core 0.28.0 → 1.0.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/README.md +1 -1
- package/dist/access/external-actor-authority.d.ts +47 -0
- package/dist/access/index.d.ts +182 -3
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/api-integration-servers.d.ts +20 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +24 -0
- package/dist/application/connect-authority.d.ts +13 -0
- package/dist/application/connect-operation.d.ts +31 -0
- package/dist/application/external-continuation.d.ts +15 -0
- package/dist/application/external-identity-lifecycle.d.ts +20 -0
- package/dist/application/external-link-work-admission.d.ts +12 -0
- package/dist/application/external-workspace-members.d.ts +34 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +39 -2
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +26 -13
- package/dist/application/session-mcp-credential-rotation.d.ts +29 -0
- package/dist/application/session-tenancy.d.ts +19 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/billing/limits.d.ts +11 -0
- package/dist/canonical-human-identities.d.ts +7 -2
- package/dist/canonical-human-identities.js +17 -4
- package/dist/canonical-human-identities.js.map +1 -1
- package/dist/{chunk-6WKPWE5S.js → chunk-2MKMRGSY.js} +108 -24
- package/dist/chunk-2MKMRGSY.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-GVO3N37G.js} +205 -37
- package/dist/chunk-GVO3N37G.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-LCQXNR62.js} +36 -10
- package/dist/chunk-LCQXNR62.js.map +1 -0
- package/dist/chunk-MM6N7CTO.js +387 -0
- package/dist/chunk-MM6N7CTO.js.map +1 -0
- package/dist/chunk-QO5GVFFO.js +235 -0
- package/dist/chunk-QO5GVFFO.js.map +1 -0
- package/dist/codex-model-availability.d.ts +14 -0
- package/dist/default-session-model.d.ts +139 -0
- package/dist/dependencies.d.ts +87 -4
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +58 -0
- package/dist/domain/capabilities.d.ts +22 -4
- package/dist/domain/company-profile-agent-admin.d.ts +25 -0
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +36 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +4 -3
- package/dist/domain/environments.d.ts +5 -9
- package/dist/domain/external-creation-attribution.d.ts +6 -0
- package/dist/domain/file-owner.d.ts +8 -0
- package/dist/domain/github-action-policies.d.ts +36 -0
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/github-skill-source.d.ts +5 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +28 -1
- package/dist/domain/knowledge-files.d.ts +20 -0
- package/dist/domain/knowledge-messages.d.ts +39 -0
- package/dist/domain/knowledge-messages.test.d.ts +1 -0
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -0
- package/dist/domain/knowledge-search.test.d.ts +1 -0
- package/dist/domain/knowledge.d.ts +9 -0
- package/dist/domain/mcp-account-bindings.d.ts +19 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/personal-connection-delegations.d.ts +87 -4
- package/dist/domain/pr-review.d.ts +249 -0
- package/dist/domain/resources.d.ts +24 -3
- package/dist/domain/scheduled-task-access.d.ts +247 -0
- package/dist/domain/scheduled-tasks.d.ts +103 -6
- package/dist/domain/session-connection-accounts.d.ts +16 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +93 -4
- package/dist/domain/sessions.d.ts +275 -28
- package/dist/domain/skill-imports.d.ts +1 -0
- package/dist/domain/skill-search.d.ts +30 -0
- package/dist/domain/skills.d.ts +117 -0
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/workspace-members.d.ts +8 -1
- package/dist/editable-artifact-live/application.d.ts +12 -3
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +5 -2
- package/dist/editable-artifact-live/ticket.d.ts +2 -2
- package/dist/editable-artifact-live/types.d.ts +14 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -4
- package/dist/index.d.ts +49 -3
- package/dist/index.js +18826 -6847
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.d.ts +131 -0
- package/dist/managed-auth-session-sets.js +51 -0
- package/dist/managed-auth-session-sets.js.map +1 -0
- package/dist/managed-session.d.ts +98 -11
- package/dist/model-catalog.d.ts +117 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +22 -5
- package/dist/rigs/provider-images.d.ts +1 -1
- package/dist/sandbox/fleet.d.ts +59 -10
- package/dist/sandbox/routing.d.ts +22 -2
- package/dist/sandbox/runtime-settings.d.ts +56 -0
- package/dist/session-authorization.d.ts +38 -1
- package/dist/site-session-origin.d.ts +9 -0
- package/dist/transcription.d.ts +12 -1
- package/dist/turn-surface.d.ts +18 -0
- package/dist/user-presence.d.ts +32 -0
- package/package.json +18 -11
- package/src/access/external-actor-authority.ts +94 -0
- package/src/access/index.ts +1386 -44
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +215 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +77 -0
- package/src/application/connect-authority.ts +77 -0
- package/src/application/connect-operation.ts +80 -0
- package/src/application/external-continuation.ts +129 -0
- package/src/application/external-identity-lifecycle.ts +48 -0
- package/src/application/external-link-work-admission.ts +90 -0
- package/src/application/external-workspace-members.ts +232 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +222 -29
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +430 -73
- package/src/application/session-mcp-credential-rotation.ts +217 -0
- package/src/application/session-tenancy.ts +445 -0
- package/src/application/user-resource-grants.ts +154 -0
- package/src/billing/limits.ts +103 -24
- package/src/canonical-human-identities.ts +22 -3
- package/src/codex-model-availability.ts +136 -0
- package/src/default-session-model.ts +656 -0
- package/src/dependencies.ts +97 -5
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +270 -0
- package/src/domain/capabilities.ts +244 -151
- package/src/domain/company-profile-agent-admin.ts +56 -0
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +371 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +61 -15
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +32 -1
- package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
- package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
- package/src/domain/environments.ts +25 -45
- package/src/domain/external-creation-attribution.ts +22 -0
- package/src/domain/file-owner.ts +67 -0
- package/src/domain/github-action-policies.ts +169 -0
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/github-skill-source.ts +233 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +270 -169
- package/src/domain/knowledge-files.ts +46 -0
- package/src/domain/knowledge-messages.test.ts +91 -0
- package/src/domain/knowledge-messages.ts +106 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +561 -0
- package/src/domain/knowledge-search.ts +256 -0
- package/src/domain/knowledge.ts +133 -0
- package/src/domain/mcp-account-bindings.ts +171 -0
- package/src/domain/mcp-account-routes.ts +121 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/organization-membership-lifecycle.ts +10 -0
- package/src/domain/personal-connection-delegations.ts +758 -115
- package/src/domain/pr-review.ts +687 -0
- package/src/domain/resources.ts +182 -25
- package/src/domain/scheduled-task-access.ts +1052 -0
- package/src/domain/scheduled-tasks.ts +1952 -215
- package/src/domain/session-connection-accounts.ts +67 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +55 -0
- package/src/domain/session-tool-policy.ts +443 -19
- package/src/domain/sessions.ts +3417 -619
- package/src/domain/skill-imports.ts +137 -17
- package/src/domain/skill-search.ts +221 -0
- package/src/domain/skills.ts +104 -0
- package/src/domain/slack-bot.ts +128 -3
- package/src/domain/workspace-members.ts +34 -2
- package/src/editable-artifact-live/application.ts +29 -4
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +167 -13
- package/src/editable-artifact-live/ticket.ts +87 -6
- package/src/editable-artifact-live/types.ts +16 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +69 -4
- package/src/managed-auth-session-sets.ts +371 -0
- package/src/managed-session.ts +520 -2
- package/src/model-catalog.ts +873 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +179 -63
- package/src/rigs/provider-images.ts +2 -3
- package/src/sandbox/fleet.ts +471 -59
- package/src/sandbox/routing.ts +160 -33
- package/src/sandbox/runtime-settings.ts +281 -0
- package/src/session-authorization.ts +262 -78
- package/src/site-session-origin.ts +22 -0
- package/src/transcription.ts +13 -1
- package/src/turn-surface.ts +45 -0
- package/src/user-presence.ts +134 -0
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-IBOEYG6N.js +0 -34
- package/dist/chunk-IBOEYG6N.js.map +0 -1
- package/dist/chunk-JJU6I5XD.js.map +0 -1
- package/dist/chunk-NYPMQ7NO.js.map +0 -1
- package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
- package/dist/domain/packs.d.ts +0 -55
- package/src/domain/durable-learning-slack-publication.ts +0 -202
- package/src/domain/packs.ts +0 -556
package/src/domain/insights.ts
CHANGED
|
@@ -5,38 +5,82 @@ import {
|
|
|
5
5
|
type WorkspaceInsightsResponse,
|
|
6
6
|
} from "@opengeni/contracts";
|
|
7
7
|
import {
|
|
8
|
-
aggregateModelCallFacts,
|
|
9
|
-
aggregateModelCallFactsByDay,
|
|
10
|
-
aggregateModelCallFactsByHour,
|
|
11
|
-
aggregateRootSessionDrivers,
|
|
12
|
-
aggregateScheduleFacts,
|
|
13
8
|
aggregateSessionDepth,
|
|
14
|
-
aggregateWarmSecondsByGroup,
|
|
15
9
|
countOnlineMachines,
|
|
16
10
|
countScheduledTaskFires,
|
|
17
11
|
countSessionsAttachedToGroups,
|
|
18
12
|
enumerateUtcDays,
|
|
19
13
|
enumerateUtcHours,
|
|
14
|
+
getSessionAccessProjection,
|
|
20
15
|
listFloorSessions,
|
|
21
16
|
listLiveWarmLeases,
|
|
22
|
-
listModelCallFacets,
|
|
23
|
-
listRecentModelCalls,
|
|
24
17
|
listScheduledTasks,
|
|
18
|
+
readWorkspaceInsightsModelBundle,
|
|
19
|
+
readWorkspaceInsightsUsageBundle,
|
|
25
20
|
requireWorkspace,
|
|
26
|
-
sumUsageQuantity,
|
|
27
|
-
sumUsageQuantityByDay,
|
|
28
|
-
sumUsageQuantityByHour,
|
|
29
|
-
sumUsageQuantityInRange,
|
|
30
21
|
type Database,
|
|
31
22
|
} from "@opengeni/db";
|
|
32
23
|
|
|
24
|
+
import { measureInsightsPhase, type InsightsPhaseObserver } from "./insights-timing";
|
|
25
|
+
export { measureInsightsPhase, workspaceInsightsPhaseMetricObserver } from "./insights-timing";
|
|
26
|
+
export type { InsightsPhaseObservation, InsightsPhaseObserver } from "./insights-timing";
|
|
33
27
|
const MACHINE_HEARTBEAT_FRESH_MS = 120_000;
|
|
28
|
+
export const WORKSPACE_INSIGHTS_PROVIDER_FILTER_MAX_UTF8_BYTES = 256;
|
|
29
|
+
export const WORKSPACE_INSIGHTS_MODEL_FILTER_MAX_UTF8_BYTES = 512;
|
|
30
|
+
|
|
31
|
+
export type WorkspaceInsightsFilterField = "provider" | "model" | "rootSessionId" | "sessionId";
|
|
32
|
+
|
|
33
|
+
export class WorkspaceInsightsFilterValidationError extends Error {
|
|
34
|
+
readonly field: WorkspaceInsightsFilterField;
|
|
35
|
+
readonly maxUtf8Bytes: number;
|
|
36
|
+
|
|
37
|
+
constructor(field: WorkspaceInsightsFilterField, maxUtf8Bytes: number, message?: string) {
|
|
38
|
+
super(message ?? `${field} must be at most ${maxUtf8Bytes} UTF-8 bytes`);
|
|
39
|
+
this.name = "WorkspaceInsightsFilterValidationError";
|
|
40
|
+
this.field = field;
|
|
41
|
+
this.maxUtf8Bytes = maxUtf8Bytes;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
46
|
+
|
|
47
|
+
/** Normalize a session drilldown scope; only exact UUIDs are accepted. */
|
|
48
|
+
export function normalizeWorkspaceInsightsSessionScope(
|
|
49
|
+
value: string | null | undefined,
|
|
50
|
+
field: "rootSessionId" | "sessionId",
|
|
51
|
+
): string | null {
|
|
52
|
+
const normalized = value?.trim() || null;
|
|
53
|
+
if (normalized === null || normalized === "all") return null;
|
|
54
|
+
if (!UUID_PATTERN.test(normalized)) {
|
|
55
|
+
throw new WorkspaceInsightsFilterValidationError(field, 36, `${field} must be a UUID`);
|
|
56
|
+
}
|
|
57
|
+
return normalized.toLowerCase();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Normalize the public filter sentinel and enforce the database authority envelope. */
|
|
61
|
+
export function normalizeWorkspaceInsightsFilter(
|
|
62
|
+
value: string | null | undefined,
|
|
63
|
+
field: WorkspaceInsightsFilterField,
|
|
64
|
+
): string | null {
|
|
65
|
+
const normalized = value?.trim() || null;
|
|
66
|
+
if (normalized === null || normalized === "all") return null;
|
|
67
|
+
const maxUtf8Bytes =
|
|
68
|
+
field === "provider"
|
|
69
|
+
? WORKSPACE_INSIGHTS_PROVIDER_FILTER_MAX_UTF8_BYTES
|
|
70
|
+
: WORKSPACE_INSIGHTS_MODEL_FILTER_MAX_UTF8_BYTES;
|
|
71
|
+
if (new TextEncoder().encode(normalized).byteLength > maxUtf8Bytes) {
|
|
72
|
+
throw new WorkspaceInsightsFilterValidationError(field, maxUtf8Bytes);
|
|
73
|
+
}
|
|
74
|
+
return normalized;
|
|
75
|
+
}
|
|
34
76
|
|
|
35
77
|
export type GetWorkspaceInsightsInput = {
|
|
36
78
|
workspaceId: string;
|
|
37
79
|
range: InsightsRange;
|
|
38
80
|
provider?: string | null;
|
|
39
81
|
model?: string | null;
|
|
82
|
+
rootSessionId?: string | null;
|
|
83
|
+
sessionId?: string | null;
|
|
40
84
|
now?: Date;
|
|
41
85
|
};
|
|
42
86
|
|
|
@@ -173,171 +217,150 @@ function pricingSourceOf(
|
|
|
173
217
|
return value === "configured_list_price" || value === "gateway_reported" ? value : null;
|
|
174
218
|
}
|
|
175
219
|
|
|
220
|
+
export function insightsSessionLabel(input: {
|
|
221
|
+
id: string;
|
|
222
|
+
title: string | null;
|
|
223
|
+
depth?: number | null;
|
|
224
|
+
}): string {
|
|
225
|
+
const title = input.title?.trim();
|
|
226
|
+
if (title) return title;
|
|
227
|
+
return `${(input.depth ?? 0) > 0 ? "Agent" : "Session"} ${input.id.slice(0, 8)}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function insightsProjectLabel(input: {
|
|
231
|
+
kind: "project" | "other" | "unfiled" | "unavailable" | "deleted";
|
|
232
|
+
name: string | null;
|
|
233
|
+
projects: number;
|
|
234
|
+
}): string {
|
|
235
|
+
switch (input.kind) {
|
|
236
|
+
case "project":
|
|
237
|
+
return input.name?.trim() || "Untitled project";
|
|
238
|
+
case "other":
|
|
239
|
+
return `${input.projects.toLocaleString("en-US")} other ${input.projects === 1 ? "project" : "projects"}`;
|
|
240
|
+
case "unfiled":
|
|
241
|
+
return "No project";
|
|
242
|
+
case "unavailable":
|
|
243
|
+
return "Root session not visible";
|
|
244
|
+
case "deleted":
|
|
245
|
+
return "Deleted chats";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
176
249
|
export async function getWorkspaceInsights(
|
|
177
250
|
db: Database,
|
|
178
251
|
settings: Settings,
|
|
179
252
|
input: GetWorkspaceInsightsInput,
|
|
253
|
+
observePhase?: InsightsPhaseObserver,
|
|
180
254
|
): Promise<WorkspaceInsightsResponse> {
|
|
181
|
-
|
|
255
|
+
const provider = normalizeWorkspaceInsightsFilter(input.provider, "provider");
|
|
256
|
+
const model = normalizeWorkspaceInsightsFilter(input.model, "model");
|
|
257
|
+
const rootSessionId = normalizeWorkspaceInsightsSessionScope(
|
|
258
|
+
input.rootSessionId,
|
|
259
|
+
"rootSessionId",
|
|
260
|
+
);
|
|
261
|
+
const sessionId = normalizeWorkspaceInsightsSessionScope(input.sessionId, "sessionId");
|
|
262
|
+
await measureInsightsPhase(observePhase, "require_workspace", () =>
|
|
263
|
+
requireWorkspace(db, input.workspaceId),
|
|
264
|
+
);
|
|
265
|
+
const [visibleRootScope, visibleSessionScope] = await Promise.all([
|
|
266
|
+
rootSessionId ? getSessionAccessProjection(db, input.workspaceId, rootSessionId) : null,
|
|
267
|
+
sessionId ? getSessionAccessProjection(db, input.workspaceId, sessionId) : null,
|
|
268
|
+
]);
|
|
182
269
|
const now = input.now ?? new Date();
|
|
183
270
|
const window = resolveRangeWindow(input.range, now);
|
|
184
|
-
const provider = input.provider?.trim() || null;
|
|
185
|
-
const model = input.model?.trim() || null;
|
|
186
271
|
const modelFilterActive = Boolean(provider || model);
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
272
|
+
const scopeActive = Boolean(rootSessionId || sessionId);
|
|
273
|
+
const filter = { provider, model, rootSessionId, sessionId };
|
|
274
|
+
|
|
275
|
+
const [modelBundle, usageBundle, liveWarm, tasks, depth, floorRows, machinesOnline] =
|
|
276
|
+
await Promise.all([
|
|
277
|
+
measureInsightsPhase(observePhase, "model_bundle", () =>
|
|
278
|
+
readWorkspaceInsightsModelBundle(db, {
|
|
279
|
+
workspaceId: input.workspaceId,
|
|
280
|
+
since: window.since,
|
|
281
|
+
until: window.until,
|
|
282
|
+
priorSince: window.priorSince,
|
|
283
|
+
priorUntil: window.priorUntil,
|
|
284
|
+
granularity: input.range === "today" ? "hour" : "day",
|
|
285
|
+
...filter,
|
|
286
|
+
}),
|
|
287
|
+
),
|
|
288
|
+
measureInsightsPhase(observePhase, "usage_bundle", () =>
|
|
289
|
+
readWorkspaceInsightsUsageBundle(db, {
|
|
290
|
+
workspaceId: input.workspaceId,
|
|
291
|
+
since: window.since,
|
|
292
|
+
until: window.until,
|
|
293
|
+
priorSince: window.priorSince,
|
|
294
|
+
priorUntil: window.priorUntil,
|
|
295
|
+
monthSince: startOfUtcMonth(now),
|
|
296
|
+
granularity: input.range === "today" ? "hour" : "day",
|
|
297
|
+
warmGroupLimit: 24,
|
|
298
|
+
}),
|
|
299
|
+
),
|
|
300
|
+
measureInsightsPhase(observePhase, "live_warm", () =>
|
|
301
|
+
listLiveWarmLeases(db, input.workspaceId),
|
|
302
|
+
),
|
|
303
|
+
measureInsightsPhase(observePhase, "scheduled_tasks", () =>
|
|
304
|
+
listScheduledTasks(db, input.workspaceId, 100),
|
|
305
|
+
),
|
|
306
|
+
measureInsightsPhase(observePhase, "session_depth", () =>
|
|
307
|
+
aggregateSessionDepth(db, input.workspaceId),
|
|
308
|
+
),
|
|
309
|
+
measureInsightsPhase(observePhase, "floor_sessions", () =>
|
|
310
|
+
listFloorSessions(db, input.workspaceId, 24),
|
|
311
|
+
),
|
|
312
|
+
settings.sandboxSelfhostedEnabled
|
|
313
|
+
? measureInsightsPhase(observePhase, "online_machines", () =>
|
|
314
|
+
countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS, now),
|
|
315
|
+
)
|
|
316
|
+
: Promise.resolve(0),
|
|
317
|
+
]);
|
|
192
318
|
|
|
193
|
-
const
|
|
319
|
+
const {
|
|
320
|
+
modelRows,
|
|
321
|
+
priorModelRows,
|
|
322
|
+
factBuckets: factDays,
|
|
323
|
+
rootDrivers,
|
|
324
|
+
priorRootDrivers,
|
|
325
|
+
projects,
|
|
326
|
+
privateChats,
|
|
327
|
+
privateChatsTruncated,
|
|
328
|
+
scheduleFacts,
|
|
329
|
+
facets,
|
|
330
|
+
recentCalls,
|
|
331
|
+
promptContributions,
|
|
332
|
+
dataThrough,
|
|
333
|
+
driverGroups,
|
|
334
|
+
driversTruncated,
|
|
335
|
+
facetsTruncated,
|
|
336
|
+
recentCallsTruncated,
|
|
337
|
+
} = modelBundle;
|
|
338
|
+
const {
|
|
194
339
|
workspaceCreditMicros,
|
|
195
340
|
priorWorkspaceCreditMicros,
|
|
196
341
|
warmSeconds,
|
|
197
342
|
priorWarmSeconds,
|
|
198
|
-
|
|
199
|
-
priorModelRows,
|
|
200
|
-
factDays,
|
|
201
|
-
warmDays,
|
|
202
|
-
costDays,
|
|
343
|
+
buckets: usageBuckets,
|
|
203
344
|
warmGroups,
|
|
204
|
-
liveWarm,
|
|
205
|
-
rootDrivers,
|
|
206
|
-
scheduleFacts,
|
|
207
|
-
tasks,
|
|
208
|
-
depth,
|
|
209
|
-
floorRows,
|
|
210
|
-
machinesOnline,
|
|
211
345
|
billableTokensUsed,
|
|
212
346
|
agentRunsUsed,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
workspaceId: input.workspaceId,
|
|
230
|
-
eventType: "sandbox.warm_seconds",
|
|
231
|
-
since: window.since,
|
|
232
|
-
until: window.until,
|
|
233
|
-
}),
|
|
234
|
-
sumUsageQuantityInRange(db, {
|
|
235
|
-
workspaceId: input.workspaceId,
|
|
236
|
-
eventType: "sandbox.warm_seconds",
|
|
237
|
-
since: window.priorSince,
|
|
238
|
-
until: window.priorUntil,
|
|
239
|
-
}),
|
|
240
|
-
aggregateModelCallFacts(db, {
|
|
241
|
-
workspaceId: input.workspaceId,
|
|
242
|
-
since: window.since,
|
|
243
|
-
until: window.until,
|
|
244
|
-
...filter,
|
|
245
|
-
}),
|
|
246
|
-
aggregateModelCallFacts(db, {
|
|
247
|
-
workspaceId: input.workspaceId,
|
|
248
|
-
since: window.priorSince,
|
|
249
|
-
until: window.priorUntil,
|
|
250
|
-
...filter,
|
|
251
|
-
}),
|
|
252
|
-
aggregateFactsForSeries(db, {
|
|
253
|
-
workspaceId: input.workspaceId,
|
|
254
|
-
since: window.since,
|
|
255
|
-
until: window.until,
|
|
256
|
-
...filter,
|
|
257
|
-
}),
|
|
258
|
-
sumUsageForSeries(db, {
|
|
259
|
-
workspaceId: input.workspaceId,
|
|
260
|
-
eventType: "sandbox.warm_seconds",
|
|
261
|
-
since: window.since,
|
|
262
|
-
until: window.until,
|
|
263
|
-
}),
|
|
264
|
-
sumUsageForSeries(db, {
|
|
265
|
-
workspaceId: input.workspaceId,
|
|
266
|
-
eventType: "model.cost",
|
|
267
|
-
since: window.since,
|
|
268
|
-
until: window.until,
|
|
269
|
-
}),
|
|
270
|
-
aggregateWarmSecondsByGroup(db, {
|
|
271
|
-
workspaceId: input.workspaceId,
|
|
272
|
-
since: window.since,
|
|
273
|
-
until: window.until,
|
|
274
|
-
limit: 24,
|
|
275
|
-
}),
|
|
276
|
-
listLiveWarmLeases(db, input.workspaceId),
|
|
277
|
-
aggregateRootSessionDrivers(db, {
|
|
278
|
-
workspaceId: input.workspaceId,
|
|
279
|
-
since: window.since,
|
|
280
|
-
until: window.until,
|
|
281
|
-
...filter,
|
|
282
|
-
limit: 8,
|
|
283
|
-
}),
|
|
284
|
-
aggregateScheduleFacts(db, {
|
|
285
|
-
workspaceId: input.workspaceId,
|
|
286
|
-
since: window.since,
|
|
287
|
-
until: window.until,
|
|
288
|
-
...filter,
|
|
289
|
-
}),
|
|
290
|
-
listScheduledTasks(db, input.workspaceId, 100),
|
|
291
|
-
aggregateSessionDepth(db, input.workspaceId),
|
|
292
|
-
listFloorSessions(db, input.workspaceId, 24),
|
|
293
|
-
settings.sandboxSelfhostedEnabled
|
|
294
|
-
? countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS, now)
|
|
295
|
-
: Promise.resolve(0),
|
|
296
|
-
sumUsageQuantity(db, {
|
|
297
|
-
workspaceId: input.workspaceId,
|
|
298
|
-
eventType: "model.tokens",
|
|
299
|
-
since: startOfUtcMonth(now),
|
|
300
|
-
}),
|
|
301
|
-
sumUsageQuantity(db, {
|
|
302
|
-
workspaceId: input.workspaceId,
|
|
303
|
-
eventType: "agent_run.created",
|
|
304
|
-
since: startOfUtcMonth(now),
|
|
305
|
-
}),
|
|
306
|
-
listModelCallFacets(db, {
|
|
307
|
-
workspaceId: input.workspaceId,
|
|
308
|
-
since: window.since,
|
|
309
|
-
until: window.until,
|
|
310
|
-
}),
|
|
311
|
-
listRecentModelCalls(db, {
|
|
312
|
-
workspaceId: input.workspaceId,
|
|
313
|
-
since: window.since,
|
|
314
|
-
until: window.until,
|
|
315
|
-
...filter,
|
|
316
|
-
limit: 50,
|
|
317
|
-
}),
|
|
318
|
-
]);
|
|
319
|
-
|
|
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),
|
|
347
|
+
} = usageBundle;
|
|
348
|
+
const [attached, fireCounts] = await Promise.all([
|
|
349
|
+
measureInsightsPhase(observePhase, "attached_sessions", () =>
|
|
350
|
+
countSessionsAttachedToGroups(
|
|
351
|
+
db,
|
|
352
|
+
input.workspaceId,
|
|
353
|
+
warmGroups.map((group) => group.groupId),
|
|
354
|
+
),
|
|
355
|
+
),
|
|
356
|
+
measureInsightsPhase(observePhase, "scheduled_fires", () =>
|
|
357
|
+
countScheduledTaskFires(db, {
|
|
358
|
+
workspaceId: input.workspaceId,
|
|
359
|
+
since: window.since,
|
|
360
|
+
until: window.until,
|
|
361
|
+
taskIds: tasks.map((task) => task.id),
|
|
362
|
+
}),
|
|
334
363
|
),
|
|
335
|
-
countScheduledTaskFires(db, {
|
|
336
|
-
workspaceId: input.workspaceId,
|
|
337
|
-
since: window.since,
|
|
338
|
-
until: window.until,
|
|
339
|
-
taskIds: tasks.map((task) => task.id),
|
|
340
|
-
}),
|
|
341
364
|
]);
|
|
342
365
|
const backendByGroup = new Map(liveWarm.map((lease) => [lease.groupId, lease.backend]));
|
|
343
366
|
const warmSecondsByGroup = new Map(warmGroups.map((group) => [group.groupId, group.warmSeconds]));
|
|
@@ -361,6 +384,8 @@ export async function getWorkspaceInsights(
|
|
|
361
384
|
creditUsd: microsToUsd(row.pricedCostMicros),
|
|
362
385
|
estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
|
|
363
386
|
estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
|
|
387
|
+
equivalentCreditUsd: microsToUsd(row.equivalentCreditCostMicros),
|
|
388
|
+
equivalentCreditCostKnownCalls: row.equivalentCreditCostKnownCalls,
|
|
364
389
|
}))
|
|
365
390
|
.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
366
391
|
|
|
@@ -382,7 +407,25 @@ export async function getWorkspaceInsights(
|
|
|
382
407
|
(sum, row) => sum + row.estimatedProviderCostKnownCalls,
|
|
383
408
|
0,
|
|
384
409
|
);
|
|
410
|
+
const equivalentCreditCostMicros = modelRows.reduce(
|
|
411
|
+
(sum, row) => sum + row.equivalentCreditCostMicros,
|
|
412
|
+
0,
|
|
413
|
+
);
|
|
414
|
+
const priorEquivalentCreditCostMicros = priorModelRows.reduce(
|
|
415
|
+
(sum, row) => sum + row.equivalentCreditCostMicros,
|
|
416
|
+
0,
|
|
417
|
+
);
|
|
418
|
+
const equivalentCreditCostKnownCalls = modelRows.reduce(
|
|
419
|
+
(sum, row) => sum + row.equivalentCreditCostKnownCalls,
|
|
420
|
+
0,
|
|
421
|
+
);
|
|
422
|
+
const priorEquivalentCreditCostKnownCalls = priorModelRows.reduce(
|
|
423
|
+
(sum, row) => sum + row.equivalentCreditCostKnownCalls,
|
|
424
|
+
0,
|
|
425
|
+
);
|
|
385
426
|
const modelCalls = modelRows.reduce((sum, row) => sum + row.calls, 0);
|
|
427
|
+
const cachedTokens = modelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
|
|
428
|
+
const cacheInputTokens = modelRows.reduce((sum, row) => sum + row.cacheInputTokens, 0);
|
|
386
429
|
const priorInputTokens = priorModelRows.reduce((sum, row) => sum + row.inputTokens, 0);
|
|
387
430
|
const priorTotalTokens = priorModelRows.reduce((sum, row) => sum + row.totalTokens, 0);
|
|
388
431
|
const priorCachedTokens = priorModelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
|
|
@@ -398,6 +441,8 @@ export async function getWorkspaceInsights(
|
|
|
398
441
|
costMicros: 0,
|
|
399
442
|
estimatedProviderCostMicros: 0,
|
|
400
443
|
estimatedProviderCostKnownCalls: 0,
|
|
444
|
+
equivalentCreditCostMicros: 0,
|
|
445
|
+
equivalentCreditCostKnownCalls: 0,
|
|
401
446
|
inputTokens: 0,
|
|
402
447
|
outputTokens: 0,
|
|
403
448
|
cachedTokens: 0,
|
|
@@ -409,15 +454,18 @@ export async function getWorkspaceInsights(
|
|
|
409
454
|
cacheKnownCalls: 0,
|
|
410
455
|
calls: 0,
|
|
411
456
|
};
|
|
412
|
-
const modelCostMicros =
|
|
413
|
-
|
|
414
|
-
|
|
457
|
+
const modelCostMicros =
|
|
458
|
+
modelFilterActive || scopeActive
|
|
459
|
+
? facts.costMicros
|
|
460
|
+
: (usageBuckets.get(bucket)?.costMicros ?? facts.costMicros);
|
|
415
461
|
return {
|
|
416
462
|
label: input.range === "today" ? bucket.slice(11) : bucket.slice(5),
|
|
417
463
|
modelCostUsd: microsToUsd(modelCostMicros),
|
|
418
464
|
estimatedProviderUsd: microsToUsd(facts.estimatedProviderCostMicros),
|
|
419
465
|
estimatedProviderCostKnownCalls: facts.estimatedProviderCostKnownCalls,
|
|
420
|
-
|
|
466
|
+
equivalentCreditUsd: microsToUsd(facts.equivalentCreditCostMicros),
|
|
467
|
+
equivalentCreditCostKnownCalls: facts.equivalentCreditCostKnownCalls,
|
|
468
|
+
warmSeconds: usageBuckets.get(bucket)?.warmSeconds ?? 0,
|
|
421
469
|
inputTokens: facts.inputTokens,
|
|
422
470
|
outputTokens: facts.outputTokens,
|
|
423
471
|
cachedTokens: facts.cachedTokens,
|
|
@@ -445,10 +493,12 @@ export async function getWorkspaceInsights(
|
|
|
445
493
|
return {
|
|
446
494
|
id: `root:${row.rootSessionId}`,
|
|
447
495
|
groupBy: "root_session" as const,
|
|
448
|
-
label:
|
|
496
|
+
label: insightsSessionLabel({ id: row.rootSessionId, title: row.title, depth: 0 }),
|
|
449
497
|
creditUsd,
|
|
450
498
|
estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
|
|
451
499
|
estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
|
|
500
|
+
equivalentCreditUsd: microsToUsd(row.equivalentCreditCostMicros),
|
|
501
|
+
equivalentCreditCostKnownCalls: row.equivalentCreditCostKnownCalls,
|
|
452
502
|
tokens: row.totalTokens,
|
|
453
503
|
cacheHitPct: cacheHitPct(row.cachedTokens, row.cacheInputTokens),
|
|
454
504
|
pctOfCreditUsd:
|
|
@@ -460,6 +510,20 @@ export async function getWorkspaceInsights(
|
|
|
460
510
|
};
|
|
461
511
|
});
|
|
462
512
|
|
|
513
|
+
const projectRows = projects.map((row) => ({
|
|
514
|
+
id: row.kind === "project" && row.channelId ? `project:${row.channelId}` : row.kind,
|
|
515
|
+
kind: row.kind,
|
|
516
|
+
label: insightsProjectLabel(row),
|
|
517
|
+
projects: row.projects,
|
|
518
|
+
rootSessions: row.rootSessions,
|
|
519
|
+
calls: row.calls,
|
|
520
|
+
creditUsd: microsToUsd(row.pricedCostMicros),
|
|
521
|
+
estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
|
|
522
|
+
estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
|
|
523
|
+
tokens: row.totalTokens,
|
|
524
|
+
cacheHitPct: cacheHitPct(row.cachedTokens, row.cacheInputTokens),
|
|
525
|
+
}));
|
|
526
|
+
|
|
463
527
|
const scheduleFactById = new Map(scheduleFacts.map((row) => [row.scheduledTaskId, row] as const));
|
|
464
528
|
const schedules = tasks.map((task) => {
|
|
465
529
|
const fact = scheduleFactById.get(task.id);
|
|
@@ -470,6 +534,8 @@ export async function getWorkspaceInsights(
|
|
|
470
534
|
creditUsd: fact ? microsToUsd(fact.pricedCostMicros) : null,
|
|
471
535
|
estimatedProviderUsd: fact ? microsToUsd(fact.estimatedProviderCostMicros) : null,
|
|
472
536
|
estimatedProviderCostKnownCalls: fact ? fact.estimatedProviderCostKnownCalls : null,
|
|
537
|
+
equivalentCreditUsd: fact ? microsToUsd(fact.equivalentCreditCostMicros) : null,
|
|
538
|
+
equivalentCreditCostKnownCalls: fact ? fact.equivalentCreditCostKnownCalls : null,
|
|
473
539
|
tokens: fact ? fact.totalTokens : null,
|
|
474
540
|
cacheHitPct: fact ? cacheHitPct(fact.cachedTokens, fact.cacheInputTokens) : null,
|
|
475
541
|
billing: fact ? billingPathOf(fact.billingPath) : null,
|
|
@@ -486,7 +552,11 @@ export async function getWorkspaceInsights(
|
|
|
486
552
|
})
|
|
487
553
|
.map((row) => ({
|
|
488
554
|
id: row.id,
|
|
489
|
-
title:
|
|
555
|
+
title: insightsSessionLabel({
|
|
556
|
+
id: row.id,
|
|
557
|
+
title: row.title,
|
|
558
|
+
depth: row.nestedAgentDepth,
|
|
559
|
+
}),
|
|
490
560
|
state: floorState(row),
|
|
491
561
|
depth: row.nestedAgentDepth,
|
|
492
562
|
model: row.model,
|
|
@@ -514,13 +584,20 @@ export async function getWorkspaceInsights(
|
|
|
514
584
|
sessions: bucket.sessions,
|
|
515
585
|
})),
|
|
516
586
|
drivers,
|
|
587
|
+
projects: projectRows,
|
|
588
|
+
privateChats,
|
|
589
|
+
privateChatsTruncated,
|
|
517
590
|
schedules,
|
|
518
591
|
recentCalls: recentCalls.map((row) => ({
|
|
519
592
|
id: row.id,
|
|
520
593
|
occurredAt: row.occurredAt.toISOString(),
|
|
521
594
|
recordedAt: row.recordedAt.toISOString(),
|
|
522
595
|
sessionId: row.sessionId,
|
|
523
|
-
sessionTitle:
|
|
596
|
+
sessionTitle: insightsSessionLabel({
|
|
597
|
+
id: row.sessionId,
|
|
598
|
+
title: row.sessionTitle,
|
|
599
|
+
depth: row.sessionDepth,
|
|
600
|
+
}),
|
|
524
601
|
turnId: row.turnId,
|
|
525
602
|
provider: row.provider,
|
|
526
603
|
providerApi: row.providerApi,
|
|
@@ -537,8 +614,11 @@ export async function getWorkspaceInsights(
|
|
|
537
614
|
row.estimatedProviderCostMicros == null
|
|
538
615
|
? null
|
|
539
616
|
: microsToUsd(row.estimatedProviderCostMicros),
|
|
617
|
+
equivalentCreditUsd:
|
|
618
|
+
row.equivalentCreditCostMicros == null ? null : microsToUsd(row.equivalentCreditCostMicros),
|
|
540
619
|
pricingSource: pricingSourceOf(row.pricingSource),
|
|
541
620
|
})),
|
|
621
|
+
promptContributions,
|
|
542
622
|
warmSeconds,
|
|
543
623
|
priorWarmSeconds,
|
|
544
624
|
warmGroups: warmGroups.map((group) => ({
|
|
@@ -569,24 +649,45 @@ export async function getWorkspaceInsights(
|
|
|
569
649
|
priorEstimatedProviderUsd: microsToUsd(priorEstimatedProviderCostMicros),
|
|
570
650
|
estimatedProviderCostKnownCalls,
|
|
571
651
|
priorEstimatedProviderCostKnownCalls,
|
|
652
|
+
equivalentCreditUsd: microsToUsd(equivalentCreditCostMicros),
|
|
653
|
+
priorEquivalentCreditUsd: microsToUsd(priorEquivalentCreditCostMicros),
|
|
654
|
+
equivalentCreditCostKnownCalls,
|
|
655
|
+
priorEquivalentCreditCostKnownCalls,
|
|
572
656
|
modelCalls,
|
|
573
657
|
priorInputTokens,
|
|
574
658
|
priorTotalTokens,
|
|
575
659
|
priorCacheHitPct: cacheHitPct(priorCachedTokens, priorCacheInputTokens),
|
|
660
|
+
priorCacheInputTokens,
|
|
576
661
|
priorCalls,
|
|
577
662
|
goalsActive: depth.goalsActive,
|
|
578
663
|
goalsCompleted: depth.goalsCompleted,
|
|
579
664
|
sessionsTouched: depth.sessionsTouched,
|
|
580
665
|
rootSessions: depth.rootSessions,
|
|
581
666
|
deepestDepth: depth.deepestDepth,
|
|
582
|
-
deepestSessionTitle: depth.
|
|
667
|
+
deepestSessionTitle: depth.deepestSessionId
|
|
668
|
+
? insightsSessionLabel({
|
|
669
|
+
id: depth.deepestSessionId,
|
|
670
|
+
title: depth.deepestSessionTitle,
|
|
671
|
+
depth: depth.deepestDepth,
|
|
672
|
+
})
|
|
673
|
+
: "",
|
|
583
674
|
avgDepth: Math.round(depth.avgDepth * 10) / 10,
|
|
584
675
|
warmIdleNow: liveWarm.filter((lease) => lease.turnHolders === 0).length,
|
|
585
676
|
billableTokensUsed,
|
|
586
677
|
billableTokenCap: limits.maxMonthlyTokensPerWorkspace ?? null,
|
|
587
678
|
agentRunsUsed,
|
|
588
679
|
agentRunCap: limits.maxMonthlyAgentRunsPerWorkspace ?? null,
|
|
589
|
-
modelFilterActive,
|
|
680
|
+
modelFilterActive: modelFilterActive || scopeActive,
|
|
681
|
+
dataThrough: dataThrough?.toISOString() ?? null,
|
|
682
|
+
cacheHitPct: cacheHitPct(cachedTokens, cacheInputTokens),
|
|
683
|
+
scope: {
|
|
684
|
+
rootSessionId: visibleRootScope ? rootSessionId : null,
|
|
685
|
+
sessionId: visibleSessionScope ? sessionId : null,
|
|
686
|
+
},
|
|
687
|
+
driverGroups,
|
|
688
|
+
driversTruncated,
|
|
689
|
+
facetsTruncated,
|
|
690
|
+
recentCallsTruncated,
|
|
590
691
|
});
|
|
591
692
|
|
|
592
693
|
return { snapshot };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
completeKnowledgeFilePreparation,
|
|
4
|
+
inspectKnowledgeFilePreparation,
|
|
5
|
+
type KnowledgeContext,
|
|
6
|
+
} from "@opengeni/db";
|
|
7
|
+
import { retryWhileMissing } from "@opengeni/storage";
|
|
8
|
+
import type { ApiRouteDeps } from "../dependencies";
|
|
9
|
+
|
|
10
|
+
/** Existing original -> one source revision, through the ordinary task policy. */
|
|
11
|
+
export async function prepareKnowledgeFile(
|
|
12
|
+
deps: Pick<ApiRouteDeps, "db" | "objectStorage" | "getDocumentServices">,
|
|
13
|
+
context: KnowledgeContext,
|
|
14
|
+
fileId: string,
|
|
15
|
+
purpose: "evidence" | "reference" = "evidence",
|
|
16
|
+
) {
|
|
17
|
+
const inspected = await inspectKnowledgeFilePreparation(deps.db, context, fileId);
|
|
18
|
+
if (inspected.status !== "prepare") return inspected;
|
|
19
|
+
if (!deps.objectStorage) throw new Error("File storage is unavailable");
|
|
20
|
+
const object = await retryWhileMissing(() =>
|
|
21
|
+
deps.objectStorage!.getObjectBytes(inspected.file.objectKey),
|
|
22
|
+
);
|
|
23
|
+
if (!object) throw new Error("The original file is unavailable");
|
|
24
|
+
const sourceVersion = createHash("sha256").update(object.bytes).digest("hex");
|
|
25
|
+
if (
|
|
26
|
+
object.bytes.byteLength !== inspected.file.sizeBytes ||
|
|
27
|
+
(inspected.file.sha256 && inspected.file.sha256.toLowerCase() !== sourceVersion)
|
|
28
|
+
) {
|
|
29
|
+
throw new Error("The original file no longer matches its retained metadata");
|
|
30
|
+
}
|
|
31
|
+
// A screenshot/diagram can support a visual finding without containing text.
|
|
32
|
+
// Keep its exact original; OCR is neither required evidence nor the finding.
|
|
33
|
+
const originalOnly = purpose === "evidence" && inspected.file.contentType.startsWith("image/");
|
|
34
|
+
const parsed = originalOnly
|
|
35
|
+
? { text: "" }
|
|
36
|
+
: await deps.getDocumentServices().parser.parse(object.bytes, inspected.file);
|
|
37
|
+
if (!originalOnly && !parsed.text.trim())
|
|
38
|
+
throw new Error("No searchable text could be extracted from this file");
|
|
39
|
+
return completeKnowledgeFilePreparation(deps.db, context, {
|
|
40
|
+
fileId,
|
|
41
|
+
title: inspected.file.filename,
|
|
42
|
+
sourceVersion,
|
|
43
|
+
content: parsed.text,
|
|
44
|
+
purpose,
|
|
45
|
+
});
|
|
46
|
+
}
|