@opengeni/api-router 0.26.1 → 0.30.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/app.d.ts +1 -0
- package/dist/app.js +5 -3
- package/dist/auth/canonical-human-session-admission.d.ts +27 -0
- package/dist/browser-auth-broker.d.ts +20 -0
- package/dist/browser-controller-authority.d.ts +19 -1
- package/dist/browser-network-route.d.ts +8 -0
- package/dist/{chunk-JIKNR5YL.js → chunk-2PQMSRCB.js} +15518 -6863
- package/dist/chunk-2PQMSRCB.js.map +1 -0
- package/dist/controller-data-plane.d.ts +12 -0
- package/dist/http/auth.d.ts +1 -1
- package/dist/http/sse.d.ts +18 -1
- package/dist/index.js +1 -1
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/fiken.d.ts +233 -0
- package/dist/integrations/google-drive.d.ts +6 -6
- package/dist/integrations/provider-oauth.d.ts +0 -2
- package/dist/integrations/slack-bot.d.ts +171 -1
- package/dist/integrations/slack-interactions.d.ts +17 -5
- package/dist/interaction-holder-heartbeat.d.ts +17 -0
- package/dist/interaction-metrics.d.ts +11 -0
- package/dist/mcp/server.d.ts +3 -2
- package/dist/model-catalog.d.ts +1 -0
- package/dist/routes/canonical-human-identities.d.ts +3 -0
- package/dist/routes/channels.d.ts +3 -0
- package/dist/routes/integration-facets.d.ts +3 -0
- package/dist/routes/interaction-resources.d.ts +5 -0
- package/dist/routes/organization-memberships.d.ts +3 -0
- package/dist/routes/sessions.d.ts +2 -1
- package/dist/routes/slack-task-policy.d.ts +3 -0
- package/dist/routes/supergrok.d.ts +3 -0
- package/dist/sandbox/channel-a.d.ts +8 -1
- package/dist/sandbox/machines.d.ts +2 -2
- package/dist/sandbox/viewer.d.ts +3 -1
- package/dist/slack-reaction-files.d.ts +27 -0
- package/dist/transcription/providers/xai-subscription.d.ts +8 -0
- package/dist/xai-realtime.d.ts +26 -0
- package/dist/xai-subscription-auth.d.ts +23 -0
- package/package.json +17 -16
- package/src/app.ts +352 -49
- package/src/auth/canonical-human-session-admission.ts +116 -0
- package/src/auth/managed-auth.ts +101 -0
- package/src/browser-auth-broker.ts +155 -0
- package/src/browser-controller-authority.ts +50 -1
- package/src/browser-network-route.ts +34 -0
- package/src/controller-data-plane.ts +35 -0
- package/src/http/auth.ts +27 -9
- package/src/http/sse.ts +229 -5
- package/src/integrations/api-integrations.ts +57 -42
- package/src/integrations/fiken.ts +1230 -0
- package/src/integrations/google-drive.ts +31 -26
- package/src/integrations/provider-oauth.ts +70 -91
- package/src/integrations/slack-bot.ts +638 -9
- package/src/integrations/slack-interactions.ts +1583 -73
- package/src/interaction-holder-heartbeat.ts +95 -0
- package/src/interaction-metrics.ts +159 -0
- package/src/mcp/documents.ts +115 -6
- package/src/mcp/server.ts +865 -242
- package/src/model-catalog.ts +19 -6
- package/src/routes/api-integrations.ts +32 -29
- package/src/routes/browser-identities.ts +24 -0
- package/src/routes/browser-sessions.ts +2122 -183
- package/src/routes/canonical-human-identities.ts +156 -0
- package/src/routes/channels.ts +90 -0
- package/src/routes/computer-sessions.ts +441 -86
- package/src/routes/connections.ts +141 -4
- package/src/routes/{integration-features.ts → integration-facets.ts} +63 -63
- package/src/routes/interaction-resources.ts +595 -0
- package/src/routes/organization-memberships.ts +53 -0
- package/src/routes/plugins.ts +2 -1
- package/src/routes/sessions.ts +419 -43
- package/src/routes/skills.ts +92 -10
- package/src/routes/slack-task-policy.ts +115 -0
- package/src/routes/supergrok.ts +717 -0
- package/src/routes/transcription-recordings.ts +9 -2
- package/src/routes/transcriptions.ts +2 -1
- package/src/routes/video-generation.ts +34 -4
- package/src/routes/workspaces.ts +22 -4
- package/src/sandbox/channel-a.ts +29 -2
- package/src/sandbox/machines.ts +5 -5
- package/src/sandbox/viewer.ts +10 -3
- package/src/slack-reaction-files.ts +181 -0
- package/src/transcription/providers/xai-subscription.ts +110 -0
- package/src/transcription/service.ts +17 -2
- package/src/xai-realtime.ts +216 -0
- package/src/xai-subscription-auth.ts +132 -0
- package/dist/chunk-JIKNR5YL.js.map +0 -1
- package/dist/routes/integration-features.d.ts +0 -3
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthRun,
|
|
3
|
+
AuthRunListResponse,
|
|
4
|
+
CreateInteractionInterventionRequest,
|
|
5
|
+
CreateNetworkRouteRequest,
|
|
6
|
+
CreateSiteAuthConnectionRequest,
|
|
7
|
+
InteractionIntervention,
|
|
8
|
+
InteractionInterventionListResponse,
|
|
9
|
+
InteractionInterventionMutationResponse,
|
|
10
|
+
NetworkRoute,
|
|
11
|
+
NetworkRouteListResponse,
|
|
12
|
+
NetworkRouteMutationResponse,
|
|
13
|
+
ResolveInteractionInterventionRequest,
|
|
14
|
+
SiteAuthConnection,
|
|
15
|
+
SiteAuthConnectionListResponse,
|
|
16
|
+
SiteAuthConnectionMutationResponse,
|
|
17
|
+
UpdateNetworkRouteRequest,
|
|
18
|
+
UpdateSiteAuthConnectionRequest,
|
|
19
|
+
type AccessGrant,
|
|
20
|
+
type SessionAuthorizationOperation,
|
|
21
|
+
} from "@opengeni/contracts";
|
|
22
|
+
import {
|
|
23
|
+
BrowserSessionNotFoundError,
|
|
24
|
+
ComputerSessionNotFoundError,
|
|
25
|
+
acceptSessionApprovalDecision,
|
|
26
|
+
InteractionResourceConflictError,
|
|
27
|
+
InteractionResourceNotFoundError,
|
|
28
|
+
InteractionResourceStateError,
|
|
29
|
+
createInteractionIntervention,
|
|
30
|
+
createNetworkRoute,
|
|
31
|
+
createSiteAuthConnection,
|
|
32
|
+
getAuthRun,
|
|
33
|
+
getBrowserSessionControlRecord,
|
|
34
|
+
getComputerSessionControlRecord,
|
|
35
|
+
getInteractionIntervention,
|
|
36
|
+
getInteractionInterventionApprovalTarget,
|
|
37
|
+
getNetworkRoute,
|
|
38
|
+
getSiteAuthConnection,
|
|
39
|
+
listAuthRuns,
|
|
40
|
+
listInteractionInterventions,
|
|
41
|
+
listNetworkRoutes,
|
|
42
|
+
listSiteAuthConnections,
|
|
43
|
+
resolveInteractionIntervention,
|
|
44
|
+
updateNetworkRoute,
|
|
45
|
+
updateSiteAuthConnection,
|
|
46
|
+
} from "@opengeni/db";
|
|
47
|
+
import {
|
|
48
|
+
SessionAuthorizationDeniedError,
|
|
49
|
+
SessionAuthorizationUnavailableError,
|
|
50
|
+
requireAccessGrant,
|
|
51
|
+
requireSessionAuthorization,
|
|
52
|
+
type ApiRouteDeps,
|
|
53
|
+
workflowIdForSession,
|
|
54
|
+
} from "@opengeni/core";
|
|
55
|
+
import { publishDurableSessionEvents } from "@opengeni/events";
|
|
56
|
+
import type { Context, Hono } from "hono";
|
|
57
|
+
import { HTTPException } from "hono/http-exception";
|
|
58
|
+
import { sseWorkspaceInteractionRevisionStream, sseWorkspaceLiveStream } from "../http/sse";
|
|
59
|
+
import { observeInterventionMutation } from "../interaction-metrics";
|
|
60
|
+
|
|
61
|
+
export function registerInteractionResourceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
62
|
+
app.get("/v1/workspaces/:workspaceId/live-events/stream", async (context) => {
|
|
63
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
64
|
+
await requireAccessGrant(context, deps, workspaceId, "workspace:read");
|
|
65
|
+
return await sseWorkspaceLiveStream(
|
|
66
|
+
deps.db,
|
|
67
|
+
deps.bus,
|
|
68
|
+
grant.accountId,
|
|
69
|
+
workspaceId,
|
|
70
|
+
nonnegativeSafeIntegerQuery(context, "controlAfter", 0),
|
|
71
|
+
nonnegativeSafeIntegerQuery(context, "interactionAfter", 0),
|
|
72
|
+
context.req.raw.signal,
|
|
73
|
+
{ observability: deps.observability },
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
app.get("/v1/workspaces/:workspaceId/interaction-events/stream", async (context) => {
|
|
78
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
79
|
+
const after = nonnegativeSafeIntegerQuery(context, "after", 0);
|
|
80
|
+
return await sseWorkspaceInteractionRevisionStream(
|
|
81
|
+
deps.db,
|
|
82
|
+
grant.accountId,
|
|
83
|
+
workspaceId,
|
|
84
|
+
after,
|
|
85
|
+
context.req.raw.signal,
|
|
86
|
+
{ observability: deps.observability },
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
app.get("/v1/workspaces/:workspaceId/network-routes", async (context) => {
|
|
91
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
92
|
+
try {
|
|
93
|
+
return context.json(
|
|
94
|
+
NetworkRouteListResponse.parse(
|
|
95
|
+
await listNetworkRoutes(deps.db, {
|
|
96
|
+
accountId: grant.accountId,
|
|
97
|
+
workspaceId,
|
|
98
|
+
includeArchived: context.req.query("includeArchived") === "true",
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw interactionResourceRouteError(error);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
app.post("/v1/workspaces/:workspaceId/network-routes", async (context) => {
|
|
108
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:control");
|
|
109
|
+
const request = await parseJsonBody(context, CreateNetworkRouteRequest);
|
|
110
|
+
try {
|
|
111
|
+
const response = await createNetworkRoute(deps.db, {
|
|
112
|
+
accountId: grant.accountId,
|
|
113
|
+
workspaceId,
|
|
114
|
+
actorSubjectId: grant.subjectId,
|
|
115
|
+
...request,
|
|
116
|
+
});
|
|
117
|
+
return context.json(
|
|
118
|
+
NetworkRouteMutationResponse.parse(response),
|
|
119
|
+
response.replayed ? 200 : 201,
|
|
120
|
+
);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw interactionResourceRouteError(error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
app.get("/v1/workspaces/:workspaceId/network-routes/:networkRouteId", async (context) => {
|
|
127
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
128
|
+
try {
|
|
129
|
+
return context.json(
|
|
130
|
+
NetworkRoute.parse(
|
|
131
|
+
await getNetworkRoute(deps.db, {
|
|
132
|
+
accountId: grant.accountId,
|
|
133
|
+
workspaceId,
|
|
134
|
+
routeId: uuidParam(context, "networkRouteId"),
|
|
135
|
+
}),
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw interactionResourceRouteError(error);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
app.patch("/v1/workspaces/:workspaceId/network-routes/:networkRouteId", async (context) => {
|
|
144
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:control");
|
|
145
|
+
const routeId = uuidParam(context, "networkRouteId");
|
|
146
|
+
const request = await parseJsonBody(context, UpdateNetworkRouteRequest);
|
|
147
|
+
try {
|
|
148
|
+
return context.json(
|
|
149
|
+
NetworkRouteMutationResponse.parse(
|
|
150
|
+
await updateNetworkRoute(deps.db, {
|
|
151
|
+
accountId: grant.accountId,
|
|
152
|
+
workspaceId,
|
|
153
|
+
actorSubjectId: grant.subjectId,
|
|
154
|
+
routeId,
|
|
155
|
+
...request,
|
|
156
|
+
}),
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
throw interactionResourceRouteError(error);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
app.get("/v1/workspaces/:workspaceId/site-auth-connections", async (context) => {
|
|
165
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
166
|
+
try {
|
|
167
|
+
return context.json(
|
|
168
|
+
SiteAuthConnectionListResponse.parse(
|
|
169
|
+
await listSiteAuthConnections(deps.db, {
|
|
170
|
+
accountId: grant.accountId,
|
|
171
|
+
workspaceId,
|
|
172
|
+
includeArchived: context.req.query("includeArchived") === "true",
|
|
173
|
+
}),
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
throw interactionResourceRouteError(error);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
app.post("/v1/workspaces/:workspaceId/site-auth-connections", async (context) => {
|
|
182
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:control");
|
|
183
|
+
const request = await parseJsonBody(context, CreateSiteAuthConnectionRequest);
|
|
184
|
+
try {
|
|
185
|
+
const response = await createSiteAuthConnection(deps.db, {
|
|
186
|
+
accountId: grant.accountId,
|
|
187
|
+
workspaceId,
|
|
188
|
+
actorSubjectId: grant.subjectId,
|
|
189
|
+
...request,
|
|
190
|
+
});
|
|
191
|
+
return context.json(
|
|
192
|
+
SiteAuthConnectionMutationResponse.parse(response),
|
|
193
|
+
response.replayed ? 200 : 201,
|
|
194
|
+
);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
throw interactionResourceRouteError(error);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
app.get(
|
|
201
|
+
"/v1/workspaces/:workspaceId/site-auth-connections/:siteAuthConnectionId",
|
|
202
|
+
async (context) => {
|
|
203
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
204
|
+
try {
|
|
205
|
+
return context.json(
|
|
206
|
+
SiteAuthConnection.parse(
|
|
207
|
+
await getSiteAuthConnection(deps.db, {
|
|
208
|
+
accountId: grant.accountId,
|
|
209
|
+
workspaceId,
|
|
210
|
+
siteAuthConnectionId: uuidParam(context, "siteAuthConnectionId"),
|
|
211
|
+
}),
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw interactionResourceRouteError(error);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
app.patch(
|
|
221
|
+
"/v1/workspaces/:workspaceId/site-auth-connections/:siteAuthConnectionId",
|
|
222
|
+
async (context) => {
|
|
223
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:control");
|
|
224
|
+
const siteAuthConnectionId = uuidParam(context, "siteAuthConnectionId");
|
|
225
|
+
const request = await parseJsonBody(context, UpdateSiteAuthConnectionRequest);
|
|
226
|
+
try {
|
|
227
|
+
return context.json(
|
|
228
|
+
SiteAuthConnectionMutationResponse.parse(
|
|
229
|
+
await updateSiteAuthConnection(deps.db, {
|
|
230
|
+
accountId: grant.accountId,
|
|
231
|
+
workspaceId,
|
|
232
|
+
actorSubjectId: grant.subjectId,
|
|
233
|
+
siteAuthConnectionId,
|
|
234
|
+
...request,
|
|
235
|
+
}),
|
|
236
|
+
),
|
|
237
|
+
);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
throw interactionResourceRouteError(error);
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
app.get("/v1/workspaces/:workspaceId/auth-runs", async (context) => {
|
|
245
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
246
|
+
const browserSessionId = optionalUuidQuery(context, "browserSessionId");
|
|
247
|
+
const siteAuthConnectionId = optionalUuidQuery(context, "siteAuthConnectionId");
|
|
248
|
+
try {
|
|
249
|
+
return context.json(
|
|
250
|
+
AuthRunListResponse.parse(
|
|
251
|
+
await listAuthRuns(deps.db, {
|
|
252
|
+
accountId: grant.accountId,
|
|
253
|
+
workspaceId,
|
|
254
|
+
...(browserSessionId ? { browserSessionId } : {}),
|
|
255
|
+
...(siteAuthConnectionId ? { siteAuthConnectionId } : {}),
|
|
256
|
+
includeSettled: context.req.query("includeSettled") === "true",
|
|
257
|
+
}),
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
throw interactionResourceRouteError(error);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
app.get("/v1/workspaces/:workspaceId/auth-runs/:authRunId", async (context) => {
|
|
266
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
267
|
+
try {
|
|
268
|
+
return context.json(
|
|
269
|
+
AuthRun.parse(
|
|
270
|
+
await getAuthRun(deps.db, {
|
|
271
|
+
accountId: grant.accountId,
|
|
272
|
+
workspaceId,
|
|
273
|
+
authRunId: uuidParam(context, "authRunId"),
|
|
274
|
+
}),
|
|
275
|
+
),
|
|
276
|
+
);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
throw interactionResourceRouteError(error);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
app.get("/v1/workspaces/:workspaceId/interaction-interventions", async (context) => {
|
|
283
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
284
|
+
const resourceKind = optionalResourceKind(context);
|
|
285
|
+
const resourceId = optionalUuidQuery(context, "resourceId");
|
|
286
|
+
try {
|
|
287
|
+
return context.json(
|
|
288
|
+
InteractionInterventionListResponse.parse(
|
|
289
|
+
await listInteractionInterventions(deps.db, {
|
|
290
|
+
accountId: grant.accountId,
|
|
291
|
+
workspaceId,
|
|
292
|
+
...(resourceKind ? { resourceKind } : {}),
|
|
293
|
+
...(resourceId ? { resourceId } : {}),
|
|
294
|
+
includeSettled: context.req.query("includeSettled") === "true",
|
|
295
|
+
}),
|
|
296
|
+
),
|
|
297
|
+
);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
throw interactionResourceRouteError(error);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
app.post("/v1/workspaces/:workspaceId/interaction-interventions", async (context) => {
|
|
304
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:control");
|
|
305
|
+
const request = await parseJsonBody(context, CreateInteractionInterventionRequest);
|
|
306
|
+
try {
|
|
307
|
+
const resourceSessionId = await resourceSourceSessionId(deps, grant, workspaceId, request);
|
|
308
|
+
await authorizeSession(deps, grant, resourceSessionId, "session.control");
|
|
309
|
+
const actor = interactionProvenance(grant);
|
|
310
|
+
const actorSessionId = actor.sessionId ?? resourceSessionId;
|
|
311
|
+
if (actorSessionId !== resourceSessionId) {
|
|
312
|
+
await authorizeSession(deps, grant, actorSessionId, "session.control");
|
|
313
|
+
}
|
|
314
|
+
const response = await createInteractionIntervention(deps.db, {
|
|
315
|
+
accountId: grant.accountId,
|
|
316
|
+
workspaceId,
|
|
317
|
+
actorSubjectId: grant.subjectId,
|
|
318
|
+
...request,
|
|
319
|
+
originatingSessionId: actorSessionId,
|
|
320
|
+
originatingTurnId: actor.turnId,
|
|
321
|
+
originatingAttemptId: actor.attemptId,
|
|
322
|
+
originatingToolOperationId: actor.attemptId ? request.operationId : null,
|
|
323
|
+
});
|
|
324
|
+
const parsed = InteractionInterventionMutationResponse.parse(response);
|
|
325
|
+
observeInterventionMutation(deps.observability, parsed);
|
|
326
|
+
return context.json(parsed, response.replayed ? 200 : 201);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
throw interactionResourceRouteError(error);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
app.get(
|
|
333
|
+
"/v1/workspaces/:workspaceId/interaction-interventions/:interventionId",
|
|
334
|
+
async (context) => {
|
|
335
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:read");
|
|
336
|
+
try {
|
|
337
|
+
return context.json(
|
|
338
|
+
InteractionIntervention.parse(
|
|
339
|
+
await getInteractionIntervention(deps.db, {
|
|
340
|
+
accountId: grant.accountId,
|
|
341
|
+
workspaceId,
|
|
342
|
+
interventionId: uuidParam(context, "interventionId"),
|
|
343
|
+
}),
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
throw interactionResourceRouteError(error);
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
app.post(
|
|
353
|
+
"/v1/workspaces/:workspaceId/interaction-interventions/:interventionId/resolve",
|
|
354
|
+
async (context) => {
|
|
355
|
+
const { workspaceId, grant } = await preamble(context, deps, "sessions:control");
|
|
356
|
+
const interventionId = uuidParam(context, "interventionId");
|
|
357
|
+
const request = await parseJsonBody(context, ResolveInteractionInterventionRequest);
|
|
358
|
+
try {
|
|
359
|
+
const approvalTarget = await getInteractionInterventionApprovalTarget(deps.db, {
|
|
360
|
+
accountId: grant.accountId,
|
|
361
|
+
workspaceId,
|
|
362
|
+
interventionId,
|
|
363
|
+
});
|
|
364
|
+
if (approvalTarget) {
|
|
365
|
+
await authorizeSession(deps, grant, approvalTarget.sessionId, "session.approval.write");
|
|
366
|
+
}
|
|
367
|
+
if (!approvalTarget) {
|
|
368
|
+
const response = InteractionInterventionMutationResponse.parse(
|
|
369
|
+
await resolveInteractionIntervention(deps.db, {
|
|
370
|
+
accountId: grant.accountId,
|
|
371
|
+
workspaceId,
|
|
372
|
+
actorSubjectId: grant.subjectId,
|
|
373
|
+
interventionId,
|
|
374
|
+
...request,
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
377
|
+
observeInterventionMutation(deps.observability, response);
|
|
378
|
+
return context.json(response);
|
|
379
|
+
}
|
|
380
|
+
const decision = request.outcome === "completed" ? "approve" : "reject";
|
|
381
|
+
const accepted = await acceptSessionApprovalDecision(deps.db, {
|
|
382
|
+
accountId: grant.accountId,
|
|
383
|
+
workspaceId,
|
|
384
|
+
sessionId: approvalTarget.sessionId,
|
|
385
|
+
subjectId: grant.subjectId,
|
|
386
|
+
payload: {
|
|
387
|
+
approvalId: approvalTarget.toolCallId,
|
|
388
|
+
decision,
|
|
389
|
+
...(decision === "reject"
|
|
390
|
+
? { message: "Human interaction was dismissed or expired." }
|
|
391
|
+
: {}),
|
|
392
|
+
},
|
|
393
|
+
clientEventId: request.operationId,
|
|
394
|
+
interactionIntervention: {
|
|
395
|
+
interventionId,
|
|
396
|
+
operationId: request.operationId,
|
|
397
|
+
expectedVersion: request.expectedVersion,
|
|
398
|
+
outcome: request.outcome,
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
if (accepted.action === "conflict") {
|
|
402
|
+
throw new InteractionResourceConflictError(
|
|
403
|
+
"Another response already advanced this waiting agent turn",
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
await publishDurableSessionEvents(
|
|
407
|
+
deps.bus,
|
|
408
|
+
workspaceId,
|
|
409
|
+
approvalTarget.sessionId,
|
|
410
|
+
accepted.events,
|
|
411
|
+
);
|
|
412
|
+
await deps.workflowClient.signalApprovalDecision({
|
|
413
|
+
accountId: grant.accountId,
|
|
414
|
+
workspaceId,
|
|
415
|
+
sessionId: approvalTarget.sessionId,
|
|
416
|
+
eventId: accepted.event.id,
|
|
417
|
+
workflowId: workflowIdForSession(approvalTarget.sessionId),
|
|
418
|
+
workflowWakeRevision: accepted.workflowWakeRevision,
|
|
419
|
+
});
|
|
420
|
+
const response = InteractionInterventionMutationResponse.parse({
|
|
421
|
+
intervention: await getInteractionIntervention(deps.db, {
|
|
422
|
+
accountId: grant.accountId,
|
|
423
|
+
workspaceId,
|
|
424
|
+
interventionId,
|
|
425
|
+
}),
|
|
426
|
+
operationId: request.operationId,
|
|
427
|
+
replayed: accepted.events.length === 0,
|
|
428
|
+
});
|
|
429
|
+
observeInterventionMutation(deps.observability, response);
|
|
430
|
+
return context.json(response);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
throw interactionResourceRouteError(error);
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function preamble(
|
|
439
|
+
context: Context,
|
|
440
|
+
deps: ApiRouteDeps,
|
|
441
|
+
permission: "sessions:read" | "sessions:control",
|
|
442
|
+
): Promise<{ workspaceId: string; grant: AccessGrant }> {
|
|
443
|
+
const workspaceId = context.req.param("workspaceId") ?? "";
|
|
444
|
+
return {
|
|
445
|
+
workspaceId,
|
|
446
|
+
grant: await requireAccessGrant(context, deps, workspaceId, permission),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function resourceSourceSessionId(
|
|
451
|
+
deps: ApiRouteDeps,
|
|
452
|
+
grant: AccessGrant,
|
|
453
|
+
workspaceId: string,
|
|
454
|
+
request: { resourceKind: "browser_session" | "computer_session"; resourceId: string },
|
|
455
|
+
): Promise<string> {
|
|
456
|
+
if (request.resourceKind === "browser_session") {
|
|
457
|
+
return (
|
|
458
|
+
await getBrowserSessionControlRecord(deps.db, {
|
|
459
|
+
accountId: grant.accountId,
|
|
460
|
+
workspaceId,
|
|
461
|
+
browserSessionId: request.resourceId,
|
|
462
|
+
})
|
|
463
|
+
).sourceSessionId;
|
|
464
|
+
}
|
|
465
|
+
return (
|
|
466
|
+
await getComputerSessionControlRecord(deps.db, {
|
|
467
|
+
accountId: grant.accountId,
|
|
468
|
+
workspaceId,
|
|
469
|
+
computerSessionId: request.resourceId,
|
|
470
|
+
})
|
|
471
|
+
).sourceSessionId;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function authorizeSession(
|
|
475
|
+
deps: ApiRouteDeps,
|
|
476
|
+
grant: AccessGrant,
|
|
477
|
+
sessionId: string,
|
|
478
|
+
operation: SessionAuthorizationOperation,
|
|
479
|
+
): Promise<void> {
|
|
480
|
+
try {
|
|
481
|
+
await requireSessionAuthorization(deps, grant, {
|
|
482
|
+
sessionId,
|
|
483
|
+
operation,
|
|
484
|
+
surface: "http",
|
|
485
|
+
});
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if (error instanceof SessionAuthorizationDeniedError) {
|
|
488
|
+
throw new HTTPException(404, { message: "session not found", cause: error });
|
|
489
|
+
}
|
|
490
|
+
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
491
|
+
throw new HTTPException(503, {
|
|
492
|
+
message: "session authorization is unavailable",
|
|
493
|
+
cause: error,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function interactionProvenance(grant: AccessGrant): {
|
|
501
|
+
sessionId: string | null;
|
|
502
|
+
turnId: string | null;
|
|
503
|
+
attemptId: string | null;
|
|
504
|
+
} {
|
|
505
|
+
if (grant.principalKind !== "agent_attempt") {
|
|
506
|
+
return { sessionId: null, turnId: null, attemptId: null };
|
|
507
|
+
}
|
|
508
|
+
const sessionId = uuidValue(grant.metadata?.["sessionId"]);
|
|
509
|
+
const turnId = uuidValue(grant.metadata?.["turnId"]);
|
|
510
|
+
const attemptId = uuidValue(grant.metadata?.["attemptId"]);
|
|
511
|
+
if (!sessionId || !turnId || !attemptId) {
|
|
512
|
+
throw new HTTPException(403, { message: "agent attempt provenance is invalid" });
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
sessionId,
|
|
516
|
+
turnId,
|
|
517
|
+
attemptId,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function parseJsonBody<T>(
|
|
522
|
+
context: Context,
|
|
523
|
+
schema: {
|
|
524
|
+
safeParse(value: unknown): { success: true; data: T } | { success: false };
|
|
525
|
+
},
|
|
526
|
+
): Promise<T> {
|
|
527
|
+
const value = await context.req.json().catch(() => undefined);
|
|
528
|
+
const parsed = schema.safeParse(value);
|
|
529
|
+
if (!parsed.success) throw new HTTPException(400, { message: "invalid request body" });
|
|
530
|
+
return parsed.data;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function optionalResourceKind(context: Context): "browser_session" | "computer_session" | null {
|
|
534
|
+
const value = context.req.query("resourceKind");
|
|
535
|
+
if (value === undefined) return null;
|
|
536
|
+
if (value !== "browser_session" && value !== "computer_session") {
|
|
537
|
+
throw new HTTPException(400, { message: "resourceKind is invalid" });
|
|
538
|
+
}
|
|
539
|
+
return value;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function nonnegativeSafeIntegerQuery(context: Context, name: string, fallback: number): number {
|
|
543
|
+
const raw = context.req.query(name);
|
|
544
|
+
if (raw === undefined || raw === "") return fallback;
|
|
545
|
+
if (!/^(0|[1-9][0-9]*)$/u.test(raw)) {
|
|
546
|
+
throw new HTTPException(400, { message: `${name} must be a non-negative integer` });
|
|
547
|
+
}
|
|
548
|
+
const value = Number(raw);
|
|
549
|
+
if (!Number.isSafeInteger(value)) {
|
|
550
|
+
throw new HTTPException(400, { message: `${name} exceeds the supported integer range` });
|
|
551
|
+
}
|
|
552
|
+
return value;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function optionalUuidQuery(context: Context, name: string): string | null {
|
|
556
|
+
const value = context.req.query(name);
|
|
557
|
+
if (value === undefined) return null;
|
|
558
|
+
const parsed = uuidValue(value);
|
|
559
|
+
if (!parsed) throw new HTTPException(400, { message: `${name} must be a UUID` });
|
|
560
|
+
return parsed;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function uuidParam(context: Context, name: string): string {
|
|
564
|
+
const value = uuidValue(context.req.param(name));
|
|
565
|
+
if (!value) throw new HTTPException(400, { message: `${name} must be a UUID` });
|
|
566
|
+
return value;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function uuidValue(value: unknown): string | null {
|
|
570
|
+
return typeof value === "string" &&
|
|
571
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value)
|
|
572
|
+
? value
|
|
573
|
+
: null;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export function interactionResourceRouteError(error: unknown): HTTPException {
|
|
577
|
+
if (error instanceof HTTPException) return error;
|
|
578
|
+
if (
|
|
579
|
+
error instanceof InteractionResourceNotFoundError ||
|
|
580
|
+
error instanceof BrowserSessionNotFoundError ||
|
|
581
|
+
error instanceof ComputerSessionNotFoundError
|
|
582
|
+
) {
|
|
583
|
+
return new HTTPException(404, { message: error.message, cause: error });
|
|
584
|
+
}
|
|
585
|
+
if (
|
|
586
|
+
error instanceof InteractionResourceConflictError ||
|
|
587
|
+
error instanceof InteractionResourceStateError
|
|
588
|
+
) {
|
|
589
|
+
return new HTTPException(409, { message: error.message, cause: error });
|
|
590
|
+
}
|
|
591
|
+
return new HTTPException(500, {
|
|
592
|
+
message: "Interaction resource request failed",
|
|
593
|
+
cause: error,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ListManagedOrganizationMembershipsResponse } from "@opengeni/contracts";
|
|
2
|
+
import { getManagedSession, type ApiRouteDeps } from "@opengeni/core";
|
|
3
|
+
import {
|
|
4
|
+
ensureManagedAccessForUserWithOrganizationMemberships,
|
|
5
|
+
nestedPostgresSqlState,
|
|
6
|
+
} from "@opengeni/db";
|
|
7
|
+
import type { Hono } from "hono";
|
|
8
|
+
import { HTTPException } from "hono/http-exception";
|
|
9
|
+
|
|
10
|
+
export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
11
|
+
app.get("/v1/organization-memberships", async (context) => {
|
|
12
|
+
if (
|
|
13
|
+
deps.settings.productAccessMode !== "managed" ||
|
|
14
|
+
!deps.managedAuth ||
|
|
15
|
+
!context.req.header("cookie") ||
|
|
16
|
+
context.req.header("authorization")
|
|
17
|
+
) {
|
|
18
|
+
throw new HTTPException(401, {
|
|
19
|
+
message: "managed human session required",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const session = await getManagedSession(context, deps.managedAuth, {
|
|
24
|
+
db: deps.db,
|
|
25
|
+
});
|
|
26
|
+
if (!session?.user) {
|
|
27
|
+
throw new HTTPException(401, {
|
|
28
|
+
message: "managed human session required",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let result;
|
|
33
|
+
try {
|
|
34
|
+
result = await ensureManagedAccessForUserWithOrganizationMemberships(deps.db, {
|
|
35
|
+
userId: session.user.id,
|
|
36
|
+
email: session.user.email,
|
|
37
|
+
name: session.user.name,
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (nestedPostgresSqlState(error) === "42501") {
|
|
41
|
+
throw new HTTPException(403, {
|
|
42
|
+
message: "organization membership is not active",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
return context.json(
|
|
48
|
+
ListManagedOrganizationMembershipsResponse.parse({
|
|
49
|
+
memberships: result.organizationMemberships,
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
}
|
package/src/routes/plugins.ts
CHANGED
|
@@ -443,7 +443,8 @@ async function resolvePluginPackage(input: {
|
|
|
443
443
|
description: resolved.preview.description,
|
|
444
444
|
category: "integrations",
|
|
445
445
|
tags: [resolved.preview.protocol, resolved.preview.provider ?? "custom", "plugin"],
|
|
446
|
-
|
|
446
|
+
definitionId: resolved.preview.definitionId,
|
|
447
|
+
definitionProvenance: resolved.preview.definitionProvenance,
|
|
447
448
|
...(resolved.provider ? { provider: resolved.provider } : {}),
|
|
448
449
|
providerDomain: resolved.preview.providerDomain,
|
|
449
450
|
protocol: resolved.preview.protocol,
|