@opengeni/api-router 0.21.0 → 0.21.7
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-5EEC7Q6C.js → chunk-RIFVFI3J.js} +1217 -764
- package/dist/chunk-RIFVFI3J.js.map +1 -0
- package/dist/http/api-error.d.ts +16 -0
- package/dist/index.js +1 -1
- package/dist/integrations/google-drive.d.ts +4 -0
- package/dist/integrations/oauth-client.d.ts +5 -0
- package/dist/workspace-state-export.d.ts +4 -0
- package/dist/workspace-state-projection.d.ts +5 -0
- package/package.json +12 -12
- package/src/app.ts +15 -4
- package/src/http/api-error.ts +25 -0
- package/src/integrations/google-drive.ts +37 -1
- package/src/integrations/oauth-client.ts +539 -98
- package/src/model-catalog.ts +1 -0
- package/src/routes/connections.ts +17 -3
- package/src/routes/workspace-state.ts +101 -76
- package/src/workspace-state-export.ts +49 -0
- package/src/workspace-state-projection.ts +23 -0
- package/dist/chunk-5EEC7Q6C.js.map +0 -1
package/src/model-catalog.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
isOpenGeniSlackBotConnection,
|
|
25
25
|
openGeniSlackBotMetadata,
|
|
26
26
|
requireAccessGrant,
|
|
27
|
+
requireAccessGrantAuthorization,
|
|
27
28
|
requireEnvironmentEncryption,
|
|
28
29
|
} from "@opengeni/core";
|
|
29
30
|
import {
|
|
@@ -338,12 +339,25 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
338
339
|
async (c) => {
|
|
339
340
|
assertIntegrationsEnabled();
|
|
340
341
|
const workspaceId = c.req.param("workspaceId");
|
|
341
|
-
const
|
|
342
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
343
|
+
c,
|
|
344
|
+
deps,
|
|
345
|
+
workspaceId,
|
|
346
|
+
"connections:write",
|
|
347
|
+
);
|
|
348
|
+
const { grant } = authorization;
|
|
342
349
|
const connection = await saveGoogleDriveSource(deps, {
|
|
350
|
+
accountId: grant.accountId,
|
|
343
351
|
workspaceId,
|
|
344
352
|
subjectId: grant.subjectId,
|
|
345
353
|
connectionId: c.req.param("connectionId"),
|
|
346
354
|
payload: await c.req.json(),
|
|
355
|
+
canManageOrganizationDestination:
|
|
356
|
+
authorization.accountGrant?.permissions.includes("account:admin") === true,
|
|
357
|
+
canManageWorkspaceDestination: hasPermission(grant.permissions, "workspace:admin"),
|
|
358
|
+
canManagePersonalDestination:
|
|
359
|
+
authorization.contextIntegrity &&
|
|
360
|
+
authorization.authenticatedSubjectId === grant.subjectId,
|
|
347
361
|
});
|
|
348
362
|
return c.json(ConnectionResponse.parse({ connection }));
|
|
349
363
|
},
|
|
@@ -503,7 +517,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
503
517
|
}
|
|
504
518
|
const payload = parsed.data;
|
|
505
519
|
const result = await startMcpOAuth(
|
|
506
|
-
{ db, settings, observability },
|
|
520
|
+
{ db, settings, observability, oauthStartDeadlineMs: deps.oauthStartDeadlineMs },
|
|
507
521
|
{
|
|
508
522
|
accountId: grant.accountId,
|
|
509
523
|
workspaceId,
|
|
@@ -518,7 +532,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
518
532
|
app.get("/v1/integrations/oauth/callback", async (c) => {
|
|
519
533
|
assertIntegrationsEnabled();
|
|
520
534
|
const result = await completeMcpOAuthCallback(
|
|
521
|
-
{ db, settings, observability },
|
|
535
|
+
{ db, settings, observability, oauthCallbackDeadlineMs: deps.oauthCallbackDeadlineMs },
|
|
522
536
|
{
|
|
523
537
|
code: c.req.query("code"),
|
|
524
538
|
state: c.req.query("state"),
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
WORKSPACE_STATE_TOPIC_MAX_CHARS,
|
|
5
5
|
WorkspaceStateQuery,
|
|
6
6
|
WorkspaceStateResponse,
|
|
7
|
+
type AccessGrant,
|
|
8
|
+
type WorkspaceStateQuery as WorkspaceStateQueryType,
|
|
7
9
|
} from "@opengeni/contracts";
|
|
8
10
|
import { hasPermission, requireAccessGrant, type ApiRouteDeps } from "@opengeni/core";
|
|
9
11
|
import {
|
|
@@ -17,90 +19,113 @@ import { getDocumentInventory } from "@opengeni/documents";
|
|
|
17
19
|
import type { Hono } from "hono";
|
|
18
20
|
import { HTTPException } from "hono/http-exception";
|
|
19
21
|
|
|
22
|
+
import { serializeWorkspaceStateExport } from "../workspace-state-export";
|
|
20
23
|
import { projectWorkspaceState } from "../workspace-state-projection";
|
|
21
24
|
|
|
25
|
+
async function readWorkspaceState(
|
|
26
|
+
deps: ApiRouteDeps,
|
|
27
|
+
input: { workspaceId: string; query: WorkspaceStateQueryType; grant: AccessGrant },
|
|
28
|
+
) {
|
|
29
|
+
const { workspaceId, query, grant } = input;
|
|
30
|
+
const generatedAt = new Date().toISOString();
|
|
31
|
+
const canInspectKnowledge = hasPermission(grant.permissions, "documents:search");
|
|
32
|
+
|
|
33
|
+
const [workspace, policies, knowledge, currentPreferences, acceptedAttempt] = await Promise.all([
|
|
34
|
+
getWorkspace(deps.db, workspaceId),
|
|
35
|
+
listWorkspaceInstructionPolicyRevisions(deps.db, workspaceId, { limit: 1 }),
|
|
36
|
+
canInspectKnowledge
|
|
37
|
+
? (async () => {
|
|
38
|
+
const [documents, memories] = await Promise.all([
|
|
39
|
+
getDocumentInventory(deps.db, workspaceId, {
|
|
40
|
+
baseLimit: WORKSPACE_STATE_MAX_BASES,
|
|
41
|
+
topicLimit: WORKSPACE_STATE_MAX_TOPICS,
|
|
42
|
+
topicMaxChars: WORKSPACE_STATE_TOPIC_MAX_CHARS,
|
|
43
|
+
access: { viewerSubjectId: grant.subjectId },
|
|
44
|
+
}),
|
|
45
|
+
listWorkspaceStateMemoryRecords(deps.db, workspaceId),
|
|
46
|
+
]);
|
|
47
|
+
return { documents, memories };
|
|
48
|
+
})()
|
|
49
|
+
: Promise.resolve(null),
|
|
50
|
+
getCurrentPreferenceRegistryGovernanceMetadata(deps.db, {
|
|
51
|
+
workspaceId,
|
|
52
|
+
subjectId: grant.subjectId,
|
|
53
|
+
}),
|
|
54
|
+
query.attemptId
|
|
55
|
+
? getWorkspaceStateAcceptedAttemptGovernance(deps.db, {
|
|
56
|
+
accountId: grant.accountId,
|
|
57
|
+
workspaceId,
|
|
58
|
+
subjectId: grant.subjectId,
|
|
59
|
+
attemptId: query.attemptId,
|
|
60
|
+
})
|
|
61
|
+
: Promise.resolve(null),
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
if (!workspace) throw new HTTPException(404, { message: "workspace not found" });
|
|
65
|
+
const attemptGovernance = query.attemptId
|
|
66
|
+
? acceptedAttempt
|
|
67
|
+
? {
|
|
68
|
+
status: "available" as const,
|
|
69
|
+
attemptId: acceptedAttempt.attemptId,
|
|
70
|
+
executionGeneration: acceptedAttempt.executionGeneration,
|
|
71
|
+
acceptedAt: acceptedAttempt.acceptedAt,
|
|
72
|
+
policySnapshot: acceptedAttempt.policySnapshot,
|
|
73
|
+
preferenceSnapshot: acceptedAttempt.preferenceSnapshot
|
|
74
|
+
? {
|
|
75
|
+
id: acceptedAttempt.preferenceSnapshot.id,
|
|
76
|
+
descriptorHash: acceptedAttempt.preferenceSnapshot.descriptorHash,
|
|
77
|
+
descriptors: acceptedAttempt.preferenceSnapshot.descriptors.map((descriptor) => ({
|
|
78
|
+
id: descriptor.id,
|
|
79
|
+
revisionId: descriptor.revisionId,
|
|
80
|
+
contentHash: descriptor.contentHash,
|
|
81
|
+
activeVersion: descriptor.activeVersion,
|
|
82
|
+
scope: descriptor.scope,
|
|
83
|
+
})),
|
|
84
|
+
truncated: acceptedAttempt.preferenceSnapshot.truncated,
|
|
85
|
+
createdAt: acceptedAttempt.preferenceSnapshot.createdAt,
|
|
86
|
+
}
|
|
87
|
+
: null,
|
|
88
|
+
currentPreferences,
|
|
89
|
+
}
|
|
90
|
+
: { status: "unavailable" as const }
|
|
91
|
+
: null;
|
|
92
|
+
|
|
93
|
+
return WorkspaceStateResponse.parse(
|
|
94
|
+
projectWorkspaceState({
|
|
95
|
+
workspaceId,
|
|
96
|
+
generatedAt,
|
|
97
|
+
workspaceAgentInstructions: workspace.agentInstructions,
|
|
98
|
+
policies,
|
|
99
|
+
preferences: currentPreferences,
|
|
100
|
+
knowledge,
|
|
101
|
+
attemptGovernance,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
22
106
|
export function registerWorkspaceStateRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
23
|
-
|
|
107
|
+
const base = "/v1/workspaces/:workspaceId/workspace-state";
|
|
108
|
+
|
|
109
|
+
app.get(base, async (context) => {
|
|
24
110
|
const workspaceId = context.req.param("workspaceId");
|
|
25
111
|
const query = WorkspaceStateQuery.parse(context.req.query());
|
|
26
112
|
const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
getWorkspace(deps.db, workspaceId),
|
|
32
|
-
listWorkspaceInstructionPolicyRevisions(deps.db, workspaceId, { limit: 1 }),
|
|
33
|
-
canInspectKnowledge
|
|
34
|
-
? (async () => {
|
|
35
|
-
const [documents, memories] = await Promise.all([
|
|
36
|
-
getDocumentInventory(deps.db, workspaceId, {
|
|
37
|
-
baseLimit: WORKSPACE_STATE_MAX_BASES,
|
|
38
|
-
topicLimit: WORKSPACE_STATE_MAX_TOPICS,
|
|
39
|
-
topicMaxChars: WORKSPACE_STATE_TOPIC_MAX_CHARS,
|
|
40
|
-
access: { viewerSubjectId: grant.subjectId },
|
|
41
|
-
}),
|
|
42
|
-
listWorkspaceStateMemoryRecords(deps.db, workspaceId),
|
|
43
|
-
]);
|
|
44
|
-
return { documents, memories };
|
|
45
|
-
})()
|
|
46
|
-
: Promise.resolve(null),
|
|
47
|
-
query.attemptId
|
|
48
|
-
? getWorkspaceStateAcceptedAttemptGovernance(deps.db, {
|
|
49
|
-
accountId: grant.accountId,
|
|
50
|
-
workspaceId,
|
|
51
|
-
subjectId: grant.subjectId,
|
|
52
|
-
attemptId: query.attemptId,
|
|
53
|
-
}).then(async (snapshot) => {
|
|
54
|
-
if (!snapshot) return { status: "unavailable" as const };
|
|
55
|
-
const currentPreferences = await getCurrentPreferenceRegistryGovernanceMetadata(
|
|
56
|
-
deps.db,
|
|
57
|
-
{
|
|
58
|
-
workspaceId,
|
|
59
|
-
subjectId: grant.subjectId,
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
return {
|
|
63
|
-
status: "available" as const,
|
|
64
|
-
attemptId: snapshot.attemptId,
|
|
65
|
-
executionGeneration: snapshot.executionGeneration,
|
|
66
|
-
acceptedAt: snapshot.acceptedAt,
|
|
67
|
-
policySnapshot: snapshot.policySnapshot,
|
|
68
|
-
preferenceSnapshot: snapshot.preferenceSnapshot
|
|
69
|
-
? {
|
|
70
|
-
id: snapshot.preferenceSnapshot.id,
|
|
71
|
-
descriptorHash: snapshot.preferenceSnapshot.descriptorHash,
|
|
72
|
-
descriptors: snapshot.preferenceSnapshot.descriptors.map((descriptor) => ({
|
|
73
|
-
id: descriptor.id,
|
|
74
|
-
revisionId: descriptor.revisionId,
|
|
75
|
-
contentHash: descriptor.contentHash,
|
|
76
|
-
activeVersion: descriptor.activeVersion,
|
|
77
|
-
scope: descriptor.scope,
|
|
78
|
-
})),
|
|
79
|
-
truncated: snapshot.preferenceSnapshot.truncated,
|
|
80
|
-
createdAt: snapshot.preferenceSnapshot.createdAt,
|
|
81
|
-
}
|
|
82
|
-
: null,
|
|
83
|
-
currentPreferences,
|
|
84
|
-
};
|
|
85
|
-
})
|
|
86
|
-
: Promise.resolve(null),
|
|
87
|
-
]);
|
|
113
|
+
const state = await readWorkspaceState(deps, { workspaceId, query, grant });
|
|
114
|
+
context.header("cache-control", "private, no-store");
|
|
115
|
+
return context.json(state);
|
|
116
|
+
});
|
|
88
117
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
app.get(`${base}/export`, async (context) => {
|
|
119
|
+
const workspaceId = context.req.param("workspaceId");
|
|
120
|
+
const query = WorkspaceStateQuery.parse(context.req.query());
|
|
121
|
+
const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
|
|
122
|
+
const state = await readWorkspaceState(deps, { workspaceId, query, grant });
|
|
92
123
|
context.header("cache-control", "private, no-store");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
generatedAt,
|
|
98
|
-
workspaceAgentInstructions: workspace.agentInstructions,
|
|
99
|
-
policies,
|
|
100
|
-
knowledge,
|
|
101
|
-
attemptGovernance,
|
|
102
|
-
}),
|
|
103
|
-
),
|
|
124
|
+
context.header("content-type", "application/json; charset=utf-8");
|
|
125
|
+
context.header(
|
|
126
|
+
"content-disposition",
|
|
127
|
+
`attachment; filename="workspace-state-${workspaceId}-sanitized.json"`,
|
|
104
128
|
);
|
|
129
|
+
return context.body(serializeWorkspaceStateExport(state));
|
|
105
130
|
});
|
|
106
131
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
WORKSPACE_STATE_EXPORT_SCHEMA_VERSION,
|
|
4
|
+
WorkspaceStateExportResponse,
|
|
5
|
+
type WorkspaceStateExportResponse as WorkspaceStateExportResponseType,
|
|
6
|
+
type WorkspaceStateResponse,
|
|
7
|
+
} from "@opengeni/contracts";
|
|
8
|
+
|
|
9
|
+
const WORKSPACE_STATE_EXPORT_OMISSIONS = [
|
|
10
|
+
"hidden_platform_prompts",
|
|
11
|
+
"policy_bodies",
|
|
12
|
+
"preference_content",
|
|
13
|
+
"document_content_and_private_metadata",
|
|
14
|
+
"memory_content_and_provenance",
|
|
15
|
+
"secret_values_and_credentials",
|
|
16
|
+
"session_messages_and_tool_outputs",
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
function canonicalize(value: unknown): unknown {
|
|
20
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
21
|
+
if (value === null || typeof value !== "object") return value;
|
|
22
|
+
return Object.fromEntries(
|
|
23
|
+
Object.entries(value as Record<string, unknown>)
|
|
24
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
25
|
+
.map(([key, nested]) => [key, canonicalize(nested)]),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function canonicalWorkspaceStateJson(value: unknown): string {
|
|
30
|
+
return `${JSON.stringify(canonicalize(value), null, 2)}\n`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createWorkspaceStateExport(
|
|
34
|
+
state: WorkspaceStateResponse,
|
|
35
|
+
): WorkspaceStateExportResponseType {
|
|
36
|
+
const canonicalState = canonicalWorkspaceStateJson(state);
|
|
37
|
+
return WorkspaceStateExportResponse.parse({
|
|
38
|
+
kind: "opengeni.workspace_state.sanitized_export",
|
|
39
|
+
schemaVersion: WORKSPACE_STATE_EXPORT_SCHEMA_VERSION,
|
|
40
|
+
generatedAt: state.generatedAt,
|
|
41
|
+
stateSha256: createHash("sha256").update(canonicalState, "utf8").digest("hex"),
|
|
42
|
+
omissions: WORKSPACE_STATE_EXPORT_OMISSIONS,
|
|
43
|
+
state,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function serializeWorkspaceStateExport(state: WorkspaceStateResponse): string {
|
|
48
|
+
return canonicalWorkspaceStateJson(createWorkspaceStateExport(state));
|
|
49
|
+
}
|
|
@@ -34,6 +34,11 @@ type PreferenceGovernanceIdentity = {
|
|
|
34
34
|
scope: "organization" | "workspace" | "user";
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
type CurrentPreferenceProjectionInput = {
|
|
38
|
+
descriptors: PreferenceGovernanceIdentity[];
|
|
39
|
+
truncated: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
37
42
|
type AttemptGovernanceProjectionInput =
|
|
38
43
|
| { status: "unavailable" }
|
|
39
44
|
| {
|
|
@@ -60,6 +65,7 @@ export type WorkspaceStateProjectionInput = {
|
|
|
60
65
|
generatedAt: string;
|
|
61
66
|
workspaceAgentInstructions: string | null;
|
|
62
67
|
policies: WorkspaceInstructionPolicyListResponse;
|
|
68
|
+
preferences: CurrentPreferenceProjectionInput;
|
|
63
69
|
knowledge: KnowledgeProjectionInput | null;
|
|
64
70
|
attemptGovernance?: AttemptGovernanceProjectionInput | null;
|
|
65
71
|
};
|
|
@@ -308,6 +314,21 @@ function policyProjection(input: WorkspaceStateProjectionInput) {
|
|
|
308
314
|
};
|
|
309
315
|
}
|
|
310
316
|
|
|
317
|
+
function preferenceProjection(input: CurrentPreferenceProjectionInput) {
|
|
318
|
+
const descriptors = [...input.descriptors].sort((left, right) =>
|
|
319
|
+
preferenceIdentity(left).localeCompare(preferenceIdentity(right)),
|
|
320
|
+
);
|
|
321
|
+
const scopeCounts = { organization: 0, workspace: 0, user: 0 };
|
|
322
|
+
for (const descriptor of descriptors) scopeCounts[descriptor.scope] += 1;
|
|
323
|
+
return {
|
|
324
|
+
authority: "preference_registry_preferences" as const,
|
|
325
|
+
activeDescriptorCount: descriptors.length,
|
|
326
|
+
activeDescriptorHash: hashIdentities(descriptors.map(preferenceIdentity)),
|
|
327
|
+
scopeCounts,
|
|
328
|
+
truncated: input.truncated,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
311
332
|
function availableKnowledgeProjection(knowledge: KnowledgeProjectionInput) {
|
|
312
333
|
const inventory = knowledge.documents;
|
|
313
334
|
const selectedBases = inventory.bases.slice(0, WORKSPACE_STATE_MAX_BASES);
|
|
@@ -402,6 +423,7 @@ function availableKnowledgeProjection(knowledge: KnowledgeProjectionInput) {
|
|
|
402
423
|
inspectedVisibleDocumentCount,
|
|
403
424
|
documentStatusCounts: aggregateStatuses,
|
|
404
425
|
sourceKindCounts: aggregateSources,
|
|
426
|
+
authorityKindCounts: { ...inventory.authorityKindCounts },
|
|
405
427
|
topics,
|
|
406
428
|
topicsTruncated: inventory.topicsTruncated || sortedTopics.length > topics.length,
|
|
407
429
|
latestDocumentUpdatedAt: inventory.latestUpdatedAt,
|
|
@@ -432,6 +454,7 @@ export function projectWorkspaceState(
|
|
|
432
454
|
attemptGovernance: attemptGovernanceProjection(input),
|
|
433
455
|
},
|
|
434
456
|
policy: policyProjection(input),
|
|
457
|
+
preferences: preferenceProjection(input.preferences),
|
|
435
458
|
knowledge: input.knowledge
|
|
436
459
|
? availableKnowledgeProjection(input.knowledge)
|
|
437
460
|
: {
|