@opengeni/api-router 0.21.3 → 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-AMMQI2D3.js → chunk-RIFVFI3J.js} +1167 -751
- 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/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/oauth-client.ts +539 -98
- package/src/model-catalog.ts +1 -0
- package/src/routes/connections.ts +2 -2
- 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-AMMQI2D3.js.map +0 -1
package/src/model-catalog.ts
CHANGED
|
@@ -517,7 +517,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
517
517
|
}
|
|
518
518
|
const payload = parsed.data;
|
|
519
519
|
const result = await startMcpOAuth(
|
|
520
|
-
{ db, settings, observability },
|
|
520
|
+
{ db, settings, observability, oauthStartDeadlineMs: deps.oauthStartDeadlineMs },
|
|
521
521
|
{
|
|
522
522
|
accountId: grant.accountId,
|
|
523
523
|
workspaceId,
|
|
@@ -532,7 +532,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
532
532
|
app.get("/v1/integrations/oauth/callback", async (c) => {
|
|
533
533
|
assertIntegrationsEnabled();
|
|
534
534
|
const result = await completeMcpOAuthCallback(
|
|
535
|
-
{ db, settings, observability },
|
|
535
|
+
{ db, settings, observability, oauthCallbackDeadlineMs: deps.oauthCallbackDeadlineMs },
|
|
536
536
|
{
|
|
537
537
|
code: c.req.query("code"),
|
|
538
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
|
: {
|