@opengeni/core 2.2.0-canary.0 → 2.4.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/session-tenancy.d.ts +1 -1
- package/dist/dependencies.d.ts +38 -0
- package/dist/domain/insights.d.ts +15 -0
- package/dist/domain/remember.d.ts +4 -1
- package/dist/domain/sessions.d.ts +4 -3
- package/dist/index.js +280 -239
- package/dist/index.js.map +1 -1
- package/dist/rigs/provider-images.d.ts +1 -1
- package/package.json +10 -10
- package/src/access/index.ts +1 -0
- package/src/application/new-session-drafts.ts +24 -10
- package/src/application/session-tenancy.ts +98 -5
- package/src/dependencies.ts +35 -0
- package/src/domain/insights.ts +112 -162
- package/src/domain/organization-membership-lifecycle.ts +2 -1
- package/src/domain/packs.ts +10 -8
- package/src/domain/remember.ts +37 -15
- package/src/domain/sessions.ts +49 -44
- package/src/rigs/index.ts +4 -5
- package/src/rigs/provider-images.ts +2 -3
- package/src/sandbox/runtime-settings.ts +11 -11
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type RigChangeVerification, type RigProviderImage, type RigProviderImages, type RigVersion, type SandboxBackend } from "@opengeni/contracts";
|
|
2
|
-
export type RigProviderImageDefinition = Pick<RigVersion, "
|
|
2
|
+
export type RigProviderImageDefinition = Pick<RigVersion, "setupScript" | "checks" | "credentialHooks" | "defaultVariableSetIds">;
|
|
3
3
|
export declare function rigProviderImageProviderBindingKeyHash(bindingKey: string): string;
|
|
4
4
|
export declare function rigProviderImageSetupHash(definition: Pick<RigProviderImageDefinition, "setupScript">): string;
|
|
5
5
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0-canary.0",
|
|
4
4
|
"description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -50,15 +50,15 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
53
|
-
"@opengeni/codex": "^0.2.19-canary.
|
|
54
|
-
"@opengeni/config": "^0.
|
|
55
|
-
"@opengeni/contracts": "^2.
|
|
56
|
-
"@opengeni/db": "^3.
|
|
57
|
-
"@opengeni/documents": "^0.
|
|
58
|
-
"@opengeni/events": "^0.
|
|
59
|
-
"@opengeni/observability": "^0.8.
|
|
60
|
-
"@opengeni/runtime": "^1.
|
|
61
|
-
"@opengeni/storage": "^0.2.
|
|
53
|
+
"@opengeni/codex": "^0.2.19-canary.1",
|
|
54
|
+
"@opengeni/config": "^0.20.1-canary.0",
|
|
55
|
+
"@opengeni/contracts": "^2.5.0-canary.0",
|
|
56
|
+
"@opengeni/db": "^3.4.0-canary.0",
|
|
57
|
+
"@opengeni/documents": "^0.8.2-canary.0",
|
|
58
|
+
"@opengeni/events": "^0.4.0-canary.0",
|
|
59
|
+
"@opengeni/observability": "^0.8.6-canary.0",
|
|
60
|
+
"@opengeni/runtime": "^1.4.0-canary.0",
|
|
61
|
+
"@opengeni/storage": "^0.2.108-canary.0",
|
|
62
62
|
"hono": "^4.12.18",
|
|
63
63
|
"zod": "^4.2.1"
|
|
64
64
|
},
|
package/src/access/index.ts
CHANGED
|
@@ -388,6 +388,7 @@ async function resolveAccessContext(c: Context, deps: AccessDeps): Promise<Acces
|
|
|
388
388
|
email: session.user.email,
|
|
389
389
|
name: session.user.name,
|
|
390
390
|
emailVerified: session.user.emailVerified,
|
|
391
|
+
provisionFallbackOrganization: false,
|
|
391
392
|
});
|
|
392
393
|
canonicalManagedCookieContexts.add(context);
|
|
393
394
|
return context;
|
|
@@ -103,18 +103,32 @@ async function hydrateNewSessionDraft(
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
const options = { ...mapped.options };
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
106
|
+
const variableSetIds = options.variableSetIds ?? [];
|
|
107
|
+
if (variableSetIds.length > 0) {
|
|
108
|
+
const selectionsAuthorized =
|
|
109
|
+
hasPermission(grant.permissions, "variable-sets:attach") &&
|
|
110
|
+
hasPermission(grant.permissions, "variable-sets:use") &&
|
|
111
|
+
(
|
|
112
|
+
await Promise.all(
|
|
113
|
+
variableSetIds.map((variableSetId) =>
|
|
114
|
+
getVariableSet(
|
|
115
|
+
deps.db,
|
|
116
|
+
{ accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
|
|
117
|
+
variableSetId,
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
).every(Boolean);
|
|
122
|
+
if (selectionsAuthorized) {
|
|
123
|
+
options.variableSetIds = variableSetIds;
|
|
124
|
+
options.variableSetId = variableSetIds[variableSetIds.length - 1];
|
|
125
|
+
} else {
|
|
126
|
+
delete options.variableSetIds;
|
|
116
127
|
delete options.variableSetId;
|
|
117
128
|
}
|
|
129
|
+
} else {
|
|
130
|
+
delete options.variableSetIds;
|
|
131
|
+
delete options.variableSetId;
|
|
118
132
|
}
|
|
119
133
|
if (options.rigId) {
|
|
120
134
|
const rig = await getRig(deps.db, workspaceId, options.rigId);
|
|
@@ -7,9 +7,11 @@ import {
|
|
|
7
7
|
type ForkSessionRequest,
|
|
8
8
|
type SessionAuthorizationSurface,
|
|
9
9
|
type UpdateSessionVisibilityRequest,
|
|
10
|
+
type VariableSet,
|
|
10
11
|
} from "@opengeni/contracts";
|
|
11
12
|
import {
|
|
12
13
|
forkSessionContent,
|
|
14
|
+
getRig,
|
|
13
15
|
getPrivateSessionCreatePolicy,
|
|
14
16
|
getSessionEventForSubject,
|
|
15
17
|
isRetryableDatabaseTransportFailure,
|
|
@@ -17,6 +19,7 @@ import {
|
|
|
17
19
|
replayAppliedSessionFork,
|
|
18
20
|
sessionTenancyProductActivated,
|
|
19
21
|
SessionTenancyAccessError,
|
|
22
|
+
SessionTenancyInvalidRequestError,
|
|
20
23
|
SessionTenancyNotActivatedError,
|
|
21
24
|
transitionSessionVisibility,
|
|
22
25
|
type Database,
|
|
@@ -26,8 +29,12 @@ import { publishDurableSessionEvents, type EventBus } from "@opengeni/events";
|
|
|
26
29
|
import { requirePermission, type AccessGrantAuthorization } from "../access";
|
|
27
30
|
import { requireSessionAuthorization } from "../session-authorization";
|
|
28
31
|
import type { AppDependencies } from "../dependencies";
|
|
32
|
+
import { validateVariableSetAttachment } from "../domain/environments";
|
|
29
33
|
|
|
30
|
-
type SessionTenancyDependencies = Pick<
|
|
34
|
+
type SessionTenancyDependencies = Pick<
|
|
35
|
+
AppDependencies,
|
|
36
|
+
"db" | "bus" | "sessionAuthorization" | "settings"
|
|
37
|
+
>;
|
|
31
38
|
|
|
32
39
|
export class SessionTenancyManagedHumanRequiredError extends Error {
|
|
33
40
|
readonly name = "SessionTenancyManagedHumanRequiredError";
|
|
@@ -286,9 +293,72 @@ export async function forkManagedHumanSession(
|
|
|
286
293
|
request.visibility === "private" ? ("user_private" as const) : ("workspace_shared" as const),
|
|
287
294
|
workspaceSharedAcknowledged: request.workspaceSharedAcknowledged,
|
|
288
295
|
operationKey: request.idempotencyKey,
|
|
296
|
+
...(request.rigId !== undefined || request.variableSetIds !== undefined
|
|
297
|
+
? {
|
|
298
|
+
runtimeRequest: {
|
|
299
|
+
rigId: request.rigId ?? null,
|
|
300
|
+
variableSetIds: request.variableSetIds ?? [],
|
|
301
|
+
},
|
|
302
|
+
}
|
|
303
|
+
: {}),
|
|
289
304
|
};
|
|
290
305
|
const recoverAppliedReceipt = async () =>
|
|
291
306
|
await runTenancyMutation(async () => await replayAppliedSessionFork(deps.db, forkInput));
|
|
307
|
+
const runtimeSetupRequested = request.rigId !== undefined || request.variableSetIds !== undefined;
|
|
308
|
+
let runtimeConfigurationPromise: Promise<{
|
|
309
|
+
variableSetIds: string[];
|
|
310
|
+
rigId: string | null;
|
|
311
|
+
rigVersionId: string | null;
|
|
312
|
+
}> | null = null;
|
|
313
|
+
const resolveRuntimeConfiguration = async () => {
|
|
314
|
+
if (runtimeConfigurationPromise) return await runtimeConfigurationPromise;
|
|
315
|
+
runtimeConfigurationPromise = (async () => {
|
|
316
|
+
const variableSets: VariableSet[] = [];
|
|
317
|
+
for (const variableSetId of request.variableSetIds ?? []) {
|
|
318
|
+
variableSets.push(
|
|
319
|
+
await validateVariableSetAttachment(
|
|
320
|
+
{ settings: deps.settings, db: deps.db },
|
|
321
|
+
authorization.grant,
|
|
322
|
+
workspaceId,
|
|
323
|
+
variableSetId,
|
|
324
|
+
),
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
let rigVersionId: string | null = null;
|
|
328
|
+
if (request.rigId) {
|
|
329
|
+
requirePermission(authorization.grant, "rigs:use");
|
|
330
|
+
const rig = await getRig(deps.db, authorization.grant, request.rigId);
|
|
331
|
+
if (!rig?.activeVersion) {
|
|
332
|
+
throw new SessionTenancyInvalidRequestError();
|
|
333
|
+
}
|
|
334
|
+
for (const defaultVariableSetId of new Set(rig.activeVersion.defaultVariableSetIds)) {
|
|
335
|
+
await validateVariableSetAttachment(
|
|
336
|
+
{ settings: deps.settings, db: deps.db },
|
|
337
|
+
authorization.grant,
|
|
338
|
+
workspaceId,
|
|
339
|
+
defaultVariableSetId,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
rigVersionId = rig.activeVersion.id;
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
variableSetIds: variableSets.map((variableSet) => variableSet.id),
|
|
346
|
+
rigId: request.rigId ?? null,
|
|
347
|
+
rigVersionId,
|
|
348
|
+
};
|
|
349
|
+
})();
|
|
350
|
+
return await runtimeConfigurationPromise;
|
|
351
|
+
};
|
|
352
|
+
const publishRuntimeConfiguration = async (result: ForkSessionContentResult) => {
|
|
353
|
+
if (!result.runtimeEventId || !result.runtimeEventSequence) return;
|
|
354
|
+
await publishExactCommittedEvent(deps, {
|
|
355
|
+
workspaceId,
|
|
356
|
+
sessionId: result.sessionId,
|
|
357
|
+
subjectId: authorization.grant.subjectId,
|
|
358
|
+
eventId: result.runtimeEventId,
|
|
359
|
+
eventSequence: result.runtimeEventSequence,
|
|
360
|
+
});
|
|
361
|
+
};
|
|
292
362
|
|
|
293
363
|
// A committed response is owned by the exact actor/key/request tuple and no
|
|
294
364
|
// longer depends on mutable source visibility. Resolve it before source/host
|
|
@@ -296,7 +366,13 @@ export async function forkManagedHumanSession(
|
|
|
296
366
|
// owner makes a formerly shared session private.
|
|
297
367
|
const applied = await recoverAppliedReceipt();
|
|
298
368
|
if (applied) {
|
|
299
|
-
|
|
369
|
+
const response = await projectAndPublishSessionFork(
|
|
370
|
+
deps,
|
|
371
|
+
authorization.grant.subjectId,
|
|
372
|
+
applied,
|
|
373
|
+
);
|
|
374
|
+
await publishRuntimeConfiguration(applied);
|
|
375
|
+
return response;
|
|
300
376
|
}
|
|
301
377
|
|
|
302
378
|
try {
|
|
@@ -311,11 +387,28 @@ export async function forkManagedHumanSession(
|
|
|
311
387
|
// exact applied receipt can convert the authorization failure into replay.
|
|
312
388
|
const racedApplied = await recoverAppliedReceipt();
|
|
313
389
|
if (racedApplied) {
|
|
314
|
-
|
|
390
|
+
const response = await projectAndPublishSessionFork(
|
|
391
|
+
deps,
|
|
392
|
+
authorization.grant.subjectId,
|
|
393
|
+
racedApplied,
|
|
394
|
+
);
|
|
395
|
+
await publishRuntimeConfiguration(racedApplied);
|
|
396
|
+
return response;
|
|
315
397
|
}
|
|
316
398
|
throw authorizationError;
|
|
317
399
|
}
|
|
318
400
|
|
|
319
|
-
|
|
320
|
-
|
|
401
|
+
// Validate all requested runtime resources before committing the independent
|
|
402
|
+
// fork so ordinary authorization failures cannot leave an unusable copy.
|
|
403
|
+
const runtimeConfiguration = runtimeSetupRequested ? await resolveRuntimeConfiguration() : null;
|
|
404
|
+
const result = await runTenancyMutation(
|
|
405
|
+
async () =>
|
|
406
|
+
await forkSessionContent(deps.db, {
|
|
407
|
+
...forkInput,
|
|
408
|
+
...(runtimeConfiguration ? { runtimeConfiguration } : {}),
|
|
409
|
+
}),
|
|
410
|
+
);
|
|
411
|
+
const response = await projectAndPublishSessionFork(deps, authorization.grant.subjectId, result);
|
|
412
|
+
await publishRuntimeConfiguration(result);
|
|
413
|
+
return response;
|
|
321
414
|
}
|
package/src/dependencies.ts
CHANGED
|
@@ -96,6 +96,36 @@ export type DocumentIndexClient = {
|
|
|
96
96
|
}) => Promise<Document | void>;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
export type ManagedEmailMessage = {
|
|
100
|
+
kind: "email_verification" | "password_reset" | "organization_user_setup";
|
|
101
|
+
from: string;
|
|
102
|
+
to: string;
|
|
103
|
+
subject: string;
|
|
104
|
+
text: string;
|
|
105
|
+
html: string;
|
|
106
|
+
idempotencyKey?: string;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type ManagedEmailDeliveryResult =
|
|
110
|
+
| { status: "sent"; providerMessageId: string | null }
|
|
111
|
+
| { status: "failed"; errorClass: string }
|
|
112
|
+
| { status: "outcome_unknown"; errorClass: string };
|
|
113
|
+
|
|
114
|
+
/** Provider-neutral, injectable boundary for every managed-auth email. */
|
|
115
|
+
export type ManagedEmailTransport = {
|
|
116
|
+
/** Effective provider sender; frozen into any idempotent payload digest. */
|
|
117
|
+
readonly sender: string;
|
|
118
|
+
/**
|
|
119
|
+
* Stable non-secret provider/account/policy namespace plus the provider's
|
|
120
|
+
* guaranteed key-retention window. Both are durably fenced before I/O.
|
|
121
|
+
*/
|
|
122
|
+
readonly idempotency: {
|
|
123
|
+
readonly scope: string;
|
|
124
|
+
readonly retentionSeconds: number;
|
|
125
|
+
};
|
|
126
|
+
send(message: ManagedEmailMessage): Promise<ManagedEmailDeliveryResult>;
|
|
127
|
+
};
|
|
128
|
+
|
|
99
129
|
export type AppDependencies = {
|
|
100
130
|
settings: Settings;
|
|
101
131
|
db: Database;
|
|
@@ -132,6 +162,8 @@ export type AppDependencies = {
|
|
|
132
162
|
* App credentials; standalone deployments fall back to @opengeni/github.
|
|
133
163
|
*/
|
|
134
164
|
githubAppApi?: GitHubAppApiPort;
|
|
165
|
+
/** Optional provider seam for the separately registered OpenGeni Lens App. */
|
|
166
|
+
prReviewGithubAppApi?: GitHubAppApiPort;
|
|
135
167
|
/**
|
|
136
168
|
* Optional host-owned connection credential seam. API-side consumers use
|
|
137
169
|
* the MCP leg for Codemode/Code Mode; worker consumers bind the same port
|
|
@@ -145,6 +177,8 @@ export type AppDependencies = {
|
|
|
145
177
|
*/
|
|
146
178
|
sessionAuthorization?: SessionAuthorizationPort | null;
|
|
147
179
|
managedAuth?: ManagedAuth | null;
|
|
180
|
+
/** Injectable managed-email transport; standalone API defaults to Resend or local capture. */
|
|
181
|
+
managedEmailTransport?: ManagedEmailTransport;
|
|
148
182
|
/** Injectable Codex HTTP transport for deterministic API/provider tests. */
|
|
149
183
|
codexFetch?: typeof fetch;
|
|
150
184
|
/** Injectable GitHub transport for deterministic personal-OAuth tests. */
|
|
@@ -188,6 +222,7 @@ export type AppDependencies = {
|
|
|
188
222
|
export type ObjectStorageDependency = ReturnType<typeof createObjectStorage>;
|
|
189
223
|
|
|
190
224
|
export type ApiRouteDeps = AppDependencies & {
|
|
225
|
+
managedEmailTransport: ManagedEmailTransport;
|
|
191
226
|
objectStorage: ObjectStorageDependency;
|
|
192
227
|
githubStateSecret: string;
|
|
193
228
|
documentIndexer: DocumentIndexClient;
|
package/src/domain/insights.ts
CHANGED
|
@@ -5,14 +5,7 @@ import {
|
|
|
5
5
|
type WorkspaceInsightsResponse,
|
|
6
6
|
} from "@opengeni/contracts";
|
|
7
7
|
import {
|
|
8
|
-
aggregateModelCallFacts,
|
|
9
|
-
aggregateModelCallFactsByDay,
|
|
10
|
-
aggregateModelCallFactsByHour,
|
|
11
|
-
aggregateModelContextContributions,
|
|
12
|
-
aggregateRootSessionDrivers,
|
|
13
|
-
aggregateScheduleFacts,
|
|
14
8
|
aggregateSessionDepth,
|
|
15
|
-
aggregateWarmSecondsByGroup,
|
|
16
9
|
countOnlineMachines,
|
|
17
10
|
countScheduledTaskFires,
|
|
18
11
|
countSessionsAttachedToGroups,
|
|
@@ -20,18 +13,47 @@ import {
|
|
|
20
13
|
enumerateUtcHours,
|
|
21
14
|
listFloorSessions,
|
|
22
15
|
listLiveWarmLeases,
|
|
23
|
-
listModelCallFacets,
|
|
24
|
-
listRecentModelCalls,
|
|
25
16
|
listScheduledTasks,
|
|
17
|
+
readWorkspaceInsightsModelBundle,
|
|
18
|
+
readWorkspaceInsightsUsageBundle,
|
|
26
19
|
requireWorkspace,
|
|
27
|
-
sumUsageQuantity,
|
|
28
|
-
sumUsageQuantityByDay,
|
|
29
|
-
sumUsageQuantityByHour,
|
|
30
|
-
sumUsageQuantityInRange,
|
|
31
20
|
type Database,
|
|
32
21
|
} from "@opengeni/db";
|
|
33
22
|
|
|
34
23
|
const MACHINE_HEARTBEAT_FRESH_MS = 120_000;
|
|
24
|
+
export const WORKSPACE_INSIGHTS_PROVIDER_FILTER_MAX_UTF8_BYTES = 256;
|
|
25
|
+
export const WORKSPACE_INSIGHTS_MODEL_FILTER_MAX_UTF8_BYTES = 512;
|
|
26
|
+
|
|
27
|
+
export type WorkspaceInsightsFilterField = "provider" | "model";
|
|
28
|
+
|
|
29
|
+
export class WorkspaceInsightsFilterValidationError extends Error {
|
|
30
|
+
readonly field: WorkspaceInsightsFilterField;
|
|
31
|
+
readonly maxUtf8Bytes: number;
|
|
32
|
+
|
|
33
|
+
constructor(field: WorkspaceInsightsFilterField, maxUtf8Bytes: number) {
|
|
34
|
+
super(`${field} must be at most ${maxUtf8Bytes} UTF-8 bytes`);
|
|
35
|
+
this.name = "WorkspaceInsightsFilterValidationError";
|
|
36
|
+
this.field = field;
|
|
37
|
+
this.maxUtf8Bytes = maxUtf8Bytes;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Normalize the public filter sentinel and enforce the database authority envelope. */
|
|
42
|
+
export function normalizeWorkspaceInsightsFilter(
|
|
43
|
+
value: string | null | undefined,
|
|
44
|
+
field: WorkspaceInsightsFilterField,
|
|
45
|
+
): string | null {
|
|
46
|
+
const normalized = value?.trim() || null;
|
|
47
|
+
if (normalized === null || normalized === "all") return null;
|
|
48
|
+
const maxUtf8Bytes =
|
|
49
|
+
field === "provider"
|
|
50
|
+
? WORKSPACE_INSIGHTS_PROVIDER_FILTER_MAX_UTF8_BYTES
|
|
51
|
+
: WORKSPACE_INSIGHTS_MODEL_FILTER_MAX_UTF8_BYTES;
|
|
52
|
+
if (new TextEncoder().encode(normalized).byteLength > maxUtf8Bytes) {
|
|
53
|
+
throw new WorkspaceInsightsFilterValidationError(field, maxUtf8Bytes);
|
|
54
|
+
}
|
|
55
|
+
return normalized;
|
|
56
|
+
}
|
|
35
57
|
|
|
36
58
|
export type GetWorkspaceInsightsInput = {
|
|
37
59
|
workspaceId: string;
|
|
@@ -174,167 +196,81 @@ function pricingSourceOf(
|
|
|
174
196
|
return value === "configured_list_price" || value === "gateway_reported" ? value : null;
|
|
175
197
|
}
|
|
176
198
|
|
|
199
|
+
export function insightsSessionLabel(input: {
|
|
200
|
+
id: string;
|
|
201
|
+
title: string | null;
|
|
202
|
+
depth?: number | null;
|
|
203
|
+
}): string {
|
|
204
|
+
const title = input.title?.trim();
|
|
205
|
+
if (title) return title;
|
|
206
|
+
return `${(input.depth ?? 0) > 0 ? "Agent" : "Session"} ${input.id.slice(0, 8)}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
177
209
|
export async function getWorkspaceInsights(
|
|
178
210
|
db: Database,
|
|
179
211
|
settings: Settings,
|
|
180
212
|
input: GetWorkspaceInsightsInput,
|
|
181
213
|
): Promise<WorkspaceInsightsResponse> {
|
|
214
|
+
const provider = normalizeWorkspaceInsightsFilter(input.provider, "provider");
|
|
215
|
+
const model = normalizeWorkspaceInsightsFilter(input.model, "model");
|
|
182
216
|
await requireWorkspace(db, input.workspaceId);
|
|
183
217
|
const now = input.now ?? new Date();
|
|
184
218
|
const window = resolveRangeWindow(input.range, now);
|
|
185
|
-
const provider = input.provider?.trim() || null;
|
|
186
|
-
const model = input.model?.trim() || null;
|
|
187
219
|
const modelFilterActive = Boolean(provider || model);
|
|
188
220
|
const filter = { provider, model };
|
|
189
|
-
const aggregateFactsForSeries =
|
|
190
|
-
input.range === "today" ? aggregateModelCallFactsByHour : aggregateModelCallFactsByDay;
|
|
191
|
-
const sumUsageForSeries =
|
|
192
|
-
input.range === "today" ? sumUsageQuantityByHour : sumUsageQuantityByDay;
|
|
193
221
|
|
|
194
|
-
const [
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
222
|
+
const [modelBundle, usageBundle, liveWarm, tasks, depth, floorRows, machinesOnline] =
|
|
223
|
+
await Promise.all([
|
|
224
|
+
readWorkspaceInsightsModelBundle(db, {
|
|
225
|
+
workspaceId: input.workspaceId,
|
|
226
|
+
since: window.since,
|
|
227
|
+
until: window.until,
|
|
228
|
+
priorSince: window.priorSince,
|
|
229
|
+
priorUntil: window.priorUntil,
|
|
230
|
+
granularity: input.range === "today" ? "hour" : "day",
|
|
231
|
+
...filter,
|
|
232
|
+
}),
|
|
233
|
+
readWorkspaceInsightsUsageBundle(db, {
|
|
234
|
+
workspaceId: input.workspaceId,
|
|
235
|
+
since: window.since,
|
|
236
|
+
until: window.until,
|
|
237
|
+
priorSince: window.priorSince,
|
|
238
|
+
priorUntil: window.priorUntil,
|
|
239
|
+
monthSince: startOfUtcMonth(now),
|
|
240
|
+
granularity: input.range === "today" ? "hour" : "day",
|
|
241
|
+
warmGroupLimit: 24,
|
|
242
|
+
}),
|
|
243
|
+
listLiveWarmLeases(db, input.workspaceId),
|
|
244
|
+
listScheduledTasks(db, input.workspaceId, 100),
|
|
245
|
+
aggregateSessionDepth(db, input.workspaceId),
|
|
246
|
+
listFloorSessions(db, input.workspaceId, 24),
|
|
247
|
+
settings.sandboxSelfhostedEnabled
|
|
248
|
+
? countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS, now)
|
|
249
|
+
: Promise.resolve(0),
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
const {
|
|
199
253
|
modelRows,
|
|
200
254
|
priorModelRows,
|
|
201
|
-
factDays,
|
|
202
|
-
warmDays,
|
|
203
|
-
costDays,
|
|
204
|
-
warmGroups,
|
|
205
|
-
liveWarm,
|
|
255
|
+
factBuckets: factDays,
|
|
206
256
|
rootDrivers,
|
|
257
|
+
priorRootDrivers,
|
|
207
258
|
scheduleFacts,
|
|
208
|
-
tasks,
|
|
209
|
-
depth,
|
|
210
|
-
floorRows,
|
|
211
|
-
machinesOnline,
|
|
212
|
-
billableTokensUsed,
|
|
213
|
-
agentRunsUsed,
|
|
214
259
|
facets,
|
|
215
260
|
recentCalls,
|
|
216
261
|
promptContributions,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}),
|
|
230
|
-
sumUsageQuantityInRange(db, {
|
|
231
|
-
workspaceId: input.workspaceId,
|
|
232
|
-
eventType: "sandbox.warm_seconds",
|
|
233
|
-
since: window.since,
|
|
234
|
-
until: window.until,
|
|
235
|
-
}),
|
|
236
|
-
sumUsageQuantityInRange(db, {
|
|
237
|
-
workspaceId: input.workspaceId,
|
|
238
|
-
eventType: "sandbox.warm_seconds",
|
|
239
|
-
since: window.priorSince,
|
|
240
|
-
until: window.priorUntil,
|
|
241
|
-
}),
|
|
242
|
-
aggregateModelCallFacts(db, {
|
|
243
|
-
workspaceId: input.workspaceId,
|
|
244
|
-
since: window.since,
|
|
245
|
-
until: window.until,
|
|
246
|
-
...filter,
|
|
247
|
-
}),
|
|
248
|
-
aggregateModelCallFacts(db, {
|
|
249
|
-
workspaceId: input.workspaceId,
|
|
250
|
-
since: window.priorSince,
|
|
251
|
-
until: window.priorUntil,
|
|
252
|
-
...filter,
|
|
253
|
-
}),
|
|
254
|
-
aggregateFactsForSeries(db, {
|
|
255
|
-
workspaceId: input.workspaceId,
|
|
256
|
-
since: window.since,
|
|
257
|
-
until: window.until,
|
|
258
|
-
...filter,
|
|
259
|
-
}),
|
|
260
|
-
sumUsageForSeries(db, {
|
|
261
|
-
workspaceId: input.workspaceId,
|
|
262
|
-
eventType: "sandbox.warm_seconds",
|
|
263
|
-
since: window.since,
|
|
264
|
-
until: window.until,
|
|
265
|
-
}),
|
|
266
|
-
sumUsageForSeries(db, {
|
|
267
|
-
workspaceId: input.workspaceId,
|
|
268
|
-
eventType: "model.cost",
|
|
269
|
-
since: window.since,
|
|
270
|
-
until: window.until,
|
|
271
|
-
}),
|
|
272
|
-
aggregateWarmSecondsByGroup(db, {
|
|
273
|
-
workspaceId: input.workspaceId,
|
|
274
|
-
since: window.since,
|
|
275
|
-
until: window.until,
|
|
276
|
-
limit: 24,
|
|
277
|
-
}),
|
|
278
|
-
listLiveWarmLeases(db, input.workspaceId),
|
|
279
|
-
aggregateRootSessionDrivers(db, {
|
|
280
|
-
workspaceId: input.workspaceId,
|
|
281
|
-
since: window.since,
|
|
282
|
-
until: window.until,
|
|
283
|
-
...filter,
|
|
284
|
-
limit: 8,
|
|
285
|
-
}),
|
|
286
|
-
aggregateScheduleFacts(db, {
|
|
287
|
-
workspaceId: input.workspaceId,
|
|
288
|
-
since: window.since,
|
|
289
|
-
until: window.until,
|
|
290
|
-
...filter,
|
|
291
|
-
}),
|
|
292
|
-
listScheduledTasks(db, input.workspaceId, 100),
|
|
293
|
-
aggregateSessionDepth(db, input.workspaceId),
|
|
294
|
-
listFloorSessions(db, input.workspaceId, 24),
|
|
295
|
-
settings.sandboxSelfhostedEnabled
|
|
296
|
-
? countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS, now)
|
|
297
|
-
: Promise.resolve(0),
|
|
298
|
-
sumUsageQuantity(db, {
|
|
299
|
-
workspaceId: input.workspaceId,
|
|
300
|
-
eventType: "model.tokens",
|
|
301
|
-
since: startOfUtcMonth(now),
|
|
302
|
-
}),
|
|
303
|
-
sumUsageQuantity(db, {
|
|
304
|
-
workspaceId: input.workspaceId,
|
|
305
|
-
eventType: "agent_run.created",
|
|
306
|
-
since: startOfUtcMonth(now),
|
|
307
|
-
}),
|
|
308
|
-
listModelCallFacets(db, {
|
|
309
|
-
workspaceId: input.workspaceId,
|
|
310
|
-
since: window.since,
|
|
311
|
-
until: window.until,
|
|
312
|
-
}),
|
|
313
|
-
listRecentModelCalls(db, {
|
|
314
|
-
workspaceId: input.workspaceId,
|
|
315
|
-
since: window.since,
|
|
316
|
-
until: window.until,
|
|
317
|
-
...filter,
|
|
318
|
-
limit: 50,
|
|
319
|
-
}),
|
|
320
|
-
aggregateModelContextContributions(db, {
|
|
321
|
-
workspaceId: input.workspaceId,
|
|
322
|
-
since: window.since,
|
|
323
|
-
until: window.until,
|
|
324
|
-
...filter,
|
|
325
|
-
}),
|
|
326
|
-
]);
|
|
327
|
-
|
|
328
|
-
const [priorRootDrivers, attached, fireCounts] = await Promise.all([
|
|
329
|
-
// Exact prior costs for the current top drivers — never a separate top-N page that
|
|
330
|
-
// drops roots and invents +$full as "new" spend.
|
|
331
|
-
aggregateRootSessionDrivers(db, {
|
|
332
|
-
workspaceId: input.workspaceId,
|
|
333
|
-
since: window.priorSince,
|
|
334
|
-
until: window.priorUntil,
|
|
335
|
-
...filter,
|
|
336
|
-
rootSessionIds: rootDrivers.map((row) => row.rootSessionId),
|
|
337
|
-
}),
|
|
262
|
+
} = modelBundle;
|
|
263
|
+
const {
|
|
264
|
+
workspaceCreditMicros,
|
|
265
|
+
priorWorkspaceCreditMicros,
|
|
266
|
+
warmSeconds,
|
|
267
|
+
priorWarmSeconds,
|
|
268
|
+
buckets: usageBuckets,
|
|
269
|
+
warmGroups,
|
|
270
|
+
billableTokensUsed,
|
|
271
|
+
agentRunsUsed,
|
|
272
|
+
} = usageBundle;
|
|
273
|
+
const [attached, fireCounts] = await Promise.all([
|
|
338
274
|
countSessionsAttachedToGroups(
|
|
339
275
|
db,
|
|
340
276
|
input.workspaceId,
|
|
@@ -419,13 +355,13 @@ export async function getWorkspaceInsights(
|
|
|
419
355
|
};
|
|
420
356
|
const modelCostMicros = modelFilterActive
|
|
421
357
|
? facts.costMicros
|
|
422
|
-
: (
|
|
358
|
+
: (usageBuckets.get(bucket)?.costMicros ?? facts.costMicros);
|
|
423
359
|
return {
|
|
424
360
|
label: input.range === "today" ? bucket.slice(11) : bucket.slice(5),
|
|
425
361
|
modelCostUsd: microsToUsd(modelCostMicros),
|
|
426
362
|
estimatedProviderUsd: microsToUsd(facts.estimatedProviderCostMicros),
|
|
427
363
|
estimatedProviderCostKnownCalls: facts.estimatedProviderCostKnownCalls,
|
|
428
|
-
warmSeconds:
|
|
364
|
+
warmSeconds: usageBuckets.get(bucket)?.warmSeconds ?? 0,
|
|
429
365
|
inputTokens: facts.inputTokens,
|
|
430
366
|
outputTokens: facts.outputTokens,
|
|
431
367
|
cachedTokens: facts.cachedTokens,
|
|
@@ -453,7 +389,7 @@ export async function getWorkspaceInsights(
|
|
|
453
389
|
return {
|
|
454
390
|
id: `root:${row.rootSessionId}`,
|
|
455
391
|
groupBy: "root_session" as const,
|
|
456
|
-
label:
|
|
392
|
+
label: insightsSessionLabel({ id: row.rootSessionId, title: row.title, depth: 0 }),
|
|
457
393
|
creditUsd,
|
|
458
394
|
estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
|
|
459
395
|
estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
|
|
@@ -494,7 +430,11 @@ export async function getWorkspaceInsights(
|
|
|
494
430
|
})
|
|
495
431
|
.map((row) => ({
|
|
496
432
|
id: row.id,
|
|
497
|
-
title:
|
|
433
|
+
title: insightsSessionLabel({
|
|
434
|
+
id: row.id,
|
|
435
|
+
title: row.title,
|
|
436
|
+
depth: row.nestedAgentDepth,
|
|
437
|
+
}),
|
|
498
438
|
state: floorState(row),
|
|
499
439
|
depth: row.nestedAgentDepth,
|
|
500
440
|
model: row.model,
|
|
@@ -528,7 +468,11 @@ export async function getWorkspaceInsights(
|
|
|
528
468
|
occurredAt: row.occurredAt.toISOString(),
|
|
529
469
|
recordedAt: row.recordedAt.toISOString(),
|
|
530
470
|
sessionId: row.sessionId,
|
|
531
|
-
sessionTitle:
|
|
471
|
+
sessionTitle: insightsSessionLabel({
|
|
472
|
+
id: row.sessionId,
|
|
473
|
+
title: row.sessionTitle,
|
|
474
|
+
depth: row.sessionDepth,
|
|
475
|
+
}),
|
|
532
476
|
turnId: row.turnId,
|
|
533
477
|
provider: row.provider,
|
|
534
478
|
providerApi: row.providerApi,
|
|
@@ -588,7 +532,13 @@ export async function getWorkspaceInsights(
|
|
|
588
532
|
sessionsTouched: depth.sessionsTouched,
|
|
589
533
|
rootSessions: depth.rootSessions,
|
|
590
534
|
deepestDepth: depth.deepestDepth,
|
|
591
|
-
deepestSessionTitle: depth.
|
|
535
|
+
deepestSessionTitle: depth.deepestSessionId
|
|
536
|
+
? insightsSessionLabel({
|
|
537
|
+
id: depth.deepestSessionId,
|
|
538
|
+
title: depth.deepestSessionTitle,
|
|
539
|
+
depth: depth.deepestDepth,
|
|
540
|
+
})
|
|
541
|
+
: "",
|
|
592
542
|
avgDepth: Math.round(depth.avgDepth * 10) / 10,
|
|
593
543
|
warmIdleNow: liveWarm.filter((lease) => lease.turnHolders === 0).length,
|
|
594
544
|
billableTokensUsed,
|
|
@@ -4,6 +4,7 @@ export function organizationMembershipHttpStatus(
|
|
|
4
4
|
if (sqlState === "22023" || sqlState === "23514") return 400;
|
|
5
5
|
if (sqlState === "42501") return 403;
|
|
6
6
|
if (sqlState === "P0002") return 404;
|
|
7
|
-
if (sqlState === "23505" || sqlState === "40001" || sqlState === "55000"
|
|
7
|
+
if (sqlState === "23505" || sqlState === "40001" || sqlState === "55000" || sqlState === "55P03")
|
|
8
|
+
return 409;
|
|
8
9
|
return null;
|
|
9
10
|
}
|