@opengeni/core 0.24.1 → 0.28.1
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/canonical-human-identities.d.ts +12 -0
- package/dist/canonical-human-identities.js +23 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/chunk-IBOEYG6N.js +34 -0
- package/dist/chunk-IBOEYG6N.js.map +1 -0
- package/dist/dependencies.d.ts +5 -1
- package/dist/domain/conversation-integrations.d.ts +225 -0
- package/dist/domain/fiken.d.ts +35 -0
- package/dist/domain/sessions.d.ts +11 -3
- package/dist/domain/video-generation-capabilities.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1667 -664
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +5 -1
- package/dist/sandbox/fleet.d.ts +8 -5
- package/dist/session-authorization.d.ts +3 -1
- package/dist/transcription.d.ts +5 -0
- package/package.json +14 -10
- package/src/access/index.ts +14 -2
- package/src/canonical-human-identities.ts +27 -0
- package/src/dependencies.ts +5 -1
- package/src/domain/capabilities.ts +220 -283
- package/src/domain/conversation-integrations.ts +1238 -0
- package/src/domain/fiken.ts +88 -0
- package/src/domain/insights.ts +27 -17
- package/src/domain/scheduled-tasks.ts +16 -0
- package/src/domain/sessions.ts +122 -18
- package/src/domain/video-generation-capabilities.ts +37 -2
- package/src/index.ts +2 -0
- package/src/managed-session.ts +19 -2
- package/src/sandbox/fleet.ts +72 -66
- package/src/session-authorization.ts +187 -55
- package/src/transcription.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -116,6 +116,9 @@ import {
|
|
|
116
116
|
ogatxEditableArtifactMutationIntentCodec,
|
|
117
117
|
validateEditableArtifactActor
|
|
118
118
|
} from "./chunk-JJU6I5XD.js";
|
|
119
|
+
import {
|
|
120
|
+
getManagedSession
|
|
121
|
+
} from "./chunk-IBOEYG6N.js";
|
|
119
122
|
|
|
120
123
|
// src/workflow-wake-contract.ts
|
|
121
124
|
var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
|
|
@@ -123,26 +126,6 @@ var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatc
|
|
|
123
126
|
var SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 1e4;
|
|
124
127
|
var TURN_ACTIVITY_CANCELLATION_HEARTBEAT_INTERVAL_MS = 500;
|
|
125
128
|
|
|
126
|
-
// src/managed-session.ts
|
|
127
|
-
async function getManagedSession(c, auth) {
|
|
128
|
-
const result = await auth.api.getSession({
|
|
129
|
-
headers: c.req.raw.headers,
|
|
130
|
-
returnHeaders: true
|
|
131
|
-
});
|
|
132
|
-
for (const cookie of setCookieHeaders(result.headers)) {
|
|
133
|
-
c.header("set-cookie", cookie, { append: true });
|
|
134
|
-
}
|
|
135
|
-
return result.response;
|
|
136
|
-
}
|
|
137
|
-
function setCookieHeaders(headers) {
|
|
138
|
-
const getSetCookie = headers.getSetCookie;
|
|
139
|
-
if (getSetCookie) {
|
|
140
|
-
return getSetCookie.call(headers);
|
|
141
|
-
}
|
|
142
|
-
const cookie = headers.get("set-cookie");
|
|
143
|
-
return cookie ? [cookie] : [];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
129
|
// src/transcription.ts
|
|
147
130
|
var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
|
|
148
131
|
var TranscriptionServiceError = class extends Error {
|
|
@@ -234,7 +217,6 @@ import {
|
|
|
234
217
|
SelfhostedSession,
|
|
235
218
|
swapTargetEstablishability
|
|
236
219
|
} from "@opengeni/runtime/sandbox";
|
|
237
|
-
import { HTTPException } from "hono/http-exception";
|
|
238
220
|
|
|
239
221
|
// src/sandbox/routing.ts
|
|
240
222
|
import { sandboxLifecycleTransitionWaitMs } from "@opengeni/config";
|
|
@@ -553,11 +535,6 @@ function wrapChannelABoxWithRouting(services, ids, established) {
|
|
|
553
535
|
// src/sandbox/fleet.ts
|
|
554
536
|
async function buildFleetContextForSession(deps, ctx) {
|
|
555
537
|
const session = await requireSession(deps.db, ctx.workspaceId, ctx.sessionId);
|
|
556
|
-
if (session.sandboxBackend === "none") {
|
|
557
|
-
throw new HTTPException(422, {
|
|
558
|
-
message: "this session has no sandbox (backend: none); the fleet is unavailable"
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
538
|
return {
|
|
562
539
|
accountId: ctx.accountId,
|
|
563
540
|
workspaceId: ctx.workspaceId,
|
|
@@ -613,40 +590,42 @@ async function listFleet(services, ctx) {
|
|
|
613
590
|
activeEpoch: 0
|
|
614
591
|
};
|
|
615
592
|
const entries = [];
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
593
|
+
if (ctx.sessionBackend !== "none") {
|
|
594
|
+
const groupActive = pointer.activeSandboxId === null;
|
|
595
|
+
const groupLease = await readLease(db, ctx.workspaceId, ctx.sessionGroupId);
|
|
596
|
+
const groupOnline = Boolean(
|
|
597
|
+
groupLease?.liveness === "warm" && groupLease.recovery.provider.status === "exists" && groupLease.recovery.workspace.status === "ready"
|
|
598
|
+
);
|
|
599
|
+
const groupRecovering = Boolean(
|
|
600
|
+
groupLease && (groupLease.liveness === "warming" || groupLease.recovery.restore.status === "pending" || groupLease.recovery.restore.status === "restoring" || groupLease.recovery.restore.status === "verifying")
|
|
601
|
+
);
|
|
602
|
+
const groupRecoveryUnavailable = Boolean(
|
|
603
|
+
groupLease && (groupLease.recovery.restore.status === "degraded" || groupLease.recovery.restore.status === "unrecoverable" || groupLease.recovery.workspace.status === "degraded" || groupLease.recovery.workspace.status === "unrecoverable")
|
|
604
|
+
);
|
|
605
|
+
const groupOperationAvailability = groupOnline ? "ready" : groupRecoveryUnavailable ? "unavailable" : groupRecovering ? "recovering" : ctx.sessionBackend === "selfhosted" ? "unavailable" : "wakeable";
|
|
606
|
+
entries.push({
|
|
607
|
+
id: ctx.sessionGroupId,
|
|
608
|
+
kind: ctx.sessionBackend === "selfhosted" ? "selfhosted" : "modal",
|
|
609
|
+
name: "session sandbox",
|
|
610
|
+
liveness: groupOnline ? "online" : groupRecovering ? "reconnecting" : "offline",
|
|
611
|
+
active: groupActive,
|
|
612
|
+
isSessionGroup: true,
|
|
613
|
+
enrollmentId: null,
|
|
614
|
+
attachable: groupOnline,
|
|
615
|
+
operationAvailability: groupOperationAvailability,
|
|
616
|
+
providerStatus: groupLease?.recovery.provider.status ?? "not_created",
|
|
617
|
+
leaseLiveness: groupLease?.liveness ?? null,
|
|
618
|
+
routeStatus: groupActive ? "attached" : "detached",
|
|
619
|
+
archiveStatus: groupLease?.recovery.archive.status ?? "none",
|
|
620
|
+
restoreStatus: groupLease?.recovery.restore.status ?? "not_required",
|
|
621
|
+
workspaceStatus: groupLease?.recovery.workspace.status ?? "unknown",
|
|
622
|
+
leaseEpoch: groupLease?.leaseEpoch ?? null,
|
|
623
|
+
routeEpoch: pointer.activeEpoch,
|
|
624
|
+
workspaceGeneration: groupLease?.workspaceGeneration ?? null,
|
|
625
|
+
archiveGeneration: groupLease?.archiveGeneration ?? null,
|
|
626
|
+
archiveComplete: groupLease?.archiveComplete ?? false
|
|
627
|
+
});
|
|
628
|
+
}
|
|
650
629
|
const sandboxes = await listSandboxes(db, ctx.workspaceId);
|
|
651
630
|
for (const sandbox of sandboxes) {
|
|
652
631
|
if (sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
|
|
@@ -691,6 +670,13 @@ async function listFleet(services, ctx) {
|
|
|
691
670
|
}
|
|
692
671
|
async function resolveTarget(services, ctx, target) {
|
|
693
672
|
if (target === ctx.sessionGroupId || target === "session" || target === "default") {
|
|
673
|
+
if (ctx.sessionBackend === "none") {
|
|
674
|
+
return {
|
|
675
|
+
ok: false,
|
|
676
|
+
reason: "this session has no home sandbox; attach a Connected Machine",
|
|
677
|
+
code: "unsupported_backend_context"
|
|
678
|
+
};
|
|
679
|
+
}
|
|
694
680
|
return { ok: true, targetSandboxId: null };
|
|
695
681
|
}
|
|
696
682
|
const sandbox = await getSandbox2(services.db, ctx.workspaceId, target);
|
|
@@ -955,12 +941,18 @@ import {
|
|
|
955
941
|
getWorkspaceGrant,
|
|
956
942
|
requireWorkspace
|
|
957
943
|
} from "@opengeni/db";
|
|
958
|
-
import { HTTPException
|
|
944
|
+
import { HTTPException } from "hono/http-exception";
|
|
959
945
|
var bearerPrefix = "Bearer ";
|
|
946
|
+
var accessContextByRequest = /* @__PURE__ */ new WeakMap();
|
|
960
947
|
async function requireAccessContext(c, deps) {
|
|
961
|
-
|
|
948
|
+
let pending = accessContextByRequest.get(c.req.raw);
|
|
949
|
+
if (!pending) {
|
|
950
|
+
pending = resolveAccessContext(c, deps);
|
|
951
|
+
accessContextByRequest.set(c.req.raw, pending);
|
|
952
|
+
}
|
|
953
|
+
const context = await pending;
|
|
962
954
|
if (!context) {
|
|
963
|
-
throw new
|
|
955
|
+
throw new HTTPException(401, { message: "authentication required" });
|
|
964
956
|
}
|
|
965
957
|
return context;
|
|
966
958
|
}
|
|
@@ -996,9 +988,9 @@ async function requireAccessGrantAuthorization(c, deps, workspaceId, permission)
|
|
|
996
988
|
if (!grant) {
|
|
997
989
|
const workspace = await requireWorkspace(deps.db, workspaceId).catch(() => null);
|
|
998
990
|
if (!workspace) {
|
|
999
|
-
throw new
|
|
991
|
+
throw new HTTPException(404, { message: "workspace not found" });
|
|
1000
992
|
}
|
|
1001
|
-
throw new
|
|
993
|
+
throw new HTTPException(403, { message: "workspace access denied" });
|
|
1002
994
|
}
|
|
1003
995
|
if (permission) {
|
|
1004
996
|
requirePermission(grant, permission);
|
|
@@ -1016,23 +1008,23 @@ function hostedHumanSessionPrincipalKind(context) {
|
|
|
1016
1008
|
function requirePermission(grant, permission) {
|
|
1017
1009
|
if (!hasPermission(grant.permissions, permission)) {
|
|
1018
1010
|
if (permission === "variable-sets:use") {
|
|
1019
|
-
throw new
|
|
1011
|
+
throw new HTTPException(403, {
|
|
1020
1012
|
message: "missing permission: variable-sets:use (deprecated alias: environments:use)"
|
|
1021
1013
|
});
|
|
1022
1014
|
}
|
|
1023
1015
|
if (permission === "variable-sets:manage") {
|
|
1024
|
-
throw new
|
|
1016
|
+
throw new HTTPException(403, {
|
|
1025
1017
|
message: "missing permission: variable-sets:manage (deprecated alias: environments:manage)"
|
|
1026
1018
|
});
|
|
1027
1019
|
}
|
|
1028
|
-
throw new
|
|
1020
|
+
throw new HTTPException(403, {
|
|
1029
1021
|
message: `missing permission: ${permission}`
|
|
1030
1022
|
});
|
|
1031
1023
|
}
|
|
1032
1024
|
}
|
|
1033
1025
|
function requireLiteralPermission(grant, permission) {
|
|
1034
1026
|
if (!hasLiteralPermission(grant.permissions, permission)) {
|
|
1035
|
-
throw new
|
|
1027
|
+
throw new HTTPException(403, {
|
|
1036
1028
|
message: `missing literal permission: ${permission}`
|
|
1037
1029
|
});
|
|
1038
1030
|
}
|
|
@@ -1122,7 +1114,7 @@ async function resolveAccessContext(c, deps) {
|
|
|
1122
1114
|
}
|
|
1123
1115
|
}
|
|
1124
1116
|
if (deps.managedAuth) {
|
|
1125
|
-
const session = await getManagedSession(c, deps.managedAuth);
|
|
1117
|
+
const session = await getManagedSession(c, deps.managedAuth, { db: deps.db });
|
|
1126
1118
|
if (session?.user) {
|
|
1127
1119
|
return await ensureManagedAccessForUser(deps.db, {
|
|
1128
1120
|
userId: session.user.id,
|
|
@@ -1207,6 +1199,8 @@ async function delegatedAccessContext(c, deps, mode, token = bearerToken(c)) {
|
|
|
1207
1199
|
delegated: true,
|
|
1208
1200
|
...payload.sessionId ? { sessionId: payload.sessionId } : {},
|
|
1209
1201
|
...payload.firstPartyMcpTools !== void 0 ? { firstPartyMcpTools: payload.firstPartyMcpTools } : {},
|
|
1202
|
+
...payload.nestedAgentDepth !== void 0 ? { nestedAgentDepth: payload.nestedAgentDepth } : {},
|
|
1203
|
+
...payload.effectiveMaxNestedAgentDepth !== void 0 ? { effectiveMaxNestedAgentDepth: payload.effectiveMaxNestedAgentDepth } : {},
|
|
1210
1204
|
// Caller identity: the turn that minted this token. Tools classify the
|
|
1211
1205
|
// CALLER from this instead of re-reading the live active pointer.
|
|
1212
1206
|
...payload.turnId ? { turnId: payload.turnId } : {},
|
|
@@ -1241,10 +1235,11 @@ import {
|
|
|
1241
1235
|
SessionAuthorizationListScope
|
|
1242
1236
|
} from "@opengeni/contracts";
|
|
1243
1237
|
import {
|
|
1238
|
+
getSessionAuthorityProjection,
|
|
1244
1239
|
getSession,
|
|
1245
|
-
getSessionRootId,
|
|
1246
1240
|
getSessionTurnForAttempt,
|
|
1247
|
-
getSlackInteractionSessionAccessForSession
|
|
1241
|
+
getSlackInteractionSessionAccessForSession,
|
|
1242
|
+
withSessionRlsActorContext
|
|
1248
1243
|
} from "@opengeni/db";
|
|
1249
1244
|
var SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS = 15e3;
|
|
1250
1245
|
var SessionAuthorizationDeniedError = class extends Error {
|
|
@@ -1262,8 +1257,48 @@ var SessionAuthorizationUnavailableError = class extends Error {
|
|
|
1262
1257
|
this.name = "SessionAuthorizationUnavailableError";
|
|
1263
1258
|
}
|
|
1264
1259
|
};
|
|
1260
|
+
function grantHasAgentAttemptAuthority(grant) {
|
|
1261
|
+
const hasAgentAttemptClaim = grant.metadata?.["turnId"] !== void 0 || grant.metadata?.["attemptId"] !== void 0 || grant.metadata?.["executionGeneration"] !== void 0;
|
|
1262
|
+
return grant.principalKind ? grant.principalKind === "agent_attempt" : hasAgentAttemptClaim;
|
|
1263
|
+
}
|
|
1264
|
+
var AGENT_PARENT_READ_OPERATIONS = /* @__PURE__ */ new Set([
|
|
1265
|
+
"session.read",
|
|
1266
|
+
"session.events.read",
|
|
1267
|
+
"session.stream.read",
|
|
1268
|
+
"session.turns.read",
|
|
1269
|
+
"session.queue.read",
|
|
1270
|
+
"session.composer.read",
|
|
1271
|
+
"session.lineage.read",
|
|
1272
|
+
"session.capture.read",
|
|
1273
|
+
"session.files.read",
|
|
1274
|
+
"session.git.read",
|
|
1275
|
+
"session.terminal.read",
|
|
1276
|
+
"session.viewer.read",
|
|
1277
|
+
"session.goal.read",
|
|
1278
|
+
"session.human_input.read"
|
|
1279
|
+
]);
|
|
1280
|
+
function enforceAgentSessionHierarchy(actor, callerParentSessionId, target, operation) {
|
|
1281
|
+
if (target.target.sessionId === actor.callerSessionId) return "root";
|
|
1282
|
+
if (target.parentSessionId === actor.callerSessionId) return "target";
|
|
1283
|
+
if (callerParentSessionId === target.target.sessionId) {
|
|
1284
|
+
if (operation === "session.append" || AGENT_PARENT_READ_OPERATIONS.has(operation)) {
|
|
1285
|
+
return "target";
|
|
1286
|
+
}
|
|
1287
|
+
throw new SessionAuthorizationDeniedError("forbidden");
|
|
1288
|
+
}
|
|
1289
|
+
throw new SessionAuthorizationDeniedError("forbidden");
|
|
1290
|
+
}
|
|
1291
|
+
function sessionRlsActorForAuthorization(authorization) {
|
|
1292
|
+
return authorization.actor.kind === "agent_attempt" ? {
|
|
1293
|
+
subjectId: authorization.actor.subjectId,
|
|
1294
|
+
initiatingHumanSubjectId: authorization.actor.initiatingHumanSubjectId
|
|
1295
|
+
} : { subjectId: authorization.actor.subjectId };
|
|
1296
|
+
}
|
|
1297
|
+
async function withResolvedSessionAuthorization(authorization, fn) {
|
|
1298
|
+
return await withSessionRlsActorContext(sessionRlsActorForAuthorization(authorization), fn);
|
|
1299
|
+
}
|
|
1265
1300
|
async function requireLiveAgentAttemptAuthorization(db, grant, callerSessionId) {
|
|
1266
|
-
const actor = await resolveSessionAuthorizationActor(db, grant);
|
|
1301
|
+
const { actor } = await resolveSessionAuthorizationActor(db, grant);
|
|
1267
1302
|
if (actor.kind !== "agent_attempt" || actor.callerSessionId !== callerSessionId) {
|
|
1268
1303
|
throw new SessionAuthorizationDeniedError("caller_stale");
|
|
1269
1304
|
}
|
|
@@ -1271,19 +1306,47 @@ async function requireLiveAgentAttemptAuthorization(db, grant, callerSessionId)
|
|
|
1271
1306
|
}
|
|
1272
1307
|
async function requireSessionAuthorization(deps, grant, input) {
|
|
1273
1308
|
const port = deps.sessionAuthorization;
|
|
1274
|
-
const
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1309
|
+
const isAgentAttempt = grantHasAgentAttemptAuthority(grant);
|
|
1310
|
+
const [slackAccess, authority] = await Promise.all([
|
|
1311
|
+
getSlackInteractionSessionAccessForSession(deps.db, {
|
|
1312
|
+
accountId: grant.accountId,
|
|
1313
|
+
workspaceId: grant.workspaceId,
|
|
1314
|
+
sessionId: input.sessionId
|
|
1315
|
+
}),
|
|
1316
|
+
getSessionAuthorityProjection(deps.db, grant.workspaceId, input.sessionId)
|
|
1317
|
+
]);
|
|
1318
|
+
if (!port && !isAgentAttempt && slackAccess?.visibility !== "private" && authority?.visibility !== "user_private") {
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
if (!authority) throw new SessionAuthorizationDeniedError("not_found");
|
|
1322
|
+
const [resolvedActor, resolvedTarget] = await Promise.all([
|
|
1323
|
+
resolveSessionAuthorizationActor(deps.db, grant),
|
|
1324
|
+
resolveSessionAuthorizationTarget(deps.db, grant, input.sessionId)
|
|
1325
|
+
]);
|
|
1326
|
+
const actor = resolvedActor.actor;
|
|
1327
|
+
const target = resolvedTarget.target;
|
|
1328
|
+
const agentRelatedSessionAccess = actor.kind === "agent_attempt" ? enforceAgentSessionHierarchy(
|
|
1329
|
+
actor,
|
|
1330
|
+
resolvedActor.callerParentSessionId,
|
|
1331
|
+
resolvedTarget,
|
|
1332
|
+
input.operation
|
|
1333
|
+
) : null;
|
|
1334
|
+
if (authority.visibility === "user_private") {
|
|
1335
|
+
const allowed = authority.ownerSubjectId !== null && (actor.kind === "subject" ? actor.subjectId === authority.ownerSubjectId : actor.initiatingHumanSubjectId === authority.ownerSubjectId);
|
|
1336
|
+
if (!allowed) throw new SessionAuthorizationDeniedError("forbidden");
|
|
1337
|
+
}
|
|
1282
1338
|
if (slackAccess?.visibility === "private") {
|
|
1283
1339
|
const allowed = actor.kind === "subject" ? actor.subjectId === slackAccess.owningSubjectId : actor.callerRootSessionId === target.rootSessionId;
|
|
1284
1340
|
if (!allowed) throw new SessionAuthorizationDeniedError("forbidden");
|
|
1285
1341
|
}
|
|
1286
|
-
if (!port)
|
|
1342
|
+
if (!port) {
|
|
1343
|
+
return {
|
|
1344
|
+
actor,
|
|
1345
|
+
target,
|
|
1346
|
+
relatedSessionAccess: agentRelatedSessionAccess ?? "root",
|
|
1347
|
+
reauthorizeAfterMs: null
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1287
1350
|
let rawDecision;
|
|
1288
1351
|
try {
|
|
1289
1352
|
rawDecision = await port.authorizeSession({
|
|
@@ -1307,14 +1370,16 @@ async function requireSessionAuthorization(deps, grant, input) {
|
|
|
1307
1370
|
return {
|
|
1308
1371
|
actor,
|
|
1309
1372
|
target,
|
|
1310
|
-
relatedSessionAccess: parsed.data.relatedSessionAccess ?? "target",
|
|
1373
|
+
relatedSessionAccess: agentRelatedSessionAccess === "target" ? "target" : parsed.data.relatedSessionAccess ?? "target",
|
|
1311
1374
|
reauthorizeAfterMs: parsed.data.reauthorizeAfterMs ?? null
|
|
1312
1375
|
};
|
|
1313
1376
|
}
|
|
1314
1377
|
async function requireSessionAuthorizationListScope(deps, grant, surface) {
|
|
1315
1378
|
const port = deps.sessionAuthorization;
|
|
1379
|
+
const isAgentAttempt = grantHasAgentAttemptAuthority(grant);
|
|
1380
|
+
if (!port && !isAgentAttempt) return null;
|
|
1381
|
+
const { actor } = await resolveSessionAuthorizationActor(deps.db, grant);
|
|
1316
1382
|
if (!port) return null;
|
|
1317
|
-
const actor = await resolveSessionAuthorizationActor(deps.db, grant);
|
|
1318
1383
|
let rawScope;
|
|
1319
1384
|
try {
|
|
1320
1385
|
rawScope = await port.resolveListScope({
|
|
@@ -1342,53 +1407,52 @@ async function resolveSessionAuthorizationTarget(db, grant, sessionId) {
|
|
|
1342
1407
|
if (!session || session.accountId !== grant.accountId) {
|
|
1343
1408
|
throw new SessionAuthorizationDeniedError("not_found");
|
|
1344
1409
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
}
|
|
1349
|
-
throw new SessionAuthorizationUnavailableError({ cause: error });
|
|
1350
|
-
}
|
|
1351
|
-
if (!rootSessionId) {
|
|
1352
|
-
throw new SessionAuthorizationDeniedError("not_found");
|
|
1353
|
-
}
|
|
1354
|
-
return { sessionId: session.id, rootSessionId };
|
|
1410
|
+
return {
|
|
1411
|
+
target: { sessionId: session.id, rootSessionId: session.rootSessionId },
|
|
1412
|
+
parentSessionId: session.parentSessionId
|
|
1413
|
+
};
|
|
1355
1414
|
}
|
|
1356
1415
|
async function resolveSessionAuthorizationActor(db, grant) {
|
|
1357
1416
|
const callerSessionId = grant.metadata?.["sessionId"];
|
|
1358
1417
|
const turnId = grant.metadata?.["turnId"];
|
|
1359
1418
|
const attemptId = grant.metadata?.["attemptId"];
|
|
1360
1419
|
const executionGeneration = grant.metadata?.["executionGeneration"];
|
|
1361
|
-
const
|
|
1362
|
-
if (!
|
|
1363
|
-
return
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1420
|
+
const isAgentAttempt = grantHasAgentAttemptAuthority(grant);
|
|
1421
|
+
if (!isAgentAttempt) {
|
|
1422
|
+
return {
|
|
1423
|
+
actor: SessionAuthorizationActor.parse({
|
|
1424
|
+
kind: "subject",
|
|
1425
|
+
subjectId: grant.subjectId,
|
|
1426
|
+
...grant.subjectLabel ? { subjectLabel: grant.subjectLabel } : {}
|
|
1427
|
+
}),
|
|
1428
|
+
callerParentSessionId: null
|
|
1429
|
+
};
|
|
1368
1430
|
}
|
|
1369
1431
|
if (typeof callerSessionId !== "string" || typeof turnId !== "string" || typeof attemptId !== "string" || typeof executionGeneration !== "number" || !Number.isSafeInteger(executionGeneration) || executionGeneration < 1) {
|
|
1370
1432
|
throw new SessionAuthorizationDeniedError("caller_stale");
|
|
1371
1433
|
}
|
|
1372
|
-
const [callerSession, turn
|
|
1434
|
+
const [callerSession, turn] = await Promise.all([
|
|
1373
1435
|
getSession(db, grant.workspaceId, callerSessionId),
|
|
1374
|
-
getSessionTurnForAttempt(db, grant.workspaceId, callerSessionId, attemptId)
|
|
1375
|
-
getSessionRootId(db, grant.workspaceId, callerSessionId).catch(() => null)
|
|
1436
|
+
getSessionTurnForAttempt(db, grant.workspaceId, callerSessionId, attemptId)
|
|
1376
1437
|
]);
|
|
1377
|
-
if (!callerSession || callerSession.accountId !== grant.accountId || !turn || turn.id !== turnId || turn.executionGeneration !== executionGeneration || callerSession.activeTurnId !== turn.id
|
|
1438
|
+
if (!callerSession || callerSession.accountId !== grant.accountId || !turn || turn.id !== turnId || turn.executionGeneration !== executionGeneration || callerSession.activeTurnId !== turn.id) {
|
|
1378
1439
|
throw new SessionAuthorizationDeniedError("caller_stale");
|
|
1379
1440
|
}
|
|
1380
|
-
return
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1441
|
+
return {
|
|
1442
|
+
actor: SessionAuthorizationActor.parse({
|
|
1443
|
+
kind: "agent_attempt",
|
|
1444
|
+
subjectId: grant.subjectId,
|
|
1445
|
+
callerSessionId,
|
|
1446
|
+
callerRootSessionId: callerSession.rootSessionId,
|
|
1447
|
+
turnId,
|
|
1448
|
+
attemptId,
|
|
1449
|
+
executionGeneration,
|
|
1450
|
+
initiator: turn.initiator,
|
|
1451
|
+
initiatorContext: turn.initiatorContext,
|
|
1452
|
+
initiatingHumanSubjectId: turn.initiatingHumanSubjectId ?? (turn.initiator.kind === "subject" ? turn.initiator.subjectId : null)
|
|
1453
|
+
}),
|
|
1454
|
+
callerParentSessionId: callerSession.parentSessionId
|
|
1455
|
+
};
|
|
1392
1456
|
}
|
|
1393
1457
|
|
|
1394
1458
|
// src/billing/limits.ts
|
|
@@ -1402,13 +1466,13 @@ import {
|
|
|
1402
1466
|
recordUsageEvent,
|
|
1403
1467
|
sumUsageQuantity
|
|
1404
1468
|
} from "@opengeni/db";
|
|
1405
|
-
import { HTTPException as
|
|
1469
|
+
import { HTTPException as HTTPException2 } from "hono/http-exception";
|
|
1406
1470
|
async function requireLimit(deps, input) {
|
|
1407
1471
|
const decision = await checkLimit(deps, input);
|
|
1408
1472
|
if (decision.allowed) {
|
|
1409
1473
|
return;
|
|
1410
1474
|
}
|
|
1411
|
-
throw new
|
|
1475
|
+
throw new HTTPException2(decision.code === "insufficient_credits" ? 402 : 429, {
|
|
1412
1476
|
message: decision.message
|
|
1413
1477
|
});
|
|
1414
1478
|
}
|
|
@@ -1579,10 +1643,12 @@ function startOfUtcMonth() {
|
|
|
1579
1643
|
// src/domain/capabilities.ts
|
|
1580
1644
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1581
1645
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1582
|
-
import { environmentsEncryptionKeyBytes
|
|
1646
|
+
import { environmentsEncryptionKeyBytes } from "@opengeni/config";
|
|
1583
1647
|
import {
|
|
1584
1648
|
CapabilityCatalogItem,
|
|
1585
|
-
capabilityCatalogItemIsTrustedForExposure
|
|
1649
|
+
capabilityCatalogItemIsTrustedForExposure,
|
|
1650
|
+
FIKEN_PROVIDER_DOMAIN as FIKEN_PROVIDER_DOMAIN2,
|
|
1651
|
+
FIRST_PARTY_MCP_TOOL_NAMES
|
|
1586
1652
|
} from "@opengeni/contracts";
|
|
1587
1653
|
import {
|
|
1588
1654
|
CODEX_APPS_MCP_SERVER_ID,
|
|
@@ -1595,120 +1661,63 @@ import {
|
|
|
1595
1661
|
decryptedCapabilityHeaders,
|
|
1596
1662
|
disableCapabilityInstallation,
|
|
1597
1663
|
enableCapabilityInstallation,
|
|
1598
|
-
enablePackInstallation,
|
|
1599
1664
|
encryptVariableSetValue,
|
|
1600
1665
|
getCapabilityCatalogItem,
|
|
1601
1666
|
getCapabilityInstallation,
|
|
1602
1667
|
getConnectionMetadata,
|
|
1603
1668
|
getCodexAppsCredentialAuthorizationForRun,
|
|
1604
1669
|
getWorkspaceGrant as getWorkspaceGrant2,
|
|
1605
|
-
getPackInstallation as getPackInstallation2,
|
|
1606
1670
|
getStoredCapabilityHeaderCiphertext,
|
|
1607
|
-
getVariableSet as getVariableSet3,
|
|
1608
1671
|
listCapabilityCatalogItems,
|
|
1609
1672
|
listCapabilityInstallations,
|
|
1610
1673
|
listConnectionsMetadata,
|
|
1611
1674
|
listEnabledMcpCapabilityServers,
|
|
1612
1675
|
listInstalledApiIntegrations,
|
|
1676
|
+
listInstalledSkills,
|
|
1613
1677
|
listPackInstallations as listPackInstallations2,
|
|
1614
1678
|
listSocialConnections,
|
|
1615
1679
|
mcpServerIdForCapability,
|
|
1616
|
-
updatePackInstallationStatus,
|
|
1617
1680
|
upsertCapabilityCatalogItem
|
|
1618
1681
|
} from "@opengeni/db";
|
|
1619
|
-
import { HTTPException as HTTPException6 } from "hono/http-exception";
|
|
1620
|
-
import {
|
|
1621
|
-
getSkillLibraryEntry,
|
|
1622
|
-
listSkillLibraryEntries
|
|
1623
|
-
} from "@opengeni/runtime/skill-library";
|
|
1624
|
-
|
|
1625
|
-
// src/domain/environments.ts
|
|
1626
|
-
import { environmentsEncryptionKeyBytes } from "@opengeni/config";
|
|
1627
|
-
import { getVariableSet, recordAuditEvent } from "@opengeni/db";
|
|
1628
1682
|
import { HTTPException as HTTPException4 } from "hono/http-exception";
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
"BASH_ENV",
|
|
1641
|
-
"NODE_OPTIONS",
|
|
1642
|
-
"PYTHONPATH",
|
|
1643
|
-
"PYTHONSTARTUP",
|
|
1644
|
-
"PERL5OPT",
|
|
1645
|
-
"PERL5LIB",
|
|
1646
|
-
"GH_TOKEN",
|
|
1647
|
-
"GITHUB_TOKEN",
|
|
1648
|
-
"GITLAB_TOKEN",
|
|
1649
|
-
"AZURE_DEVOPS_EXT_PAT",
|
|
1650
|
-
"GIT_ASKPASS",
|
|
1651
|
-
"GIT_TERMINAL_PROMPT"
|
|
1652
|
-
]);
|
|
1653
|
-
var reservedPrefixes = [
|
|
1654
|
-
"OPENGENI_",
|
|
1655
|
-
"GIT_CONFIG_",
|
|
1656
|
-
"GIT_AUTHOR_",
|
|
1657
|
-
"GIT_COMMITTER_",
|
|
1658
|
-
"LD_",
|
|
1659
|
-
"DYLD_"
|
|
1660
|
-
];
|
|
1661
|
-
function assertAllowedVariableSetVariableName(name) {
|
|
1662
|
-
if (reservedExactNames.has(name) || reservedPrefixes.some((prefix) => name.startsWith(prefix))) {
|
|
1663
|
-
throw new HTTPException4(422, {
|
|
1664
|
-
message: `reserved variable set variable name / reserved environment variable name: ${name}`
|
|
1665
|
-
});
|
|
1666
|
-
}
|
|
1683
|
+
|
|
1684
|
+
// src/domain/fiken.ts
|
|
1685
|
+
import {
|
|
1686
|
+
FIKEN_CREDENTIAL_LABEL,
|
|
1687
|
+
FIKEN_CREDENTIAL_ROLE,
|
|
1688
|
+
FIKEN_PROVIDER_DOMAIN,
|
|
1689
|
+
FikenConnectionMetadata
|
|
1690
|
+
} from "@opengeni/contracts";
|
|
1691
|
+
function fikenConnectionMetadata(metadata) {
|
|
1692
|
+
const parsed = FikenConnectionMetadata.safeParse(metadata);
|
|
1693
|
+
return parsed.success ? parsed.data : null;
|
|
1667
1694
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
const key = environmentsEncryptionKeyBytes(settings);
|
|
1671
|
-
if (!key) {
|
|
1672
|
-
throw new HTTPException4(503, {
|
|
1673
|
-
message: "variable sets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
|
|
1674
|
-
});
|
|
1675
|
-
}
|
|
1676
|
-
return key;
|
|
1695
|
+
function isFikenConnection(connection) {
|
|
1696
|
+
return connection.subjectId === null && connection.providerDomain === FIKEN_PROVIDER_DOMAIN && (connection.kind === "api_key" || connection.kind === "oauth2") && fikenConnectionMetadata(connection.metadata)?.credentialRole === FIKEN_CREDENTIAL_ROLE;
|
|
1677
1697
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
const variableSet = await getVariableSet(db, workspaceId, variableSetId);
|
|
1681
|
-
if (!variableSet) {
|
|
1682
|
-
throw new HTTPException4(404, { message: "variableSet not found" });
|
|
1683
|
-
}
|
|
1684
|
-
return variableSet;
|
|
1698
|
+
function hasReservedFikenMetadata(metadata) {
|
|
1699
|
+
return metadata?.credentialRole === FIKEN_CREDENTIAL_ROLE || metadata?.credentialLabel === FIKEN_CREDENTIAL_LABEL;
|
|
1685
1700
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
if (
|
|
1689
|
-
|
|
1701
|
+
function resolveFikenDefaultCompanySlug(input) {
|
|
1702
|
+
const accessible = (slug) => slug !== null && input.companies.some((company) => company.slug === slug);
|
|
1703
|
+
if (input.requested !== null) {
|
|
1704
|
+
return accessible(input.requested) ? input.requested : null;
|
|
1690
1705
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
throw new HTTPException4(422, { message: "unknown variableSetId" });
|
|
1706
|
+
if (accessible(input.previous)) {
|
|
1707
|
+
return input.previous;
|
|
1694
1708
|
}
|
|
1695
|
-
return
|
|
1709
|
+
return input.companies.length === 1 ? input.companies[0].slug : null;
|
|
1696
1710
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
action: input.action,
|
|
1703
|
-
targetType: "workspace_variable_set",
|
|
1704
|
-
targetId: input.variableSetId,
|
|
1705
|
-
metadata: {
|
|
1706
|
-
variableSetId: input.variableSetId,
|
|
1707
|
-
...input.variableName ? { name: input.variableName } : {}
|
|
1708
|
-
}
|
|
1709
|
-
});
|
|
1711
|
+
function preferredFikenConnection(connections) {
|
|
1712
|
+
const statusRank = (status) => status === "active" ? 0 : status === "needs_reauth" ? 1 : 2;
|
|
1713
|
+
return [...connections].sort(
|
|
1714
|
+
(left, right) => statusRank(left.status) - statusRank(right.status) || right.updatedAt.localeCompare(left.updatedAt) || right.id.localeCompare(left.id)
|
|
1715
|
+
)[0] ?? null;
|
|
1710
1716
|
}
|
|
1711
1717
|
|
|
1718
|
+
// src/domain/capabilities.ts
|
|
1719
|
+
import { listSkillLibraryEntries } from "@opengeni/runtime/skill-library";
|
|
1720
|
+
|
|
1712
1721
|
// src/domain/packs.ts
|
|
1713
1722
|
import { createHash } from "crypto";
|
|
1714
1723
|
import {
|
|
@@ -1718,7 +1727,7 @@ import {
|
|
|
1718
1727
|
import {
|
|
1719
1728
|
getPackInstallation,
|
|
1720
1729
|
getRig,
|
|
1721
|
-
getVariableSet
|
|
1730
|
+
getVariableSet,
|
|
1722
1731
|
getWorkspacePack,
|
|
1723
1732
|
listPackInstallations,
|
|
1724
1733
|
listWorkspacePacks,
|
|
@@ -1726,7 +1735,7 @@ import {
|
|
|
1726
1735
|
resolvePackInlineSkillReferences
|
|
1727
1736
|
} from "@opengeni/db";
|
|
1728
1737
|
import { buildPortableSkillArtifact } from "@opengeni/runtime/skill-library";
|
|
1729
|
-
import { HTTPException as
|
|
1738
|
+
import { HTTPException as HTTPException3 } from "hono/http-exception";
|
|
1730
1739
|
var MARKETING_SOCIAL_PACK_ID = "marketing-social-daily-analysis";
|
|
1731
1740
|
var marketingSocialPack = {
|
|
1732
1741
|
id: MARKETING_SOCIAL_PACK_ID,
|
|
@@ -1915,7 +1924,7 @@ function capabilityPackRequiresInstallationPlan(pack) {
|
|
|
1915
1924
|
function inlinePackSkillInstall(pack, skill) {
|
|
1916
1925
|
const artifact = buildPortableSkillArtifact(skill.files);
|
|
1917
1926
|
if (artifact.name.toLowerCase() !== skill.name.toLowerCase()) {
|
|
1918
|
-
throw new
|
|
1927
|
+
throw new HTTPException3(422, {
|
|
1919
1928
|
message: `Pack Skill ${skill.name} has SKILL.md name ${artifact.name}; the names must match`
|
|
1920
1929
|
});
|
|
1921
1930
|
}
|
|
@@ -1979,7 +1988,7 @@ async function previewCapabilityPackInstallation(db, workspaceId, pack, options
|
|
|
1979
1988
|
if (pack.variableSet?.required && !variableSetId) {
|
|
1980
1989
|
blockers.push("Choose saved configuration before installing this Pack");
|
|
1981
1990
|
} else if (variableSetId) {
|
|
1982
|
-
const variableSet = await
|
|
1991
|
+
const variableSet = await getVariableSet(db, workspaceId, variableSetId);
|
|
1983
1992
|
if (!variableSet) {
|
|
1984
1993
|
blockers.push("The selected configuration no longer exists in this workspace");
|
|
1985
1994
|
} else {
|
|
@@ -2094,7 +2103,7 @@ async function assertPackSandboxImageCompatible(db, workspaceId, pack) {
|
|
|
2094
2103
|
}
|
|
2095
2104
|
const other = await resolveCapabilityPack(db, workspaceId, installation.packId);
|
|
2096
2105
|
if (other?.sandboxImage) {
|
|
2097
|
-
throw new
|
|
2106
|
+
throw new HTTPException3(409, {
|
|
2098
2107
|
message: `pack ${pack.id} declares a sandbox image, but enabled pack ${other.id} already declares one; only one enabled pack per workspace may declare sandboxImage \u2014 disable ${other.id} first`
|
|
2099
2108
|
});
|
|
2100
2109
|
}
|
|
@@ -2159,7 +2168,9 @@ async function buildCapabilityCatalog(input) {
|
|
|
2159
2168
|
packInstallations,
|
|
2160
2169
|
workspacePacks,
|
|
2161
2170
|
socialConnections,
|
|
2171
|
+
workspaceConnections,
|
|
2162
2172
|
curatedLibrarySkills,
|
|
2173
|
+
installedSkills,
|
|
2163
2174
|
codexAppsCredentialId
|
|
2164
2175
|
] = await Promise.all([
|
|
2165
2176
|
listCapabilityCatalogItems(input.db, input.workspaceId),
|
|
@@ -2167,11 +2178,19 @@ async function buildCapabilityCatalog(input) {
|
|
|
2167
2178
|
listPackInstallations2(input.db, input.workspaceId),
|
|
2168
2179
|
listWorkspaceCapabilityPacks(input.db, input.workspaceId),
|
|
2169
2180
|
listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
|
|
2181
|
+
listConnectionsMetadata(input.db, input.workspaceId, null),
|
|
2170
2182
|
discoverCuratedSkillLibraryItems(),
|
|
2183
|
+
listInstalledSkills(input.db, input.workspaceId),
|
|
2171
2184
|
input.settings.codexConnectedAppsEnabled ? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId) : Promise.resolve(null)
|
|
2172
2185
|
]);
|
|
2186
|
+
const catalogInstallations = capabilityInstallations.filter(
|
|
2187
|
+
(installation) => installation.kind === "mcp"
|
|
2188
|
+
);
|
|
2173
2189
|
const capabilityInstallationById = new Map(
|
|
2174
|
-
|
|
2190
|
+
catalogInstallations.map((installation) => [installation.capabilityId, installation])
|
|
2191
|
+
);
|
|
2192
|
+
const installedSkillById = new Map(
|
|
2193
|
+
installedSkills.filter((skill) => skill.owners.some((owner) => owner.kind === "direct")).map((skill) => [skill.capabilityId, skill])
|
|
2175
2194
|
);
|
|
2176
2195
|
const activePackIds = new Set(
|
|
2177
2196
|
packInstallations.filter((installation) => installation.status === "active").map((installation) => installation.packId)
|
|
@@ -2183,66 +2202,54 @@ async function buildCapabilityCatalog(input) {
|
|
|
2183
2202
|
),
|
|
2184
2203
|
...configuredMcpCatalogItems(input.settings),
|
|
2185
2204
|
...providerIntegrationCatalogItems(socialConnections),
|
|
2186
|
-
|
|
2205
|
+
fikenCatalogItem(workspaceConnections.filter(isFikenConnection)),
|
|
2206
|
+
...curatedLibrarySkills,
|
|
2207
|
+
...installedSkills.filter(
|
|
2208
|
+
(skill) => skill.source !== "library" && skill.owners.some((owner) => owner.kind === "direct")
|
|
2209
|
+
).map(installedSkillCatalogItem)
|
|
2187
2210
|
];
|
|
2188
2211
|
const codexApps = input.settings.codexConnectedAppsEnabled ? codexAppsCatalogItem(codexAppsCredentialId !== null) : null;
|
|
2189
2212
|
const items = dedupeCatalogItems([
|
|
2190
2213
|
...builtIns,
|
|
2191
|
-
...persistedItems.filter(
|
|
2214
|
+
...persistedItems.filter(
|
|
2215
|
+
(item) => item.kind !== "skill" && item.kind !== "api" && item.kind !== "plugin" && !isReservedCodexAppsCatalogItem(item)
|
|
2216
|
+
),
|
|
2192
2217
|
// Keep the reserved, server-derived item authoritative over any stale
|
|
2193
2218
|
// legacy catalog row with the same id.
|
|
2194
2219
|
...codexApps ? [codexApps] : []
|
|
2195
2220
|
]).map((item) => {
|
|
2196
|
-
const
|
|
2197
|
-
const projected = applyCapabilityEnablement(
|
|
2198
|
-
runtimeItem,
|
|
2199
|
-
capabilityInstallationById.get(runtimeItem.id),
|
|
2200
|
-
activePackIds
|
|
2201
|
-
);
|
|
2221
|
+
const projected = item.kind === "skill" ? applyInstalledSkillEnablement(item, installedSkillById.get(item.id)) : applyCapabilityEnablement(item, capabilityInstallationById.get(item.id), activePackIds);
|
|
2202
2222
|
return applyCapabilityLifecycle(projected);
|
|
2203
2223
|
}).sort(compareCatalogItems);
|
|
2204
2224
|
return {
|
|
2205
2225
|
items,
|
|
2206
|
-
installations:
|
|
2207
|
-
};
|
|
2208
|
-
}
|
|
2209
|
-
function applyInstalledArtifactRuntime(item) {
|
|
2210
|
-
if (item.kind === "api" && item.metadata.platformVersion === 2 && typeof item.metadata.pluginVersionId === "string" && typeof item.metadata.apiFacetId === "string" && typeof item.metadata.revisionId === "string" && typeof item.metadata.serverId === "string") {
|
|
2211
|
-
return {
|
|
2212
|
-
...item,
|
|
2213
|
-
runtime: {
|
|
2214
|
-
available: true,
|
|
2215
|
-
mcpServerId: item.metadata.serverId,
|
|
2216
|
-
transport: "local-adapter",
|
|
2217
|
-
notes: "Available through an immutable workspace API Integration revision."
|
|
2218
|
-
}
|
|
2219
|
-
};
|
|
2220
|
-
}
|
|
2221
|
-
if (item.kind !== "skill" || item.metadata.platformVersion !== 2 || typeof item.metadata.pluginVersionId !== "string" || typeof item.metadata.facetId !== "string" || typeof item.metadata.sourceCommit !== "string" || typeof item.metadata.contentSha256 !== "string") {
|
|
2222
|
-
return item;
|
|
2223
|
-
}
|
|
2224
|
-
return {
|
|
2225
|
-
...item,
|
|
2226
|
-
runtime: {
|
|
2227
|
-
available: true,
|
|
2228
|
-
notes: "Installed from an immutable workspace Skill artifact."
|
|
2229
|
-
}
|
|
2226
|
+
installations: catalogInstallations
|
|
2230
2227
|
};
|
|
2231
2228
|
}
|
|
2232
2229
|
async function createCatalogItem(input) {
|
|
2233
2230
|
const id = input.payload.id?.trim() || generatedCapabilityId(input.payload);
|
|
2234
2231
|
if (id.startsWith("pack:")) {
|
|
2235
|
-
throw new
|
|
2232
|
+
throw new HTTPException4(422, {
|
|
2236
2233
|
message: "packs are managed by OpenGeni and cannot be manually created"
|
|
2237
2234
|
});
|
|
2238
2235
|
}
|
|
2239
2236
|
if (id.startsWith("skill:")) {
|
|
2240
|
-
throw new
|
|
2241
|
-
message: "
|
|
2237
|
+
throw new HTTPException4(422, {
|
|
2238
|
+
message: "Skills are installed through the Skill library or source import flow"
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
if (id.startsWith("api:")) {
|
|
2242
|
+
throw new HTTPException4(422, {
|
|
2243
|
+
message: "API Integrations are installed from typed Integration Definitions"
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
if (id.startsWith("plugin:")) {
|
|
2247
|
+
throw new HTTPException4(422, {
|
|
2248
|
+
message: "Plugins are installed through the Plugin Package flow"
|
|
2242
2249
|
});
|
|
2243
2250
|
}
|
|
2244
2251
|
if (input.payload.kind === "mcp" && (id === `mcp:${CODEX_APPS_MCP_SERVER_ID}` || typeof input.payload.metadata.mcpServerId === "string" && input.payload.metadata.mcpServerId.trim() === CODEX_APPS_MCP_SERVER_ID)) {
|
|
2245
|
-
throw new
|
|
2252
|
+
throw new HTTPException4(422, {
|
|
2246
2253
|
message: `${CODEX_APPS_MCP_SERVER_ID} is reserved for the canonical Codex Apps service`
|
|
2247
2254
|
});
|
|
2248
2255
|
}
|
|
@@ -2275,8 +2282,28 @@ async function enableCapability(input) {
|
|
|
2275
2282
|
input.settings,
|
|
2276
2283
|
input.capabilityId
|
|
2277
2284
|
);
|
|
2285
|
+
if (item.kind === "skill") {
|
|
2286
|
+
throw new HTTPException4(409, {
|
|
2287
|
+
message: "Install Skills through the Skill library or source import flow"
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
if (item.kind === "api") {
|
|
2291
|
+
throw new HTTPException4(409, {
|
|
2292
|
+
message: "Install API Integrations through the Integration Definitions flow"
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
if (item.kind === "plugin") {
|
|
2296
|
+
throw new HTTPException4(409, {
|
|
2297
|
+
message: "Install Plugins through the Plugin Package flow"
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
if (item.kind === "pack") {
|
|
2301
|
+
throw new HTTPException4(409, {
|
|
2302
|
+
message: "Install Packs through the Pack installation preview flow"
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2278
2305
|
if (item.kind === "mcp" && !item.runtime.available) {
|
|
2279
|
-
throw new
|
|
2306
|
+
throw new HTTPException4(422, {
|
|
2280
2307
|
message: "MCP capabilities need a remote streamable HTTP endpoint before they can be enabled"
|
|
2281
2308
|
});
|
|
2282
2309
|
}
|
|
@@ -2286,41 +2313,6 @@ async function enableCapability(input) {
|
|
|
2286
2313
|
delete installationConfig.headersEncrypted;
|
|
2287
2314
|
delete installationConfig.headerNames;
|
|
2288
2315
|
delete installationConfig.connectionRef;
|
|
2289
|
-
if (item.kind === "skill" && item.source === "library") {
|
|
2290
|
-
const libraryId = stringMetadata(item.metadata.libraryId);
|
|
2291
|
-
const catalogVersion = stringMetadata(item.metadata.version);
|
|
2292
|
-
if (!libraryId || !catalogVersion) {
|
|
2293
|
-
throw new HTTPException6(422, {
|
|
2294
|
-
message: `skill library metadata is incomplete for ${item.id}`
|
|
2295
|
-
});
|
|
2296
|
-
}
|
|
2297
|
-
const requestedVersion = input.payload.config.version;
|
|
2298
|
-
if (requestedVersion !== void 0 && typeof requestedVersion !== "string") {
|
|
2299
|
-
throw new HTTPException6(422, {
|
|
2300
|
-
message: "skill activation config.version must be a string"
|
|
2301
|
-
});
|
|
2302
|
-
}
|
|
2303
|
-
const normalizedVersion = requestedVersion?.trim() || catalogVersion;
|
|
2304
|
-
if (normalizedVersion !== catalogVersion) {
|
|
2305
|
-
throw new HTTPException6(422, {
|
|
2306
|
-
message: `skill ${libraryId} only supports immutable version ${catalogVersion}`
|
|
2307
|
-
});
|
|
2308
|
-
}
|
|
2309
|
-
const entry = getSkillLibraryEntry(libraryId, normalizedVersion);
|
|
2310
|
-
if (!entry) {
|
|
2311
|
-
throw new HTTPException6(422, {
|
|
2312
|
-
message: `skill library entry is unavailable: ${libraryId}@${normalizedVersion}`
|
|
2313
|
-
});
|
|
2314
|
-
}
|
|
2315
|
-
installationConfig = { version: entry.version };
|
|
2316
|
-
installationMetadata = {
|
|
2317
|
-
libraryId: entry.id,
|
|
2318
|
-
libraryVersion: entry.version,
|
|
2319
|
-
contentSha256: entry.contentSha256,
|
|
2320
|
-
sourceCommit: entry.sourceCommit,
|
|
2321
|
-
provenance: entry.provenance
|
|
2322
|
-
};
|
|
2323
|
-
}
|
|
2324
2316
|
if (item.kind === "mcp") {
|
|
2325
2317
|
const headers = await resolveMcpCredentialHeaders(input, item);
|
|
2326
2318
|
const connectionRef = input.payload.connectionRef ? await validateMcpCapabilityConnectionRef(input, item, input.payload.connectionRef) : null;
|
|
@@ -2339,71 +2331,6 @@ async function enableCapability(input) {
|
|
|
2339
2331
|
);
|
|
2340
2332
|
}
|
|
2341
2333
|
}
|
|
2342
|
-
if (item.kind === "pack") {
|
|
2343
|
-
const packId = packIdFromCapabilityId(item.id);
|
|
2344
|
-
const pack = await resolveCapabilityPack(input.db, input.workspaceId, packId);
|
|
2345
|
-
if (!pack) {
|
|
2346
|
-
throw new HTTPException6(404, { message: "pack not found" });
|
|
2347
|
-
}
|
|
2348
|
-
if (capabilityPackRequiresInstallationPlan(pack)) {
|
|
2349
|
-
throw new HTTPException6(409, {
|
|
2350
|
-
message: "This Pack uses component or Rig requirements. Preview and install it through the Pack installation flow."
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
await assertPackSandboxImageCompatible(input.db, input.workspaceId, pack);
|
|
2354
|
-
const existing = await getPackInstallation2(input.db, input.workspaceId, packId);
|
|
2355
|
-
const storedVariableSetId2 = typeof existing?.metadata.variableSetId === "string" ? existing.metadata.variableSetId : typeof existing?.metadata.environmentId === "string" ? existing.metadata.environmentId : void 0;
|
|
2356
|
-
const requestedVariableSetId = input.payload.variableSetId;
|
|
2357
|
-
const variableSetId = requestedVariableSetId ?? storedVariableSetId2;
|
|
2358
|
-
if (pack.variableSet?.required && !variableSetId) {
|
|
2359
|
-
throw new HTTPException6(422, {
|
|
2360
|
-
message: `pack ${packId} requires an variableSet attachment; pass variableSetId`
|
|
2361
|
-
});
|
|
2362
|
-
}
|
|
2363
|
-
if (variableSetId) {
|
|
2364
|
-
if (requestedVariableSetId) {
|
|
2365
|
-
const variableSet = await validateVariableSetAttachment(
|
|
2366
|
-
{ settings: input.settings, db: input.db },
|
|
2367
|
-
input.grant,
|
|
2368
|
-
input.workspaceId,
|
|
2369
|
-
requestedVariableSetId
|
|
2370
|
-
);
|
|
2371
|
-
const missing = (pack.variableSet?.requiredVariables ?? []).filter(
|
|
2372
|
-
(name) => !variableSet.variables.some((variable) => variable.name === name)
|
|
2373
|
-
);
|
|
2374
|
-
if (missing.length > 0) {
|
|
2375
|
-
throw new HTTPException6(422, {
|
|
2376
|
-
message: `variable set is missing required variable(s): ${missing.join(", ")}`
|
|
2377
|
-
});
|
|
2378
|
-
}
|
|
2379
|
-
} else {
|
|
2380
|
-
const variableSet = await getVariableSet3(input.db, input.workspaceId, variableSetId);
|
|
2381
|
-
if (!variableSet) {
|
|
2382
|
-
throw new HTTPException6(422, {
|
|
2383
|
-
message: `the stored variableSet attachment for pack ${packId} no longer exists; re-enable it with variableSetId`
|
|
2384
|
-
});
|
|
2385
|
-
}
|
|
2386
|
-
const missing = (pack.variableSet?.requiredVariables ?? []).filter(
|
|
2387
|
-
(name) => !variableSet.variables.some((variable) => variable.name === name)
|
|
2388
|
-
);
|
|
2389
|
-
if (missing.length > 0) {
|
|
2390
|
-
throw new HTTPException6(422, {
|
|
2391
|
-
message: `variable set is missing required variable(s): ${missing.join(", ")}`
|
|
2392
|
-
});
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
await enablePackInstallation(input.db, {
|
|
2397
|
-
accountId: input.accountId,
|
|
2398
|
-
workspaceId: input.workspaceId,
|
|
2399
|
-
packId,
|
|
2400
|
-
metadata: {
|
|
2401
|
-
...input.payload.metadata,
|
|
2402
|
-
packVersion: pack.version,
|
|
2403
|
-
...variableSetId ? { variableSetId } : {}
|
|
2404
|
-
}
|
|
2405
|
-
});
|
|
2406
|
-
}
|
|
2407
2334
|
return await enableCapabilityInstallation(input.db, {
|
|
2408
2335
|
accountId: input.accountId,
|
|
2409
2336
|
workspaceId: input.workspaceId,
|
|
@@ -2436,7 +2363,7 @@ async function resolveMcpCredentialHeaders(input, item) {
|
|
|
2436
2363
|
])
|
|
2437
2364
|
);
|
|
2438
2365
|
} catch {
|
|
2439
|
-
throw new
|
|
2366
|
+
throw new HTTPException4(422, {
|
|
2440
2367
|
message: `stored credential headers for "${item.name}" could not be decrypted; supply them again in the enable request "headers" field`
|
|
2441
2368
|
});
|
|
2442
2369
|
}
|
|
@@ -2447,31 +2374,31 @@ function normalizedMcpCredentialHeaders(headers) {
|
|
|
2447
2374
|
return null;
|
|
2448
2375
|
}
|
|
2449
2376
|
if (entries.length > maxMcpCredentialHeaders) {
|
|
2450
|
-
throw new
|
|
2377
|
+
throw new HTTPException4(422, {
|
|
2451
2378
|
message: `an MCP capability supports at most ${maxMcpCredentialHeaders} credential headers`
|
|
2452
2379
|
});
|
|
2453
2380
|
}
|
|
2454
2381
|
const seen = /* @__PURE__ */ new Set();
|
|
2455
2382
|
for (const [name, value] of entries) {
|
|
2456
2383
|
if (!mcpCredentialHeaderName.test(name)) {
|
|
2457
|
-
throw new
|
|
2384
|
+
throw new HTTPException4(422, {
|
|
2458
2385
|
message: `invalid credential header name: ${name}`
|
|
2459
2386
|
});
|
|
2460
2387
|
}
|
|
2461
2388
|
const lower = name.toLowerCase();
|
|
2462
2389
|
if (seen.has(lower)) {
|
|
2463
|
-
throw new
|
|
2390
|
+
throw new HTTPException4(422, {
|
|
2464
2391
|
message: `duplicate credential header name: ${name}`
|
|
2465
2392
|
});
|
|
2466
2393
|
}
|
|
2467
2394
|
seen.add(lower);
|
|
2468
2395
|
if (value.length === 0 || value.length > maxMcpCredentialHeaderValueLength) {
|
|
2469
|
-
throw new
|
|
2396
|
+
throw new HTTPException4(422, {
|
|
2470
2397
|
message: `credential header ${name} must be 1-${maxMcpCredentialHeaderValueLength} characters`
|
|
2471
2398
|
});
|
|
2472
2399
|
}
|
|
2473
2400
|
if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
|
|
2474
|
-
throw new
|
|
2401
|
+
throw new HTTPException4(422, {
|
|
2475
2402
|
message: `credential header ${name} contains forbidden control characters`
|
|
2476
2403
|
});
|
|
2477
2404
|
}
|
|
@@ -2482,7 +2409,7 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
|
|
|
2482
2409
|
const subjectScope = ref.subjectScope ?? "workspace";
|
|
2483
2410
|
const personalOnly = item.metadata.connectionOwnership === "personal_only" || item.endpointUrl?.replace(/\/+$/, "") === officialGmailMcpUrl;
|
|
2484
2411
|
if (personalOnly && subjectScope !== "subject") {
|
|
2485
|
-
throw new
|
|
2412
|
+
throw new HTTPException4(422, {
|
|
2486
2413
|
message: "this capability requires a personal connection; each workspace member must connect their own account"
|
|
2487
2414
|
});
|
|
2488
2415
|
}
|
|
@@ -2501,12 +2428,12 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
|
|
|
2501
2428
|
} : {}
|
|
2502
2429
|
};
|
|
2503
2430
|
if (!normalized.providerDomain) {
|
|
2504
|
-
throw new
|
|
2431
|
+
throw new HTTPException4(422, {
|
|
2505
2432
|
message: "connectionRef.providerDomain is required"
|
|
2506
2433
|
});
|
|
2507
2434
|
}
|
|
2508
2435
|
if (!item.endpointUrl || !item.runtime.mcpServerId) {
|
|
2509
|
-
throw new
|
|
2436
|
+
throw new HTTPException4(422, {
|
|
2510
2437
|
message: "MCP capabilities need a remote streamable HTTP endpoint before they can use a connectionRef"
|
|
2511
2438
|
});
|
|
2512
2439
|
}
|
|
@@ -2527,27 +2454,27 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
|
|
|
2527
2454
|
) ?? null;
|
|
2528
2455
|
}
|
|
2529
2456
|
if (!connection) {
|
|
2530
|
-
throw new
|
|
2457
|
+
throw new HTTPException4(422, {
|
|
2531
2458
|
message: "connectionRef does not reference a visible active connection"
|
|
2532
2459
|
});
|
|
2533
2460
|
}
|
|
2534
2461
|
if (subjectScope === "subject" && connection.subjectId !== input.grant.subjectId || subjectScope === "workspace" && connection.subjectId !== null) {
|
|
2535
|
-
throw new
|
|
2462
|
+
throw new HTTPException4(422, {
|
|
2536
2463
|
message: `connectionRef does not reference a ${subjectScope}-owned connection`
|
|
2537
2464
|
});
|
|
2538
2465
|
}
|
|
2539
2466
|
if (connection.status !== "active") {
|
|
2540
|
-
throw new
|
|
2467
|
+
throw new HTTPException4(422, {
|
|
2541
2468
|
message: `connectionRef.connectionId is not active (${connection.status})`
|
|
2542
2469
|
});
|
|
2543
2470
|
}
|
|
2544
2471
|
if (connection.providerDomain !== normalized.providerDomain) {
|
|
2545
|
-
throw new
|
|
2472
|
+
throw new HTTPException4(422, {
|
|
2546
2473
|
message: "connectionRef.providerDomain does not match the referenced connection"
|
|
2547
2474
|
});
|
|
2548
2475
|
}
|
|
2549
2476
|
if (normalized.kind && connection.kind !== normalized.kind) {
|
|
2550
|
-
throw new
|
|
2477
|
+
throw new HTTPException4(422, {
|
|
2551
2478
|
message: "connectionRef.kind does not match the referenced connection"
|
|
2552
2479
|
});
|
|
2553
2480
|
}
|
|
@@ -2574,12 +2501,12 @@ function assertRequiredMcpCredentialHeaders(item, headers, connectionRef) {
|
|
|
2574
2501
|
const names = new Set(Object.keys(headers ?? {}).map((name) => name.toLowerCase()));
|
|
2575
2502
|
const missing = required.filter((name) => !names.has(name.toLowerCase()));
|
|
2576
2503
|
if (missing.length > 0) {
|
|
2577
|
-
throw new
|
|
2504
|
+
throw new HTTPException4(422, {
|
|
2578
2505
|
message: `MCP capability "${item.name}" requires credential header(s) ${missing.join(", ")}; pass them in the enable request "headers" field`
|
|
2579
2506
|
});
|
|
2580
2507
|
}
|
|
2581
2508
|
if (item.authModel && names.size === 0) {
|
|
2582
|
-
throw new
|
|
2509
|
+
throw new HTTPException4(422, {
|
|
2583
2510
|
message: `MCP capability "${item.name}" requires credentials; pass them in the enable request "headers" field`
|
|
2584
2511
|
});
|
|
2585
2512
|
}
|
|
@@ -2592,9 +2519,9 @@ function requiredCapabilityHeaders(metadata) {
|
|
|
2592
2519
|
return value.filter((name) => typeof name === "string" && name.trim().length > 0).map((name) => name.trim());
|
|
2593
2520
|
}
|
|
2594
2521
|
function requireCapabilityHeaderEncryption(settings) {
|
|
2595
|
-
const key =
|
|
2522
|
+
const key = environmentsEncryptionKeyBytes(settings);
|
|
2596
2523
|
if (!key) {
|
|
2597
|
-
throw new
|
|
2524
|
+
throw new HTTPException4(503, {
|
|
2598
2525
|
message: "MCP credential headers require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
|
|
2599
2526
|
});
|
|
2600
2527
|
}
|
|
@@ -2605,7 +2532,7 @@ async function validateMcpCapabilityConnection(item, probe = probeStreamableHttp
|
|
|
2605
2532
|
return {};
|
|
2606
2533
|
}
|
|
2607
2534
|
if (!item.endpointUrl || !item.runtime.mcpServerId) {
|
|
2608
|
-
throw new
|
|
2535
|
+
throw new HTTPException4(422, {
|
|
2609
2536
|
message: "MCP capabilities need a remote streamable HTTP endpoint before they can be enabled"
|
|
2610
2537
|
});
|
|
2611
2538
|
}
|
|
@@ -2625,7 +2552,7 @@ async function validateMcpCapabilityConnection(item, probe = probeStreamableHttp
|
|
|
2625
2552
|
}
|
|
2626
2553
|
};
|
|
2627
2554
|
} catch (error) {
|
|
2628
|
-
throw new
|
|
2555
|
+
throw new HTTPException4(422, {
|
|
2629
2556
|
message: `MCP capability "${item.name}" could not be enabled because ${mcpProbeErrorMessage(error, item.endpointUrl)}`
|
|
2630
2557
|
});
|
|
2631
2558
|
}
|
|
@@ -2683,40 +2610,33 @@ async function disableCapability(input) {
|
|
|
2683
2610
|
input.settings,
|
|
2684
2611
|
input.capabilityId
|
|
2685
2612
|
);
|
|
2686
|
-
if (
|
|
2687
|
-
throw new
|
|
2688
|
-
message: "
|
|
2613
|
+
if (item.kind === "skill") {
|
|
2614
|
+
throw new HTTPException4(409, {
|
|
2615
|
+
message: "Uninstall Skills through the Skill uninstall preview flow"
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
if (item.kind === "api") {
|
|
2619
|
+
throw new HTTPException4(409, {
|
|
2620
|
+
message: "Remove API Integrations through the Integration instance flow"
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
if (item.kind === "plugin") {
|
|
2624
|
+
throw new HTTPException4(409, {
|
|
2625
|
+
message: "Remove Plugins through the Plugin Package flow"
|
|
2689
2626
|
});
|
|
2690
2627
|
}
|
|
2691
2628
|
if (item.kind === "pack") {
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
input.db,
|
|
2704
|
-
input.workspaceId,
|
|
2705
|
-
packIdFromCapabilityId(item.id),
|
|
2706
|
-
"disabled"
|
|
2707
|
-
).catch(() => void 0);
|
|
2708
|
-
if (!await getCapabilityInstallation(input.db, input.workspaceId, item.id)) {
|
|
2709
|
-
await enableCapabilityInstallation(input.db, {
|
|
2710
|
-
accountId: input.accountId,
|
|
2711
|
-
workspaceId: input.workspaceId,
|
|
2712
|
-
capabilityId: item.id,
|
|
2713
|
-
kind: "pack",
|
|
2714
|
-
metadata: {},
|
|
2715
|
-
config: {}
|
|
2716
|
-
});
|
|
2717
|
-
}
|
|
2718
|
-
} else if (!await getCapabilityInstallation(input.db, input.workspaceId, item.id)) {
|
|
2719
|
-
throw new HTTPException6(409, {
|
|
2629
|
+
throw new HTTPException4(409, {
|
|
2630
|
+
message: "Uninstall Packs through the Pack uninstall preview flow"
|
|
2631
|
+
});
|
|
2632
|
+
}
|
|
2633
|
+
if (item.source === "built_in" || item.source === "configured") {
|
|
2634
|
+
throw new HTTPException4(409, {
|
|
2635
|
+
message: "built-in and configured capabilities are always available; remove them from configuration to disable them"
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
if (!await getCapabilityInstallation(input.db, input.workspaceId, item.id)) {
|
|
2639
|
+
throw new HTTPException4(409, {
|
|
2720
2640
|
message: "capability is not currently enabled"
|
|
2721
2641
|
});
|
|
2722
2642
|
}
|
|
@@ -2836,7 +2756,7 @@ function settingsWithMcpCapabilityServers(settings, enabled) {
|
|
|
2836
2756
|
if (enabled.length === 0) {
|
|
2837
2757
|
return settings;
|
|
2838
2758
|
}
|
|
2839
|
-
const encryptionKey =
|
|
2759
|
+
const encryptionKey = environmentsEncryptionKeyBytes(settings);
|
|
2840
2760
|
const existingIds = new Set(settings.mcpServers.map((server) => server.id));
|
|
2841
2761
|
const dynamicServers = enabled.filter((server) => !existingIds.has(server.id)).flatMap((server) => {
|
|
2842
2762
|
const headers = decryptedCapabilityHeaders(server, encryptionKey);
|
|
@@ -2916,21 +2836,21 @@ async function fetchMcpRegistryPage(url, options = {}) {
|
|
|
2916
2836
|
try {
|
|
2917
2837
|
const response = await fetchImpl(url, { signal: controller.signal });
|
|
2918
2838
|
if (!response.ok) {
|
|
2919
|
-
throw new
|
|
2839
|
+
throw new HTTPException4(502, {
|
|
2920
2840
|
message: `MCP registry returned ${response.status}`
|
|
2921
2841
|
});
|
|
2922
2842
|
}
|
|
2923
2843
|
return await response.json();
|
|
2924
2844
|
} catch (error) {
|
|
2925
|
-
if (error instanceof
|
|
2845
|
+
if (error instanceof HTTPException4) {
|
|
2926
2846
|
throw error;
|
|
2927
2847
|
}
|
|
2928
2848
|
if (error instanceof Error && error.name === "AbortError") {
|
|
2929
|
-
throw new
|
|
2849
|
+
throw new HTTPException4(504, {
|
|
2930
2850
|
message: "MCP registry request timed out"
|
|
2931
2851
|
});
|
|
2932
2852
|
}
|
|
2933
|
-
throw new
|
|
2853
|
+
throw new HTTPException4(502, {
|
|
2934
2854
|
message: `MCP registry request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2935
2855
|
});
|
|
2936
2856
|
} finally {
|
|
@@ -2941,7 +2861,7 @@ async function requireCatalogItem(db, workspaceId, settings, capabilityId) {
|
|
|
2941
2861
|
const catalog = await buildCapabilityCatalog({ db, workspaceId, settings });
|
|
2942
2862
|
const item = catalog.items.find((candidate) => candidate.id === capabilityId) ?? await getCapabilityCatalogItem(db, workspaceId, capabilityId);
|
|
2943
2863
|
if (!item) {
|
|
2944
|
-
throw new
|
|
2864
|
+
throw new HTTPException4(404, { message: "capability not found" });
|
|
2945
2865
|
}
|
|
2946
2866
|
return item;
|
|
2947
2867
|
}
|
|
@@ -3083,6 +3003,39 @@ var SOCIAL_PROVIDER_TOOL_NAMES = {
|
|
|
3083
3003
|
"reddit_post_reply"
|
|
3084
3004
|
]
|
|
3085
3005
|
};
|
|
3006
|
+
function fikenCatalogItem(fikenConnections) {
|
|
3007
|
+
const fikenConnection = preferredFikenConnection(fikenConnections);
|
|
3008
|
+
const fikenEnabled = fikenConnection?.status === "active" || fikenConnection?.status === "needs_reauth";
|
|
3009
|
+
return CapabilityCatalogItem.parse({
|
|
3010
|
+
id: "api:fiken",
|
|
3011
|
+
kind: "api",
|
|
3012
|
+
source: "built_in",
|
|
3013
|
+
name: "Fiken",
|
|
3014
|
+
description: "Connect Fiken accounting for contacts, products, invoices, invoice drafts, purchases, sales, and bank accounts.",
|
|
3015
|
+
category: "finance",
|
|
3016
|
+
tags: ["api", "fiken", "accounting", "invoicing", "norway"],
|
|
3017
|
+
homepageUrl: "https://fiken.no",
|
|
3018
|
+
authModel: "personal_api_token",
|
|
3019
|
+
providerDomain: FIKEN_PROVIDER_DOMAIN2,
|
|
3020
|
+
surfaceType: "first_party_fiken",
|
|
3021
|
+
authKind: "api_key",
|
|
3022
|
+
tools: [{ kind: "mcp", id: "opengeni" }],
|
|
3023
|
+
runtime: {
|
|
3024
|
+
available: true,
|
|
3025
|
+
mcpServerId: "opengeni",
|
|
3026
|
+
notes: "Fiken access is provided through OpenGeni's first-party fiken tools."
|
|
3027
|
+
},
|
|
3028
|
+
enabled: fikenEnabled,
|
|
3029
|
+
enabledReason: fikenEnabled ? fikenConnection.status === "active" ? "workspace Fiken connection active" : "workspace Fiken connection needs reconnection" : null,
|
|
3030
|
+
metadata: {
|
|
3031
|
+
connectorMode: "first_party_fiken",
|
|
3032
|
+
ownership: "workspace",
|
|
3033
|
+
// Derived from the contracts catalog so a new fiken_* tool cannot be
|
|
3034
|
+
// registered without also appearing on the capability tile.
|
|
3035
|
+
firstPartyMcpTools: FIRST_PARTY_MCP_TOOL_NAMES.filter((name) => name.startsWith("fiken_"))
|
|
3036
|
+
}
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3086
3039
|
function providerIntegrationCatalogItems(socialConnections) {
|
|
3087
3040
|
return SOCIAL_PROVIDER_INTEGRATIONS.map((definition) => {
|
|
3088
3041
|
const counts = socialConnectionCounts(socialConnections, definition.provider);
|
|
@@ -3212,6 +3165,51 @@ function curatedSkillCatalogItem(entry) {
|
|
|
3212
3165
|
}
|
|
3213
3166
|
});
|
|
3214
3167
|
}
|
|
3168
|
+
function installedSkillCatalogItem(skill) {
|
|
3169
|
+
return CapabilityCatalogItem.parse({
|
|
3170
|
+
id: skill.capabilityId,
|
|
3171
|
+
kind: "skill",
|
|
3172
|
+
source: skill.source === "library" ? "library" : "manual",
|
|
3173
|
+
name: skill.name,
|
|
3174
|
+
description: skill.description,
|
|
3175
|
+
category: skill.category,
|
|
3176
|
+
tags: skill.tags,
|
|
3177
|
+
homepageUrl: skill.repositoryUrl,
|
|
3178
|
+
installUrl: skill.sourceUrl,
|
|
3179
|
+
provenance: skill.provenance,
|
|
3180
|
+
tier: skill.source === "library" ? "verified" : "community",
|
|
3181
|
+
runtime: {
|
|
3182
|
+
available: true,
|
|
3183
|
+
notes: "Available from an immutable Skill installation."
|
|
3184
|
+
},
|
|
3185
|
+
metadata: {
|
|
3186
|
+
version: skill.version,
|
|
3187
|
+
contentSha256: skill.contentSha256,
|
|
3188
|
+
sourceCommit: skill.sourceCommit,
|
|
3189
|
+
sourcePath: skill.sourcePath,
|
|
3190
|
+
sourceUrl: skill.sourceUrl,
|
|
3191
|
+
repositoryUrl: skill.repositoryUrl,
|
|
3192
|
+
provenance: skill.provenance,
|
|
3193
|
+
license: skill.license,
|
|
3194
|
+
installedSkill: installedSkillMetadata(skill)
|
|
3195
|
+
}
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
function installedSkillMetadata(skill) {
|
|
3199
|
+
return {
|
|
3200
|
+
pluginKey: skill.pluginKey,
|
|
3201
|
+
installationVersion: skill.installationVersion,
|
|
3202
|
+
source: skill.source,
|
|
3203
|
+
version: skill.version,
|
|
3204
|
+
sourceCommit: skill.sourceCommit,
|
|
3205
|
+
contentSha256: skill.contentSha256,
|
|
3206
|
+
fileCount: skill.fileCount,
|
|
3207
|
+
totalBytes: skill.totalBytes,
|
|
3208
|
+
installedAt: skill.installedAt,
|
|
3209
|
+
updatedAt: skill.updatedAt,
|
|
3210
|
+
owners: skill.owners.map((owner) => ({ ...owner }))
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3215
3213
|
function stringMetadata(value) {
|
|
3216
3214
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3217
3215
|
}
|
|
@@ -3233,8 +3231,11 @@ function socialProviderConnectionCounts(item) {
|
|
|
3233
3231
|
};
|
|
3234
3232
|
}
|
|
3235
3233
|
function applyCapabilityEnablement(item, installation, activePackIds) {
|
|
3234
|
+
if (item.kind === "skill" || item.kind === "api" || item.kind === "plugin") {
|
|
3235
|
+
return { ...item, enabled: false, enabledReason: null, connectionRef: null };
|
|
3236
|
+
}
|
|
3236
3237
|
if (item.kind === "pack") {
|
|
3237
|
-
const enabled2 = activePackIds.has(packIdFromCapabilityId(item.id))
|
|
3238
|
+
const enabled2 = activePackIds.has(packIdFromCapabilityId(item.id));
|
|
3238
3239
|
return {
|
|
3239
3240
|
...item,
|
|
3240
3241
|
enabled: enabled2,
|
|
@@ -3244,6 +3245,9 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
|
|
|
3244
3245
|
if (isSocialProviderIntegration(item)) {
|
|
3245
3246
|
return { ...item, connectionRef: null };
|
|
3246
3247
|
}
|
|
3248
|
+
if (item.surfaceType === "first_party_fiken") {
|
|
3249
|
+
return { ...item, connectionRef: null };
|
|
3250
|
+
}
|
|
3247
3251
|
if (item.surfaceType === "codex_apps") {
|
|
3248
3252
|
return { ...item, connectionRef: null };
|
|
3249
3253
|
}
|
|
@@ -3258,33 +3262,8 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
|
|
|
3258
3262
|
if (item.source === "built_in") {
|
|
3259
3263
|
return { ...item, connectionRef: null };
|
|
3260
3264
|
}
|
|
3261
|
-
if (item.source === "library") {
|
|
3262
|
-
const enabled2 = installation?.status === "active" && skillLibraryInstallationRuntimeReady(item, installation);
|
|
3263
|
-
return {
|
|
3264
|
-
...item,
|
|
3265
|
-
enabled: enabled2,
|
|
3266
|
-
enabledReason: enabled2 ? "explicitly selected" : null,
|
|
3267
|
-
connectionRef: null
|
|
3268
|
-
};
|
|
3269
|
-
}
|
|
3270
3265
|
const activeInstallation = installation?.status === "active";
|
|
3271
3266
|
const enabled = !!activeInstallation && capabilityInstallationRuntimeReady(item, installation);
|
|
3272
|
-
if (item.kind === "api" && item.metadata.platformVersion === 2) {
|
|
3273
|
-
const connectionBound = installation?.metadata.connectionBound === true;
|
|
3274
|
-
const providerDomain = stringMetadata(installation?.metadata.providerDomain);
|
|
3275
|
-
const connectionKind = stringMetadata(installation?.metadata.connectionKind);
|
|
3276
|
-
const connectionOwnership = stringMetadata(installation?.metadata.connectionOwnership);
|
|
3277
|
-
return {
|
|
3278
|
-
...item,
|
|
3279
|
-
enabled,
|
|
3280
|
-
enabledReason: enabled ? "installed immutable Integration revision" : null,
|
|
3281
|
-
connectionRef: enabled && connectionBound && providerDomain && connectionKind ? {
|
|
3282
|
-
providerDomain,
|
|
3283
|
-
kind: connectionKind,
|
|
3284
|
-
...connectionOwnership === "subject" ? { subjectScope: "subject" } : {}
|
|
3285
|
-
} : null
|
|
3286
|
-
};
|
|
3287
|
-
}
|
|
3288
3267
|
return {
|
|
3289
3268
|
...item,
|
|
3290
3269
|
enabled,
|
|
@@ -3357,23 +3336,23 @@ function applyCapabilityLifecycle(item) {
|
|
|
3357
3336
|
actions
|
|
3358
3337
|
};
|
|
3359
3338
|
}
|
|
3360
|
-
function
|
|
3361
|
-
if (
|
|
3362
|
-
return false;
|
|
3363
|
-
}
|
|
3364
|
-
const libraryId = stringMetadata(item.metadata.libraryId);
|
|
3365
|
-
const version = stringMetadata(item.metadata.version);
|
|
3366
|
-
const contentSha256 = stringMetadata(item.metadata.contentSha256);
|
|
3367
|
-
const sourceCommit = stringMetadata(item.metadata.sourceCommit);
|
|
3368
|
-
const provenance = stringMetadata(item.metadata.provenance);
|
|
3369
|
-
if (!libraryId || !version || !contentSha256 || !sourceCommit || !provenance) {
|
|
3370
|
-
return false;
|
|
3339
|
+
function applyInstalledSkillEnablement(item, installation) {
|
|
3340
|
+
if (!installation) {
|
|
3341
|
+
return { ...item, enabled: false, enabledReason: null, connectionRef: null };
|
|
3371
3342
|
}
|
|
3372
|
-
const
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3343
|
+
const catalogVersion = stringMetadata(item.metadata.version);
|
|
3344
|
+
const current = catalogVersion === null || catalogVersion === installation.version && stringMetadata(item.metadata.contentSha256) === installation.contentSha256 && stringMetadata(item.metadata.sourceCommit) === installation.sourceCommit;
|
|
3345
|
+
return {
|
|
3346
|
+
...item,
|
|
3347
|
+
enabled: true,
|
|
3348
|
+
enabledReason: current ? "explicitly installed" : `version ${installation.version} installed; update available`,
|
|
3349
|
+
connectionRef: null,
|
|
3350
|
+
metadata: {
|
|
3351
|
+
...item.metadata,
|
|
3352
|
+
installedSkill: installedSkillMetadata(installation),
|
|
3353
|
+
updateAvailable: !current
|
|
3354
|
+
}
|
|
3355
|
+
};
|
|
3377
3356
|
}
|
|
3378
3357
|
function installationConnectionRef(config) {
|
|
3379
3358
|
const ref = config.connectionRef;
|
|
@@ -3611,7 +3590,7 @@ import {
|
|
|
3611
3590
|
PORTABLE_SKILL_MAX_FILES,
|
|
3612
3591
|
PORTABLE_SKILL_MAX_TOTAL_BYTES
|
|
3613
3592
|
} from "@opengeni/runtime/skill-library";
|
|
3614
|
-
import { HTTPException as
|
|
3593
|
+
import { HTTPException as HTTPException5 } from "hono/http-exception";
|
|
3615
3594
|
var githubSegment = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/u;
|
|
3616
3595
|
var gitCommit = /^[0-9a-f]{40,64}$/u;
|
|
3617
3596
|
var maxConcurrentBlobReads = 8;
|
|
@@ -3619,19 +3598,19 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
3619
3598
|
const parsed = parseSkillSource(rawUrl);
|
|
3620
3599
|
const sourceCommit = await client.resolveCommit(parsed.owner, parsed.repository, parsed.ref);
|
|
3621
3600
|
if (!gitCommit.test(sourceCommit)) {
|
|
3622
|
-
throw new
|
|
3601
|
+
throw new HTTPException5(422, { message: "GitHub returned an invalid source commit" });
|
|
3623
3602
|
}
|
|
3624
3603
|
const tree = await client.listTree(parsed.owner, parsed.repository, sourceCommit);
|
|
3625
3604
|
const sourcePath = selectSkillRoot(parsed, tree);
|
|
3626
3605
|
const entries = skillFilesUnderRoot(tree, sourcePath);
|
|
3627
3606
|
const declaredBytes = entries.reduce((sum, entry) => sum + (entry.size ?? 0), 0);
|
|
3628
3607
|
if (entries.length > PORTABLE_SKILL_MAX_FILES) {
|
|
3629
|
-
throw new
|
|
3608
|
+
throw new HTTPException5(422, {
|
|
3630
3609
|
message: `Skill contains more than ${PORTABLE_SKILL_MAX_FILES} files`
|
|
3631
3610
|
});
|
|
3632
3611
|
}
|
|
3633
3612
|
if (declaredBytes > PORTABLE_SKILL_MAX_TOTAL_BYTES) {
|
|
3634
|
-
throw new
|
|
3613
|
+
throw new HTTPException5(422, {
|
|
3635
3614
|
message: `Skill exceeds ${PORTABLE_SKILL_MAX_TOTAL_BYTES} bytes`
|
|
3636
3615
|
});
|
|
3637
3616
|
}
|
|
@@ -3642,7 +3621,7 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
3642
3621
|
await client.readBlob(parsed.owner, parsed.repository, entry.sha)
|
|
3643
3622
|
);
|
|
3644
3623
|
} catch {
|
|
3645
|
-
throw new
|
|
3624
|
+
throw new HTTPException5(422, {
|
|
3646
3625
|
message: `Skill file is not valid UTF-8 text: ${relativeSkillPath(entry.path, sourcePath)}`
|
|
3647
3626
|
});
|
|
3648
3627
|
}
|
|
@@ -3652,7 +3631,7 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
3652
3631
|
try {
|
|
3653
3632
|
artifact = buildPortableSkillArtifact2(files);
|
|
3654
3633
|
} catch (error) {
|
|
3655
|
-
throw new
|
|
3634
|
+
throw new HTTPException5(422, {
|
|
3656
3635
|
message: error instanceof Error ? error.message : "Skill artifact is invalid"
|
|
3657
3636
|
});
|
|
3658
3637
|
}
|
|
@@ -3709,24 +3688,24 @@ function parseSkillSource(rawUrl) {
|
|
|
3709
3688
|
try {
|
|
3710
3689
|
url = new URL(rawUrl);
|
|
3711
3690
|
} catch {
|
|
3712
|
-
throw new
|
|
3691
|
+
throw new HTTPException5(422, { message: "Enter a valid GitHub or skills.sh URL" });
|
|
3713
3692
|
}
|
|
3714
3693
|
if (url.protocol !== "https:" || url.username || url.password || url.hash) {
|
|
3715
|
-
throw new
|
|
3694
|
+
throw new HTTPException5(422, {
|
|
3716
3695
|
message: "Skill imports require a credential-free HTTPS URL without a fragment"
|
|
3717
3696
|
});
|
|
3718
3697
|
}
|
|
3719
3698
|
const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
3720
3699
|
if (url.hostname === "skills.sh" || url.hostname === "www.skills.sh") {
|
|
3721
3700
|
if (segments.length !== 3) {
|
|
3722
|
-
throw new
|
|
3701
|
+
throw new HTTPException5(422, {
|
|
3723
3702
|
message: "A skills.sh URL must identify one owner, repository, and Skill"
|
|
3724
3703
|
});
|
|
3725
3704
|
}
|
|
3726
3705
|
const [owner2, repository2, skillSlug] = segments;
|
|
3727
3706
|
assertGitHubRepository(owner2, repository2);
|
|
3728
3707
|
if (!skillSlug || !githubSegment.test(skillSlug)) {
|
|
3729
|
-
throw new
|
|
3708
|
+
throw new HTTPException5(422, { message: "The skills.sh Skill name is invalid" });
|
|
3730
3709
|
}
|
|
3731
3710
|
return {
|
|
3732
3711
|
source: "skills_sh",
|
|
@@ -3739,12 +3718,12 @@ function parseSkillSource(rawUrl) {
|
|
|
3739
3718
|
};
|
|
3740
3719
|
}
|
|
3741
3720
|
if (url.hostname !== "github.com" && url.hostname !== "www.github.com") {
|
|
3742
|
-
throw new
|
|
3721
|
+
throw new HTTPException5(422, {
|
|
3743
3722
|
message: "Only github.com and skills.sh imports are supported"
|
|
3744
3723
|
});
|
|
3745
3724
|
}
|
|
3746
3725
|
if (segments.length < 2) {
|
|
3747
|
-
throw new
|
|
3726
|
+
throw new HTTPException5(422, { message: "A GitHub URL must identify a repository" });
|
|
3748
3727
|
}
|
|
3749
3728
|
const owner = segments[0];
|
|
3750
3729
|
const repository = stripGitSuffix(segments[1]);
|
|
@@ -3762,15 +3741,15 @@ function parseSkillSource(rawUrl) {
|
|
|
3762
3741
|
}
|
|
3763
3742
|
const mode = segments[2];
|
|
3764
3743
|
if (mode !== "tree" && mode !== "blob") {
|
|
3765
|
-
throw new
|
|
3744
|
+
throw new HTTPException5(422, {
|
|
3766
3745
|
message: "Use a GitHub repository, tree, folder, or SKILL.md URL"
|
|
3767
3746
|
});
|
|
3768
3747
|
}
|
|
3769
3748
|
const ref = segments[3];
|
|
3770
|
-
if (!ref) throw new
|
|
3749
|
+
if (!ref) throw new HTTPException5(422, { message: "The GitHub URL is missing a revision" });
|
|
3771
3750
|
const pathSegments = segments.slice(4);
|
|
3772
3751
|
if (pathSegments.length === 0) {
|
|
3773
|
-
throw new
|
|
3752
|
+
throw new HTTPException5(422, { message: "The GitHub URL is missing a Skill folder" });
|
|
3774
3753
|
}
|
|
3775
3754
|
const requestedPath = normalizeGitHubPath(
|
|
3776
3755
|
mode === "blob" && pathSegments.at(-1)?.toLowerCase() === "skill.md" ? pathSegments.slice(0, -1) : pathSegments
|
|
@@ -3792,7 +3771,7 @@ function selectSkillRoot(source, tree) {
|
|
|
3792
3771
|
if (source.requestedPath) {
|
|
3793
3772
|
const root = source.requestedPath;
|
|
3794
3773
|
if (!skillFiles.includes(root)) {
|
|
3795
|
-
throw new
|
|
3774
|
+
throw new HTTPException5(422, {
|
|
3796
3775
|
message: `No top-level SKILL.md was found in ${root}`
|
|
3797
3776
|
});
|
|
3798
3777
|
}
|
|
@@ -3800,10 +3779,10 @@ function selectSkillRoot(source, tree) {
|
|
|
3800
3779
|
}
|
|
3801
3780
|
const candidates = source.skillSlug ? skillFiles.filter((path) => path.split("/").at(-1) === source.skillSlug) : skillFiles;
|
|
3802
3781
|
if (candidates.length === 0) {
|
|
3803
|
-
throw new
|
|
3782
|
+
throw new HTTPException5(422, { message: "No Skill folder with SKILL.md was found" });
|
|
3804
3783
|
}
|
|
3805
3784
|
if (candidates.length > 1) {
|
|
3806
|
-
throw new
|
|
3785
|
+
throw new HTTPException5(422, {
|
|
3807
3786
|
message: "This source contains multiple Skills; paste the exact GitHub folder URL"
|
|
3808
3787
|
});
|
|
3809
3788
|
}
|
|
@@ -3816,13 +3795,13 @@ function skillFilesUnderRoot(tree, root) {
|
|
|
3816
3795
|
(entry) => entry.type === "commit" || entry.type === "blob" && entry.mode === "120000"
|
|
3817
3796
|
);
|
|
3818
3797
|
if (unsupported) {
|
|
3819
|
-
throw new
|
|
3798
|
+
throw new HTTPException5(422, {
|
|
3820
3799
|
message: `Skill folders may not contain symbolic links or submodules (${unsupported.path})`
|
|
3821
3800
|
});
|
|
3822
3801
|
}
|
|
3823
3802
|
const files = inside.filter((entry) => entry.type === "blob").sort((left, right) => left.path.localeCompare(right.path));
|
|
3824
3803
|
if (files.length === 0) {
|
|
3825
|
-
throw new
|
|
3804
|
+
throw new HTTPException5(422, { message: "The selected Skill folder is empty" });
|
|
3826
3805
|
}
|
|
3827
3806
|
return files;
|
|
3828
3807
|
}
|
|
@@ -3833,7 +3812,7 @@ function normalizeGitHubPath(segments) {
|
|
|
3833
3812
|
if (segments.length === 0 || segments.some(
|
|
3834
3813
|
(segment) => segment.length === 0 || segment === "." || segment === ".." || segment.includes("\\") || /[\u0000-\u001f\u007f]/u.test(segment)
|
|
3835
3814
|
)) {
|
|
3836
|
-
throw new
|
|
3815
|
+
throw new HTTPException5(422, { message: "The GitHub Skill path is invalid" });
|
|
3837
3816
|
}
|
|
3838
3817
|
return segments.join("/");
|
|
3839
3818
|
}
|
|
@@ -3845,7 +3824,7 @@ function stripGitSuffix(value) {
|
|
|
3845
3824
|
}
|
|
3846
3825
|
function assertGitHubRepository(owner, repository) {
|
|
3847
3826
|
if (!githubSegment.test(owner) || !githubSegment.test(stripGitSuffix(repository))) {
|
|
3848
|
-
throw new
|
|
3827
|
+
throw new HTTPException5(422, { message: "The GitHub owner or repository name is invalid" });
|
|
3849
3828
|
}
|
|
3850
3829
|
}
|
|
3851
3830
|
async function mapConcurrent(values, concurrency, map) {
|
|
@@ -3867,6 +3846,93 @@ function sha256Hex2(bytes) {
|
|
|
3867
3846
|
return createHash2("sha256").update(bytes).digest("hex");
|
|
3868
3847
|
}
|
|
3869
3848
|
|
|
3849
|
+
// src/domain/environments.ts
|
|
3850
|
+
import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2 } from "@opengeni/config";
|
|
3851
|
+
import { getVariableSet as getVariableSet2, recordAuditEvent } from "@opengeni/db";
|
|
3852
|
+
import { HTTPException as HTTPException6 } from "hono/http-exception";
|
|
3853
|
+
var MAX_ENVIRONMENTS_PER_WORKSPACE = 25;
|
|
3854
|
+
var MAX_VARIABLES_PER_ENVIRONMENT = 100;
|
|
3855
|
+
var reservedExactNames = /* @__PURE__ */ new Set([
|
|
3856
|
+
"HOME",
|
|
3857
|
+
"PATH",
|
|
3858
|
+
"SHELL",
|
|
3859
|
+
"USER",
|
|
3860
|
+
"LOGNAME",
|
|
3861
|
+
"TMPDIR",
|
|
3862
|
+
"IFS",
|
|
3863
|
+
"ENV",
|
|
3864
|
+
"BASH_ENV",
|
|
3865
|
+
"NODE_OPTIONS",
|
|
3866
|
+
"PYTHONPATH",
|
|
3867
|
+
"PYTHONSTARTUP",
|
|
3868
|
+
"PERL5OPT",
|
|
3869
|
+
"PERL5LIB",
|
|
3870
|
+
"GH_TOKEN",
|
|
3871
|
+
"GITHUB_TOKEN",
|
|
3872
|
+
"GITLAB_TOKEN",
|
|
3873
|
+
"AZURE_DEVOPS_EXT_PAT",
|
|
3874
|
+
"GIT_ASKPASS",
|
|
3875
|
+
"GIT_TERMINAL_PROMPT"
|
|
3876
|
+
]);
|
|
3877
|
+
var reservedPrefixes = [
|
|
3878
|
+
"OPENGENI_",
|
|
3879
|
+
"GIT_CONFIG_",
|
|
3880
|
+
"GIT_AUTHOR_",
|
|
3881
|
+
"GIT_COMMITTER_",
|
|
3882
|
+
"LD_",
|
|
3883
|
+
"DYLD_"
|
|
3884
|
+
];
|
|
3885
|
+
function assertAllowedVariableSetVariableName(name) {
|
|
3886
|
+
if (reservedExactNames.has(name) || reservedPrefixes.some((prefix) => name.startsWith(prefix))) {
|
|
3887
|
+
throw new HTTPException6(422, {
|
|
3888
|
+
message: `reserved variable set variable name / reserved environment variable name: ${name}`
|
|
3889
|
+
});
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
var assertAllowedEnvironmentVariableName = assertAllowedVariableSetVariableName;
|
|
3893
|
+
function requireVariableSetEncryption(settings) {
|
|
3894
|
+
const key = environmentsEncryptionKeyBytes2(settings);
|
|
3895
|
+
if (!key) {
|
|
3896
|
+
throw new HTTPException6(503, {
|
|
3897
|
+
message: "variable sets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
|
|
3898
|
+
});
|
|
3899
|
+
}
|
|
3900
|
+
return key;
|
|
3901
|
+
}
|
|
3902
|
+
var requireEnvironmentEncryption = requireVariableSetEncryption;
|
|
3903
|
+
async function requireVariableSetForApi(db, workspaceId, variableSetId) {
|
|
3904
|
+
const variableSet = await getVariableSet2(db, workspaceId, variableSetId);
|
|
3905
|
+
if (!variableSet) {
|
|
3906
|
+
throw new HTTPException6(404, { message: "variableSet not found" });
|
|
3907
|
+
}
|
|
3908
|
+
return variableSet;
|
|
3909
|
+
}
|
|
3910
|
+
async function validateVariableSetAttachment(deps, grant, workspaceId, variableSetId, options = {}) {
|
|
3911
|
+
requireVariableSetEncryption(deps.settings);
|
|
3912
|
+
if (!options.preauthorized) {
|
|
3913
|
+
requirePermission(grant, "variable-sets:use");
|
|
3914
|
+
}
|
|
3915
|
+
const variableSet = await getVariableSet2(deps.db, workspaceId, variableSetId);
|
|
3916
|
+
if (!variableSet) {
|
|
3917
|
+
throw new HTTPException6(422, { message: "unknown variableSetId" });
|
|
3918
|
+
}
|
|
3919
|
+
return variableSet;
|
|
3920
|
+
}
|
|
3921
|
+
async function recordVariableSetAuditEvent(db, input) {
|
|
3922
|
+
await recordAuditEvent(db, {
|
|
3923
|
+
accountId: input.grant.accountId,
|
|
3924
|
+
workspaceId: input.grant.workspaceId,
|
|
3925
|
+
subjectId: input.grant.subjectId,
|
|
3926
|
+
action: input.action,
|
|
3927
|
+
targetType: "workspace_variable_set",
|
|
3928
|
+
targetId: input.variableSetId,
|
|
3929
|
+
metadata: {
|
|
3930
|
+
variableSetId: input.variableSetId,
|
|
3931
|
+
...input.variableName ? { name: input.variableName } : {}
|
|
3932
|
+
}
|
|
3933
|
+
});
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3870
3936
|
// src/rigs/index.ts
|
|
3871
3937
|
import {
|
|
3872
3938
|
activateRigVersion,
|
|
@@ -3880,7 +3946,7 @@ import {
|
|
|
3880
3946
|
getRigByName,
|
|
3881
3947
|
getRigChange,
|
|
3882
3948
|
getRigVersion,
|
|
3883
|
-
getVariableSet as
|
|
3949
|
+
getVariableSet as getVariableSet3,
|
|
3884
3950
|
listRigChanges,
|
|
3885
3951
|
listRigVersions,
|
|
3886
3952
|
recordAuditEvent as recordAuditEvent2,
|
|
@@ -3888,7 +3954,7 @@ import {
|
|
|
3888
3954
|
RigChangeTransitionError,
|
|
3889
3955
|
updateRig
|
|
3890
3956
|
} from "@opengeni/db";
|
|
3891
|
-
import { HTTPException as
|
|
3957
|
+
import { HTTPException as HTTPException7 } from "hono/http-exception";
|
|
3892
3958
|
|
|
3893
3959
|
// src/rigs/provider-images.ts
|
|
3894
3960
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -3962,21 +4028,21 @@ function rigActorForGrant(grant) {
|
|
|
3962
4028
|
async function requireRigForApi(db, workspaceId, rigId) {
|
|
3963
4029
|
const rig = await getRig2(db, workspaceId, rigId);
|
|
3964
4030
|
if (!rig) {
|
|
3965
|
-
throw new
|
|
4031
|
+
throw new HTTPException7(404, { message: "rig not found" });
|
|
3966
4032
|
}
|
|
3967
4033
|
return rig;
|
|
3968
4034
|
}
|
|
3969
4035
|
async function requireRigChangeForApi(db, workspaceId, rigId, changeId) {
|
|
3970
4036
|
const change = await getRigChange(db, workspaceId, changeId);
|
|
3971
4037
|
if (!change || change.rigId !== rigId) {
|
|
3972
|
-
throw new
|
|
4038
|
+
throw new HTTPException7(404, { message: "rig change not found" });
|
|
3973
4039
|
}
|
|
3974
4040
|
return change;
|
|
3975
4041
|
}
|
|
3976
4042
|
function trimmedRigName(name) {
|
|
3977
4043
|
const trimmed = name.trim();
|
|
3978
4044
|
if (!trimmed) {
|
|
3979
|
-
throw new
|
|
4045
|
+
throw new HTTPException7(422, { message: "rig name is required" });
|
|
3980
4046
|
}
|
|
3981
4047
|
return trimmed;
|
|
3982
4048
|
}
|
|
@@ -3987,7 +4053,7 @@ function assertUniqueCheckNames(checks) {
|
|
|
3987
4053
|
const seen = /* @__PURE__ */ new Set();
|
|
3988
4054
|
for (const check of checks) {
|
|
3989
4055
|
if (seen.has(check.name)) {
|
|
3990
|
-
throw new
|
|
4056
|
+
throw new HTTPException7(422, { message: `duplicate rig check name: ${check.name}` });
|
|
3991
4057
|
}
|
|
3992
4058
|
seen.add(check.name);
|
|
3993
4059
|
}
|
|
@@ -3998,9 +4064,9 @@ async function assertVariableSetsExist(db, workspaceId, ids) {
|
|
|
3998
4064
|
}
|
|
3999
4065
|
const unique = [...new Set(ids)];
|
|
4000
4066
|
for (const id of unique) {
|
|
4001
|
-
const variableSet = await
|
|
4067
|
+
const variableSet = await getVariableSet3(db, workspaceId, id);
|
|
4002
4068
|
if (!variableSet) {
|
|
4003
|
-
throw new
|
|
4069
|
+
throw new HTTPException7(422, { message: `unknown defaultVariableSetId: ${id}` });
|
|
4004
4070
|
}
|
|
4005
4071
|
}
|
|
4006
4072
|
}
|
|
@@ -4010,12 +4076,12 @@ async function createRigForApi(deps, grant, payload) {
|
|
|
4010
4076
|
assertUniqueCheckNames(payload.checks);
|
|
4011
4077
|
await assertVariableSetsExist(deps.db, workspaceId, payload.defaultVariableSetIds);
|
|
4012
4078
|
if (await countRigs(deps.db, workspaceId) >= MAX_RIGS_PER_WORKSPACE) {
|
|
4013
|
-
throw new
|
|
4079
|
+
throw new HTTPException7(422, {
|
|
4014
4080
|
message: `a workspace supports at most ${MAX_RIGS_PER_WORKSPACE} rigs`
|
|
4015
4081
|
});
|
|
4016
4082
|
}
|
|
4017
4083
|
if (await getRigByName(deps.db, workspaceId, name)) {
|
|
4018
|
-
throw new
|
|
4084
|
+
throw new HTTPException7(409, { message: `rig name is already in use: ${name}` });
|
|
4019
4085
|
}
|
|
4020
4086
|
const createdBy = rigActorForGrant(grant);
|
|
4021
4087
|
const rig = await createRig(deps.db, {
|
|
@@ -4043,7 +4109,7 @@ async function updateRigForApi(deps, grant, rig, payload) {
|
|
|
4043
4109
|
if (name !== void 0 && name !== rig.name) {
|
|
4044
4110
|
const existing = await getRigByName(deps.db, workspaceId, name);
|
|
4045
4111
|
if (existing && existing.id !== rig.id) {
|
|
4046
|
-
throw new
|
|
4112
|
+
throw new HTTPException7(409, { message: `rig name is already in use: ${name}` });
|
|
4047
4113
|
}
|
|
4048
4114
|
}
|
|
4049
4115
|
const updated = await updateRig(deps.db, workspaceId, rig.id, {
|
|
@@ -4057,19 +4123,19 @@ async function deleteRigForApi(deps, grant, rig) {
|
|
|
4057
4123
|
const workspaceId = grant.workspaceId;
|
|
4058
4124
|
const deleted = await deleteRigIfNoActiveSessions(deps.db, workspaceId, rig.id);
|
|
4059
4125
|
if (deleted.activeSessionCount > 0) {
|
|
4060
|
-
throw new
|
|
4126
|
+
throw new HTTPException7(409, {
|
|
4061
4127
|
message: `rig is referenced by ${deleted.activeSessionCount} active session(s); it cannot be deleted`
|
|
4062
4128
|
});
|
|
4063
4129
|
}
|
|
4064
4130
|
if (!deleted.deleted) {
|
|
4065
|
-
throw new
|
|
4131
|
+
throw new HTTPException7(404, { message: "rig not found" });
|
|
4066
4132
|
}
|
|
4067
4133
|
await recordRigAuditEvent(deps.db, { grant, action: "rig.deleted", rigId: rig.id });
|
|
4068
4134
|
}
|
|
4069
4135
|
async function proposeRigChangeForApi(deps, grant, rig, request, options = {}) {
|
|
4070
4136
|
const workspaceId = grant.workspaceId;
|
|
4071
4137
|
if (!rig.activeVersion) {
|
|
4072
|
-
throw new
|
|
4138
|
+
throw new HTTPException7(422, { message: "rig has no active version to base a change on" });
|
|
4073
4139
|
}
|
|
4074
4140
|
if (request.kind === "definition_edit") {
|
|
4075
4141
|
assertUniqueCheckNames(request.payload.checks);
|
|
@@ -4118,35 +4184,35 @@ async function promoteChangeWithActiveCas(deps, workspaceId, rigId, changeId, in
|
|
|
4118
4184
|
return await createRigVersionForChangePromotion(deps.db, workspaceId, rigId, changeId, input);
|
|
4119
4185
|
} catch (error) {
|
|
4120
4186
|
if (error instanceof RigActiveVersionChangedError) {
|
|
4121
|
-
throw new
|
|
4187
|
+
throw new HTTPException7(409, {
|
|
4122
4188
|
message: `rig moved since this change was verified (base ${error.expectedVersionId}, now ${error.actualVersionId ?? "none"}); re-verify before promoting`
|
|
4123
4189
|
});
|
|
4124
4190
|
}
|
|
4125
4191
|
if (error instanceof RigChangeTransitionError) {
|
|
4126
|
-
throw new
|
|
4192
|
+
throw new HTTPException7(409, { message: error.message });
|
|
4127
4193
|
}
|
|
4128
4194
|
throw error;
|
|
4129
4195
|
}
|
|
4130
4196
|
}
|
|
4131
4197
|
async function promoteSetupAppendChange(deps, grant, rig, change) {
|
|
4132
4198
|
if (change.kind !== "setup_append") {
|
|
4133
|
-
throw new
|
|
4199
|
+
throw new HTTPException7(422, {
|
|
4134
4200
|
message: "only setup_append changes auto-promote through this path"
|
|
4135
4201
|
});
|
|
4136
4202
|
}
|
|
4137
4203
|
if (change.status !== "proposed" && change.status !== "verifying") {
|
|
4138
|
-
throw new
|
|
4204
|
+
throw new HTTPException7(409, { message: `rig change is ${change.status}; cannot promote` });
|
|
4139
4205
|
}
|
|
4140
4206
|
if (!change.baseVersionId) {
|
|
4141
|
-
throw new
|
|
4207
|
+
throw new HTTPException7(422, { message: "rig change has no base version" });
|
|
4142
4208
|
}
|
|
4143
4209
|
const base = await getRigVersion(deps.db, grant.workspaceId, rig.id, change.baseVersionId);
|
|
4144
4210
|
if (!base) {
|
|
4145
|
-
throw new
|
|
4211
|
+
throw new HTTPException7(404, { message: "base rig version not found" });
|
|
4146
4212
|
}
|
|
4147
4213
|
const payload = change.payload;
|
|
4148
4214
|
if (typeof payload.command !== "string" || !payload.command.trim()) {
|
|
4149
|
-
throw new
|
|
4215
|
+
throw new HTTPException7(422, { message: "setup_append change is missing command" });
|
|
4150
4216
|
}
|
|
4151
4217
|
const nextDefinition = {
|
|
4152
4218
|
image: base.image,
|
|
@@ -4184,22 +4250,22 @@ async function promoteSetupAppendChange(deps, grant, rig, change) {
|
|
|
4184
4250
|
}
|
|
4185
4251
|
async function promoteVerifiedDefinitionEditChangeForApi(deps, grant, rig, change) {
|
|
4186
4252
|
if (change.kind !== "definition_edit") {
|
|
4187
|
-
throw new
|
|
4253
|
+
throw new HTTPException7(422, { message: "only definition_edit changes use explicit promote" });
|
|
4188
4254
|
}
|
|
4189
4255
|
if (change.status !== "proposed") {
|
|
4190
|
-
throw new
|
|
4256
|
+
throw new HTTPException7(409, { message: `rig change is ${change.status}; cannot promote` });
|
|
4191
4257
|
}
|
|
4192
4258
|
if (change.verification?.passed !== true) {
|
|
4193
|
-
throw new
|
|
4259
|
+
throw new HTTPException7(422, {
|
|
4194
4260
|
message: "definition_edit change must pass verification before promote"
|
|
4195
4261
|
});
|
|
4196
4262
|
}
|
|
4197
4263
|
if (!change.baseVersionId) {
|
|
4198
|
-
throw new
|
|
4264
|
+
throw new HTTPException7(422, { message: "rig change has no base version" });
|
|
4199
4265
|
}
|
|
4200
4266
|
const base = await getRigVersion(deps.db, grant.workspaceId, rig.id, change.baseVersionId);
|
|
4201
4267
|
if (!base) {
|
|
4202
|
-
throw new
|
|
4268
|
+
throw new HTTPException7(404, { message: "base rig version not found" });
|
|
4203
4269
|
}
|
|
4204
4270
|
const payload = change.payload;
|
|
4205
4271
|
const nextDefinition = {
|
|
@@ -4238,7 +4304,7 @@ async function promoteVerifiedDefinitionEditChangeForApi(deps, grant, rig, chang
|
|
|
4238
4304
|
}
|
|
4239
4305
|
async function createRigVersionForApi(deps, grant, rig, payload) {
|
|
4240
4306
|
if (!rig.activeVersion) {
|
|
4241
|
-
throw new
|
|
4307
|
+
throw new HTTPException7(422, { message: "rig has no active version" });
|
|
4242
4308
|
}
|
|
4243
4309
|
assertUniqueCheckNames(payload.checks);
|
|
4244
4310
|
await assertVariableSetsExist(
|
|
@@ -4594,13 +4660,13 @@ import {
|
|
|
4594
4660
|
stableJson as stableJson3
|
|
4595
4661
|
} from "@opengeni/contracts";
|
|
4596
4662
|
import { areGitHubRepositoriesAllowedForWorkspace, requireFile } from "@opengeni/db";
|
|
4597
|
-
import { HTTPException as
|
|
4663
|
+
import { HTTPException as HTTPException8 } from "hono/http-exception";
|
|
4598
4664
|
function validateToolRefs(tools, settings) {
|
|
4599
4665
|
const mcpServerIds = new Set(settings.mcpServers.map((server) => server.id));
|
|
4600
4666
|
const out = [];
|
|
4601
4667
|
for (const tool of tools) {
|
|
4602
4668
|
if (tool.kind !== "mcp") {
|
|
4603
|
-
throw new
|
|
4669
|
+
throw new HTTPException8(422, {
|
|
4604
4670
|
message: `unsupported tool kind: ${tool.kind}`
|
|
4605
4671
|
});
|
|
4606
4672
|
}
|
|
@@ -4609,7 +4675,7 @@ function validateToolRefs(tools, settings) {
|
|
|
4609
4675
|
if (optional) {
|
|
4610
4676
|
continue;
|
|
4611
4677
|
}
|
|
4612
|
-
throw new
|
|
4678
|
+
throw new HTTPException8(422, { message: `unknown MCP server id: ${tool.id}` });
|
|
4613
4679
|
}
|
|
4614
4680
|
out.push(
|
|
4615
4681
|
optional ? { kind: "mcp", id: tool.id, optional: true } : { kind: "mcp", id: tool.id }
|
|
@@ -4632,7 +4698,7 @@ function assertToolRefsSubset(requested, allowed, message = "requested tools exc
|
|
|
4632
4698
|
const allowedIds = new Set(allowed.map((tool) => `${tool.kind}:${tool.id}`));
|
|
4633
4699
|
const widened = requested.find((tool) => !allowedIds.has(`${tool.kind}:${tool.id}`));
|
|
4634
4700
|
if (widened) {
|
|
4635
|
-
throw new
|
|
4701
|
+
throw new HTTPException8(403, { message: `${message}: ${widened.id}` });
|
|
4636
4702
|
}
|
|
4637
4703
|
}
|
|
4638
4704
|
function validateToolRefsForSessionPolicy(input) {
|
|
@@ -4661,7 +4727,7 @@ function normalizeResources(resources) {
|
|
|
4661
4727
|
try {
|
|
4662
4728
|
normalizedUri = normalizeRepositoryTransportUri(resource.uri);
|
|
4663
4729
|
} catch (error) {
|
|
4664
|
-
throw new
|
|
4730
|
+
throw new HTTPException8(422, {
|
|
4665
4731
|
message: error instanceof Error ? error.message : "invalid repository URI"
|
|
4666
4732
|
});
|
|
4667
4733
|
}
|
|
@@ -4670,14 +4736,14 @@ function normalizeResources(resources) {
|
|
|
4670
4736
|
);
|
|
4671
4737
|
const credentialBindingId = gitCredentialBindingIdForRepository(resource, credentialProvider);
|
|
4672
4738
|
if ((resource.credentialBindingId || resource.connectionId || resource.access) && !credentialProvider) {
|
|
4673
|
-
throw new
|
|
4739
|
+
throw new HTTPException8(422, {
|
|
4674
4740
|
message: "repository credential bindings and access intent require a Git provider"
|
|
4675
4741
|
});
|
|
4676
4742
|
}
|
|
4677
4743
|
if (credentialProvider && credentialBindingId) {
|
|
4678
4744
|
const boundProvider = credentialBindingProviders.get(credentialBindingId);
|
|
4679
4745
|
if (boundProvider && boundProvider !== credentialProvider) {
|
|
4680
|
-
throw new
|
|
4746
|
+
throw new HTTPException8(422, {
|
|
4681
4747
|
message: `credential binding ${credentialBindingId} is assigned to multiple Git providers`
|
|
4682
4748
|
});
|
|
4683
4749
|
}
|
|
@@ -4704,7 +4770,7 @@ function normalizeResources(resources) {
|
|
|
4704
4770
|
const mountCollisionKey = normalized.mountPath ? resourceMountPathCollisionKey(normalized.mountPath) : void 0;
|
|
4705
4771
|
const mounted = mountCollisionKey ? mountPaths.get(mountCollisionKey) : void 0;
|
|
4706
4772
|
if (mounted && mounted !== key) {
|
|
4707
|
-
throw new
|
|
4773
|
+
throw new HTTPException8(422, {
|
|
4708
4774
|
message: `duplicate resource mount path: ${normalized.mountPath}`
|
|
4709
4775
|
});
|
|
4710
4776
|
}
|
|
@@ -4714,7 +4780,7 @@ function normalizeResources(resources) {
|
|
|
4714
4780
|
const identity = resourceIdentityKey(normalized);
|
|
4715
4781
|
const seenIdentity = identities.get(identity);
|
|
4716
4782
|
if (seenIdentity && seenIdentity !== key) {
|
|
4717
|
-
throw new
|
|
4783
|
+
throw new HTTPException8(422, {
|
|
4718
4784
|
message: `duplicate resource with different settings: ${identity}`
|
|
4719
4785
|
});
|
|
4720
4786
|
}
|
|
@@ -4731,7 +4797,7 @@ function mergeResourceRefs(existing, additions) {
|
|
|
4731
4797
|
return mergeContractResourceRefs(existing, additions, { rejectConflicts: true });
|
|
4732
4798
|
} catch (error) {
|
|
4733
4799
|
if (error instanceof ResourceRefConflictError) {
|
|
4734
|
-
throw new
|
|
4800
|
+
throw new HTTPException8(422, { message: error.message });
|
|
4735
4801
|
}
|
|
4736
4802
|
throw error;
|
|
4737
4803
|
}
|
|
@@ -4746,7 +4812,7 @@ function validateGitHubRepositorySelectionShapes(resources) {
|
|
|
4746
4812
|
function validateGitHubRepositorySelectionShape(resources) {
|
|
4747
4813
|
const installationIds = validateGitHubRepositorySelectionShapes(resources);
|
|
4748
4814
|
if (installationIds.length > 1) {
|
|
4749
|
-
throw new
|
|
4815
|
+
throw new HTTPException8(422, {
|
|
4750
4816
|
message: "GitHub App repository resources must belong to one installation"
|
|
4751
4817
|
});
|
|
4752
4818
|
}
|
|
@@ -4768,7 +4834,7 @@ function gitHubRepositorySelections(resources) {
|
|
|
4768
4834
|
const installationId = positiveInteger(installationRaw);
|
|
4769
4835
|
const repositoryId = positiveInteger(repositoryRaw);
|
|
4770
4836
|
if (!installationId || !repositoryId) {
|
|
4771
|
-
throw new
|
|
4837
|
+
throw new HTTPException8(422, {
|
|
4772
4838
|
message: "GitHub App repository resources require positive github_installation_id and github_repository_id"
|
|
4773
4839
|
});
|
|
4774
4840
|
}
|
|
@@ -4789,14 +4855,14 @@ async function validateGitHubRepositorySelection(db, workspaceId, resources) {
|
|
|
4789
4855
|
installationId,
|
|
4790
4856
|
repositoryIds
|
|
4791
4857
|
)) {
|
|
4792
|
-
throw new
|
|
4858
|
+
throw new HTTPException8(422, {
|
|
4793
4859
|
message: "GitHub App repository resources must be authorized for a GitHub App installation linked to this workspace"
|
|
4794
4860
|
});
|
|
4795
4861
|
}
|
|
4796
4862
|
}
|
|
4797
4863
|
}
|
|
4798
4864
|
function isAuthoritativeGitHubRepositorySelectionError(error) {
|
|
4799
|
-
return error instanceof
|
|
4865
|
+
return error instanceof HTTPException8 && error.status === 422;
|
|
4800
4866
|
}
|
|
4801
4867
|
async function validateFileResources(db, workspaceId, resources) {
|
|
4802
4868
|
const fileIds = /* @__PURE__ */ new Set();
|
|
@@ -4805,15 +4871,15 @@ async function validateFileResources(db, workspaceId, resources) {
|
|
|
4805
4871
|
continue;
|
|
4806
4872
|
}
|
|
4807
4873
|
if (fileIds.has(resource.fileId)) {
|
|
4808
|
-
throw new
|
|
4874
|
+
throw new HTTPException8(422, { message: `duplicate file resource: ${resource.fileId}` });
|
|
4809
4875
|
}
|
|
4810
4876
|
fileIds.add(resource.fileId);
|
|
4811
4877
|
const file = await requireFile(db, workspaceId, resource.fileId).catch(() => null);
|
|
4812
4878
|
if (!file) {
|
|
4813
|
-
throw new
|
|
4879
|
+
throw new HTTPException8(422, { message: `unknown file resource: ${resource.fileId}` });
|
|
4814
4880
|
}
|
|
4815
4881
|
if (file.status !== "ready") {
|
|
4816
|
-
throw new
|
|
4882
|
+
throw new HTTPException8(422, {
|
|
4817
4883
|
message: `file resource ${resource.fileId} is ${file.status}`
|
|
4818
4884
|
});
|
|
4819
4885
|
}
|
|
@@ -4824,7 +4890,7 @@ function normalizeMountPath(path) {
|
|
|
4824
4890
|
return normalizeResourceMountPath(path);
|
|
4825
4891
|
} catch (error) {
|
|
4826
4892
|
if (!(error instanceof ResourceMountPathError)) throw error;
|
|
4827
|
-
throw new
|
|
4893
|
+
throw new HTTPException8(422, { message: `invalid resource mount path: ${path}` });
|
|
4828
4894
|
}
|
|
4829
4895
|
}
|
|
4830
4896
|
function positiveInteger(value) {
|
|
@@ -4976,27 +5042,31 @@ import {
|
|
|
4976
5042
|
getRig as getRig4,
|
|
4977
5043
|
getScheduledTask,
|
|
4978
5044
|
getScheduledTaskPersonalConnectionDelegations,
|
|
5045
|
+
getSessionTurnXaiProviderAccountAuthoritySnapshot as getSessionTurnXaiProviderAccountAuthoritySnapshot2,
|
|
4979
5046
|
getSession as getSession3,
|
|
4980
5047
|
requireWorkspace as requireWorkspace2,
|
|
4981
5048
|
scopedKnowledgeScopeKey,
|
|
4982
|
-
updateScheduledTask
|
|
5049
|
+
updateScheduledTask,
|
|
5050
|
+
resolveXaiProviderAccountAuthoritySnapshotForAcceptance
|
|
4983
5051
|
} from "@opengeni/db";
|
|
4984
|
-
import { HTTPException as
|
|
5052
|
+
import { HTTPException as HTTPException12 } from "hono/http-exception";
|
|
4985
5053
|
|
|
4986
5054
|
// src/domain/sessions.ts
|
|
4987
5055
|
import { CODEX_MODEL_ID_PREFIX, isCodexBilledModel } from "@opengeni/codex";
|
|
4988
5056
|
import {
|
|
4989
5057
|
canonicalizeConfiguredModelId,
|
|
4990
5058
|
configuredAllowedModels,
|
|
5059
|
+
resolveFirstPartyMcpToolPolicy,
|
|
4991
5060
|
policyProviderIdForModel,
|
|
4992
5061
|
resolveTurnExecutionPolicyV1,
|
|
4993
|
-
WORKSPACE_GATEWAY_MODEL_ID_PREFIX
|
|
5062
|
+
WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
|
|
5063
|
+
XAI_SUBSCRIPTION_MODEL_ID_PREFIX
|
|
4994
5064
|
} from "@opengeni/config";
|
|
4995
5065
|
import {
|
|
4996
5066
|
CreateSessionRequest,
|
|
4997
5067
|
DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
|
|
4998
5068
|
DEFAULT_FIRST_PARTY_MCP_TOOLS,
|
|
4999
|
-
FIRST_PARTY_MCP_TOOL_NAMES,
|
|
5069
|
+
FIRST_PARTY_MCP_TOOL_NAMES as FIRST_PARTY_MCP_TOOL_NAMES2,
|
|
5000
5070
|
OPENGENI_SLACK_BOT_SESSION_METADATA_KEY as OPENGENI_SLACK_BOT_SESSION_METADATA_KEY2,
|
|
5001
5071
|
SessionSpawnDenial,
|
|
5002
5072
|
ServiceTurnInitiator,
|
|
@@ -5013,6 +5083,7 @@ import {
|
|
|
5013
5083
|
encryptVariableSetValue as encryptVariableSetValue2,
|
|
5014
5084
|
getAnySessionInGroup,
|
|
5015
5085
|
getEnrollment as getEnrollment3,
|
|
5086
|
+
getChannel,
|
|
5016
5087
|
getRig as getRig3,
|
|
5017
5088
|
getWorkspaceDefaultRigId,
|
|
5018
5089
|
listDistinctVariableSetIdsInGroup,
|
|
@@ -5024,6 +5095,7 @@ import {
|
|
|
5024
5095
|
getWorkspaceControlEvent,
|
|
5025
5096
|
getSessionLineage,
|
|
5026
5097
|
getSessionTurn,
|
|
5098
|
+
getSessionTurnXaiProviderAccountAuthoritySnapshot,
|
|
5027
5099
|
getWorkspaceModelPolicy,
|
|
5028
5100
|
initializeSessionStartAtomically,
|
|
5029
5101
|
listSessionTurns,
|
|
@@ -5045,7 +5117,7 @@ import {
|
|
|
5045
5117
|
publishDurableSessionEvents,
|
|
5046
5118
|
publishDurableWorkspaceControlEvent
|
|
5047
5119
|
} from "@opengeni/events";
|
|
5048
|
-
import { HTTPException as
|
|
5120
|
+
import { HTTPException as HTTPException11 } from "hono/http-exception";
|
|
5049
5121
|
|
|
5050
5122
|
// src/domain/timeline-annotations.ts
|
|
5051
5123
|
import {
|
|
@@ -5054,11 +5126,11 @@ import {
|
|
|
5054
5126
|
numberTimelineAnnotations
|
|
5055
5127
|
} from "@opengeni/contracts";
|
|
5056
5128
|
import { getSessionEvent } from "@opengeni/db";
|
|
5057
|
-
import { HTTPException as
|
|
5129
|
+
import { HTTPException as HTTPException9 } from "hono/http-exception";
|
|
5058
5130
|
var SOURCE_CONTEXT_BYTES = 160;
|
|
5059
5131
|
var ANSI_SEQUENCE = new RegExp("\\u001B\\[[0-?]*[ -/]*[@-~]", "g");
|
|
5060
5132
|
function invalidAnnotationSource() {
|
|
5061
|
-
throw new
|
|
5133
|
+
throw new HTTPException9(422, { message: "Invalid timeline annotation source" });
|
|
5062
5134
|
}
|
|
5063
5135
|
function safeJson(value) {
|
|
5064
5136
|
try {
|
|
@@ -5208,7 +5280,7 @@ import {
|
|
|
5208
5280
|
import {
|
|
5209
5281
|
getConnectionMetadata as getConnectionMetadata3
|
|
5210
5282
|
} from "@opengeni/db";
|
|
5211
|
-
import { HTTPException as
|
|
5283
|
+
import { HTTPException as HTTPException10 } from "hono/http-exception";
|
|
5212
5284
|
function openGeniSlackBotMetadata(metadata) {
|
|
5213
5285
|
const parsed = OpenGeniSlackBotConnectionMetadata.safeParse(metadata);
|
|
5214
5286
|
return parsed.success ? parsed.data : null;
|
|
@@ -5227,12 +5299,12 @@ function hasReservedOpenGeniSlackBotSessionMetadata(metadata) {
|
|
|
5227
5299
|
async function requireOpenGeniSlackBotConnection(db, workspaceId, connectionId) {
|
|
5228
5300
|
const connection = await getConnectionMetadata3(db, workspaceId, connectionId, null);
|
|
5229
5301
|
if (!connection || !isOpenGeniSlackBotConnection(connection)) {
|
|
5230
|
-
throw new
|
|
5302
|
+
throw new HTTPException10(422, {
|
|
5231
5303
|
message: "slackBotConnectionId must reference an OpenGeni Slack bot connection"
|
|
5232
5304
|
});
|
|
5233
5305
|
}
|
|
5234
5306
|
if (connection.status !== "active") {
|
|
5235
|
-
throw new
|
|
5307
|
+
throw new HTTPException10(422, {
|
|
5236
5308
|
message: `OpenGeni Slack bot connection is not active (${connection.status})`
|
|
5237
5309
|
});
|
|
5238
5310
|
}
|
|
@@ -5269,10 +5341,14 @@ var SessionSpawnDeniedError = class extends Error {
|
|
|
5269
5341
|
this.denial = denial;
|
|
5270
5342
|
}
|
|
5271
5343
|
};
|
|
5272
|
-
function resolveFirstPartyMcpToolsForCreate(requested, parentStored
|
|
5344
|
+
function resolveFirstPartyMcpToolsForCreate(requested, parentStored, policy = {
|
|
5345
|
+
default: DEFAULT_FIRST_PARTY_MCP_TOOLS,
|
|
5346
|
+
allowed: FIRST_PARTY_MCP_TOOL_NAMES2
|
|
5347
|
+
}) {
|
|
5273
5348
|
if (requested !== void 0) return [...requested];
|
|
5274
|
-
|
|
5275
|
-
|
|
5349
|
+
const allowed = new Set(policy.allowed);
|
|
5350
|
+
const inherited = parentStored === void 0 ? policy.default : parentStored ?? policy.default;
|
|
5351
|
+
return [...inherited].filter((tool) => allowed.has(tool));
|
|
5276
5352
|
}
|
|
5277
5353
|
function sessionSpawnDeniedMessage(denial) {
|
|
5278
5354
|
if (denial.code === "nested_agent_depth_override_forbidden") {
|
|
@@ -5292,7 +5368,7 @@ function sessionSpawnDenialEnvelope(error) {
|
|
|
5292
5368
|
function serviceInitiatorForGrant(grant) {
|
|
5293
5369
|
if (!grant.serviceInitiator) {
|
|
5294
5370
|
if (grant.serviceInitiatorContext) {
|
|
5295
|
-
throw new
|
|
5371
|
+
throw new HTTPException11(403, {
|
|
5296
5372
|
message: "service initiator context requires a signed service initiator"
|
|
5297
5373
|
});
|
|
5298
5374
|
}
|
|
@@ -5300,13 +5376,13 @@ function serviceInitiatorForGrant(grant) {
|
|
|
5300
5376
|
}
|
|
5301
5377
|
const initiator = ServiceTurnInitiator.safeParse(grant.serviceInitiator);
|
|
5302
5378
|
if (!initiator.success) {
|
|
5303
|
-
throw new
|
|
5379
|
+
throw new HTTPException11(403, {
|
|
5304
5380
|
message: "a delegated command initiator must be a bounded service principal"
|
|
5305
5381
|
});
|
|
5306
5382
|
}
|
|
5307
5383
|
const context = ServiceTurnInitiatorContext.safeParse(grant.serviceInitiatorContext ?? {});
|
|
5308
5384
|
if (!context.success) {
|
|
5309
|
-
throw new
|
|
5385
|
+
throw new HTTPException11(403, {
|
|
5310
5386
|
message: "delegated service initiator context is invalid or reserved"
|
|
5311
5387
|
});
|
|
5312
5388
|
}
|
|
@@ -5314,7 +5390,7 @@ function serviceInitiatorForGrant(grant) {
|
|
|
5314
5390
|
const callerAttemptId = grant.metadata?.["attemptId"];
|
|
5315
5391
|
const callerExecutionGeneration = grant.metadata?.["executionGeneration"];
|
|
5316
5392
|
if (callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0) {
|
|
5317
|
-
throw new
|
|
5393
|
+
throw new HTTPException11(403, {
|
|
5318
5394
|
message: "a service initiator cannot replace an exact agent-attempt initiator"
|
|
5319
5395
|
});
|
|
5320
5396
|
}
|
|
@@ -5332,7 +5408,7 @@ function creationInitiatorForGrant(grant) {
|
|
|
5332
5408
|
const hasCallerTurnClaim = callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0;
|
|
5333
5409
|
if (hasCallerTurnClaim) {
|
|
5334
5410
|
if (typeof callerSessionId !== "string" || typeof callerTurnId !== "string" || typeof callerAttemptId !== "string" || typeof callerExecutionGeneration !== "number" || !Number.isSafeInteger(callerExecutionGeneration) || callerExecutionGeneration < 1) {
|
|
5335
|
-
throw new
|
|
5411
|
+
throw new HTTPException11(403, {
|
|
5336
5412
|
message: "caller attempt claims are incomplete"
|
|
5337
5413
|
});
|
|
5338
5414
|
}
|
|
@@ -5363,31 +5439,31 @@ function normalizedSessionMcpCredentialHeaders(headers) {
|
|
|
5363
5439
|
}
|
|
5364
5440
|
const entries = Object.entries(headers).map(([name, value]) => [name.trim(), value]).filter(([name]) => name.length > 0);
|
|
5365
5441
|
if (entries.length > maxSessionMcpCredentialHeaders) {
|
|
5366
|
-
throw new
|
|
5442
|
+
throw new HTTPException11(422, {
|
|
5367
5443
|
message: `a session MCP server supports at most ${maxSessionMcpCredentialHeaders} credential headers`
|
|
5368
5444
|
});
|
|
5369
5445
|
}
|
|
5370
5446
|
const seen = /* @__PURE__ */ new Set();
|
|
5371
5447
|
for (const [name, value] of entries) {
|
|
5372
5448
|
if (!sessionMcpCredentialHeaderName.test(name)) {
|
|
5373
|
-
throw new
|
|
5449
|
+
throw new HTTPException11(422, {
|
|
5374
5450
|
message: `invalid credential header name: ${name}`
|
|
5375
5451
|
});
|
|
5376
5452
|
}
|
|
5377
5453
|
const lower = name.toLowerCase();
|
|
5378
5454
|
if (seen.has(lower)) {
|
|
5379
|
-
throw new
|
|
5455
|
+
throw new HTTPException11(422, {
|
|
5380
5456
|
message: `duplicate credential header name: ${name}`
|
|
5381
5457
|
});
|
|
5382
5458
|
}
|
|
5383
5459
|
seen.add(lower);
|
|
5384
5460
|
if (value.length === 0 || value.length > maxSessionMcpCredentialHeaderValueLength) {
|
|
5385
|
-
throw new
|
|
5461
|
+
throw new HTTPException11(422, {
|
|
5386
5462
|
message: `credential header ${name} must be 1-${maxSessionMcpCredentialHeaderValueLength} characters`
|
|
5387
5463
|
});
|
|
5388
5464
|
}
|
|
5389
5465
|
if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
|
|
5390
|
-
throw new
|
|
5466
|
+
throw new HTTPException11(422, {
|
|
5391
5467
|
message: `credential header ${name} contains forbidden control characters`
|
|
5392
5468
|
});
|
|
5393
5469
|
}
|
|
@@ -5454,13 +5530,13 @@ function validateSessionMcpServersForCreate(settings, grant, servers) {
|
|
|
5454
5530
|
const metadata = [];
|
|
5455
5531
|
for (const server of servers) {
|
|
5456
5532
|
if (seenIds.has(server.id)) {
|
|
5457
|
-
throw new
|
|
5533
|
+
throw new HTTPException11(422, {
|
|
5458
5534
|
message: `duplicate session MCP server id: ${server.id}`
|
|
5459
5535
|
});
|
|
5460
5536
|
}
|
|
5461
5537
|
seenIds.add(server.id);
|
|
5462
5538
|
if (reservedSessionMcpServerIds.has(server.id) || existingIds.has(server.id)) {
|
|
5463
|
-
throw new
|
|
5539
|
+
throw new HTTPException11(422, {
|
|
5464
5540
|
message: `MCP server id already exists: ${server.id}`
|
|
5465
5541
|
});
|
|
5466
5542
|
}
|
|
@@ -5502,13 +5578,13 @@ function validateInheritedSessionMcpServersForCreate(servers) {
|
|
|
5502
5578
|
const seenIds = /* @__PURE__ */ new Set();
|
|
5503
5579
|
for (const server of servers) {
|
|
5504
5580
|
if (seenIds.has(server.id)) {
|
|
5505
|
-
throw new
|
|
5581
|
+
throw new HTTPException11(422, {
|
|
5506
5582
|
message: `duplicate inherited session MCP server id: ${server.id}`
|
|
5507
5583
|
});
|
|
5508
5584
|
}
|
|
5509
5585
|
seenIds.add(server.id);
|
|
5510
5586
|
if (reservedSessionMcpServerIds.has(server.id)) {
|
|
5511
|
-
throw new
|
|
5587
|
+
throw new HTTPException11(422, {
|
|
5512
5588
|
message: `reserved inherited session MCP server id: ${server.id}`
|
|
5513
5589
|
});
|
|
5514
5590
|
}
|
|
@@ -5540,13 +5616,13 @@ function validateSessionMcpCredentialUpdates(input) {
|
|
|
5540
5616
|
const seenIds = /* @__PURE__ */ new Set();
|
|
5541
5617
|
const encryptedUpdates = input.updates.map((update) => {
|
|
5542
5618
|
if (seenIds.has(update.id)) {
|
|
5543
|
-
throw new
|
|
5619
|
+
throw new HTTPException11(422, {
|
|
5544
5620
|
message: `duplicate session MCP credential update id: ${update.id}`
|
|
5545
5621
|
});
|
|
5546
5622
|
}
|
|
5547
5623
|
seenIds.add(update.id);
|
|
5548
5624
|
if (!knownIds.has(update.id)) {
|
|
5549
|
-
throw new
|
|
5625
|
+
throw new HTTPException11(422, {
|
|
5550
5626
|
message: `unknown session MCP server id: ${update.id}`
|
|
5551
5627
|
});
|
|
5552
5628
|
}
|
|
@@ -5590,6 +5666,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
5590
5666
|
variableSetId: input.variableSet?.id ?? null,
|
|
5591
5667
|
rigId: input.rigId ?? null,
|
|
5592
5668
|
rigVersionId: input.rigVersionId ?? null,
|
|
5669
|
+
channelId: input.channelId ?? null,
|
|
5593
5670
|
firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
|
|
5594
5671
|
firstPartyMcpTools: input.firstPartyMcpTools,
|
|
5595
5672
|
instructions: input.instructions ?? null,
|
|
@@ -5600,9 +5677,13 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
5600
5677
|
...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
|
|
5601
5678
|
mcpServers: input.mcpServers ?? [],
|
|
5602
5679
|
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
5680
|
+
...input.xaiProviderAccountAuthoritySnapshot ? {
|
|
5681
|
+
initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot
|
|
5682
|
+
} : {},
|
|
5603
5683
|
maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
|
|
5604
5684
|
allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
|
|
5605
|
-
subjectId: input.subjectId ?? null
|
|
5685
|
+
subjectId: input.subjectId ?? null,
|
|
5686
|
+
...input.beforeCreateCommit ? { beforeCreateCommit: input.beforeCreateCommit } : {}
|
|
5606
5687
|
});
|
|
5607
5688
|
if (keyedResult.denied) {
|
|
5608
5689
|
throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(keyedResult.denial));
|
|
@@ -5649,6 +5730,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
5649
5730
|
variableSetId: input.variableSet?.id ?? null,
|
|
5650
5731
|
rigId: input.rigId ?? null,
|
|
5651
5732
|
rigVersionId: input.rigVersionId ?? null,
|
|
5733
|
+
channelId: input.channelId ?? null,
|
|
5652
5734
|
firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
|
|
5653
5735
|
firstPartyMcpTools: input.firstPartyMcpTools,
|
|
5654
5736
|
instructions: input.instructions ?? null,
|
|
@@ -5658,9 +5740,13 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
5658
5740
|
...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
|
|
5659
5741
|
mcpServers: input.mcpServers ?? [],
|
|
5660
5742
|
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
5743
|
+
...input.xaiProviderAccountAuthoritySnapshot ? {
|
|
5744
|
+
initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot
|
|
5745
|
+
} : {},
|
|
5661
5746
|
maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
|
|
5662
5747
|
allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
|
|
5663
|
-
subjectId: input.subjectId ?? null
|
|
5748
|
+
subjectId: input.subjectId ?? null,
|
|
5749
|
+
...input.beforeCreateCommit ? { beforeCreateCommit: input.beforeCreateCommit } : {}
|
|
5664
5750
|
});
|
|
5665
5751
|
} catch (error) {
|
|
5666
5752
|
if (error instanceof SessionSpawnDeniedDbError) {
|
|
@@ -5682,7 +5768,7 @@ async function createAndStartSession(input) {
|
|
|
5682
5768
|
async function finishStartSession(input, session) {
|
|
5683
5769
|
if (input.seedTargetSandbox) {
|
|
5684
5770
|
if (session.sandboxBackend === "none") {
|
|
5685
|
-
throw new
|
|
5771
|
+
throw new HTTPException11(422, {
|
|
5686
5772
|
message: "cannot target a machine for a session with no sandbox (backend: none)"
|
|
5687
5773
|
});
|
|
5688
5774
|
}
|
|
@@ -5706,7 +5792,7 @@ async function finishStartSession(input, session) {
|
|
|
5706
5792
|
input.seedTargetSandbox.workingDir ?? null
|
|
5707
5793
|
);
|
|
5708
5794
|
if (!seeded.swapped) {
|
|
5709
|
-
throw new
|
|
5795
|
+
throw new HTTPException11(422, {
|
|
5710
5796
|
message: `cannot target sandbox ${input.seedTargetSandbox.sandboxId}: ${seeded.reason ?? "target is not attachable"}`
|
|
5711
5797
|
});
|
|
5712
5798
|
}
|
|
@@ -5765,10 +5851,13 @@ function canonicalConfiguredModel(settings, model) {
|
|
|
5765
5851
|
if (settings.codexSubscriptionEnabled && canonicalModel.startsWith(CODEX_MODEL_ID_PREFIX)) {
|
|
5766
5852
|
return canonicalModel;
|
|
5767
5853
|
}
|
|
5854
|
+
if (settings.supergrokSubscriptionEnabled && canonicalModel.startsWith(XAI_SUBSCRIPTION_MODEL_ID_PREFIX)) {
|
|
5855
|
+
return canonicalModel;
|
|
5856
|
+
}
|
|
5768
5857
|
if (canonicalModel.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
|
|
5769
5858
|
return canonicalModel;
|
|
5770
5859
|
}
|
|
5771
|
-
throw new
|
|
5860
|
+
throw new HTTPException11(422, { message: `model is not available: ${model}` });
|
|
5772
5861
|
}
|
|
5773
5862
|
function assertConfiguredModel(settings, model) {
|
|
5774
5863
|
canonicalConfiguredModel(settings, model);
|
|
@@ -5809,7 +5898,7 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
|
|
|
5809
5898
|
modelId: canonicalModel
|
|
5810
5899
|
});
|
|
5811
5900
|
if (!verdict.allowed) {
|
|
5812
|
-
throw new
|
|
5901
|
+
throw new HTTPException11(422, {
|
|
5813
5902
|
message: verdict.reason === "provider" ? `model "${canonicalModel}" is not allowed by this workspace's model policy: provider "${providerId}" is not in the allowed providers` : `model "${canonicalModel}" is not allowed by this workspace's model policy`
|
|
5814
5903
|
});
|
|
5815
5904
|
}
|
|
@@ -5817,10 +5906,10 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
|
|
|
5817
5906
|
async function requireQueuedTurnForApi(db, workspaceId, sessionId, turnId) {
|
|
5818
5907
|
const turn = await getSessionTurn(db, workspaceId, turnId);
|
|
5819
5908
|
if (!turn || turn.sessionId !== sessionId) {
|
|
5820
|
-
throw new
|
|
5909
|
+
throw new HTTPException11(404, { message: "session turn not found" });
|
|
5821
5910
|
}
|
|
5822
5911
|
if (turn.status !== "queued") {
|
|
5823
|
-
throw new
|
|
5912
|
+
throw new HTTPException11(409, {
|
|
5824
5913
|
message: `turn is ${turn.status}; only queued turns can be changed`
|
|
5825
5914
|
});
|
|
5826
5915
|
}
|
|
@@ -5844,7 +5933,7 @@ async function postUserMessageTurn(input) {
|
|
|
5844
5933
|
assertSessionAllowsProductModel(sessionForModelGate, effectiveModelForGate);
|
|
5845
5934
|
} catch (error) {
|
|
5846
5935
|
if (error instanceof CodexCompactionV2ProviderLockedError) {
|
|
5847
|
-
throw new
|
|
5936
|
+
throw new HTTPException11(422, { message: error.message, cause: error });
|
|
5848
5937
|
}
|
|
5849
5938
|
throw error;
|
|
5850
5939
|
}
|
|
@@ -5893,13 +5982,13 @@ async function postUserMessageTurn(input) {
|
|
|
5893
5982
|
);
|
|
5894
5983
|
} catch (error) {
|
|
5895
5984
|
if (error instanceof QueueCommandConflictError || error instanceof SessionControlConflictError) {
|
|
5896
|
-
throw new
|
|
5985
|
+
throw new HTTPException11(409, { message: error.message });
|
|
5897
5986
|
}
|
|
5898
5987
|
if (error instanceof Error && error.message.includes("cancelled")) {
|
|
5899
|
-
throw new
|
|
5988
|
+
throw new HTTPException11(409, { message: error.message });
|
|
5900
5989
|
}
|
|
5901
5990
|
if (error instanceof Error && error.message.startsWith("Unknown session MCP server")) {
|
|
5902
|
-
throw new
|
|
5991
|
+
throw new HTTPException11(422, { message: error.message });
|
|
5903
5992
|
}
|
|
5904
5993
|
throw error;
|
|
5905
5994
|
}
|
|
@@ -5966,7 +6055,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
5966
6055
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
5967
6056
|
const payload = CreateSessionRequest.parse(rawPayload);
|
|
5968
6057
|
if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
|
|
5969
|
-
throw new
|
|
6058
|
+
throw new HTTPException11(422, {
|
|
5970
6059
|
message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY2} is reserved for scheduler routing`
|
|
5971
6060
|
});
|
|
5972
6061
|
}
|
|
@@ -5982,30 +6071,45 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
5982
6071
|
}
|
|
5983
6072
|
const parentSessionId = typeof grant.metadata?.["sessionId"] === "string" ? grant.metadata["sessionId"] : null;
|
|
5984
6073
|
if (parentSessionId) {
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
6074
|
+
try {
|
|
6075
|
+
await requireSessionAuthorization(deps, grant, {
|
|
6076
|
+
sessionId: parentSessionId,
|
|
6077
|
+
operation: "session.child.create",
|
|
6078
|
+
surface: "core"
|
|
6079
|
+
});
|
|
6080
|
+
} catch (error) {
|
|
6081
|
+
if (error instanceof SessionAuthorizationDeniedError) {
|
|
6082
|
+
throw new HTTPException11(403, { message: error.message, cause: error });
|
|
6083
|
+
}
|
|
6084
|
+
throw error;
|
|
6085
|
+
}
|
|
5990
6086
|
}
|
|
5991
6087
|
const parentSession = parentSessionId ? await getSession2(db, workspaceId, parentSessionId) : null;
|
|
5992
6088
|
if (parentSessionId && !parentSession) {
|
|
5993
|
-
throw new
|
|
6089
|
+
throw new HTTPException11(404, {
|
|
5994
6090
|
message: `parent session not found in workspace: ${parentSessionId}`
|
|
5995
6091
|
});
|
|
5996
6092
|
}
|
|
5997
6093
|
const creationInitiator = creationInitiatorForGrant(grant);
|
|
5998
6094
|
const parentCallingTurn = parentSession && creationInitiator.actor ? await getSessionTurn(db, workspaceId, creationInitiator.actor.turnId) : null;
|
|
5999
6095
|
if (creationInitiator.actor && (!parentCallingTurn || parentCallingTurn.sessionId !== parentSession?.id)) {
|
|
6000
|
-
throw new
|
|
6096
|
+
throw new HTTPException11(403, {
|
|
6001
6097
|
message: "caller attempt does not belong to the parent session"
|
|
6002
6098
|
});
|
|
6003
6099
|
}
|
|
6100
|
+
const xaiProviderAccountAuthoritySnapshot = parentSession && creationInitiator.actor ? await getSessionTurnXaiProviderAccountAuthoritySnapshot(
|
|
6101
|
+
db,
|
|
6102
|
+
workspaceId,
|
|
6103
|
+
parentSession.id,
|
|
6104
|
+
creationInitiator.actor.turnId
|
|
6105
|
+
) : void 0;
|
|
6004
6106
|
const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
6005
6107
|
db,
|
|
6006
6108
|
workspaceId,
|
|
6007
6109
|
settings,
|
|
6008
|
-
{
|
|
6110
|
+
{
|
|
6111
|
+
subjectId: grant.subjectId
|
|
6112
|
+
}
|
|
6009
6113
|
);
|
|
6010
6114
|
const sessionMcpServers = hasOwnProperty(rawPayload, "mcpServers") ? validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers) : parentSession ? validateInheritedSessionMcpServersForCreate(
|
|
6011
6115
|
await listSessionMcpServersForChildInheritance(db, workspaceId, parentSession.id)
|
|
@@ -6074,7 +6178,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6074
6178
|
});
|
|
6075
6179
|
await validateGitHubRepositorySelection(db, workspaceId, resources);
|
|
6076
6180
|
if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
6077
|
-
throw new
|
|
6181
|
+
throw new HTTPException11(503, {
|
|
6078
6182
|
message: "object storage is not configured"
|
|
6079
6183
|
});
|
|
6080
6184
|
}
|
|
@@ -6092,7 +6196,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6092
6196
|
const rig = await getRig3(db, workspaceId, requestedRigId);
|
|
6093
6197
|
if (!rig || !rig.activeVersion) {
|
|
6094
6198
|
if (payload.rigId) {
|
|
6095
|
-
throw new
|
|
6199
|
+
throw new HTTPException11(422, {
|
|
6096
6200
|
message: rig ? `rig ${payload.rigId} has no active version to bind` : `unknown rigId: ${payload.rigId}`
|
|
6097
6201
|
});
|
|
6098
6202
|
}
|
|
@@ -6101,6 +6205,16 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6101
6205
|
frozenRigVersionId = rig.activeVersion.id;
|
|
6102
6206
|
}
|
|
6103
6207
|
}
|
|
6208
|
+
let channelId = null;
|
|
6209
|
+
if (payload.channelId) {
|
|
6210
|
+
const channel = await getChannel(db, workspaceId, payload.channelId);
|
|
6211
|
+
if (!channel) {
|
|
6212
|
+
throw new HTTPException11(422, {
|
|
6213
|
+
message: `unknown channelId: ${payload.channelId}`
|
|
6214
|
+
});
|
|
6215
|
+
}
|
|
6216
|
+
channelId = channel.id;
|
|
6217
|
+
}
|
|
6104
6218
|
const inheritedModel = parentCallingTurn?.model ?? parentSession?.model ?? settings.openaiModel;
|
|
6105
6219
|
const model = canonicalConfiguredModel(settings, payload.model ?? inheritedModel);
|
|
6106
6220
|
if (model === null || model === void 0) {
|
|
@@ -6125,7 +6239,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6125
6239
|
if (parentFirstPartyMcpPermissions && payload.firstPartyMcpPermissions?.some(
|
|
6126
6240
|
(permission) => !hasPermission(parentFirstPartyMcpPermissions, permission)
|
|
6127
6241
|
)) {
|
|
6128
|
-
throw new
|
|
6242
|
+
throw new HTTPException11(403, {
|
|
6129
6243
|
message: "child first-party MCP permissions may only narrow the parent session grant"
|
|
6130
6244
|
});
|
|
6131
6245
|
}
|
|
@@ -6133,32 +6247,42 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6133
6247
|
(permission) => hasPermission(grant.permissions, permission)
|
|
6134
6248
|
) : null);
|
|
6135
6249
|
if (firstPartyMcpPermissions && firstPartyMcpPermissions.length === 0) {
|
|
6136
|
-
throw new
|
|
6250
|
+
throw new HTTPException11(422, {
|
|
6137
6251
|
message: "firstPartyMcpPermissions must not be empty; omit it for the default worker permission set"
|
|
6138
6252
|
});
|
|
6139
6253
|
}
|
|
6140
6254
|
for (const permission of firstPartyMcpPermissions ?? []) {
|
|
6141
6255
|
if (!hasPermission(grant.permissions, permission)) {
|
|
6142
|
-
throw new
|
|
6256
|
+
throw new HTTPException11(403, {
|
|
6143
6257
|
message: `cannot grant first-party MCP permission beyond the creating grant: ${permission}`
|
|
6144
6258
|
});
|
|
6145
6259
|
}
|
|
6146
6260
|
}
|
|
6147
6261
|
if (payload.goal && firstPartyMcpPermissions && !firstPartyMcpPermissions.includes("goals:manage")) {
|
|
6148
|
-
throw new
|
|
6262
|
+
throw new HTTPException11(422, {
|
|
6149
6263
|
message: "goal-bearing sessions require goals:manage in the resulting first-party MCP permission set"
|
|
6150
6264
|
});
|
|
6151
6265
|
}
|
|
6266
|
+
const deploymentFirstPartyMcpToolPolicy = resolveFirstPartyMcpToolPolicy(settings);
|
|
6267
|
+
const disallowedFirstPartyMcpTool = payload.firstPartyMcpTools?.find(
|
|
6268
|
+
(tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool)
|
|
6269
|
+
);
|
|
6270
|
+
if (disallowedFirstPartyMcpTool) {
|
|
6271
|
+
throw new HTTPException11(422, {
|
|
6272
|
+
message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`
|
|
6273
|
+
});
|
|
6274
|
+
}
|
|
6152
6275
|
const firstPartyMcpTools = resolveFirstPartyMcpToolsForCreate(
|
|
6153
6276
|
payload.firstPartyMcpTools,
|
|
6154
|
-
parentSession ? parentSession.firstPartyMcpTools : void 0
|
|
6277
|
+
parentSession ? parentSession.firstPartyMcpTools : void 0,
|
|
6278
|
+
deploymentFirstPartyMcpToolPolicy
|
|
6155
6279
|
);
|
|
6156
6280
|
if (payload.goal) {
|
|
6157
|
-
const missingGoalTools = ["goal_update", "goal_complete", "goal_pause"].filter(
|
|
6281
|
+
const missingGoalTools = ["goal_update", "goal_progress", "goal_complete", "goal_pause"].filter(
|
|
6158
6282
|
(name) => !firstPartyMcpTools.includes(name)
|
|
6159
6283
|
);
|
|
6160
6284
|
if (missingGoalTools.length > 0) {
|
|
6161
|
-
throw new
|
|
6285
|
+
throw new HTTPException11(422, {
|
|
6162
6286
|
message: `goal-bearing sessions require first-party MCP tools: ${missingGoalTools.join(", ")}`
|
|
6163
6287
|
});
|
|
6164
6288
|
}
|
|
@@ -6171,7 +6295,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6171
6295
|
const rigVersionMatchesGroup = (memberRigVersionId) => memberRigVersionId === frozenRigVersionId;
|
|
6172
6296
|
if (sandboxChoice === "shared") {
|
|
6173
6297
|
if (!parentSessionId) {
|
|
6174
|
-
throw new
|
|
6298
|
+
throw new HTTPException11(422, {
|
|
6175
6299
|
message: "sandbox:'shared' requires a parent session (spawn from inside a session); use 'new' for a top-level create."
|
|
6176
6300
|
});
|
|
6177
6301
|
}
|
|
@@ -6194,7 +6318,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6194
6318
|
}
|
|
6195
6319
|
if (variableSetMismatch || rigMismatch) {
|
|
6196
6320
|
if (payload.sandbox === "shared") {
|
|
6197
|
-
throw new
|
|
6321
|
+
throw new HTTPException11(422, {
|
|
6198
6322
|
message: variableSetMismatch ? "sandbox:'shared' requires the same variableSet / same environment as the creator's box (the box variable set/environment is fixed at creation); omit sandbox or pass 'new' when attaching a different variableSet/environment." : "sandbox:'shared' requires the same rig as the creator's box (the box's rig setup is fixed at creation); omit sandbox or pass 'new' when binding a different rig."
|
|
6199
6323
|
});
|
|
6200
6324
|
}
|
|
@@ -6205,7 +6329,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6205
6329
|
} else if (typeof sandboxChoice === "object") {
|
|
6206
6330
|
const member = await getAnySessionInGroup(db, workspaceId, sandboxChoice.groupId);
|
|
6207
6331
|
if (!member) {
|
|
6208
|
-
throw new
|
|
6332
|
+
throw new HTTPException11(404, {
|
|
6209
6333
|
message: `sandbox group not found in workspace: ${sandboxChoice.groupId}`
|
|
6210
6334
|
});
|
|
6211
6335
|
}
|
|
@@ -6218,7 +6342,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6218
6342
|
if (!memberVariableSetIds.every(
|
|
6219
6343
|
(memberVariableSetId) => variableSetMatchesGroup(memberVariableSetId)
|
|
6220
6344
|
)) {
|
|
6221
|
-
throw new
|
|
6345
|
+
throw new HTTPException11(422, {
|
|
6222
6346
|
message: `sandbox group ${sandboxChoice.groupId} runs a different variableSet / different environment (the box variable set/environment is fixed at creation); create with the group's variableSet/environment or omit sandbox for an own box.`
|
|
6223
6347
|
});
|
|
6224
6348
|
}
|
|
@@ -6230,7 +6354,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6230
6354
|
if (!memberRigVersionIds.every(
|
|
6231
6355
|
(memberRigVersionId) => rigVersionMatchesGroup(memberRigVersionId)
|
|
6232
6356
|
)) {
|
|
6233
|
-
throw new
|
|
6357
|
+
throw new HTTPException11(422, {
|
|
6234
6358
|
message: `sandbox group ${sandboxChoice.groupId} runs a different rig (the box's rig setup is fixed at creation); create with the group's rig or omit sandbox for an own box.`
|
|
6235
6359
|
});
|
|
6236
6360
|
}
|
|
@@ -6239,7 +6363,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6239
6363
|
inheritedBackend = member.sandboxBackend;
|
|
6240
6364
|
}
|
|
6241
6365
|
if (payload.workingDir !== void 0 && !payload.targetSandboxId) {
|
|
6242
|
-
throw new
|
|
6366
|
+
throw new HTTPException11(422, {
|
|
6243
6367
|
message: "workingDir requires targetSandboxId (it is the targeted machine's working directory)"
|
|
6244
6368
|
});
|
|
6245
6369
|
}
|
|
@@ -6306,6 +6430,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6306
6430
|
// Frozen rig binding (M3): both null for a rig-less session (today's path).
|
|
6307
6431
|
rigId: frozenRigId,
|
|
6308
6432
|
rigVersionId: frozenRigVersionId,
|
|
6433
|
+
channelId,
|
|
6309
6434
|
goal: payload.goal ?? null,
|
|
6310
6435
|
// Per-session persona instructions (already trimmed/validated by the
|
|
6311
6436
|
// contracts schema). Persisted on the row; composed system-level at turn
|
|
@@ -6317,6 +6442,7 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6317
6442
|
mcpServers: sessionMcpServers.dbServers,
|
|
6318
6443
|
sessionMcpServers: sessionMcpServers.metadata,
|
|
6319
6444
|
personalConnectionDelegations,
|
|
6445
|
+
...xaiProviderAccountAuthoritySnapshot ? { xaiProviderAccountAuthoritySnapshot } : {},
|
|
6320
6446
|
parentSessionId,
|
|
6321
6447
|
createIdempotencyKey: payload.idempotencyKey ?? null,
|
|
6322
6448
|
maxNestedAgentDepthOverride: payload.maxNestedAgentDepth ?? null,
|
|
@@ -6338,10 +6464,10 @@ async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawP
|
|
|
6338
6464
|
});
|
|
6339
6465
|
} catch (error) {
|
|
6340
6466
|
if (error instanceof AgentCommandAuthorityError) {
|
|
6341
|
-
throw new
|
|
6467
|
+
throw new HTTPException11(403, { message: error.message });
|
|
6342
6468
|
}
|
|
6343
6469
|
if (error instanceof SessionIdConflictError) {
|
|
6344
|
-
throw new
|
|
6470
|
+
throw new HTTPException11(409, {
|
|
6345
6471
|
message: "requested session id is already in use"
|
|
6346
6472
|
});
|
|
6347
6473
|
}
|
|
@@ -6387,6 +6513,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
6387
6513
|
}
|
|
6388
6514
|
async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, sessionId, input) {
|
|
6389
6515
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
6516
|
+
const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
|
|
6390
6517
|
await requireSessionAuthorization(deps, grant, {
|
|
6391
6518
|
sessionId,
|
|
6392
6519
|
operation: input.delivery === "steer" ? "session.steer" : "session.append",
|
|
@@ -6402,7 +6529,7 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
|
|
|
6402
6529
|
assertSessionAllowsProductModel(existingSession, effectiveModel);
|
|
6403
6530
|
} catch (error) {
|
|
6404
6531
|
if (error instanceof CodexCompactionV2ProviderLockedError) {
|
|
6405
|
-
throw new
|
|
6532
|
+
throw new HTTPException11(422, { message: error.message, cause: error });
|
|
6406
6533
|
}
|
|
6407
6534
|
throw error;
|
|
6408
6535
|
}
|
|
@@ -6437,7 +6564,7 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
|
|
|
6437
6564
|
model: effectiveModel
|
|
6438
6565
|
});
|
|
6439
6566
|
if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
6440
|
-
throw new
|
|
6567
|
+
throw new HTTPException11(503, {
|
|
6441
6568
|
message: "object storage is not configured"
|
|
6442
6569
|
});
|
|
6443
6570
|
}
|
|
@@ -6462,7 +6589,6 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
|
|
|
6462
6589
|
tools: existingSession.tools,
|
|
6463
6590
|
source: personalConnectionDelegationSourceForGrant(grant)
|
|
6464
6591
|
});
|
|
6465
|
-
const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
|
|
6466
6592
|
const { accepted, turn, interruptionCount, replay } = await postUserMessageTurn({
|
|
6467
6593
|
db,
|
|
6468
6594
|
bus,
|
|
@@ -6558,7 +6684,7 @@ async function updateSessionMcpApprovalPolicy(deps, grant, sessionId, serverId,
|
|
|
6558
6684
|
async (_session, context) => {
|
|
6559
6685
|
const result = await context.updateSessionMcpApprovalPolicy(serverId, normalizedPolicy);
|
|
6560
6686
|
if (!result.server) {
|
|
6561
|
-
throw new
|
|
6687
|
+
throw new HTTPException11(404, {
|
|
6562
6688
|
message: "session MCP server not found"
|
|
6563
6689
|
});
|
|
6564
6690
|
}
|
|
@@ -6638,18 +6764,27 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
6638
6764
|
(tool) => !validatedIds.has(`${tool.kind}:${tool.id}`)
|
|
6639
6765
|
);
|
|
6640
6766
|
if (unknown) {
|
|
6641
|
-
throw new
|
|
6767
|
+
throw new HTTPException11(422, {
|
|
6642
6768
|
message: `unknown MCP server id: ${unknown.id}`
|
|
6643
6769
|
});
|
|
6644
6770
|
}
|
|
6645
6771
|
return withFirstPartyTools(validatedTools, runtimeSettings);
|
|
6646
6772
|
})() : null;
|
|
6647
6773
|
const explicitRequestedFirstPartyTools = explicitRequest ? [...explicitRequest.firstPartyMcpTools] : null;
|
|
6774
|
+
const deploymentFirstPartyMcpToolPolicy = resolveFirstPartyMcpToolPolicy(deps.settings);
|
|
6775
|
+
const disallowedFirstPartyMcpTool = explicitRequestedFirstPartyTools?.find(
|
|
6776
|
+
(tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool)
|
|
6777
|
+
);
|
|
6778
|
+
if (disallowedFirstPartyMcpTool) {
|
|
6779
|
+
throw new HTTPException11(422, {
|
|
6780
|
+
message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`
|
|
6781
|
+
});
|
|
6782
|
+
}
|
|
6648
6783
|
const workspaceDefaultTools = withFirstPartyTools(
|
|
6649
6784
|
withDefaultEnabledCapabilityMcpTools([], deps.settings, capabilityRuntimeSettings),
|
|
6650
6785
|
runtimeSettings
|
|
6651
6786
|
);
|
|
6652
|
-
const workspaceDefaultFirstPartyTools = [...
|
|
6787
|
+
const workspaceDefaultFirstPartyTools = [...deploymentFirstPartyMcpToolPolicy.default];
|
|
6653
6788
|
const events = await appendSessionEventsWithLockedSessionUpdate(
|
|
6654
6789
|
deps.db,
|
|
6655
6790
|
grant.workspaceId,
|
|
@@ -6665,7 +6800,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
6665
6800
|
if (session.parentSessionId) {
|
|
6666
6801
|
const parent = await context.getLockedSession(session.parentSessionId);
|
|
6667
6802
|
if (!parent) {
|
|
6668
|
-
throw new
|
|
6803
|
+
throw new HTTPException11(409, {
|
|
6669
6804
|
message: "parent session is no longer available"
|
|
6670
6805
|
});
|
|
6671
6806
|
}
|
|
@@ -6678,12 +6813,15 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
6678
6813
|
) : parent.tools,
|
|
6679
6814
|
runtimeSettings
|
|
6680
6815
|
);
|
|
6816
|
+
const deploymentAllowedFirstPartyMcpTools = new Set(
|
|
6817
|
+
deploymentFirstPartyMcpToolPolicy.allowed
|
|
6818
|
+
);
|
|
6681
6819
|
const parentFirstPartyMcpTools = [
|
|
6682
|
-
...parent.firstPartyMcpTools ??
|
|
6683
|
-
];
|
|
6820
|
+
...parent.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default
|
|
6821
|
+
].filter((tool) => deploymentAllowedFirstPartyMcpTools.has(tool));
|
|
6684
6822
|
if (requestedMode === "workspace_default") {
|
|
6685
6823
|
if (!parentTracksWorkspaceDefaults) {
|
|
6686
|
-
throw new
|
|
6824
|
+
throw new HTTPException11(403, {
|
|
6687
6825
|
message: "a child may adopt workspace defaults only while its parent tracks workspace defaults"
|
|
6688
6826
|
});
|
|
6689
6827
|
}
|
|
@@ -6705,7 +6843,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
6705
6843
|
(tool) => !parentFirstPartySet.has(tool)
|
|
6706
6844
|
);
|
|
6707
6845
|
if (widenedFirstPartyTool) {
|
|
6708
|
-
throw new
|
|
6846
|
+
throw new HTTPException11(403, {
|
|
6709
6847
|
message: `session OpenGeni tools may only narrow the parent policy: ${widenedFirstPartyTool}`
|
|
6710
6848
|
});
|
|
6711
6849
|
}
|
|
@@ -6723,7 +6861,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
6723
6861
|
const currentPolicy = session.toolPolicy;
|
|
6724
6862
|
const unchanged = stableJson4({
|
|
6725
6863
|
tools: session.tools,
|
|
6726
|
-
firstPartyMcpTools: session.firstPartyMcpTools ??
|
|
6864
|
+
firstPartyMcpTools: session.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default,
|
|
6727
6865
|
policy: currentPolicy
|
|
6728
6866
|
}) === stableJson4({
|
|
6729
6867
|
tools: nextTools,
|
|
@@ -6742,7 +6880,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
6742
6880
|
before: toolPolicyAuditSnapshot(
|
|
6743
6881
|
session,
|
|
6744
6882
|
session.tools,
|
|
6745
|
-
[...session.firstPartyMcpTools ??
|
|
6883
|
+
[...session.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default],
|
|
6746
6884
|
currentPolicy
|
|
6747
6885
|
),
|
|
6748
6886
|
after: toolPolicyAuditSnapshot(
|
|
@@ -6781,13 +6919,13 @@ async function readSessionLineage(deps, grant, sessionId) {
|
|
|
6781
6919
|
if (authorization?.relatedSessionAccess === "target") {
|
|
6782
6920
|
const session = await getSession2(deps.db, grant.workspaceId, sessionId);
|
|
6783
6921
|
if (!session) {
|
|
6784
|
-
throw new
|
|
6922
|
+
throw new HTTPException11(404, { message: "session not found" });
|
|
6785
6923
|
}
|
|
6786
6924
|
return { ancestors: [], children: [], truncated: false };
|
|
6787
6925
|
}
|
|
6788
6926
|
const lineage = await getSessionLineage(deps.db, grant.workspaceId, sessionId);
|
|
6789
6927
|
if (!lineage) {
|
|
6790
|
-
throw new
|
|
6928
|
+
throw new HTTPException11(404, { message: "session not found" });
|
|
6791
6929
|
}
|
|
6792
6930
|
return lineage;
|
|
6793
6931
|
}
|
|
@@ -6865,6 +7003,15 @@ async function createValidatedScheduledTask(input) {
|
|
|
6865
7003
|
source: personalConnectionDelegationSourceForGrant(input.grant)
|
|
6866
7004
|
});
|
|
6867
7005
|
const creationInitiator = creationInitiatorForGrant(input.grant);
|
|
7006
|
+
const xaiProviderAccountAuthoritySnapshot = creationInitiator.actor ? await getSessionTurnXaiProviderAccountAuthoritySnapshot2(
|
|
7007
|
+
input.db,
|
|
7008
|
+
input.grant.workspaceId,
|
|
7009
|
+
creationInitiator.actor.sessionId,
|
|
7010
|
+
creationInitiator.actor.turnId
|
|
7011
|
+
) : await resolveXaiProviderAccountAuthoritySnapshotForAcceptance(input.db, {
|
|
7012
|
+
workspaceId: input.grant.workspaceId,
|
|
7013
|
+
subjectId: input.grant.subjectId
|
|
7014
|
+
});
|
|
6868
7015
|
return await createScheduledTask(input.db, {
|
|
6869
7016
|
id,
|
|
6870
7017
|
accountId: input.grant.accountId,
|
|
@@ -6881,6 +7028,7 @@ async function createValidatedScheduledTask(input) {
|
|
|
6881
7028
|
...creationInitiator.context ? { createdByContext: creationInitiator.context } : {},
|
|
6882
7029
|
createdByActor: creationInitiator.actor ?? null,
|
|
6883
7030
|
personalConnectionDelegations,
|
|
7031
|
+
xaiProviderAccountAuthoritySnapshot,
|
|
6884
7032
|
targetSessionId: target?.id ?? null,
|
|
6885
7033
|
variableSetId: input.payload.variableSetId ?? null,
|
|
6886
7034
|
rigId: input.payload.rigId ?? null,
|
|
@@ -6890,20 +7038,20 @@ async function createValidatedScheduledTask(input) {
|
|
|
6890
7038
|
async function validateScheduledTaskTarget(input) {
|
|
6891
7039
|
if (input.runMode !== "existing_session") {
|
|
6892
7040
|
if (input.targetSessionId) {
|
|
6893
|
-
throw new
|
|
7041
|
+
throw new HTTPException12(422, {
|
|
6894
7042
|
message: "targetSessionId requires runMode=existing_session"
|
|
6895
7043
|
});
|
|
6896
7044
|
}
|
|
6897
7045
|
return null;
|
|
6898
7046
|
}
|
|
6899
7047
|
if (!input.targetSessionId) {
|
|
6900
|
-
throw new
|
|
7048
|
+
throw new HTTPException12(input.missingTargetStatus ?? 422, {
|
|
6901
7049
|
message: input.missingTargetStatus === 404 ? "target session not found" : "targetSessionId is required when runMode=existing_session"
|
|
6902
7050
|
});
|
|
6903
7051
|
}
|
|
6904
7052
|
requirePermission(input.grant, "sessions:control");
|
|
6905
7053
|
if (input.agentConfig.goal) {
|
|
6906
|
-
throw new
|
|
7054
|
+
throw new HTTPException12(422, {
|
|
6907
7055
|
message: "agentConfig.goal cannot be used with an existing-session target"
|
|
6908
7056
|
});
|
|
6909
7057
|
}
|
|
@@ -6922,39 +7070,39 @@ async function validateScheduledTaskTarget(input) {
|
|
|
6922
7070
|
);
|
|
6923
7071
|
} catch (error) {
|
|
6924
7072
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
6925
|
-
throw new
|
|
7073
|
+
throw new HTTPException12(404, { message: "target session not found" });
|
|
6926
7074
|
}
|
|
6927
7075
|
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
6928
|
-
throw new
|
|
7076
|
+
throw new HTTPException12(503, { message: "session authorization is unavailable" });
|
|
6929
7077
|
}
|
|
6930
7078
|
throw error;
|
|
6931
7079
|
}
|
|
6932
7080
|
const session = await getSession3(input.db, input.grant.workspaceId, input.targetSessionId);
|
|
6933
7081
|
if (!session || session.accountId !== input.grant.accountId) {
|
|
6934
|
-
throw new
|
|
7082
|
+
throw new HTTPException12(404, { message: "target session not found" });
|
|
6935
7083
|
}
|
|
6936
7084
|
if (session.status === "cancelled") {
|
|
6937
|
-
throw new
|
|
7085
|
+
throw new HTTPException12(409, {
|
|
6938
7086
|
message: "target session is cancelled; choose a revivable session"
|
|
6939
7087
|
});
|
|
6940
7088
|
}
|
|
6941
7089
|
if ((session.variableSetId ?? null) !== (input.variableSetId ?? null)) {
|
|
6942
|
-
throw new
|
|
7090
|
+
throw new HTTPException12(422, {
|
|
6943
7091
|
message: "target session variableSet attachment does not match the scheduled task"
|
|
6944
7092
|
});
|
|
6945
7093
|
}
|
|
6946
7094
|
if (input.rigId && input.rigId !== session.rigId) {
|
|
6947
|
-
throw new
|
|
7095
|
+
throw new HTTPException12(422, {
|
|
6948
7096
|
message: "target session rig does not match the scheduled task"
|
|
6949
7097
|
});
|
|
6950
7098
|
}
|
|
6951
7099
|
if (input.agentConfig.sandboxBackend !== void 0 && input.agentConfig.sandboxBackend !== session.sandboxBackend) {
|
|
6952
|
-
throw new
|
|
7100
|
+
throw new HTTPException12(422, {
|
|
6953
7101
|
message: "target session sandbox backend does not match the scheduled task"
|
|
6954
7102
|
});
|
|
6955
7103
|
}
|
|
6956
7104
|
if (scheduledSlackBotConnectionId(session.metadata) !== (input.agentConfig.slackBotConnectionId ?? null)) {
|
|
6957
|
-
throw new
|
|
7105
|
+
throw new HTTPException12(422, {
|
|
6958
7106
|
message: "target session OpenGeni Slack bot binding does not match the scheduled task"
|
|
6959
7107
|
});
|
|
6960
7108
|
}
|
|
@@ -6975,25 +7123,25 @@ function scheduledTaskRunForGrant(run, grant) {
|
|
|
6975
7123
|
async function requireScheduledTaskRig(db, workspaceId, rigId) {
|
|
6976
7124
|
const rig = await getRig4(db, workspaceId, rigId);
|
|
6977
7125
|
if (!rig) {
|
|
6978
|
-
throw new
|
|
7126
|
+
throw new HTTPException12(422, { message: `unknown rigId: ${rigId}` });
|
|
6979
7127
|
}
|
|
6980
7128
|
}
|
|
6981
7129
|
async function validatedScheduledTaskUpdate(input) {
|
|
6982
7130
|
const update = {};
|
|
6983
7131
|
const existingKnowledge = input.existing.action.kind === "knowledge_source_sync";
|
|
6984
7132
|
if (input.payload.action && input.payload.action.kind !== input.existing.action.kind) {
|
|
6985
|
-
throw new
|
|
7133
|
+
throw new HTTPException12(409, {
|
|
6986
7134
|
message: "scheduled task action kind is immutable; create a new schedule"
|
|
6987
7135
|
});
|
|
6988
7136
|
}
|
|
6989
7137
|
if (existingKnowledge) {
|
|
6990
7138
|
if (input.payload.agentConfig !== void 0 || input.payload.runMode !== void 0 || input.payload.targetSessionId !== void 0 || input.payload.variableSetId !== void 0 || input.payload.rigId !== void 0) {
|
|
6991
|
-
throw new
|
|
7139
|
+
throw new HTTPException12(422, {
|
|
6992
7140
|
message: "knowledge source schedules do not accept agent/session configuration"
|
|
6993
7141
|
});
|
|
6994
7142
|
}
|
|
6995
7143
|
if (input.payload.overlapPolicy === "allow_concurrent") {
|
|
6996
|
-
throw new
|
|
7144
|
+
throw new HTTPException12(422, {
|
|
6997
7145
|
message: "knowledge source schedules require skip or buffer_one overlap"
|
|
6998
7146
|
});
|
|
6999
7147
|
}
|
|
@@ -7022,7 +7170,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
7022
7170
|
const nextRunMode = input.payload.runMode ?? input.existing.runMode;
|
|
7023
7171
|
const nextTargetSessionId = input.payload.targetSessionId !== void 0 ? input.payload.targetSessionId : nextRunMode === "existing_session" ? existingTarget : null;
|
|
7024
7172
|
if (input.existing.runMode === "reusable_session" && input.existing.reusableSessionId && nextRunMode === "existing_session") {
|
|
7025
|
-
throw new
|
|
7173
|
+
throw new HTTPException12(409, {
|
|
7026
7174
|
message: "cannot target an existing session after this task created its reusable session; create a new task"
|
|
7027
7175
|
});
|
|
7028
7176
|
}
|
|
@@ -7048,7 +7196,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
7048
7196
|
if (input.payload.variableSetId !== void 0) {
|
|
7049
7197
|
const nextVariableSetId = input.payload.variableSetId;
|
|
7050
7198
|
if ((input.existing.variableSetId ?? null) !== (nextVariableSetId ?? null) && input.existing.runMode === "reusable_session" && input.existing.reusableSessionId) {
|
|
7051
|
-
throw new
|
|
7199
|
+
throw new HTTPException12(409, {
|
|
7052
7200
|
message: "cannot change variableSet of a task with a live reusable session; recreate the task"
|
|
7053
7201
|
});
|
|
7054
7202
|
}
|
|
@@ -7088,7 +7236,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
7088
7236
|
...input.toolsProvided !== void 0 ? { toolsProvided: input.toolsProvided } : {}
|
|
7089
7237
|
});
|
|
7090
7238
|
if (input.existing.reusableSessionId && input.existing.runMode === "reusable_session" && (input.existing.agentConfig.slackBotConnectionId ?? null) !== (nextAgentConfig.slackBotConnectionId ?? null)) {
|
|
7091
|
-
throw new
|
|
7239
|
+
throw new HTTPException12(409, {
|
|
7092
7240
|
message: "cannot change the OpenGeni Slack bot connection of a task with a live reusable session; recreate the task"
|
|
7093
7241
|
});
|
|
7094
7242
|
}
|
|
@@ -7113,7 +7261,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
7113
7261
|
input.existing.id
|
|
7114
7262
|
);
|
|
7115
7263
|
if (!personalConnectionDelegationsEqual(existingDelegations, personalConnectionDelegations)) {
|
|
7116
|
-
throw new
|
|
7264
|
+
throw new HTTPException12(409, {
|
|
7117
7265
|
message: "cannot change personal MCP connections of a task with a live reusable session; recreate the task"
|
|
7118
7266
|
});
|
|
7119
7267
|
}
|
|
@@ -7152,7 +7300,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
7152
7300
|
async function requireScheduledTaskForApi(db, workspaceId, taskId) {
|
|
7153
7301
|
const task = await getScheduledTask(db, workspaceId, taskId);
|
|
7154
7302
|
if (!task) {
|
|
7155
|
-
throw new
|
|
7303
|
+
throw new HTTPException12(404, { message: "scheduled task not found" });
|
|
7156
7304
|
}
|
|
7157
7305
|
return task;
|
|
7158
7306
|
}
|
|
@@ -7185,12 +7333,12 @@ async function restoreScheduledTask(db, previous) {
|
|
|
7185
7333
|
}
|
|
7186
7334
|
async function validateKnowledgeSourceSyncAction(input) {
|
|
7187
7335
|
if (input.action.initiatingSubjectId !== input.grant.subjectId) {
|
|
7188
|
-
throw new
|
|
7336
|
+
throw new HTTPException12(403, {
|
|
7189
7337
|
message: "knowledge source sync must preserve the exact initiating subject"
|
|
7190
7338
|
});
|
|
7191
7339
|
}
|
|
7192
7340
|
if (input.action.connection.ownerSubjectId !== input.grant.subjectId) {
|
|
7193
|
-
throw new
|
|
7341
|
+
throw new HTTPException12(403, {
|
|
7194
7342
|
message: "knowledge source connection must belong to the initiating subject"
|
|
7195
7343
|
});
|
|
7196
7344
|
}
|
|
@@ -7201,10 +7349,10 @@ async function validateKnowledgeSourceSyncAction(input) {
|
|
|
7201
7349
|
initiatingSubjectId: input.grant.subjectId
|
|
7202
7350
|
});
|
|
7203
7351
|
if (!resolved || resolved.source.lifecycleState !== "active") {
|
|
7204
|
-
throw new
|
|
7352
|
+
throw new HTTPException12(404, { message: "knowledge source not found" });
|
|
7205
7353
|
}
|
|
7206
7354
|
if (resolved.source.syncGeneration !== input.action.sourceGeneration || resolved.source.lifecycleGeneration !== input.action.sourceLifecycleGeneration || scopedKnowledgeScopeKey(resolved.source.scope) !== scopedKnowledgeScopeKey(input.action.destination)) {
|
|
7207
|
-
throw new
|
|
7355
|
+
throw new HTTPException12(409, {
|
|
7208
7356
|
message: "knowledge source authority or generation changed"
|
|
7209
7357
|
});
|
|
7210
7358
|
}
|
|
@@ -7215,7 +7363,7 @@ async function validateKnowledgeSourceSyncAction(input) {
|
|
|
7215
7363
|
input.grant.subjectId
|
|
7216
7364
|
);
|
|
7217
7365
|
if (!connection || connection.accountId !== input.grant.accountId || connection.workspaceId !== input.grant.workspaceId || connection.subjectId !== input.action.connection.ownerSubjectId || connection.version !== input.action.connection.connectionVersion || connection.providerDomain.toLowerCase() !== input.action.connection.providerDomain.toLowerCase() || connection.kind !== input.action.connection.kind || connection.status !== "active") {
|
|
7218
|
-
throw new
|
|
7366
|
+
throw new HTTPException12(409, {
|
|
7219
7367
|
message: "knowledge source connection authority changed or requires reconnect"
|
|
7220
7368
|
});
|
|
7221
7369
|
}
|
|
@@ -7285,16 +7433,16 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
7285
7433
|
const tools = input.toolsProvided ?? true ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, input.settings, runtimeSettings);
|
|
7286
7434
|
const prompt = input.payload.agentConfig.prompt.trim();
|
|
7287
7435
|
if (!prompt) {
|
|
7288
|
-
throw new
|
|
7436
|
+
throw new HTTPException12(422, { message: "scheduled task prompt is required" });
|
|
7289
7437
|
}
|
|
7290
7438
|
if (hasReservedOpenGeniSlackBotSessionMetadata(input.payload.agentConfig.metadata)) {
|
|
7291
|
-
throw new
|
|
7439
|
+
throw new HTTPException12(422, {
|
|
7292
7440
|
message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY3} is reserved for scheduler routing`
|
|
7293
7441
|
});
|
|
7294
7442
|
}
|
|
7295
7443
|
await validateGitHubRepositorySelection(input.db, input.workspaceId, resources);
|
|
7296
7444
|
if (resources.some((resource) => resource.kind === "file") && !input.objectStorage) {
|
|
7297
|
-
throw new
|
|
7445
|
+
throw new HTTPException12(503, { message: "object storage is not configured" });
|
|
7298
7446
|
}
|
|
7299
7447
|
await validateFileResources(input.db, input.workspaceId, resources);
|
|
7300
7448
|
if (input.payload.agentConfig.slackBotConnectionId) {
|
|
@@ -7312,7 +7460,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
7312
7460
|
const deploymentPolicy = await getNestedAgentDepthDeploymentPolicy(input.db);
|
|
7313
7461
|
const inheritedMaxDepth = typeof workspaceMaxDepth === "number" ? workspaceMaxDepth : deploymentPolicy.maxNestedAgentDepth;
|
|
7314
7462
|
if (requestedMaxDepth > inheritedMaxDepth && !hasPermission(input.grant.permissions, "workspace:admin")) {
|
|
7315
|
-
throw new
|
|
7463
|
+
throw new HTTPException12(403, {
|
|
7316
7464
|
message: `scheduled task maxNestedAgentDepth ${requestedMaxDepth} exceeds inherited limit ${inheritedMaxDepth}; workspace:admin is required to increase it`
|
|
7317
7465
|
});
|
|
7318
7466
|
}
|
|
@@ -7330,13 +7478,13 @@ function validateScheduledTaskSchedule(schedule) {
|
|
|
7330
7478
|
return;
|
|
7331
7479
|
}
|
|
7332
7480
|
if (new Date(schedule.startAt).getTime() >= new Date(schedule.endAt).getTime()) {
|
|
7333
|
-
throw new
|
|
7481
|
+
throw new HTTPException12(422, { message: "interval schedule endAt must be after startAt" });
|
|
7334
7482
|
}
|
|
7335
7483
|
}
|
|
7336
7484
|
function trimmedScheduledTaskName(name) {
|
|
7337
7485
|
const trimmed = name.trim();
|
|
7338
7486
|
if (!trimmed) {
|
|
7339
|
-
throw new
|
|
7487
|
+
throw new HTTPException12(422, { message: "scheduled task name is required" });
|
|
7340
7488
|
}
|
|
7341
7489
|
return trimmed;
|
|
7342
7490
|
}
|
|
@@ -7349,6 +7497,7 @@ import {
|
|
|
7349
7497
|
import {
|
|
7350
7498
|
aggregateModelCallFacts,
|
|
7351
7499
|
aggregateModelCallFactsByDay,
|
|
7500
|
+
aggregateModelCallFactsByHour,
|
|
7352
7501
|
aggregateRootSessionDrivers,
|
|
7353
7502
|
aggregateScheduleFacts,
|
|
7354
7503
|
aggregateSessionDepth,
|
|
@@ -7357,6 +7506,7 @@ import {
|
|
|
7357
7506
|
countScheduledTaskFires,
|
|
7358
7507
|
countSessionsAttachedToGroups,
|
|
7359
7508
|
enumerateUtcDays,
|
|
7509
|
+
enumerateUtcHours,
|
|
7360
7510
|
listFloorSessions,
|
|
7361
7511
|
listLiveWarmLeases,
|
|
7362
7512
|
listModelCallFacets,
|
|
@@ -7365,6 +7515,7 @@ import {
|
|
|
7365
7515
|
requireWorkspace as requireWorkspace3,
|
|
7366
7516
|
sumUsageQuantity as sumUsageQuantity2,
|
|
7367
7517
|
sumUsageQuantityByDay,
|
|
7518
|
+
sumUsageQuantityByHour,
|
|
7368
7519
|
sumUsageQuantityInRange
|
|
7369
7520
|
} from "@opengeni/db";
|
|
7370
7521
|
var MACHINE_HEARTBEAT_FRESH_MS = 12e4;
|
|
@@ -7396,29 +7547,29 @@ function resolveRangeWindow(range, now) {
|
|
|
7396
7547
|
since = startOfUtcDay(now);
|
|
7397
7548
|
rangeLabel = "Today (UTC)";
|
|
7398
7549
|
priorLabel = "Prior equal window";
|
|
7399
|
-
seriesLabel = "Credit $
|
|
7400
|
-
cacheSeriesLabel = "Cache hit %";
|
|
7550
|
+
seriesLabel = "Credit $ / UTC hour";
|
|
7551
|
+
cacheSeriesLabel = "Cache hit % / UTC hour";
|
|
7401
7552
|
break;
|
|
7402
7553
|
case "week":
|
|
7403
7554
|
since = new Date(startOfUtcDay(now).getTime() - 6 * 24 * 60 * 60 * 1e3);
|
|
7404
7555
|
rangeLabel = "Last 7 days (UTC)";
|
|
7405
7556
|
priorLabel = "Prior 7 days";
|
|
7406
|
-
seriesLabel = "Credit $ / day";
|
|
7407
|
-
cacheSeriesLabel = "Cache hit % / day";
|
|
7557
|
+
seriesLabel = "Credit $ / UTC day";
|
|
7558
|
+
cacheSeriesLabel = "Cache hit % / UTC day";
|
|
7408
7559
|
break;
|
|
7409
7560
|
case "month":
|
|
7410
7561
|
since = startOfUtcMonth2(now);
|
|
7411
7562
|
rangeLabel = "This month (UTC)";
|
|
7412
7563
|
priorLabel = "Prior equal window";
|
|
7413
|
-
seriesLabel = "Credit $ / day";
|
|
7414
|
-
cacheSeriesLabel = "Cache hit % / day";
|
|
7564
|
+
seriesLabel = "Credit $ / UTC day";
|
|
7565
|
+
cacheSeriesLabel = "Cache hit % / UTC day";
|
|
7415
7566
|
break;
|
|
7416
7567
|
case "ytd":
|
|
7417
7568
|
since = startOfUtcYear(now);
|
|
7418
7569
|
rangeLabel = "Year to date (UTC)";
|
|
7419
7570
|
priorLabel = "Prior equal window";
|
|
7420
|
-
seriesLabel = "Credit $ / day";
|
|
7421
|
-
cacheSeriesLabel = "Cache hit % / day";
|
|
7571
|
+
seriesLabel = "Credit $ / UTC day";
|
|
7572
|
+
cacheSeriesLabel = "Cache hit % / UTC day";
|
|
7422
7573
|
break;
|
|
7423
7574
|
default: {
|
|
7424
7575
|
const _exhaustive = range;
|
|
@@ -7482,6 +7633,8 @@ async function getWorkspaceInsights(db, settings, input) {
|
|
|
7482
7633
|
const model = input.model?.trim() || null;
|
|
7483
7634
|
const modelFilterActive = Boolean(provider || model);
|
|
7484
7635
|
const filter = { provider, model };
|
|
7636
|
+
const aggregateFactsForSeries = input.range === "today" ? aggregateModelCallFactsByHour : aggregateModelCallFactsByDay;
|
|
7637
|
+
const sumUsageForSeries = input.range === "today" ? sumUsageQuantityByHour : sumUsageQuantityByDay;
|
|
7485
7638
|
const [
|
|
7486
7639
|
workspaceCreditMicros,
|
|
7487
7640
|
priorWorkspaceCreditMicros,
|
|
@@ -7541,19 +7694,19 @@ async function getWorkspaceInsights(db, settings, input) {
|
|
|
7541
7694
|
until: window.priorUntil,
|
|
7542
7695
|
...filter
|
|
7543
7696
|
}),
|
|
7544
|
-
|
|
7697
|
+
aggregateFactsForSeries(db, {
|
|
7545
7698
|
workspaceId: input.workspaceId,
|
|
7546
7699
|
since: window.since,
|
|
7547
7700
|
until: window.until,
|
|
7548
7701
|
...filter
|
|
7549
7702
|
}),
|
|
7550
|
-
|
|
7703
|
+
sumUsageForSeries(db, {
|
|
7551
7704
|
workspaceId: input.workspaceId,
|
|
7552
7705
|
eventType: "sandbox.warm_seconds",
|
|
7553
7706
|
since: window.since,
|
|
7554
7707
|
until: window.until
|
|
7555
7708
|
}),
|
|
7556
|
-
|
|
7709
|
+
sumUsageForSeries(db, {
|
|
7557
7710
|
workspaceId: input.workspaceId,
|
|
7558
7711
|
eventType: "model.cost",
|
|
7559
7712
|
since: window.since,
|
|
@@ -7673,9 +7826,9 @@ async function getWorkspaceInsights(db, settings, input) {
|
|
|
7673
7826
|
const priorCachedTokens = priorModelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
|
|
7674
7827
|
const priorCacheInputTokens = priorModelRows.reduce((sum, row) => sum + row.cacheInputTokens, 0);
|
|
7675
7828
|
const priorCalls = priorModelRows.reduce((sum, row) => sum + row.calls, 0);
|
|
7676
|
-
const
|
|
7677
|
-
const series =
|
|
7678
|
-
const facts = factDays.get(
|
|
7829
|
+
const buckets = input.range === "today" ? enumerateUtcHours(window.since, window.until) : enumerateUtcDays(window.since, window.until);
|
|
7830
|
+
const series = buckets.map((bucket) => {
|
|
7831
|
+
const facts = factDays.get(bucket) ?? {
|
|
7679
7832
|
costMicros: 0,
|
|
7680
7833
|
estimatedProviderCostMicros: 0,
|
|
7681
7834
|
estimatedProviderCostKnownCalls: 0,
|
|
@@ -7690,13 +7843,13 @@ async function getWorkspaceInsights(db, settings, input) {
|
|
|
7690
7843
|
cacheKnownCalls: 0,
|
|
7691
7844
|
calls: 0
|
|
7692
7845
|
};
|
|
7693
|
-
const modelCostMicros = modelFilterActive ? facts.costMicros : costDays.get(
|
|
7846
|
+
const modelCostMicros = modelFilterActive ? facts.costMicros : costDays.get(bucket) ?? facts.costMicros;
|
|
7694
7847
|
return {
|
|
7695
|
-
label:
|
|
7848
|
+
label: input.range === "today" ? bucket.slice(11) : bucket.slice(5),
|
|
7696
7849
|
modelCostUsd: microsToUsd(modelCostMicros),
|
|
7697
7850
|
estimatedProviderUsd: microsToUsd(facts.estimatedProviderCostMicros),
|
|
7698
7851
|
estimatedProviderCostKnownCalls: facts.estimatedProviderCostKnownCalls,
|
|
7699
|
-
warmSeconds: warmDays.get(
|
|
7852
|
+
warmSeconds: warmDays.get(bucket) ?? 0,
|
|
7700
7853
|
inputTokens: facts.inputTokens,
|
|
7701
7854
|
outputTokens: facts.outputTokens,
|
|
7702
7855
|
cachedTokens: facts.cachedTokens,
|
|
@@ -8488,8 +8641,797 @@ function createCompanyProfileDurableLearningAdapter(options) {
|
|
|
8488
8641
|
};
|
|
8489
8642
|
}
|
|
8490
8643
|
|
|
8644
|
+
// src/domain/conversation-integrations.ts
|
|
8645
|
+
import { createHash as createHash5 } from "crypto";
|
|
8646
|
+
var CONVERSATION_INTEGRATION_SCHEMA_VERSION = 1;
|
|
8647
|
+
var CONVERSATION_PROVIDER_NAMESPACE_MAX_UTF8_BYTES = 128;
|
|
8648
|
+
var CONVERSATION_PROVIDER_OPAQUE_ID_MAX_UTF8_BYTES = 512;
|
|
8649
|
+
var CONVERSATION_TEXT_MAX_UTF8_BYTES = 32 * 1024;
|
|
8650
|
+
var CONVERSATION_ATTACHMENT_MAX_COUNT = 16;
|
|
8651
|
+
var CONVERSATION_ATTACHMENT_NAME_MAX_UTF8_BYTES = 1024;
|
|
8652
|
+
var CONVERSATION_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES = 128;
|
|
8653
|
+
var CONVERSATION_OPERATION_KEY_MAX_UTF8_BYTES = 512;
|
|
8654
|
+
var CONVERSATION_OUTCOME_CODE_MAX_UTF8_BYTES = 128;
|
|
8655
|
+
var PROVIDER_NAMESPACE_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/u;
|
|
8656
|
+
var OUTCOME_CODE_PATTERN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/u;
|
|
8657
|
+
var MEDIA_TYPE_PATTERN = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/iu;
|
|
8658
|
+
var SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
8659
|
+
var ABSOLUTE_URL_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//iu;
|
|
8660
|
+
var textEncoder = new TextEncoder();
|
|
8661
|
+
function normalizeConversationInboundEnvelope(input) {
|
|
8662
|
+
assertExactObject(input, "inbound envelope", [
|
|
8663
|
+
"provider",
|
|
8664
|
+
"installationId",
|
|
8665
|
+
"actor",
|
|
8666
|
+
"route",
|
|
8667
|
+
"providerEventId",
|
|
8668
|
+
"providerMessageId",
|
|
8669
|
+
"occurredAt",
|
|
8670
|
+
"signal",
|
|
8671
|
+
"text",
|
|
8672
|
+
"attachments"
|
|
8673
|
+
]);
|
|
8674
|
+
const provider = normalizeConversationProviderNamespace(input.provider);
|
|
8675
|
+
const installation = normalizeConversationInstallationIdentity({
|
|
8676
|
+
provider,
|
|
8677
|
+
providerInstallationId: input.installationId
|
|
8678
|
+
});
|
|
8679
|
+
const actor = normalizeConversationActor(installation, input.actor);
|
|
8680
|
+
const route = normalizeConversationRoute(installation, input.route);
|
|
8681
|
+
const providerEventId = providerOpaqueId(provider, "event", input.providerEventId);
|
|
8682
|
+
const providerMessageId = providerOpaqueId(provider, "message", input.providerMessageId);
|
|
8683
|
+
const occurredAt = canonicalTimestamp(input.occurredAt, "inbound occurredAt");
|
|
8684
|
+
const signal = normalizeSignal(input.signal);
|
|
8685
|
+
const text = boundedExactText(input.text, "inbound text", true);
|
|
8686
|
+
const rawAttachments = input.attachments ?? [];
|
|
8687
|
+
if (!Array.isArray(rawAttachments)) throw new TypeError("inbound attachments must be an array");
|
|
8688
|
+
if (rawAttachments.length > CONVERSATION_ATTACHMENT_MAX_COUNT) {
|
|
8689
|
+
throw new RangeError(
|
|
8690
|
+
`inbound attachments must contain at most ${CONVERSATION_ATTACHMENT_MAX_COUNT} entries`
|
|
8691
|
+
);
|
|
8692
|
+
}
|
|
8693
|
+
const attachments = Object.freeze(
|
|
8694
|
+
rawAttachments.map((attachment) => normalizeAttachment(provider, attachment))
|
|
8695
|
+
);
|
|
8696
|
+
if ((signal.kind === "start" || signal.kind === "continue") && !text && !attachments.length) {
|
|
8697
|
+
throw new TypeError("start and continue signals require text or an attachment reference");
|
|
8698
|
+
}
|
|
8699
|
+
return Object.freeze({
|
|
8700
|
+
schemaVersion: CONVERSATION_INTEGRATION_SCHEMA_VERSION,
|
|
8701
|
+
installation,
|
|
8702
|
+
actor,
|
|
8703
|
+
route,
|
|
8704
|
+
providerEventId,
|
|
8705
|
+
providerMessageId,
|
|
8706
|
+
eventIdentity: deriveConversationEventIdentity(installation, providerEventId),
|
|
8707
|
+
occurredAt,
|
|
8708
|
+
signal,
|
|
8709
|
+
text,
|
|
8710
|
+
attachments
|
|
8711
|
+
});
|
|
8712
|
+
}
|
|
8713
|
+
function normalizeConversationProviderNamespace(value) {
|
|
8714
|
+
return providerNamespace(value);
|
|
8715
|
+
}
|
|
8716
|
+
function normalizeConversationInstallationIdentity(input) {
|
|
8717
|
+
assertExactObject(input, "installation identity", ["provider", "providerInstallationId"]);
|
|
8718
|
+
return providerOpaqueId(
|
|
8719
|
+
providerNamespace(input.provider),
|
|
8720
|
+
"installation",
|
|
8721
|
+
input.providerInstallationId
|
|
8722
|
+
);
|
|
8723
|
+
}
|
|
8724
|
+
function normalizeConversationActor(installation, input) {
|
|
8725
|
+
assertConversationInstallationIdentity(installation);
|
|
8726
|
+
assertExactObject(input, "inbound actor", ["providerActorId", "kind"]);
|
|
8727
|
+
if (input.kind !== "human" && input.kind !== "bot" && input.kind !== "system") {
|
|
8728
|
+
throw new TypeError("inbound actor kind is unsupported");
|
|
8729
|
+
}
|
|
8730
|
+
const providerActorId = providerOpaqueId(installation.provider, "actor", input.providerActorId);
|
|
8731
|
+
return Object.freeze({
|
|
8732
|
+
installation,
|
|
8733
|
+
providerActorId,
|
|
8734
|
+
kind: input.kind,
|
|
8735
|
+
identity: deriveConversationActorIdentity(installation, providerActorId)
|
|
8736
|
+
});
|
|
8737
|
+
}
|
|
8738
|
+
function normalizeConversationRoute(installation, input) {
|
|
8739
|
+
assertConversationInstallationIdentity(installation);
|
|
8740
|
+
assertExactObject(input, "inbound route", ["providerConversationId", "providerThreadId"]);
|
|
8741
|
+
const providerConversationId = providerOpaqueId(
|
|
8742
|
+
installation.provider,
|
|
8743
|
+
"conversation",
|
|
8744
|
+
input.providerConversationId
|
|
8745
|
+
);
|
|
8746
|
+
const providerThreadId = input.providerThreadId === void 0 || input.providerThreadId === null ? null : providerOpaqueId(installation.provider, "thread", input.providerThreadId);
|
|
8747
|
+
const identity = deriveConversationRouteIdentity(
|
|
8748
|
+
installation,
|
|
8749
|
+
providerConversationId,
|
|
8750
|
+
providerThreadId
|
|
8751
|
+
);
|
|
8752
|
+
return Object.freeze({ installation, providerConversationId, providerThreadId, identity });
|
|
8753
|
+
}
|
|
8754
|
+
function normalizeConversationDeliveryCommand(input) {
|
|
8755
|
+
const keys = input.kind === "post" ? ["kind", "logicalOperationKey", "installation", "route", "text"] : input.kind === "update" ? [
|
|
8756
|
+
"kind",
|
|
8757
|
+
"logicalOperationKey",
|
|
8758
|
+
"installation",
|
|
8759
|
+
"route",
|
|
8760
|
+
"targetProviderMessageId",
|
|
8761
|
+
"text"
|
|
8762
|
+
] : ["kind", "logicalOperationKey", "installation", "route", "targetProviderMessageId"];
|
|
8763
|
+
assertExactObject(input, "delivery command", keys);
|
|
8764
|
+
if (input.kind !== "post" && input.kind !== "update" && input.kind !== "delete") {
|
|
8765
|
+
throw new TypeError("delivery command kind is unsupported");
|
|
8766
|
+
}
|
|
8767
|
+
assertConversationInstallationIdentity(input.installation);
|
|
8768
|
+
assertConversationRoute(input.route);
|
|
8769
|
+
assertSameInstallation(input.installation, input.route.installation, "delivery route");
|
|
8770
|
+
const logicalOperationKey = boundedOpaqueString(
|
|
8771
|
+
input.logicalOperationKey,
|
|
8772
|
+
"logical operation key",
|
|
8773
|
+
CONVERSATION_OPERATION_KEY_MAX_UTF8_BYTES
|
|
8774
|
+
);
|
|
8775
|
+
const operationId = deriveConversationOperationIdentity(input.installation, logicalOperationKey);
|
|
8776
|
+
const targetProviderMessageId = input.kind === "post" ? null : providerOpaqueId(input.installation.provider, "message", input.targetProviderMessageId);
|
|
8777
|
+
const text = input.kind === "delete" ? null : boundedExactText(input.text, "delivery text", false);
|
|
8778
|
+
const requestDigest = deliveryRequestDigest({
|
|
8779
|
+
kind: input.kind,
|
|
8780
|
+
installation: input.installation,
|
|
8781
|
+
route: input.route,
|
|
8782
|
+
targetProviderMessageId,
|
|
8783
|
+
text
|
|
8784
|
+
});
|
|
8785
|
+
return Object.freeze({
|
|
8786
|
+
schemaVersion: CONVERSATION_INTEGRATION_SCHEMA_VERSION,
|
|
8787
|
+
operationId,
|
|
8788
|
+
logicalOperationKey,
|
|
8789
|
+
requestDigest,
|
|
8790
|
+
installation: input.installation,
|
|
8791
|
+
route: input.route,
|
|
8792
|
+
kind: input.kind,
|
|
8793
|
+
targetProviderMessageId,
|
|
8794
|
+
text
|
|
8795
|
+
});
|
|
8796
|
+
}
|
|
8797
|
+
function normalizeConversationProviderReceipt(input) {
|
|
8798
|
+
assertExactObject(input, "provider receipt", [
|
|
8799
|
+
"command",
|
|
8800
|
+
"providerMessageId",
|
|
8801
|
+
"providerReceiptId",
|
|
8802
|
+
"observedAt"
|
|
8803
|
+
]);
|
|
8804
|
+
assertConversationDeliveryCommand(input.command);
|
|
8805
|
+
const providerMessageId = providerOpaqueId(
|
|
8806
|
+
input.command.installation.provider,
|
|
8807
|
+
"message",
|
|
8808
|
+
input.providerMessageId
|
|
8809
|
+
);
|
|
8810
|
+
if (input.command.targetProviderMessageId && providerMessageId.value !== input.command.targetProviderMessageId.value) {
|
|
8811
|
+
throw new TypeError("provider receipt message does not match the mutation target");
|
|
8812
|
+
}
|
|
8813
|
+
const providerReceiptId = input.providerReceiptId === void 0 || input.providerReceiptId === null ? null : providerOpaqueId(input.command.installation.provider, "receipt", input.providerReceiptId);
|
|
8814
|
+
return Object.freeze({
|
|
8815
|
+
schemaVersion: CONVERSATION_INTEGRATION_SCHEMA_VERSION,
|
|
8816
|
+
operationId: input.command.operationId,
|
|
8817
|
+
requestDigest: input.command.requestDigest,
|
|
8818
|
+
installation: input.command.installation,
|
|
8819
|
+
providerMessageId,
|
|
8820
|
+
providerReceiptId,
|
|
8821
|
+
observedAt: canonicalTimestamp(input.observedAt, "provider receipt observedAt")
|
|
8822
|
+
});
|
|
8823
|
+
}
|
|
8824
|
+
function normalizeConversationDeliveryOutcome(input) {
|
|
8825
|
+
if (input.status === "success") {
|
|
8826
|
+
assertExactObject(input, "delivery success", ["status", "receipt"]);
|
|
8827
|
+
assertConversationProviderReceipt(input.receipt);
|
|
8828
|
+
return Object.freeze({ status: "success", receipt: input.receipt });
|
|
8829
|
+
}
|
|
8830
|
+
if (input.status === "retryable_failure") {
|
|
8831
|
+
assertExactObject(input, "retryable delivery failure", ["status", "code", "retryAfterMs"]);
|
|
8832
|
+
const retryAfterMs = input.retryAfterMs ?? null;
|
|
8833
|
+
if (retryAfterMs !== null && (!Number.isSafeInteger(retryAfterMs) || retryAfterMs < 0)) {
|
|
8834
|
+
throw new RangeError("retryAfterMs must be a non-negative safe integer or null");
|
|
8835
|
+
}
|
|
8836
|
+
return Object.freeze({
|
|
8837
|
+
status: "retryable_failure",
|
|
8838
|
+
code: outcomeCode(input.code),
|
|
8839
|
+
retryAfterMs
|
|
8840
|
+
});
|
|
8841
|
+
}
|
|
8842
|
+
if (input.status !== "not_started" && input.status !== "unknown" && input.status !== "permanent_failure") {
|
|
8843
|
+
throw new TypeError("delivery outcome status is unsupported");
|
|
8844
|
+
}
|
|
8845
|
+
assertExactObject(input, "delivery outcome", ["status", "code"]);
|
|
8846
|
+
return Object.freeze({ status: input.status, code: outcomeCode(input.code) });
|
|
8847
|
+
}
|
|
8848
|
+
function conversationDeliveryNextAction(outcome) {
|
|
8849
|
+
assertConversationDeliveryOutcome(outcome);
|
|
8850
|
+
if (outcome.status === "unknown") return "reconcile";
|
|
8851
|
+
if (outcome.status === "not_started" || outcome.status === "retryable_failure") {
|
|
8852
|
+
return "retry_same_operation";
|
|
8853
|
+
}
|
|
8854
|
+
return outcome.status === "success" ? "complete" : "stop";
|
|
8855
|
+
}
|
|
8856
|
+
function assertConversationDeliveryMayRetry(outcome) {
|
|
8857
|
+
const action = conversationDeliveryNextAction(outcome);
|
|
8858
|
+
if (action === "retry_same_operation") return;
|
|
8859
|
+
if (action === "reconcile") {
|
|
8860
|
+
throw new Error("unknown delivery outcome must be reconciled before retry");
|
|
8861
|
+
}
|
|
8862
|
+
throw new Error(`delivery outcome ${outcome.status} may not be retried`);
|
|
8863
|
+
}
|
|
8864
|
+
function assertConversationDeliveryCommandMatches(expected, candidate) {
|
|
8865
|
+
assertConversationDeliveryCommand(expected);
|
|
8866
|
+
assertConversationDeliveryCommand(candidate);
|
|
8867
|
+
if (expected.operationId !== candidate.operationId) {
|
|
8868
|
+
throw new Error("delivery operation identity does not match");
|
|
8869
|
+
}
|
|
8870
|
+
if (expected.requestDigest !== candidate.requestDigest) {
|
|
8871
|
+
throw new Error("delivery operation identity was reused for different request content");
|
|
8872
|
+
}
|
|
8873
|
+
}
|
|
8874
|
+
function assertConversationInboundEnvelope(value) {
|
|
8875
|
+
assertExactObject(value, "normalized inbound envelope", [
|
|
8876
|
+
"schemaVersion",
|
|
8877
|
+
"installation",
|
|
8878
|
+
"actor",
|
|
8879
|
+
"route",
|
|
8880
|
+
"providerEventId",
|
|
8881
|
+
"providerMessageId",
|
|
8882
|
+
"eventIdentity",
|
|
8883
|
+
"occurredAt",
|
|
8884
|
+
"signal",
|
|
8885
|
+
"text",
|
|
8886
|
+
"attachments"
|
|
8887
|
+
]);
|
|
8888
|
+
if (value.schemaVersion !== CONVERSATION_INTEGRATION_SCHEMA_VERSION) {
|
|
8889
|
+
throw new TypeError("inbound envelope schema version is unsupported");
|
|
8890
|
+
}
|
|
8891
|
+
assertConversationInstallationIdentity(value.installation);
|
|
8892
|
+
assertConversationActor(value.actor);
|
|
8893
|
+
assertConversationRoute(value.route);
|
|
8894
|
+
assertProviderOpaqueId(value.providerEventId, "event");
|
|
8895
|
+
assertProviderOpaqueId(value.providerMessageId, "message");
|
|
8896
|
+
assertSameInstallation(value.installation, value.actor.installation, "inbound actor");
|
|
8897
|
+
assertSameInstallation(value.installation, value.route.installation, "inbound route");
|
|
8898
|
+
assertSameProvider(value.installation.provider, value.providerEventId.provider, "event id");
|
|
8899
|
+
assertSameProvider(value.installation.provider, value.providerMessageId.provider, "message id");
|
|
8900
|
+
const expectedEventIdentity = deriveConversationEventIdentity(
|
|
8901
|
+
value.installation,
|
|
8902
|
+
value.providerEventId
|
|
8903
|
+
);
|
|
8904
|
+
if (value.eventIdentity !== expectedEventIdentity) {
|
|
8905
|
+
throw new TypeError("inbound event identity is not canonical");
|
|
8906
|
+
}
|
|
8907
|
+
canonicalTimestamp(value.occurredAt, "inbound occurredAt");
|
|
8908
|
+
const signal = normalizeSignal(value.signal);
|
|
8909
|
+
const text = boundedExactText(value.text, "inbound text", true);
|
|
8910
|
+
if (!Array.isArray(value.attachments))
|
|
8911
|
+
throw new TypeError("inbound attachments must be an array");
|
|
8912
|
+
if (value.attachments.length > CONVERSATION_ATTACHMENT_MAX_COUNT) {
|
|
8913
|
+
throw new RangeError("inbound attachment count exceeds the contract bound");
|
|
8914
|
+
}
|
|
8915
|
+
for (const attachment of value.attachments) {
|
|
8916
|
+
assertConversationAttachmentReference(attachment);
|
|
8917
|
+
assertSameProvider(
|
|
8918
|
+
value.installation.provider,
|
|
8919
|
+
attachment.providerAttachmentId.provider,
|
|
8920
|
+
"attachment id"
|
|
8921
|
+
);
|
|
8922
|
+
}
|
|
8923
|
+
if ((signal.kind === "start" || signal.kind === "continue") && !text && !value.attachments.length) {
|
|
8924
|
+
throw new TypeError("start and continue signals require text or an attachment reference");
|
|
8925
|
+
}
|
|
8926
|
+
}
|
|
8927
|
+
function assertConversationDeliveryCommand(value) {
|
|
8928
|
+
assertExactObject(value, "normalized delivery command", [
|
|
8929
|
+
"schemaVersion",
|
|
8930
|
+
"operationId",
|
|
8931
|
+
"logicalOperationKey",
|
|
8932
|
+
"requestDigest",
|
|
8933
|
+
"installation",
|
|
8934
|
+
"route",
|
|
8935
|
+
"kind",
|
|
8936
|
+
"targetProviderMessageId",
|
|
8937
|
+
"text"
|
|
8938
|
+
]);
|
|
8939
|
+
if (value.schemaVersion !== CONVERSATION_INTEGRATION_SCHEMA_VERSION) {
|
|
8940
|
+
throw new TypeError("delivery command schema version is unsupported");
|
|
8941
|
+
}
|
|
8942
|
+
if (value.kind !== "post" && value.kind !== "update" && value.kind !== "delete") {
|
|
8943
|
+
throw new TypeError("delivery command kind is unsupported");
|
|
8944
|
+
}
|
|
8945
|
+
assertConversationInstallationIdentity(value.installation);
|
|
8946
|
+
assertConversationRoute(value.route);
|
|
8947
|
+
assertSameInstallation(value.installation, value.route.installation, "delivery route");
|
|
8948
|
+
const logicalOperationKey = boundedOpaqueString(
|
|
8949
|
+
value.logicalOperationKey,
|
|
8950
|
+
"logical operation key",
|
|
8951
|
+
CONVERSATION_OPERATION_KEY_MAX_UTF8_BYTES
|
|
8952
|
+
);
|
|
8953
|
+
if (value.operationId !== deriveConversationOperationIdentity(value.installation, logicalOperationKey)) {
|
|
8954
|
+
throw new TypeError("delivery operation identity is not canonical");
|
|
8955
|
+
}
|
|
8956
|
+
let target;
|
|
8957
|
+
let text;
|
|
8958
|
+
if (value.kind === "post") {
|
|
8959
|
+
if (value.targetProviderMessageId !== null) {
|
|
8960
|
+
throw new TypeError("post command cannot carry a target message");
|
|
8961
|
+
}
|
|
8962
|
+
target = null;
|
|
8963
|
+
text = boundedExactText(value.text, "delivery text", false);
|
|
8964
|
+
} else {
|
|
8965
|
+
assertProviderOpaqueId(value.targetProviderMessageId, "message");
|
|
8966
|
+
target = value.targetProviderMessageId;
|
|
8967
|
+
assertSameProvider(value.installation.provider, target.provider, "delivery target");
|
|
8968
|
+
if (value.kind === "delete") {
|
|
8969
|
+
if (value.text !== null) throw new TypeError("delete command cannot carry text");
|
|
8970
|
+
text = null;
|
|
8971
|
+
} else {
|
|
8972
|
+
text = boundedExactText(value.text, "delivery text", false);
|
|
8973
|
+
}
|
|
8974
|
+
}
|
|
8975
|
+
const expectedDigest = deliveryRequestDigest({
|
|
8976
|
+
kind: value.kind,
|
|
8977
|
+
installation: value.installation,
|
|
8978
|
+
route: value.route,
|
|
8979
|
+
targetProviderMessageId: target,
|
|
8980
|
+
text
|
|
8981
|
+
});
|
|
8982
|
+
if (value.requestDigest !== expectedDigest) {
|
|
8983
|
+
throw new TypeError("delivery request digest is not canonical");
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
function assertConversationProviderReceipt(value) {
|
|
8987
|
+
assertExactObject(value, "normalized provider receipt", [
|
|
8988
|
+
"schemaVersion",
|
|
8989
|
+
"operationId",
|
|
8990
|
+
"requestDigest",
|
|
8991
|
+
"installation",
|
|
8992
|
+
"providerMessageId",
|
|
8993
|
+
"providerReceiptId",
|
|
8994
|
+
"observedAt"
|
|
8995
|
+
]);
|
|
8996
|
+
if (value.schemaVersion !== CONVERSATION_INTEGRATION_SCHEMA_VERSION) {
|
|
8997
|
+
throw new TypeError("provider receipt schema version is unsupported");
|
|
8998
|
+
}
|
|
8999
|
+
assertIdentity(value.operationId, /^ciop1_[0-9a-f]{64}$/u, "provider receipt operation id");
|
|
9000
|
+
assertIdentity(value.requestDigest, /^sha256:[0-9a-f]{64}$/u, "provider receipt request digest");
|
|
9001
|
+
assertConversationInstallationIdentity(value.installation);
|
|
9002
|
+
assertProviderOpaqueId(value.providerMessageId, "message");
|
|
9003
|
+
assertSameProvider(
|
|
9004
|
+
value.installation.provider,
|
|
9005
|
+
value.providerMessageId.provider,
|
|
9006
|
+
"receipt message"
|
|
9007
|
+
);
|
|
9008
|
+
if (value.providerReceiptId !== null) {
|
|
9009
|
+
assertProviderOpaqueId(value.providerReceiptId, "receipt");
|
|
9010
|
+
assertSameProvider(
|
|
9011
|
+
value.installation.provider,
|
|
9012
|
+
value.providerReceiptId.provider,
|
|
9013
|
+
"provider receipt id"
|
|
9014
|
+
);
|
|
9015
|
+
}
|
|
9016
|
+
canonicalTimestamp(value.observedAt, "provider receipt observedAt");
|
|
9017
|
+
}
|
|
9018
|
+
function assertConversationDeliveryOutcome(value) {
|
|
9019
|
+
normalizeConversationDeliveryOutcome(value);
|
|
9020
|
+
}
|
|
9021
|
+
function canonicalConversationInboundEnvelopeJson(envelope) {
|
|
9022
|
+
assertConversationInboundEnvelope(envelope);
|
|
9023
|
+
return JSON.stringify({
|
|
9024
|
+
schemaVersion: envelope.schemaVersion,
|
|
9025
|
+
installation: providerIdWire(envelope.installation),
|
|
9026
|
+
actor: actorWire(envelope.actor),
|
|
9027
|
+
route: routeWire(envelope.route),
|
|
9028
|
+
providerEventId: providerIdWire(envelope.providerEventId),
|
|
9029
|
+
providerMessageId: providerIdWire(envelope.providerMessageId),
|
|
9030
|
+
eventIdentity: envelope.eventIdentity,
|
|
9031
|
+
occurredAt: envelope.occurredAt,
|
|
9032
|
+
signal: signalWire(envelope.signal),
|
|
9033
|
+
text: envelope.text,
|
|
9034
|
+
attachments: envelope.attachments.map(attachmentWire)
|
|
9035
|
+
});
|
|
9036
|
+
}
|
|
9037
|
+
function canonicalConversationDeliveryCommandJson(command) {
|
|
9038
|
+
assertConversationDeliveryCommand(command);
|
|
9039
|
+
return JSON.stringify(deliveryCommandWire(command));
|
|
9040
|
+
}
|
|
9041
|
+
function canonicalConversationProviderReceiptJson(receipt2) {
|
|
9042
|
+
assertConversationProviderReceipt(receipt2);
|
|
9043
|
+
return JSON.stringify(providerReceiptWire(receipt2));
|
|
9044
|
+
}
|
|
9045
|
+
function canonicalConversationDeliveryOutcomeJson(outcome) {
|
|
9046
|
+
assertConversationDeliveryOutcome(outcome);
|
|
9047
|
+
if (outcome.status === "success") {
|
|
9048
|
+
return JSON.stringify({ status: "success", receipt: providerReceiptWire(outcome.receipt) });
|
|
9049
|
+
}
|
|
9050
|
+
if (outcome.status === "retryable_failure") {
|
|
9051
|
+
return JSON.stringify({
|
|
9052
|
+
status: outcome.status,
|
|
9053
|
+
code: outcome.code,
|
|
9054
|
+
retryAfterMs: outcome.retryAfterMs
|
|
9055
|
+
});
|
|
9056
|
+
}
|
|
9057
|
+
return JSON.stringify({ status: outcome.status, code: outcome.code });
|
|
9058
|
+
}
|
|
9059
|
+
function normalizeSignal(input) {
|
|
9060
|
+
assertExactObject(input, "conversation signal", ["kind", "control"]);
|
|
9061
|
+
if (input.kind === "start" || input.kind === "continue") {
|
|
9062
|
+
assertExactObject(input, "conversation signal", ["kind"]);
|
|
9063
|
+
return Object.freeze({ kind: input.kind });
|
|
9064
|
+
}
|
|
9065
|
+
if (input.kind === "control") {
|
|
9066
|
+
assertExactObject(input, "conversation control signal", ["kind", "control"]);
|
|
9067
|
+
if (input.control !== "stop" && input.control !== "resume") {
|
|
9068
|
+
throw new TypeError("conversation control signal is unsupported");
|
|
9069
|
+
}
|
|
9070
|
+
return Object.freeze({ kind: "control", control: input.control });
|
|
9071
|
+
}
|
|
9072
|
+
throw new TypeError("conversation signal is unsupported");
|
|
9073
|
+
}
|
|
9074
|
+
function normalizeAttachment(provider, input) {
|
|
9075
|
+
assertExactObject(input, "attachment reference", [
|
|
9076
|
+
"providerAttachmentId",
|
|
9077
|
+
"fileName",
|
|
9078
|
+
"mediaType",
|
|
9079
|
+
"byteSize",
|
|
9080
|
+
"contentSha256"
|
|
9081
|
+
]);
|
|
9082
|
+
if (ABSOLUTE_URL_PATTERN.test(input.providerAttachmentId) || input.providerAttachmentId.startsWith("//")) {
|
|
9083
|
+
throw new TypeError("attachment references cannot contain provider URLs");
|
|
9084
|
+
}
|
|
9085
|
+
const providerAttachmentId = providerOpaqueId(provider, "attachment", input.providerAttachmentId);
|
|
9086
|
+
const fileName = nullableBoundedString(
|
|
9087
|
+
input.fileName,
|
|
9088
|
+
"attachment fileName",
|
|
9089
|
+
CONVERSATION_ATTACHMENT_NAME_MAX_UTF8_BYTES
|
|
9090
|
+
);
|
|
9091
|
+
if (fileName?.includes("/") || fileName?.includes("\\")) {
|
|
9092
|
+
throw new TypeError("attachment fileName must not contain a path");
|
|
9093
|
+
}
|
|
9094
|
+
const mediaType = nullableBoundedString(
|
|
9095
|
+
input.mediaType,
|
|
9096
|
+
"attachment mediaType",
|
|
9097
|
+
CONVERSATION_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES
|
|
9098
|
+
);
|
|
9099
|
+
if (mediaType !== null && !MEDIA_TYPE_PATTERN.test(mediaType)) {
|
|
9100
|
+
throw new TypeError("attachment mediaType is invalid");
|
|
9101
|
+
}
|
|
9102
|
+
const byteSize = input.byteSize ?? null;
|
|
9103
|
+
if (byteSize !== null && (!Number.isSafeInteger(byteSize) || byteSize < 0)) {
|
|
9104
|
+
throw new RangeError("attachment byteSize must be a non-negative safe integer or null");
|
|
9105
|
+
}
|
|
9106
|
+
const contentSha256 = input.contentSha256 ?? null;
|
|
9107
|
+
if (contentSha256 !== null && !SHA256_PATTERN.test(contentSha256)) {
|
|
9108
|
+
throw new TypeError("attachment contentSha256 must be 64 lowercase hexadecimal characters");
|
|
9109
|
+
}
|
|
9110
|
+
return Object.freeze({
|
|
9111
|
+
providerAttachmentId,
|
|
9112
|
+
fileName,
|
|
9113
|
+
mediaType,
|
|
9114
|
+
byteSize,
|
|
9115
|
+
contentSha256
|
|
9116
|
+
});
|
|
9117
|
+
}
|
|
9118
|
+
function providerNamespace(value) {
|
|
9119
|
+
const bounded = boundedOpaqueString(
|
|
9120
|
+
value,
|
|
9121
|
+
"provider namespace",
|
|
9122
|
+
CONVERSATION_PROVIDER_NAMESPACE_MAX_UTF8_BYTES
|
|
9123
|
+
);
|
|
9124
|
+
if (!PROVIDER_NAMESPACE_PATTERN.test(bounded)) {
|
|
9125
|
+
throw new TypeError("provider namespace must be canonical lowercase segments");
|
|
9126
|
+
}
|
|
9127
|
+
return bounded;
|
|
9128
|
+
}
|
|
9129
|
+
function providerOpaqueId(provider, kind, value) {
|
|
9130
|
+
return Object.freeze({
|
|
9131
|
+
provider,
|
|
9132
|
+
kind,
|
|
9133
|
+
value: boundedOpaqueString(
|
|
9134
|
+
value,
|
|
9135
|
+
`provider ${kind} id`,
|
|
9136
|
+
CONVERSATION_PROVIDER_OPAQUE_ID_MAX_UTF8_BYTES
|
|
9137
|
+
)
|
|
9138
|
+
});
|
|
9139
|
+
}
|
|
9140
|
+
function deriveConversationEventIdentity(installation, providerEventId) {
|
|
9141
|
+
return `ciev1_${hashParts("opengeni:conversation-event:v1", [
|
|
9142
|
+
installation.provider,
|
|
9143
|
+
installation.value,
|
|
9144
|
+
providerEventId.value
|
|
9145
|
+
])}`;
|
|
9146
|
+
}
|
|
9147
|
+
function deriveConversationRouteIdentity(installation, conversationId, threadId) {
|
|
9148
|
+
return `cirt1_${hashParts("opengeni:conversation-route:v1", [
|
|
9149
|
+
installation.provider,
|
|
9150
|
+
installation.value,
|
|
9151
|
+
conversationId.value,
|
|
9152
|
+
threadId?.value ?? null
|
|
9153
|
+
])}`;
|
|
9154
|
+
}
|
|
9155
|
+
function deriveConversationActorIdentity(installation, actorId) {
|
|
9156
|
+
return `ciac1_${hashParts("opengeni:conversation-actor:v1", [
|
|
9157
|
+
installation.provider,
|
|
9158
|
+
installation.value,
|
|
9159
|
+
actorId.value
|
|
9160
|
+
])}`;
|
|
9161
|
+
}
|
|
9162
|
+
function deriveConversationOperationIdentity(installation, logicalOperationKey) {
|
|
9163
|
+
return `ciop1_${hashParts("opengeni:conversation-operation:v1", [
|
|
9164
|
+
installation.provider,
|
|
9165
|
+
installation.value,
|
|
9166
|
+
logicalOperationKey
|
|
9167
|
+
])}`;
|
|
9168
|
+
}
|
|
9169
|
+
function deliveryRequestDigest(input) {
|
|
9170
|
+
return `sha256:${createHash5("sha256").update("opengeni:conversation-delivery-request:v1\0", "utf8").update(
|
|
9171
|
+
JSON.stringify({
|
|
9172
|
+
kind: input.kind,
|
|
9173
|
+
installation: providerIdWire(input.installation),
|
|
9174
|
+
route: routeWire(input.route),
|
|
9175
|
+
targetProviderMessageId: input.targetProviderMessageId ? providerIdWire(input.targetProviderMessageId) : null,
|
|
9176
|
+
text: input.text
|
|
9177
|
+
}),
|
|
9178
|
+
"utf8"
|
|
9179
|
+
).digest("hex")}`;
|
|
9180
|
+
}
|
|
9181
|
+
function hashParts(domain, parts) {
|
|
9182
|
+
const hash = createHash5("sha256");
|
|
9183
|
+
hash.update(domain, "utf8").update("\0", "utf8");
|
|
9184
|
+
for (const part of parts) {
|
|
9185
|
+
if (part === null) {
|
|
9186
|
+
hash.update("n:", "utf8");
|
|
9187
|
+
continue;
|
|
9188
|
+
}
|
|
9189
|
+
const bytes = textEncoder.encode(part);
|
|
9190
|
+
hash.update(`s:${bytes.byteLength}:`, "utf8").update(bytes);
|
|
9191
|
+
}
|
|
9192
|
+
return hash.digest("hex");
|
|
9193
|
+
}
|
|
9194
|
+
function assertConversationInstallationIdentity(value) {
|
|
9195
|
+
assertProviderOpaqueId(value, "installation");
|
|
9196
|
+
}
|
|
9197
|
+
function assertConversationActor(value) {
|
|
9198
|
+
assertExactObject(value, "normalized conversation actor", [
|
|
9199
|
+
"installation",
|
|
9200
|
+
"providerActorId",
|
|
9201
|
+
"kind",
|
|
9202
|
+
"identity"
|
|
9203
|
+
]);
|
|
9204
|
+
assertConversationInstallationIdentity(value.installation);
|
|
9205
|
+
assertProviderOpaqueId(value.providerActorId, "actor");
|
|
9206
|
+
if (value.kind !== "human" && value.kind !== "bot" && value.kind !== "system") {
|
|
9207
|
+
throw new TypeError("conversation actor kind is unsupported");
|
|
9208
|
+
}
|
|
9209
|
+
assertSameProvider(value.installation.provider, value.providerActorId.provider, "actor id");
|
|
9210
|
+
if (value.identity !== deriveConversationActorIdentity(value.installation, value.providerActorId)) {
|
|
9211
|
+
throw new TypeError("conversation actor identity is not canonical");
|
|
9212
|
+
}
|
|
9213
|
+
}
|
|
9214
|
+
function assertConversationRoute(value) {
|
|
9215
|
+
assertExactObject(value, "normalized conversation route", [
|
|
9216
|
+
"installation",
|
|
9217
|
+
"providerConversationId",
|
|
9218
|
+
"providerThreadId",
|
|
9219
|
+
"identity"
|
|
9220
|
+
]);
|
|
9221
|
+
assertConversationInstallationIdentity(value.installation);
|
|
9222
|
+
assertProviderOpaqueId(value.providerConversationId, "conversation");
|
|
9223
|
+
if (value.providerThreadId !== null) assertProviderOpaqueId(value.providerThreadId, "thread");
|
|
9224
|
+
assertSameProvider(
|
|
9225
|
+
value.installation.provider,
|
|
9226
|
+
value.providerConversationId.provider,
|
|
9227
|
+
"conversation id"
|
|
9228
|
+
);
|
|
9229
|
+
if (value.providerThreadId) {
|
|
9230
|
+
assertSameProvider(value.installation.provider, value.providerThreadId.provider, "thread id");
|
|
9231
|
+
}
|
|
9232
|
+
if (value.identity !== deriveConversationRouteIdentity(
|
|
9233
|
+
value.installation,
|
|
9234
|
+
value.providerConversationId,
|
|
9235
|
+
value.providerThreadId
|
|
9236
|
+
)) {
|
|
9237
|
+
throw new TypeError("conversation route identity is not canonical");
|
|
9238
|
+
}
|
|
9239
|
+
}
|
|
9240
|
+
function assertConversationAttachmentReference(value) {
|
|
9241
|
+
assertExactObject(value, "normalized attachment reference", [
|
|
9242
|
+
"providerAttachmentId",
|
|
9243
|
+
"fileName",
|
|
9244
|
+
"mediaType",
|
|
9245
|
+
"byteSize",
|
|
9246
|
+
"contentSha256"
|
|
9247
|
+
]);
|
|
9248
|
+
assertProviderOpaqueId(value.providerAttachmentId, "attachment");
|
|
9249
|
+
if (ABSOLUTE_URL_PATTERN.test(value.providerAttachmentId.value) || value.providerAttachmentId.value.startsWith("//")) {
|
|
9250
|
+
throw new TypeError("attachment references cannot contain provider URLs");
|
|
9251
|
+
}
|
|
9252
|
+
const fileName = nullableBoundedString(
|
|
9253
|
+
value.fileName,
|
|
9254
|
+
"attachment fileName",
|
|
9255
|
+
CONVERSATION_ATTACHMENT_NAME_MAX_UTF8_BYTES
|
|
9256
|
+
);
|
|
9257
|
+
if (fileName?.includes("/") || fileName?.includes("\\")) {
|
|
9258
|
+
throw new TypeError("attachment fileName must not contain a path");
|
|
9259
|
+
}
|
|
9260
|
+
const mediaType = nullableBoundedString(
|
|
9261
|
+
value.mediaType,
|
|
9262
|
+
"attachment mediaType",
|
|
9263
|
+
CONVERSATION_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES
|
|
9264
|
+
);
|
|
9265
|
+
if (mediaType !== null && !MEDIA_TYPE_PATTERN.test(mediaType)) {
|
|
9266
|
+
throw new TypeError("attachment mediaType is invalid");
|
|
9267
|
+
}
|
|
9268
|
+
const byteSize = value.byteSize;
|
|
9269
|
+
if (byteSize !== null) {
|
|
9270
|
+
if (typeof byteSize !== "number" || !Number.isSafeInteger(byteSize) || byteSize < 0) {
|
|
9271
|
+
throw new RangeError("attachment byteSize must be a non-negative safe integer or null");
|
|
9272
|
+
}
|
|
9273
|
+
}
|
|
9274
|
+
const contentSha256 = value.contentSha256;
|
|
9275
|
+
if (contentSha256 !== null) {
|
|
9276
|
+
if (typeof contentSha256 !== "string" || !SHA256_PATTERN.test(contentSha256)) {
|
|
9277
|
+
throw new TypeError("attachment contentSha256 must be 64 lowercase hexadecimal characters");
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9280
|
+
}
|
|
9281
|
+
function assertProviderOpaqueId(value, kind) {
|
|
9282
|
+
assertExactObject(value, `provider ${kind} id`, ["provider", "kind", "value"]);
|
|
9283
|
+
providerNamespace(value.provider);
|
|
9284
|
+
if (value.kind !== kind) throw new TypeError(`provider id must have kind ${kind}`);
|
|
9285
|
+
boundedOpaqueString(
|
|
9286
|
+
value.value,
|
|
9287
|
+
`provider ${kind} id`,
|
|
9288
|
+
CONVERSATION_PROVIDER_OPAQUE_ID_MAX_UTF8_BYTES
|
|
9289
|
+
);
|
|
9290
|
+
}
|
|
9291
|
+
function assertSameProvider(expected, actual, label) {
|
|
9292
|
+
if (expected !== actual) throw new TypeError(`${label} provider namespace does not match`);
|
|
9293
|
+
}
|
|
9294
|
+
function assertSameInstallation(expected, actual, label) {
|
|
9295
|
+
if (expected.provider !== actual.provider || expected.value !== actual.value) {
|
|
9296
|
+
throw new TypeError(`${label} installation identity does not match`);
|
|
9297
|
+
}
|
|
9298
|
+
}
|
|
9299
|
+
function boundedExactText(value, label, allowEmpty) {
|
|
9300
|
+
return boundedString(value, label, allowEmpty ? 0 : 1, CONVERSATION_TEXT_MAX_UTF8_BYTES, true);
|
|
9301
|
+
}
|
|
9302
|
+
function boundedOpaqueString(value, label, maxBytes) {
|
|
9303
|
+
const result = boundedString(value, label, 1, maxBytes, false);
|
|
9304
|
+
if (result !== result.trim())
|
|
9305
|
+
throw new TypeError(`${label} must not have surrounding whitespace`);
|
|
9306
|
+
return result;
|
|
9307
|
+
}
|
|
9308
|
+
function nullableBoundedString(value, label, maxBytes) {
|
|
9309
|
+
if (value === void 0 || value === null) return null;
|
|
9310
|
+
return boundedString(value, label, 1, maxBytes, false);
|
|
9311
|
+
}
|
|
9312
|
+
function boundedString(value, label, minBytes, maxBytes, allowNewlines) {
|
|
9313
|
+
if (typeof value !== "string") throw new TypeError(`${label} must be a string`);
|
|
9314
|
+
if (!isWellFormedUnicode(value)) throw new TypeError(`${label} must be well-formed Unicode`);
|
|
9315
|
+
if (value.includes("\0")) throw new TypeError(`${label} must not contain NUL`);
|
|
9316
|
+
if (!allowNewlines && /[\u0001-\u001f\u007f]/u.test(value)) {
|
|
9317
|
+
throw new TypeError(`${label} must not contain control characters`);
|
|
9318
|
+
}
|
|
9319
|
+
const byteLength = textEncoder.encode(value).byteLength;
|
|
9320
|
+
if (byteLength < minBytes || byteLength > maxBytes) {
|
|
9321
|
+
throw new RangeError(`${label} must contain ${minBytes}-${maxBytes} UTF-8 bytes`);
|
|
9322
|
+
}
|
|
9323
|
+
return value;
|
|
9324
|
+
}
|
|
9325
|
+
function isWellFormedUnicode(value) {
|
|
9326
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
9327
|
+
const code = value.charCodeAt(index);
|
|
9328
|
+
if (code >= 55296 && code <= 56319) {
|
|
9329
|
+
const next = value.charCodeAt(index + 1);
|
|
9330
|
+
if (!(next >= 56320 && next <= 57343)) return false;
|
|
9331
|
+
index += 1;
|
|
9332
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
9333
|
+
return false;
|
|
9334
|
+
}
|
|
9335
|
+
}
|
|
9336
|
+
return true;
|
|
9337
|
+
}
|
|
9338
|
+
function canonicalTimestamp(value, label) {
|
|
9339
|
+
const timestamp = boundedString(value, label, 20, 32, false);
|
|
9340
|
+
const milliseconds = Date.parse(timestamp);
|
|
9341
|
+
if (!Number.isFinite(milliseconds) || new Date(milliseconds).toISOString() !== timestamp) {
|
|
9342
|
+
throw new TypeError(`${label} must be a canonical UTC timestamp`);
|
|
9343
|
+
}
|
|
9344
|
+
return timestamp;
|
|
9345
|
+
}
|
|
9346
|
+
function outcomeCode(value) {
|
|
9347
|
+
const code = boundedOpaqueString(
|
|
9348
|
+
value,
|
|
9349
|
+
"delivery outcome code",
|
|
9350
|
+
CONVERSATION_OUTCOME_CODE_MAX_UTF8_BYTES
|
|
9351
|
+
);
|
|
9352
|
+
if (!OUTCOME_CODE_PATTERN.test(code)) {
|
|
9353
|
+
throw new TypeError("delivery outcome code must use canonical lowercase segments");
|
|
9354
|
+
}
|
|
9355
|
+
return code;
|
|
9356
|
+
}
|
|
9357
|
+
function assertExactObject(value, label, allowedKeys) {
|
|
9358
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
9359
|
+
throw new TypeError(`${label} must be an object`);
|
|
9360
|
+
}
|
|
9361
|
+
const prototype = Object.getPrototypeOf(value);
|
|
9362
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
9363
|
+
throw new TypeError(`${label} must be a plain object`);
|
|
9364
|
+
}
|
|
9365
|
+
const allowed = new Set(allowedKeys);
|
|
9366
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
9367
|
+
if (typeof key !== "string" || !allowed.has(key)) {
|
|
9368
|
+
throw new TypeError(`${label} contains an unsupported field`);
|
|
9369
|
+
}
|
|
9370
|
+
}
|
|
9371
|
+
}
|
|
9372
|
+
function assertIdentity(value, pattern, label) {
|
|
9373
|
+
if (typeof value !== "string" || !pattern.test(value)) {
|
|
9374
|
+
throw new TypeError(`${label} is invalid`);
|
|
9375
|
+
}
|
|
9376
|
+
}
|
|
9377
|
+
function providerIdWire(id) {
|
|
9378
|
+
return { provider: id.provider, kind: id.kind, value: id.value };
|
|
9379
|
+
}
|
|
9380
|
+
function actorWire(actor) {
|
|
9381
|
+
return {
|
|
9382
|
+
installation: providerIdWire(actor.installation),
|
|
9383
|
+
providerActorId: providerIdWire(actor.providerActorId),
|
|
9384
|
+
kind: actor.kind,
|
|
9385
|
+
identity: actor.identity
|
|
9386
|
+
};
|
|
9387
|
+
}
|
|
9388
|
+
function routeWire(route) {
|
|
9389
|
+
return {
|
|
9390
|
+
installation: providerIdWire(route.installation),
|
|
9391
|
+
providerConversationId: providerIdWire(route.providerConversationId),
|
|
9392
|
+
providerThreadId: route.providerThreadId ? providerIdWire(route.providerThreadId) : null,
|
|
9393
|
+
identity: route.identity
|
|
9394
|
+
};
|
|
9395
|
+
}
|
|
9396
|
+
function signalWire(signal) {
|
|
9397
|
+
return signal.kind === "control" ? { kind: "control", control: signal.control } : { kind: signal.kind };
|
|
9398
|
+
}
|
|
9399
|
+
function attachmentWire(attachment) {
|
|
9400
|
+
return {
|
|
9401
|
+
providerAttachmentId: providerIdWire(attachment.providerAttachmentId),
|
|
9402
|
+
fileName: attachment.fileName,
|
|
9403
|
+
mediaType: attachment.mediaType,
|
|
9404
|
+
byteSize: attachment.byteSize,
|
|
9405
|
+
contentSha256: attachment.contentSha256
|
|
9406
|
+
};
|
|
9407
|
+
}
|
|
9408
|
+
function deliveryCommandWire(command) {
|
|
9409
|
+
return {
|
|
9410
|
+
schemaVersion: command.schemaVersion,
|
|
9411
|
+
operationId: command.operationId,
|
|
9412
|
+
logicalOperationKey: command.logicalOperationKey,
|
|
9413
|
+
requestDigest: command.requestDigest,
|
|
9414
|
+
installation: providerIdWire(command.installation),
|
|
9415
|
+
route: routeWire(command.route),
|
|
9416
|
+
kind: command.kind,
|
|
9417
|
+
targetProviderMessageId: command.targetProviderMessageId ? providerIdWire(command.targetProviderMessageId) : null,
|
|
9418
|
+
text: command.text
|
|
9419
|
+
};
|
|
9420
|
+
}
|
|
9421
|
+
function providerReceiptWire(receipt2) {
|
|
9422
|
+
return {
|
|
9423
|
+
schemaVersion: receipt2.schemaVersion,
|
|
9424
|
+
operationId: receipt2.operationId,
|
|
9425
|
+
requestDigest: receipt2.requestDigest,
|
|
9426
|
+
installation: providerIdWire(receipt2.installation),
|
|
9427
|
+
providerMessageId: providerIdWire(receipt2.providerMessageId),
|
|
9428
|
+
providerReceiptId: receipt2.providerReceiptId ? providerIdWire(receipt2.providerReceiptId) : null,
|
|
9429
|
+
observedAt: receipt2.observedAt
|
|
9430
|
+
};
|
|
9431
|
+
}
|
|
9432
|
+
|
|
8491
9433
|
// src/domain/workspace-members.ts
|
|
8492
|
-
import { HTTPException as
|
|
9434
|
+
import { HTTPException as HTTPException13 } from "hono/http-exception";
|
|
8493
9435
|
var MEMBER_ADMIN_PERMISSIONS = ["workspace:admin", "members:manage"];
|
|
8494
9436
|
function memberCanAdminister(member) {
|
|
8495
9437
|
return member.permissions.some((permission) => MEMBER_ADMIN_PERMISSIONS.includes(permission));
|
|
@@ -8499,25 +9441,25 @@ function isUserMember(member) {
|
|
|
8499
9441
|
}
|
|
8500
9442
|
function resolveMemberSubjectId(userId) {
|
|
8501
9443
|
if (!userId) {
|
|
8502
|
-
throw new
|
|
9444
|
+
throw new HTTPException13(404, { message: "user is not registered" });
|
|
8503
9445
|
}
|
|
8504
9446
|
return `user:${userId}`;
|
|
8505
9447
|
}
|
|
8506
9448
|
function assertWorkspaceMemberRemovable(input) {
|
|
8507
9449
|
const { members, subjectId, callerSubjectId } = input;
|
|
8508
9450
|
if (subjectId === callerSubjectId) {
|
|
8509
|
-
throw new
|
|
9451
|
+
throw new HTTPException13(409, { message: "you cannot remove your own membership" });
|
|
8510
9452
|
}
|
|
8511
9453
|
const target = members.find((member) => member.subjectId === subjectId);
|
|
8512
9454
|
if (!target) {
|
|
8513
|
-
throw new
|
|
9455
|
+
throw new HTTPException13(404, { message: "member not found" });
|
|
8514
9456
|
}
|
|
8515
9457
|
if (memberCanAdminister(target)) {
|
|
8516
9458
|
const remainingAdmins = members.filter(
|
|
8517
9459
|
(member) => member.subjectId !== subjectId && memberCanAdminister(member)
|
|
8518
9460
|
);
|
|
8519
9461
|
if (remainingAdmins.length === 0) {
|
|
8520
|
-
throw new
|
|
9462
|
+
throw new HTTPException13(409, {
|
|
8521
9463
|
message: "cannot remove the last member who can manage this workspace"
|
|
8522
9464
|
});
|
|
8523
9465
|
}
|
|
@@ -8525,10 +9467,10 @@ function assertWorkspaceMemberRemovable(input) {
|
|
|
8525
9467
|
}
|
|
8526
9468
|
function assertWorkspaceDeletable(input) {
|
|
8527
9469
|
if (input.workspaceCountForAccount <= 1) {
|
|
8528
|
-
throw new
|
|
9470
|
+
throw new HTTPException13(409, { message: "cannot delete the account's only workspace" });
|
|
8529
9471
|
}
|
|
8530
9472
|
if (input.activeSessionCount > 0) {
|
|
8531
|
-
throw new
|
|
9473
|
+
throw new HTTPException13(409, {
|
|
8532
9474
|
message: "stop the workspace's running sessions before deleting it"
|
|
8533
9475
|
});
|
|
8534
9476
|
}
|
|
@@ -8538,7 +9480,7 @@ function assertWorkspaceDeletable(input) {
|
|
|
8538
9480
|
import {
|
|
8539
9481
|
GenerateVideoToolInput
|
|
8540
9482
|
} from "@opengeni/contracts";
|
|
8541
|
-
import { createHash as
|
|
9483
|
+
import { createHash as createHash6 } from "crypto";
|
|
8542
9484
|
var VIDEO_GENERATION_ADAPTER_VERSION = "gateway-video-v4/1";
|
|
8543
9485
|
var TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
8544
9486
|
"completed",
|
|
@@ -8677,13 +9619,14 @@ function assertReferenceRoles(sourceMode, references) {
|
|
|
8677
9619
|
}
|
|
8678
9620
|
}
|
|
8679
9621
|
function sha2562(prefix, ...parts) {
|
|
8680
|
-
const hash =
|
|
9622
|
+
const hash = createHash6("sha256").update(prefix);
|
|
8681
9623
|
for (const part of parts) hash.update(part).update("\0");
|
|
8682
9624
|
return hash.digest("hex");
|
|
8683
9625
|
}
|
|
8684
9626
|
|
|
8685
9627
|
// src/domain/video-generation-capabilities.ts
|
|
8686
9628
|
import {
|
|
9629
|
+
GROK_IMAGINE_VIDEO_1_5_MODEL_ID,
|
|
8687
9630
|
SEEDANCE_2_5_MODEL_ID,
|
|
8688
9631
|
VideoGenerationCapabilities,
|
|
8689
9632
|
VideoGenerationPolicy
|
|
@@ -8702,10 +9645,32 @@ var VIDEO_GENERATION_MODEL_CATALOG = Object.freeze([
|
|
|
8702
9645
|
]),
|
|
8703
9646
|
resolutions: Object.freeze(["480p", "720p"]),
|
|
8704
9647
|
aspectRatios: Object.freeze(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"]),
|
|
8705
|
-
duration: Object.freeze({
|
|
9648
|
+
duration: Object.freeze({
|
|
9649
|
+
minSeconds: 4,
|
|
9650
|
+
maxSeconds: 30,
|
|
9651
|
+
stepSeconds: 1
|
|
9652
|
+
}),
|
|
9653
|
+
supportsAudio: true
|
|
9654
|
+
}),
|
|
9655
|
+
Object.freeze({
|
|
9656
|
+
modelId: GROK_IMAGINE_VIDEO_1_5_MODEL_ID,
|
|
9657
|
+
label: "Grok Imagine Video 1.5",
|
|
9658
|
+
providerLabel: "Connected SuperGrok",
|
|
9659
|
+
sourceModes: Object.freeze(["text", "first_frame", "image_reference"]),
|
|
9660
|
+
resolutions: Object.freeze(["480p", "720p"]),
|
|
9661
|
+
aspectRatios: Object.freeze(["16:9", "4:3", "1:1", "3:4", "9:16"]),
|
|
9662
|
+
duration: Object.freeze({
|
|
9663
|
+
minSeconds: 4,
|
|
9664
|
+
maxSeconds: 15,
|
|
9665
|
+
stepSeconds: 1
|
|
9666
|
+
}),
|
|
9667
|
+
// xAI emits audio but exposes no separate audio wire option.
|
|
8706
9668
|
supportsAudio: true
|
|
8707
9669
|
})
|
|
8708
9670
|
]);
|
|
9671
|
+
function videoGenerationModelSupportsFundingSource(modelId, fundingSource) {
|
|
9672
|
+
return modelId === GROK_IMAGINE_VIDEO_1_5_MODEL_ID ? fundingSource === "supergrok_subscription" : modelId === SEEDANCE_2_5_MODEL_ID ? fundingSource === "opengeni_credits" || fundingSource === "workspace_gateway" : false;
|
|
9673
|
+
}
|
|
8709
9674
|
function defaultVideoGenerationPolicy() {
|
|
8710
9675
|
return VideoGenerationPolicy.parse({
|
|
8711
9676
|
schemaVersion: 1,
|
|
@@ -8718,7 +9683,9 @@ function defaultVideoGenerationPolicy() {
|
|
|
8718
9683
|
function videoGenerationCapabilitiesForPolicy(input) {
|
|
8719
9684
|
const policy = VideoGenerationPolicy.parse(input.policy);
|
|
8720
9685
|
const enabled = new Set(policy.enabledModelIds);
|
|
8721
|
-
const models = VIDEO_GENERATION_MODEL_CATALOG.filter(
|
|
9686
|
+
const models = VIDEO_GENERATION_MODEL_CATALOG.filter(
|
|
9687
|
+
(model) => enabled.has(model.modelId) && videoGenerationModelSupportsFundingSource(model.modelId, policy.fundingSource)
|
|
9688
|
+
);
|
|
8722
9689
|
if (models.length === 0 || policy.defaultModelId === null) {
|
|
8723
9690
|
throw new Error("Video generation is disabled for this workspace");
|
|
8724
9691
|
}
|
|
@@ -8751,7 +9718,7 @@ import {
|
|
|
8751
9718
|
getEnrollment as getEnrollment4,
|
|
8752
9719
|
getRig as getRig5,
|
|
8753
9720
|
getSandbox as getSandbox4,
|
|
8754
|
-
getVariableSet as
|
|
9721
|
+
getVariableSet as getVariableSet4,
|
|
8755
9722
|
NewSessionDraftAccessError,
|
|
8756
9723
|
newSessionDraftToolsProvided,
|
|
8757
9724
|
publicNewSessionDraftOptions,
|
|
@@ -8759,7 +9726,7 @@ import {
|
|
|
8759
9726
|
saveNewSessionDraftInTransaction,
|
|
8760
9727
|
withWorkspaceSubjectRls
|
|
8761
9728
|
} from "@opengeni/db";
|
|
8762
|
-
import { HTTPException as
|
|
9729
|
+
import { HTTPException as HTTPException14 } from "hono/http-exception";
|
|
8763
9730
|
function hasOwn(value, key) {
|
|
8764
9731
|
return typeof value === "object" && value !== null && Object.hasOwn(value, key);
|
|
8765
9732
|
}
|
|
@@ -8810,7 +9777,7 @@ async function hydrateNewSessionDraft(deps, grant, workspaceId, row) {
|
|
|
8810
9777
|
}
|
|
8811
9778
|
const options = { ...mapped.options };
|
|
8812
9779
|
if (options.variableSetId) {
|
|
8813
|
-
if (!hasPermission(grant.permissions, "variable-sets:use") || !await
|
|
9780
|
+
if (!hasPermission(grant.permissions, "variable-sets:use") || !await getVariableSet4(deps.db, workspaceId, options.variableSetId)) {
|
|
8814
9781
|
delete options.variableSetId;
|
|
8815
9782
|
}
|
|
8816
9783
|
}
|
|
@@ -8885,7 +9852,7 @@ async function saveActorNewSessionDraft(deps, grant, workspaceId, rawInput) {
|
|
|
8885
9852
|
const tools = toolsProvided ? validateToolRefs(input.tools, runtimeSettings) : [];
|
|
8886
9853
|
await validateGitHubRepositorySelection(deps.db, workspaceId, resources);
|
|
8887
9854
|
if (resources.some((resource) => resource.kind === "file") && !deps.objectStorage) {
|
|
8888
|
-
throw new
|
|
9855
|
+
throw new HTTPException14(503, { message: "object storage is not configured" });
|
|
8889
9856
|
}
|
|
8890
9857
|
await validateFileResources(deps.db, workspaceId, resources);
|
|
8891
9858
|
assertConfiguredModel(deps.settings, input.model);
|
|
@@ -8920,7 +9887,7 @@ async function saveActorNewSessionDraft(deps, grant, workspaceId, rawInput) {
|
|
|
8920
9887
|
return mapNewSessionDraft(saved);
|
|
8921
9888
|
} catch (error) {
|
|
8922
9889
|
if (error instanceof NewSessionDraftAccessError) {
|
|
8923
|
-
throw new
|
|
9890
|
+
throw new HTTPException14(403, { message: error.message });
|
|
8924
9891
|
}
|
|
8925
9892
|
throw error;
|
|
8926
9893
|
}
|
|
@@ -9465,6 +10432,15 @@ async function saveHumanComposerDraft(deps, context, input) {
|
|
|
9465
10432
|
}
|
|
9466
10433
|
export {
|
|
9467
10434
|
CODEX_COMPACTION_V2_PROVIDER_LOCKED,
|
|
10435
|
+
CONVERSATION_ATTACHMENT_MAX_COUNT,
|
|
10436
|
+
CONVERSATION_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES,
|
|
10437
|
+
CONVERSATION_ATTACHMENT_NAME_MAX_UTF8_BYTES,
|
|
10438
|
+
CONVERSATION_INTEGRATION_SCHEMA_VERSION,
|
|
10439
|
+
CONVERSATION_OPERATION_KEY_MAX_UTF8_BYTES,
|
|
10440
|
+
CONVERSATION_OUTCOME_CODE_MAX_UTF8_BYTES,
|
|
10441
|
+
CONVERSATION_PROVIDER_NAMESPACE_MAX_UTF8_BYTES,
|
|
10442
|
+
CONVERSATION_PROVIDER_OPAQUE_ID_MAX_UTF8_BYTES,
|
|
10443
|
+
CONVERSATION_TEXT_MAX_UTF8_BYTES,
|
|
9468
10444
|
CodexCompactionV2ProviderLockedError,
|
|
9469
10445
|
DEFAULT_MEMORY_SLACK_PUBLICATION_POLICY,
|
|
9470
10446
|
EDITABLE_ARTIFACT_COMPACTION_BYTE_THRESHOLD,
|
|
@@ -9570,6 +10546,12 @@ export {
|
|
|
9570
10546
|
assertBoundedKernelVersion,
|
|
9571
10547
|
assertBoundedOpaqueReference,
|
|
9572
10548
|
assertConfiguredModel,
|
|
10549
|
+
assertConversationDeliveryCommand,
|
|
10550
|
+
assertConversationDeliveryCommandMatches,
|
|
10551
|
+
assertConversationDeliveryMayRetry,
|
|
10552
|
+
assertConversationDeliveryOutcome,
|
|
10553
|
+
assertConversationInboundEnvelope,
|
|
10554
|
+
assertConversationProviderReceipt,
|
|
9573
10555
|
assertIsoTimestamp,
|
|
9574
10556
|
assertKnownVideoGenerationModelIds,
|
|
9575
10557
|
assertNonnegativeSafeInteger,
|
|
@@ -9588,6 +10570,10 @@ export {
|
|
|
9588
10570
|
buildFleetContextForSession,
|
|
9589
10571
|
buildMarketingDailyAnalysisAgentConfig,
|
|
9590
10572
|
canonicalConfiguredModel,
|
|
10573
|
+
canonicalConversationDeliveryCommandJson,
|
|
10574
|
+
canonicalConversationDeliveryOutcomeJson,
|
|
10575
|
+
canonicalConversationInboundEnvelopeJson,
|
|
10576
|
+
canonicalConversationProviderReceiptJson,
|
|
9591
10577
|
canonicalVideoGenerationRequestJson,
|
|
9592
10578
|
capabilityPackManifestDigest,
|
|
9593
10579
|
capabilityPackRequiresInstallationPlan,
|
|
@@ -9603,6 +10589,7 @@ export {
|
|
|
9603
10589
|
controlHumanSessionWorkstream,
|
|
9604
10590
|
controlHumanSessionWorkstreamWithOutcome,
|
|
9605
10591
|
controlHumanWorkspace,
|
|
10592
|
+
conversationDeliveryNextAction,
|
|
9606
10593
|
correctWorkspaceMemoryWithSlackPublication,
|
|
9607
10594
|
createAndStartSession,
|
|
9608
10595
|
createAndStartSessionWithOutcome,
|
|
@@ -9660,6 +10647,7 @@ export {
|
|
|
9660
10647
|
encodeEditableArtifactLiveServerWireFrame,
|
|
9661
10648
|
evaluateMemorySlackPublication,
|
|
9662
10649
|
executeRunOnSelfhostedMachine,
|
|
10650
|
+
fikenConnectionMetadata,
|
|
9663
10651
|
filenameForMimeType,
|
|
9664
10652
|
freezePersonalConnectionDelegations,
|
|
9665
10653
|
getActorNewSessionDraft,
|
|
@@ -9669,6 +10657,7 @@ export {
|
|
|
9669
10657
|
getWorkspaceInsights,
|
|
9670
10658
|
hasLiteralPermission,
|
|
9671
10659
|
hasPermission,
|
|
10660
|
+
hasReservedFikenMetadata,
|
|
9672
10661
|
hasReservedOpenGeniSlackBotMetadata,
|
|
9673
10662
|
hasReservedOpenGeniSlackBotSessionMetadata,
|
|
9674
10663
|
hashEditableArtifactCreateRequest,
|
|
@@ -9678,6 +10667,7 @@ export {
|
|
|
9678
10667
|
isAcceptedMimeType,
|
|
9679
10668
|
isAuthoritativeGitHubRepositorySelectionError,
|
|
9680
10669
|
isBuiltInCapabilityPack,
|
|
10670
|
+
isFikenConnection,
|
|
9681
10671
|
isOpenGeniSlackBotConnection,
|
|
9682
10672
|
isTerminalVideoGenerationState,
|
|
9683
10673
|
isTrustedScheduledSlackBotSession,
|
|
@@ -9696,6 +10686,14 @@ export {
|
|
|
9696
10686
|
mergeToolRefs,
|
|
9697
10687
|
moveHumanQueuePrompt,
|
|
9698
10688
|
nativeConnectionCapabilityRecommendations,
|
|
10689
|
+
normalizeConversationActor,
|
|
10690
|
+
normalizeConversationDeliveryCommand,
|
|
10691
|
+
normalizeConversationDeliveryOutcome,
|
|
10692
|
+
normalizeConversationInboundEnvelope,
|
|
10693
|
+
normalizeConversationInstallationIdentity,
|
|
10694
|
+
normalizeConversationProviderNamespace,
|
|
10695
|
+
normalizeConversationProviderReceipt,
|
|
10696
|
+
normalizeConversationRoute,
|
|
9699
10697
|
normalizeMimeType,
|
|
9700
10698
|
normalizeResources,
|
|
9701
10699
|
normalizeVideoGenerationRequest,
|
|
@@ -9711,6 +10709,7 @@ export {
|
|
|
9711
10709
|
portableSkillCapabilityId,
|
|
9712
10710
|
portableSkillPluginKey,
|
|
9713
10711
|
postUserMessageTurn,
|
|
10712
|
+
preferredFikenConnection,
|
|
9714
10713
|
previewCapabilityPackInstallation,
|
|
9715
10714
|
promoteSetupAppendChange,
|
|
9716
10715
|
promoteVerifiedDefinitionEditChangeForApi,
|
|
@@ -9744,6 +10743,7 @@ export {
|
|
|
9744
10743
|
requireVariableSetForApi,
|
|
9745
10744
|
resolveCapabilityPack,
|
|
9746
10745
|
resolveCodexAppsCredentialIdForRun,
|
|
10746
|
+
resolveFikenDefaultCompanySlug,
|
|
9747
10747
|
resolveFirstPartyMcpToolsForCreate,
|
|
9748
10748
|
resolveMemberSubjectId,
|
|
9749
10749
|
resolveSessionToolPolicy,
|
|
@@ -9771,6 +10771,7 @@ export {
|
|
|
9771
10771
|
searchCapabilityCatalogItems,
|
|
9772
10772
|
selectedPersonalConnectionServers,
|
|
9773
10773
|
sendAgentSessionMessage,
|
|
10774
|
+
sessionRlsActorForAuthorization,
|
|
9774
10775
|
sessionSpawnDenialEnvelope,
|
|
9775
10776
|
sessionWithEffectiveToolPolicy,
|
|
9776
10777
|
settingsWithApiIntegrationServers,
|
|
@@ -9806,10 +10807,12 @@ export {
|
|
|
9806
10807
|
videoGenerationAdmissionKey,
|
|
9807
10808
|
videoGenerationCapabilitiesForPolicy,
|
|
9808
10809
|
videoGenerationCapabilityRevision,
|
|
10810
|
+
videoGenerationModelSupportsFundingSource,
|
|
9809
10811
|
videoGenerationProviderIdempotencyKey,
|
|
9810
10812
|
videoGenerationRequestDigest,
|
|
9811
10813
|
withDefaultEnabledCapabilityMcpTools,
|
|
9812
10814
|
withFrozenPersonalConnectionDelegations,
|
|
10815
|
+
withResolvedSessionAuthorization,
|
|
9813
10816
|
workflowIdForSession,
|
|
9814
10817
|
workspaceSessionToolPolicyDefaultServerIds,
|
|
9815
10818
|
workspaceSessionToolPolicyServerIds,
|