@opengeni/api-router 2.13.0-canary.0 → 2.13.0-canary.2
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.js +1 -1
- package/dist/{chunk-BJWHVMOL.js → chunk-K2NU4KRK.js} +3165 -3662
- package/dist/{chunk-BJWHVMOL.js.map → chunk-K2NU4KRK.js.map} +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/integrations/github-lens-connect.d.ts +2 -2
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/mcp/documents.d.ts +5 -13
- package/dist/mcp/files.d.ts +2 -2
- package/dist/mcp/first-party-tool-permissions.d.ts +36 -0
- package/dist/mcp/knowledge-entries.d.ts +5 -0
- package/dist/mcp/server.d.ts +4 -4
- package/dist/routes/host-mcp-resolvers.d.ts +3 -0
- package/dist/routes/knowledge.d.ts +3 -0
- package/dist/sandbox-file-artifacts.d.ts +6 -2
- package/dist/workspace-state-projection.d.ts +3 -5
- package/dist/workspace-tool-gateway.d.ts +2 -1
- package/package.json +19 -19
- package/src/access-grant-rls.ts +13 -1
- package/src/app.ts +4 -11
- package/src/codemode.ts +4 -8
- package/src/index.ts +4 -3
- package/src/integrations/atlassian.ts +31 -28
- package/src/integrations/google-drive.ts +33 -30
- package/src/integrations/marketplace-plugin.ts +1 -1
- package/src/integrations/provider-oauth.ts +11 -2
- package/src/mcp/documents.ts +57 -428
- package/src/mcp/files.ts +76 -58
- package/src/mcp/first-party-tool-permissions.ts +9 -0
- package/src/mcp/knowledge-entries.ts +276 -0
- package/src/mcp/server.ts +7 -361
- package/src/routes/company-brain.ts +58 -107
- package/src/routes/documents.ts +78 -294
- package/src/routes/files.ts +68 -3
- package/src/routes/host-mcp-resolvers.ts +41 -0
- package/src/routes/knowledge.ts +386 -0
- package/src/routes/organization-memberships.ts +33 -0
- package/src/routes/scheduled-tasks.ts +9 -1
- package/src/routes/sessions.ts +20 -3
- package/src/routes/skill-content.ts +8 -2
- package/src/routes/video-generation.ts +7 -5
- package/src/routes/workspace-learning.ts +12 -82
- package/src/routes/workspace-state.ts +34 -25
- package/src/routes/workspaces.ts +20 -10
- package/src/sandbox-file-artifacts.ts +54 -2
- package/src/scheduled-task-deletion.ts +3 -2
- package/src/workspace-state-projection.ts +18 -148
- package/src/workspace-tool-gateway.ts +14 -25
- package/dist/mcp/company-brain-governed-writes.d.ts +0 -26
- package/dist/mcp/remember.d.ts +0 -28
- package/src/mcp/company-brain-governed-writes.ts +0 -271
- package/src/mcp/remember.ts +0 -191
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { retryWhileMissing } from "@opengeni/storage";
|
|
2
|
+
import { allowedFirstPartyMcpToolsForSession } from "@opengeni/config";
|
|
3
|
+
import {
|
|
4
|
+
type FirstPartyMcpToolName,
|
|
5
|
+
AgentLearningContext,
|
|
6
|
+
AgentLearningOverridePatch,
|
|
7
|
+
KnowledgeEntryListRequest,
|
|
8
|
+
KnowledgeReviewBatchListRequest,
|
|
9
|
+
KnowledgeEntryBatchReviewRequest,
|
|
10
|
+
AgentInstructionReviewRequest,
|
|
11
|
+
KnowledgeEntryReviewRequest,
|
|
12
|
+
KnowledgeEntryRestoreRequest,
|
|
13
|
+
KnowledgeEntrySaveRequest,
|
|
14
|
+
KnowledgeOriginalFileDownload,
|
|
15
|
+
} from "@opengeni/contracts";
|
|
16
|
+
import {
|
|
17
|
+
prepareKnowledgeFile,
|
|
18
|
+
searchKnowledgeEntries,
|
|
19
|
+
hasPermission,
|
|
20
|
+
knowledgeContextForAccess,
|
|
21
|
+
requireAccessGrantAuthorization,
|
|
22
|
+
requireSessionAuthorization,
|
|
23
|
+
requireWorkspaceSettingsGrant,
|
|
24
|
+
type ApiRouteDeps,
|
|
25
|
+
} from "@opengeni/core";
|
|
26
|
+
import {
|
|
27
|
+
getKnowledgeOriginalFile,
|
|
28
|
+
recordAuditEvent,
|
|
29
|
+
archiveKnowledgeEntry,
|
|
30
|
+
getAgentLearningSettings,
|
|
31
|
+
getKnowledgeEntry,
|
|
32
|
+
listAgentLearningOverrides,
|
|
33
|
+
listKnowledgeEntryHistory,
|
|
34
|
+
listKnowledgeReviewBatches,
|
|
35
|
+
nestedPostgresSqlState,
|
|
36
|
+
restoreKnowledgeEntry,
|
|
37
|
+
reviewKnowledgeEntry,
|
|
38
|
+
saveAgentLearningSettings,
|
|
39
|
+
saveKnowledgeEntry,
|
|
40
|
+
reviewKnowledgeEntries,
|
|
41
|
+
reviewAgentInstruction,
|
|
42
|
+
listAgentInstructionReviews,
|
|
43
|
+
} from "@opengeni/db";
|
|
44
|
+
import type { Context, Hono } from "hono";
|
|
45
|
+
import { HTTPException } from "hono/http-exception";
|
|
46
|
+
import { z } from "zod";
|
|
47
|
+
import { withAccessGrantSessionRlsContext } from "../access-grant-rls";
|
|
48
|
+
|
|
49
|
+
const ReadSettings = z
|
|
50
|
+
.object({
|
|
51
|
+
scope: z.enum(["workspace", "personal", "context"]).default("workspace"),
|
|
52
|
+
source: AgentLearningContext.optional(),
|
|
53
|
+
})
|
|
54
|
+
.strict();
|
|
55
|
+
const WriteSettings = ReadSettings.extend({
|
|
56
|
+
scope: z.enum(["workspace", "personal"]).default("workspace"),
|
|
57
|
+
operationId: z.uuid(),
|
|
58
|
+
expectedVersion: z.number().int().nonnegative(),
|
|
59
|
+
settings: AgentLearningOverridePatch,
|
|
60
|
+
});
|
|
61
|
+
const Id = z.uuid();
|
|
62
|
+
function knowledgeHttpError(error: unknown): never {
|
|
63
|
+
if (error instanceof HTTPException) throw error;
|
|
64
|
+
if (error instanceof z.ZodError)
|
|
65
|
+
throw new HTTPException(422, {
|
|
66
|
+
message: error.issues[0]?.message ?? "Invalid Knowledge request",
|
|
67
|
+
});
|
|
68
|
+
const state = nestedPostgresSqlState(error);
|
|
69
|
+
if (state === "40001" || state === "23505")
|
|
70
|
+
throw new HTTPException(409, { message: "This item changed. Refresh it and try again." });
|
|
71
|
+
if (state === "42501")
|
|
72
|
+
throw new HTTPException(403, {
|
|
73
|
+
message: "This Knowledge operation is not available with your access or learning settings.",
|
|
74
|
+
});
|
|
75
|
+
if (state === "22023" || state === "23514")
|
|
76
|
+
throw new HTTPException(422, { message: "Invalid Knowledge operation or reference." });
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function registerKnowledgeRoutes(app: Hono, deps: ApiRouteDeps) {
|
|
81
|
+
const base = "/v1/workspaces/:workspaceId/knowledge/entries";
|
|
82
|
+
async function run<T>(
|
|
83
|
+
c: Context,
|
|
84
|
+
write: boolean,
|
|
85
|
+
fn: (context: Awaited<ReturnType<typeof knowledgeContextForAccess>>) => Promise<T>,
|
|
86
|
+
) {
|
|
87
|
+
try {
|
|
88
|
+
const access = await requireAccessGrantAuthorization(
|
|
89
|
+
c,
|
|
90
|
+
deps,
|
|
91
|
+
c.req.param("workspaceId")!,
|
|
92
|
+
write ? "documents:manage" : "documents:search",
|
|
93
|
+
);
|
|
94
|
+
return await withAccessGrantSessionRlsContext(deps, access.grant, async () => {
|
|
95
|
+
const context = await knowledgeContextForAccess(
|
|
96
|
+
deps,
|
|
97
|
+
access,
|
|
98
|
+
write ? "documents:manage" : "documents:search",
|
|
99
|
+
);
|
|
100
|
+
c.header("cache-control", "private, no-store");
|
|
101
|
+
return fn(context);
|
|
102
|
+
});
|
|
103
|
+
} catch (error) {
|
|
104
|
+
knowledgeHttpError(error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
app.post("/v1/workspaces/:workspaceId/knowledge/files/:fileId/prepare", (c) =>
|
|
108
|
+
run(c, false, async (context) => {
|
|
109
|
+
const access = await requireAccessGrantAuthorization(
|
|
110
|
+
c,
|
|
111
|
+
deps,
|
|
112
|
+
context.workspaceId,
|
|
113
|
+
"files:read",
|
|
114
|
+
);
|
|
115
|
+
const selected = access.grant.metadata?.firstPartyMcpTools as
|
|
116
|
+
| FirstPartyMcpToolName[]
|
|
117
|
+
| undefined;
|
|
118
|
+
if (
|
|
119
|
+
context.actor.kind !== "agent" ||
|
|
120
|
+
!selected ||
|
|
121
|
+
!allowedFirstPartyMcpToolsForSession(deps.settings, selected).includes(
|
|
122
|
+
"knowledge_retain_file",
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
throw new HTTPException(403, {
|
|
126
|
+
message: "Source preparation is unavailable for this task",
|
|
127
|
+
});
|
|
128
|
+
return c.json(await prepareKnowledgeFile(deps, context, Id.parse(c.req.param("fileId"))));
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
app.post(`${base}/:entryId/file/download-url`, (c) =>
|
|
132
|
+
run(c, false, async (context) => {
|
|
133
|
+
const access = await requireAccessGrantAuthorization(
|
|
134
|
+
c,
|
|
135
|
+
deps,
|
|
136
|
+
context.workspaceId,
|
|
137
|
+
"files:read",
|
|
138
|
+
);
|
|
139
|
+
const { revisionId } = z
|
|
140
|
+
.object({ revisionId: z.uuid().optional() })
|
|
141
|
+
.strict()
|
|
142
|
+
.parse(await c.req.json());
|
|
143
|
+
const entryId = Id.parse(c.req.param("entryId"));
|
|
144
|
+
const file = await getKnowledgeOriginalFile(deps.db, context, entryId, revisionId);
|
|
145
|
+
if (!file) throw new HTTPException(404, { message: "Original file unavailable" });
|
|
146
|
+
if (!deps.objectStorage)
|
|
147
|
+
throw new HTTPException(503, { message: "File storage is unavailable" });
|
|
148
|
+
if (
|
|
149
|
+
!(await retryWhileMissing(async () =>
|
|
150
|
+
(await deps.objectStorage!.fileExists(file)) ? true : null,
|
|
151
|
+
))
|
|
152
|
+
)
|
|
153
|
+
throw new HTTPException(410, { message: "Original file bytes are unavailable" });
|
|
154
|
+
const signed = await deps.objectStorage.createGetUrl({ key: file.objectKey });
|
|
155
|
+
await recordAuditEvent(deps.db, {
|
|
156
|
+
accountId: context.accountId,
|
|
157
|
+
workspaceId: context.workspaceId,
|
|
158
|
+
subjectId: access.grant.subjectId,
|
|
159
|
+
action: "file.signed_url.issued",
|
|
160
|
+
targetType: "knowledge_entry",
|
|
161
|
+
targetId: entryId,
|
|
162
|
+
metadata: { fileId: file.id, kind: "download", expiresAt: signed.expiresAt.toISOString() },
|
|
163
|
+
});
|
|
164
|
+
return c.json(
|
|
165
|
+
KnowledgeOriginalFileDownload.parse({
|
|
166
|
+
url: signed.url,
|
|
167
|
+
expiresAt: signed.expiresAt.toISOString(),
|
|
168
|
+
fileId: file.id,
|
|
169
|
+
filename: file.filename,
|
|
170
|
+
contentType: file.contentType,
|
|
171
|
+
sizeBytes: file.sizeBytes,
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
app.post(`${base}/search`, (c) =>
|
|
177
|
+
run(c, false, async (context) =>
|
|
178
|
+
c.json(
|
|
179
|
+
await searchKnowledgeEntries(
|
|
180
|
+
deps.db,
|
|
181
|
+
context,
|
|
182
|
+
KnowledgeEntryListRequest.parse(await c.req.json()),
|
|
183
|
+
() => deps.getDocumentServices().embedder,
|
|
184
|
+
),
|
|
185
|
+
),
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
app.get("/v1/workspaces/:workspaceId/knowledge/review-groups", (c) =>
|
|
189
|
+
run(c, true, async (context) => {
|
|
190
|
+
const query = c.req.query();
|
|
191
|
+
return c.json(
|
|
192
|
+
await listKnowledgeReviewBatches(
|
|
193
|
+
deps.db,
|
|
194
|
+
context,
|
|
195
|
+
KnowledgeReviewBatchListRequest.parse({
|
|
196
|
+
...query,
|
|
197
|
+
...(query.limit ? { limit: Number(query.limit) } : {}),
|
|
198
|
+
}),
|
|
199
|
+
),
|
|
200
|
+
);
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
app.post(`${base}/review`, (c) =>
|
|
204
|
+
run(c, true, async (context) =>
|
|
205
|
+
c.json(
|
|
206
|
+
await reviewKnowledgeEntries(
|
|
207
|
+
deps.db,
|
|
208
|
+
context,
|
|
209
|
+
KnowledgeEntryBatchReviewRequest.parse(await c.req.json()),
|
|
210
|
+
),
|
|
211
|
+
),
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
app.get(base, (c) =>
|
|
215
|
+
run(c, false, async (context) => {
|
|
216
|
+
const query = c.req.query();
|
|
217
|
+
return c.json(
|
|
218
|
+
await searchKnowledgeEntries(
|
|
219
|
+
deps.db,
|
|
220
|
+
context,
|
|
221
|
+
KnowledgeEntryListRequest.parse({
|
|
222
|
+
...query,
|
|
223
|
+
...(query.limit ? { limit: Number(query.limit) } : {}),
|
|
224
|
+
}),
|
|
225
|
+
() => deps.getDocumentServices().embedder,
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
}),
|
|
229
|
+
);
|
|
230
|
+
app.post(base, (c) =>
|
|
231
|
+
run(c, true, async (context) => {
|
|
232
|
+
const result = await saveKnowledgeEntry(
|
|
233
|
+
deps.db,
|
|
234
|
+
context,
|
|
235
|
+
KnowledgeEntrySaveRequest.parse(await c.req.json()),
|
|
236
|
+
);
|
|
237
|
+
return c.json(result, result.replayed ? 200 : 201);
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
app.get(`${base}/:entryId/history`, (c) =>
|
|
241
|
+
run(c, false, async (context) =>
|
|
242
|
+
c.json(
|
|
243
|
+
await listKnowledgeEntryHistory(
|
|
244
|
+
deps.db,
|
|
245
|
+
context,
|
|
246
|
+
Id.parse(c.req.param("entryId")),
|
|
247
|
+
c.req.query("beforeRevision")
|
|
248
|
+
? z.coerce.number().int().positive().parse(c.req.query("beforeRevision"))
|
|
249
|
+
: undefined,
|
|
250
|
+
),
|
|
251
|
+
),
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
app.get(`${base}/:entryId`, (c) =>
|
|
255
|
+
run(c, false, async (context) => {
|
|
256
|
+
const options = z
|
|
257
|
+
.object({
|
|
258
|
+
revisionId: Id.optional(),
|
|
259
|
+
view: z.enum(["published", "needs_review", "archived", "rejected"]).optional(),
|
|
260
|
+
})
|
|
261
|
+
.strict()
|
|
262
|
+
.parse(c.req.query());
|
|
263
|
+
const entry = await getKnowledgeEntry(
|
|
264
|
+
deps.db,
|
|
265
|
+
context,
|
|
266
|
+
Id.parse(c.req.param("entryId")),
|
|
267
|
+
options,
|
|
268
|
+
);
|
|
269
|
+
if (!entry) throw new HTTPException(404, { message: "Knowledge entry not found" });
|
|
270
|
+
return c.json(entry);
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
app.post(`${base}/:entryId/review`, (c) =>
|
|
274
|
+
run(c, true, async (context) => {
|
|
275
|
+
const request = KnowledgeEntryReviewRequest.parse({
|
|
276
|
+
...(await c.req.json()),
|
|
277
|
+
entryId: c.req.param("entryId"),
|
|
278
|
+
});
|
|
279
|
+
return c.json(await reviewKnowledgeEntry(deps.db, context, request));
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
app.post(`${base}/:entryId/restore`, (c) =>
|
|
283
|
+
run(c, true, async (context) => {
|
|
284
|
+
const request = KnowledgeEntryRestoreRequest.parse({
|
|
285
|
+
...(await c.req.json()),
|
|
286
|
+
entryId: c.req.param("entryId"),
|
|
287
|
+
});
|
|
288
|
+
return c.json(await restoreKnowledgeEntry(deps.db, context, request));
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
app.post(`${base}/:entryId/archive`, (c) =>
|
|
292
|
+
run(c, true, async (context) => {
|
|
293
|
+
const request = z
|
|
294
|
+
.object({ operationId: Id, expectedVersion: z.number().int().positive() })
|
|
295
|
+
.strict()
|
|
296
|
+
.parse(await c.req.json());
|
|
297
|
+
return c.json(
|
|
298
|
+
await archiveKnowledgeEntry(deps.db, context, {
|
|
299
|
+
...request,
|
|
300
|
+
entryId: Id.parse(c.req.param("entryId")),
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const learning = "/v1/workspaces/:workspaceId/agent-learning";
|
|
307
|
+
async function instructionReviewContext(
|
|
308
|
+
c: Context,
|
|
309
|
+
context: Awaited<ReturnType<typeof knowledgeContextForAccess>>,
|
|
310
|
+
) {
|
|
311
|
+
if (context.actor.kind !== "human")
|
|
312
|
+
throw new HTTPException(403, {
|
|
313
|
+
message: "Instruction review requires an authenticated person",
|
|
314
|
+
});
|
|
315
|
+
await requireWorkspaceSettingsGrant(c, deps, context.workspaceId);
|
|
316
|
+
context.actor.settingsScopes = ["workspace"];
|
|
317
|
+
return context;
|
|
318
|
+
}
|
|
319
|
+
app.get(`${learning}/instructions/reviews`, (c) =>
|
|
320
|
+
run(c, false, async (context) =>
|
|
321
|
+
c.json(
|
|
322
|
+
await listAgentInstructionReviews(
|
|
323
|
+
deps.db,
|
|
324
|
+
await instructionReviewContext(c, context),
|
|
325
|
+
c.req.query("cursor"),
|
|
326
|
+
),
|
|
327
|
+
),
|
|
328
|
+
),
|
|
329
|
+
);
|
|
330
|
+
app.post(`${learning}/instructions/review`, (c) =>
|
|
331
|
+
run(c, false, async (context) =>
|
|
332
|
+
c.json(
|
|
333
|
+
await reviewAgentInstruction(
|
|
334
|
+
deps.db,
|
|
335
|
+
await instructionReviewContext(c, context),
|
|
336
|
+
AgentInstructionReviewRequest.parse(await c.req.json()),
|
|
337
|
+
),
|
|
338
|
+
),
|
|
339
|
+
),
|
|
340
|
+
);
|
|
341
|
+
app.post(`${learning}/read`, (c) =>
|
|
342
|
+
run(c, false, async (context) => {
|
|
343
|
+
const request = ReadSettings.parse(await c.req.json());
|
|
344
|
+
return c.json(
|
|
345
|
+
await getAgentLearningSettings(deps.db, context, request.scope, request.source),
|
|
346
|
+
);
|
|
347
|
+
}),
|
|
348
|
+
);
|
|
349
|
+
app.get(`${learning}/overrides`, (c) =>
|
|
350
|
+
run(c, false, async (context) => {
|
|
351
|
+
const scope = z
|
|
352
|
+
.enum(["workspace", "personal"])
|
|
353
|
+
.default("workspace")
|
|
354
|
+
.parse(c.req.query("scope"));
|
|
355
|
+
return c.json(await listAgentLearningOverrides(deps.db, context, scope));
|
|
356
|
+
}),
|
|
357
|
+
);
|
|
358
|
+
app.post(learning, (c) =>
|
|
359
|
+
run(c, false, async (context) => {
|
|
360
|
+
const request = WriteSettings.parse(await c.req.json());
|
|
361
|
+
if (context.actor.kind !== "human")
|
|
362
|
+
throw new HTTPException(403, { message: "Agents cannot change learning settings" });
|
|
363
|
+
const access = await requireAccessGrantAuthorization(c, deps, context.workspaceId);
|
|
364
|
+
if (request.source?.kind === "chat") {
|
|
365
|
+
if (!hasPermission(access.grant.permissions, "sessions:control"))
|
|
366
|
+
throw new HTTPException(403, { message: "Chat settings require session control" });
|
|
367
|
+
await requireSessionAuthorization(deps, access.grant, {
|
|
368
|
+
sessionId: request.source.id,
|
|
369
|
+
operation: "session.context.write",
|
|
370
|
+
surface: "http",
|
|
371
|
+
});
|
|
372
|
+
context.actor.settingsScopes = [request.scope];
|
|
373
|
+
} else if (request.source?.kind === "scheduled_task") {
|
|
374
|
+
if (!hasPermission(access.grant.permissions, "scheduled_tasks:manage"))
|
|
375
|
+
throw new HTTPException(403, {
|
|
376
|
+
message: "Scheduled task settings require task management",
|
|
377
|
+
});
|
|
378
|
+
context.actor.settingsScopes = [request.scope];
|
|
379
|
+
} else if (request.scope === "workspace") {
|
|
380
|
+
await requireWorkspaceSettingsGrant(c, deps, context.workspaceId);
|
|
381
|
+
context.actor.settingsScopes = ["workspace"];
|
|
382
|
+
}
|
|
383
|
+
return c.json(await saveAgentLearningSettings(deps.db, context, request));
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
lookupExternalIdentityForRequest,
|
|
3
|
+
cancelExternalWorkspaceMemberGrantForRequest,
|
|
4
|
+
} from "@opengeni/core";
|
|
1
5
|
import {
|
|
2
6
|
AcceptOrganizationInvitationRequest,
|
|
3
7
|
CreateAdditionalOrganizationRequest,
|
|
@@ -165,6 +169,22 @@ function rethrowMembershipError(error: unknown, resourceLimitMessage?: string):
|
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
172
|
+
// POST carries an opaque identity reference; this lookup never provisions.
|
|
173
|
+
app.post("/v1/organizations/:organizationId/external-identities/lookup", async (context) => {
|
|
174
|
+
const organizationId = parseId(
|
|
175
|
+
OrganizationId,
|
|
176
|
+
context.req.param("organizationId"),
|
|
177
|
+
"organization id",
|
|
178
|
+
);
|
|
179
|
+
return context.json(
|
|
180
|
+
await lookupExternalIdentityForRequest(
|
|
181
|
+
context,
|
|
182
|
+
deps,
|
|
183
|
+
organizationId,
|
|
184
|
+
await context.req.json().catch(() => null),
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
});
|
|
168
188
|
app.patch("/v1/organizations/:organizationId/external-members/:membershipId", async (context) => {
|
|
169
189
|
const organizationId = parseId(
|
|
170
190
|
OrganizationId,
|
|
@@ -461,6 +481,19 @@ export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDe
|
|
|
461
481
|
app.post(
|
|
462
482
|
"/v1/organizations/:organizationId/workspaces/:workspaceId/members/:membershipId/revoke",
|
|
463
483
|
async (context) => {
|
|
484
|
+
const input: unknown = await context.req.json().catch(() => null);
|
|
485
|
+
if (input && typeof input === "object" && "cancelGrantOperationId" in input) {
|
|
486
|
+
return context.json(
|
|
487
|
+
await cancelExternalWorkspaceMemberGrantForRequest(
|
|
488
|
+
context,
|
|
489
|
+
deps,
|
|
490
|
+
parseId(OrganizationId, context.req.param("organizationId"), "organization id"),
|
|
491
|
+
parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id"),
|
|
492
|
+
parseId(MembershipId, context.req.param("membershipId"), "membership id"),
|
|
493
|
+
input,
|
|
494
|
+
),
|
|
495
|
+
);
|
|
496
|
+
}
|
|
464
497
|
const { subjectId } = await requireManagedHuman(context, deps);
|
|
465
498
|
const organizationId = parseId(
|
|
466
499
|
OrganizationId,
|
|
@@ -150,7 +150,15 @@ export function registerScheduledTaskRoutes(app: Hono, deps: ApiRouteDeps): void
|
|
|
150
150
|
sessionAuthorization: deps.sessionAuthorization,
|
|
151
151
|
authorizationSurface: "http",
|
|
152
152
|
});
|
|
153
|
-
const task = await updateScheduledTaskForApi(
|
|
153
|
+
const task = await updateScheduledTaskForApi(
|
|
154
|
+
db,
|
|
155
|
+
workspaceId,
|
|
156
|
+
taskId,
|
|
157
|
+
update,
|
|
158
|
+
payload.agentLearning
|
|
159
|
+
? { authorization, request: payload.agentLearning, restoreState: previous }
|
|
160
|
+
: undefined,
|
|
161
|
+
);
|
|
154
162
|
await syncUpdatedScheduledTask({ db, workflowClient, previous, task });
|
|
155
163
|
return c.json(scheduledTaskForGrant(task, grant));
|
|
156
164
|
});
|
package/src/routes/sessions.ts
CHANGED
|
@@ -599,13 +599,24 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
|
|
|
599
599
|
|
|
600
600
|
app.get("/v1/workspaces/:workspaceId/new-session-draft", async (c) => {
|
|
601
601
|
const workspaceId = c.req.param("workspaceId");
|
|
602
|
-
const
|
|
602
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
603
|
+
c,
|
|
604
|
+
deps,
|
|
605
|
+
workspaceId,
|
|
606
|
+
"sessions:read",
|
|
607
|
+
);
|
|
608
|
+
const grant = authorization.grant;
|
|
603
609
|
const catalog = await resolveWorkspaceCatalogSettings(db, settings, {
|
|
604
610
|
accountId: grant.accountId,
|
|
605
611
|
workspaceId,
|
|
606
612
|
});
|
|
607
613
|
return c.json(
|
|
608
|
-
await getActorNewSessionDraft(
|
|
614
|
+
await getActorNewSessionDraft(
|
|
615
|
+
{ settings: catalog.settings, db },
|
|
616
|
+
grant,
|
|
617
|
+
workspaceId,
|
|
618
|
+
authorization,
|
|
619
|
+
),
|
|
609
620
|
);
|
|
610
621
|
});
|
|
611
622
|
|
|
@@ -3999,12 +4010,18 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
|
|
|
3999
4010
|
|
|
4000
4011
|
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/artifacts/publish", async (c) => {
|
|
4001
4012
|
const workspaceId = c.req.param("workspaceId");
|
|
4002
|
-
|
|
4013
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
4014
|
+
c,
|
|
4015
|
+
deps,
|
|
4016
|
+
workspaceId,
|
|
4017
|
+
"files:upload",
|
|
4018
|
+
);
|
|
4003
4019
|
const ctx = await channelAPreamble(c, "files:read", "artifact.publish");
|
|
4004
4020
|
const request = await parseChannelABody(c, PublishSandboxFileArtifactRequest);
|
|
4005
4021
|
return c.json(
|
|
4006
4022
|
await publishSandboxFileArtifact(deps, {
|
|
4007
4023
|
grant: ctx.grant,
|
|
4024
|
+
authorization,
|
|
4008
4025
|
session: ctx.session,
|
|
4009
4026
|
path: request.path,
|
|
4010
4027
|
signal: ctx.waitSignal,
|
|
@@ -4,6 +4,7 @@ import { HTTPException } from "hono/http-exception";
|
|
|
4
4
|
import { SkillFile, type SkillWriteReceipt } from "@opengeni/contracts";
|
|
5
5
|
import {
|
|
6
6
|
approveSkill,
|
|
7
|
+
rejectSkill,
|
|
7
8
|
listSkills,
|
|
8
9
|
readSkill,
|
|
9
10
|
requireAccessGrant,
|
|
@@ -168,7 +169,7 @@ export function registerSkillContentRoutes(app: Hono, deps: ApiRouteDeps): void
|
|
|
168
169
|
);
|
|
169
170
|
return c.json(receipt);
|
|
170
171
|
});
|
|
171
|
-
for (const operation of ["approve", "restore"] as const) {
|
|
172
|
+
for (const operation of ["approve", "reject", "restore"] as const) {
|
|
172
173
|
app.post(`${base}/:skillId/${operation}`, async (c) => {
|
|
173
174
|
const workspaceId = c.req.param("workspaceId");
|
|
174
175
|
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "workspace:read");
|
|
@@ -181,7 +182,12 @@ export function registerSkillContentRoutes(app: Hono, deps: ApiRouteDeps): void
|
|
|
181
182
|
);
|
|
182
183
|
if (!current) throw new HTTPException(404, { message: "Skill not found" });
|
|
183
184
|
authorizePreferenceRegistryScopeMutation(access, current.scope);
|
|
184
|
-
const apply =
|
|
185
|
+
const apply =
|
|
186
|
+
operation === "approve"
|
|
187
|
+
? approveSkill
|
|
188
|
+
: operation === "reject"
|
|
189
|
+
? rejectSkill
|
|
190
|
+
: restoreSkill;
|
|
185
191
|
return c.json(
|
|
186
192
|
await skillMutation(() =>
|
|
187
193
|
apply(deps.db, {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "@opengeni/contracts";
|
|
7
7
|
import {
|
|
8
8
|
getVideoGenerationOperationSummary,
|
|
9
|
+
withSessionRlsActorContext,
|
|
9
10
|
getWorkspaceVercelAiGatewayConnectionMetadata,
|
|
10
11
|
getWorkspaceVideoGenerationPolicy,
|
|
11
12
|
updateWorkspaceVideoGenerationPolicy,
|
|
@@ -14,6 +15,8 @@ import {
|
|
|
14
15
|
} from "@opengeni/db";
|
|
15
16
|
import {
|
|
16
17
|
requireAccessGrant,
|
|
18
|
+
requireAccessGrantAuthorization,
|
|
19
|
+
fileOwnerContextForAccess,
|
|
17
20
|
requireWorkspaceSettingsGrant,
|
|
18
21
|
VIDEO_GENERATION_MODEL_CATALOG,
|
|
19
22
|
videoGenerationModelSupportsFundingSource,
|
|
@@ -108,11 +111,10 @@ export function registerVideoGenerationRoutes(app: Hono, deps: ApiRouteDeps): vo
|
|
|
108
111
|
|
|
109
112
|
app.get("/v1/workspaces/:workspaceId/video-generation/operations/:operationId", async (c) => {
|
|
110
113
|
const workspaceId = c.req.param("workspaceId");
|
|
111
|
-
await
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
workspaceId,
|
|
115
|
-
c.req.param("operationId"),
|
|
114
|
+
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "workspace:read");
|
|
115
|
+
const actor = await fileOwnerContextForAccess(deps, access, "workspace:read");
|
|
116
|
+
const summary = await withSessionRlsActorContext(actor, () =>
|
|
117
|
+
getVideoGenerationOperationSummary(deps.db, workspaceId, c.req.param("operationId")),
|
|
116
118
|
);
|
|
117
119
|
if (!summary)
|
|
118
120
|
throw new HTTPException(404, {
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import {
|
|
3
|
-
ActivateWorkspaceLearningPolicyRevisionRequest,
|
|
4
|
-
CreateWorkspaceLearningPolicyRevisionRequest,
|
|
5
|
-
RollbackWorkspaceLearningPolicyRevisionRequest,
|
|
6
3
|
UndoGovernedLearningActivationHttpRequest,
|
|
7
4
|
WorkspaceLearningHistoryResponse,
|
|
8
5
|
WorkspaceLearningPolicyHistoryQuery,
|
|
9
|
-
WorkspaceLearningPolicyMutationResponse,
|
|
10
|
-
WorkspaceLearningPolicyRevision,
|
|
11
6
|
} from "@opengeni/contracts";
|
|
12
7
|
import {
|
|
13
8
|
publishGovernedLearningEventToSlack,
|
|
@@ -16,8 +11,6 @@ import {
|
|
|
16
11
|
type ApiRouteDeps,
|
|
17
12
|
} from "@opengeni/core";
|
|
18
13
|
import {
|
|
19
|
-
activateWorkspaceLearningPolicyRevision,
|
|
20
|
-
createWorkspaceLearningPolicyRevision,
|
|
21
14
|
GovernedLearningActivationAuthorityError,
|
|
22
15
|
GovernedLearningActivationConflictError,
|
|
23
16
|
GovernedLearningActivationInvalidOperationError,
|
|
@@ -25,7 +18,6 @@ import {
|
|
|
25
18
|
listGovernedLearningActivationHistory,
|
|
26
19
|
listGovernedLearningDecisionReceipts,
|
|
27
20
|
listWorkspaceLearningPolicyHistory,
|
|
28
|
-
rollbackWorkspaceLearningPolicyRevision,
|
|
29
21
|
undoGovernedLearningActivation,
|
|
30
22
|
WorkspaceLearningPolicyAuthorityError,
|
|
31
23
|
WorkspaceLearningPolicyConflictError,
|
|
@@ -121,82 +113,20 @@ export function registerWorkspaceLearningRoutes(app: Hono, deps: ApiRouteDeps):
|
|
|
121
113
|
}
|
|
122
114
|
});
|
|
123
115
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const request = await parseBody(context, CreateWorkspaceLearningPolicyRevisionRequest);
|
|
128
|
-
try {
|
|
129
|
-
return context.json(
|
|
130
|
-
WorkspaceLearningPolicyRevision.parse(
|
|
131
|
-
await createWorkspaceLearningPolicyRevision(deps.db, {
|
|
132
|
-
operationId: request.operationId ?? randomUUID(),
|
|
133
|
-
accountId: grant.accountId,
|
|
134
|
-
workspaceId,
|
|
135
|
-
workspaceMode: request.workspaceMode,
|
|
136
|
-
sourceOverrides: request.sourceOverrides,
|
|
137
|
-
supersedesRevisionId: request.supersedesRevisionId,
|
|
138
|
-
actorSubjectId: grant.subjectId,
|
|
139
|
-
principalKind: grant.principalKind,
|
|
140
|
-
}),
|
|
141
|
-
),
|
|
142
|
-
201,
|
|
143
|
-
);
|
|
144
|
-
} catch (error) {
|
|
145
|
-
learningError(error);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
app.post(`${base}/revisions/:revisionId/activate`, async (context) => {
|
|
150
|
-
const workspaceId = context.req.param("workspaceId");
|
|
151
|
-
const revisionId = z.string().uuid().safeParse(context.req.param("revisionId"));
|
|
152
|
-
if (!revisionId.success) throw new HTTPException(422, { message: "Invalid revision id" });
|
|
153
|
-
const grant = await requireWorkspaceSettingsGrant(context, deps, workspaceId);
|
|
154
|
-
const request = await parseBody(context, ActivateWorkspaceLearningPolicyRevisionRequest);
|
|
155
|
-
try {
|
|
156
|
-
return context.json(
|
|
157
|
-
WorkspaceLearningPolicyMutationResponse.parse(
|
|
158
|
-
await activateWorkspaceLearningPolicyRevision(deps.db, {
|
|
159
|
-
operationId: request.operationId ?? randomUUID(),
|
|
160
|
-
accountId: grant.accountId,
|
|
161
|
-
workspaceId,
|
|
162
|
-
revisionId: revisionId.data,
|
|
163
|
-
expectedCurrentRevisionId: request.expectedCurrentRevisionId,
|
|
164
|
-
expectedActivationVersion: request.expectedActivationVersion,
|
|
165
|
-
actorSubjectId: grant.subjectId,
|
|
166
|
-
principalKind: grant.principalKind,
|
|
167
|
-
reason: request.reason,
|
|
168
|
-
}),
|
|
169
|
-
),
|
|
170
|
-
);
|
|
171
|
-
} catch (error) {
|
|
172
|
-
learningError(error);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
app.post(`${base}/rollback`, async (context) => {
|
|
177
|
-
const workspaceId = context.req.param("workspaceId");
|
|
178
|
-
const grant = await requireWorkspaceSettingsGrant(context, deps, workspaceId);
|
|
179
|
-
const request = await parseBody(context, RollbackWorkspaceLearningPolicyRevisionRequest);
|
|
180
|
-
try {
|
|
116
|
+
for (const path of ["/revisions", "/revisions/:revisionId/activate", "/rollback"]) {
|
|
117
|
+
app.post(`${base}${path}`, async (context) => {
|
|
118
|
+
await requireWorkspaceSettingsGrant(context, deps, context.req.param("workspaceId")!);
|
|
181
119
|
return context.json(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
expectedActivationVersion: request.expectedActivationVersion,
|
|
190
|
-
actorSubjectId: grant.subjectId,
|
|
191
|
-
principalKind: grant.principalKind,
|
|
192
|
-
reason: request.reason,
|
|
193
|
-
}),
|
|
194
|
-
),
|
|
120
|
+
{
|
|
121
|
+
error: {
|
|
122
|
+
code: "learning_settings_replaced",
|
|
123
|
+
message: "Configure Knowledge, instructions and Skills in Agent learning settings.",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
410,
|
|
195
127
|
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
200
130
|
|
|
201
131
|
app.post(`${base}/activations/:activationReceiptId/undo`, async (context) => {
|
|
202
132
|
const workspaceId = context.req.param("workspaceId");
|