@opengeni/api-router 3.0.1 → 4.0.0
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/admission-trace.d.ts +8 -0
- package/dist/app.js +1 -1
- package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +2 -0
- package/dist/auth/managed-auth.d.ts +1 -0
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/{chunk-AJ62T5QN.js → chunk-E7TZFJ65.js} +22300 -21113
- package/dist/{chunk-AJ62T5QN.js.map → chunk-E7TZFJ65.js.map} +1 -1
- package/dist/connection-ownership.d.ts +1 -2
- package/dist/fatal-process-boundary.d.ts +1 -1
- package/dist/index.js +24 -6
- package/dist/index.js.map +1 -1
- package/dist/integrations/atlassian.d.ts +8 -3
- package/dist/integrations/fiken.d.ts +3 -1
- package/dist/integrations/github-lens-connect.d.ts +5 -65
- package/dist/integrations/google-drive.d.ts +2 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/oauth-client.d.ts +119 -11
- package/dist/integrations/oauth-profiles.d.ts +8 -13
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/first-party-tool-permissions.d.ts +4 -0
- package/dist/mcp-oauth.d.ts +22 -1
- package/dist/routes/api-integrations.d.ts +1 -0
- package/dist/routes/codex.d.ts +28 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/dist/routes/sessions.d.ts +24 -2
- package/dist/scheduled-task-deletion.d.ts +3 -4
- package/dist/workspace-tool-gateway.d.ts +3 -1
- package/package.json +15 -15
- package/src/admission-trace.ts +14 -0
- package/src/app.ts +141 -94
- package/src/auth/managed-auth-attempt-context.ts +9 -0
- package/src/auth/managed-auth-session-adapter.ts +11 -0
- package/src/auth/managed-auth.ts +39 -2
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/connection-ownership.ts +2 -9
- package/src/editable-artifact-production.ts +6 -0
- package/src/fatal-process-boundary.ts +17 -2
- package/src/index.ts +3 -4
- package/src/integrations/atlassian.ts +184 -76
- package/src/integrations/fiken.ts +57 -35
- package/src/integrations/github-app-connect.ts +51 -15
- package/src/integrations/github-lens-connect.ts +7 -28
- package/src/integrations/google-drive.ts +296 -125
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/oauth-client.ts +231 -132
- package/src/integrations/oauth-profiles.ts +10 -45
- package/src/integrations/personal-github.ts +55 -31
- package/src/integrations/provider-oauth.ts +57 -82
- package/src/integrations/social-oauth.ts +83 -56
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/editable-artifacts.ts +7 -2
- package/src/mcp/first-party-tool-permissions.ts +1 -0
- package/src/mcp/knowledge-entries.ts +25 -8
- package/src/mcp/server.ts +135 -43
- package/src/mcp-oauth.ts +336 -34
- package/src/routes/api-integrations.ts +116 -4
- package/src/routes/automations.ts +5 -13
- package/src/routes/billing.ts +52 -0
- package/src/routes/canonical-human-identities.ts +61 -0
- package/src/routes/capabilities.ts +51 -1
- package/src/routes/codex.ts +4 -2
- package/src/routes/connect.ts +90 -31
- package/src/routes/connections.ts +487 -307
- package/src/routes/files.ts +21 -12
- package/src/routes/github.ts +48 -19
- package/src/routes/insights.ts +17 -7
- package/src/routes/integration-facets.ts +66 -25
- package/src/routes/knowledge.ts +48 -1
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/organization-integration-policy.ts +113 -0
- package/src/routes/organization-memberships.ts +22 -2
- package/src/routes/plugins.ts +42 -4
- package/src/routes/pr-review-github.ts +67 -57
- package/src/routes/pr-review.ts +74 -71
- package/src/routes/rigs.ts +32 -13
- package/src/routes/scheduled-tasks.ts +6 -6
- package/src/routes/sessions.ts +233 -11
- package/src/routes/skill-content.ts +19 -3
- package/src/routes/social.ts +24 -13
- package/src/sandbox/channel-a.ts +7 -1
- package/src/sandbox/rematerialize.ts +2 -1
- package/src/scheduled-task-deletion.ts +13 -3
- package/src/workspace-tool-gateway.ts +31 -31
- package/dist/connection-authority-owner.d.ts +0 -59
- package/dist/routes/connection-authorities.d.ts +0 -3
- package/dist/routes/host-mcp-bindings.d.ts +0 -5
- package/dist/routes/host-mcp-resolvers.d.ts +0 -3
- package/dist/routes/packs.d.ts +0 -3
- package/src/connection-authority-owner.ts +0 -61
- package/src/routes/connection-authorities.ts +0 -139
- package/src/routes/host-mcp-bindings.ts +0 -122
- package/src/routes/host-mcp-resolvers.ts +0 -41
- package/src/routes/packs.ts +0 -784
package/src/routes/files.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readSessionAttachmentFiles } from "@opengeni/core";
|
|
1
2
|
import { ZodError } from "zod";
|
|
2
3
|
import {
|
|
3
4
|
CompleteFileUploadResponse,
|
|
@@ -405,15 +406,28 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
405
406
|
);
|
|
406
407
|
});
|
|
407
408
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
409
|
+
const readRequestedFile = async (
|
|
410
|
+
c: Context,
|
|
411
|
+
grant: Awaited<ReturnType<typeof requireAccessGrant>>,
|
|
412
|
+
) => {
|
|
413
|
+
const fileId = c.req.param("fileId") ?? "";
|
|
414
|
+
const sessionId = c.req.query("sessionId");
|
|
415
|
+
if (sessionId) {
|
|
416
|
+
if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/iu.test(sessionId)) return null;
|
|
417
|
+
return (await readSessionAttachmentFiles(deps, grant, sessionId, [fileId]))[0] ?? null;
|
|
418
|
+
}
|
|
419
|
+
return requireFileForSubject(db, {
|
|
412
420
|
accountId: grant.accountId,
|
|
413
|
-
workspaceId,
|
|
421
|
+
workspaceId: grant.workspaceId,
|
|
414
422
|
subjectId: grant.subjectId,
|
|
415
|
-
fileId
|
|
423
|
+
fileId,
|
|
416
424
|
}).catch(() => null);
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
app.get("/v1/workspaces/:workspaceId/files/:fileId", async (c) => {
|
|
428
|
+
const workspaceId = c.req.param("workspaceId");
|
|
429
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "files:read");
|
|
430
|
+
const file = await readRequestedFile(c, grant).catch(() => null);
|
|
417
431
|
if (!file) {
|
|
418
432
|
throw new HTTPException(404, { message: "file not found" });
|
|
419
433
|
}
|
|
@@ -589,12 +603,7 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
589
603
|
message: "object storage is not configured",
|
|
590
604
|
});
|
|
591
605
|
}
|
|
592
|
-
const file = await
|
|
593
|
-
accountId: grant.accountId,
|
|
594
|
-
workspaceId,
|
|
595
|
-
subjectId: grant.subjectId,
|
|
596
|
-
fileId: c.req.param("fileId"),
|
|
597
|
-
}).catch(() => null);
|
|
606
|
+
const file = await readRequestedFile(c, grant).catch(() => null);
|
|
598
607
|
if (!file) {
|
|
599
608
|
throw new HTTPException(404, { message: "file not found" });
|
|
600
609
|
}
|
package/src/routes/github.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type GitHubInstallationBindingCandidate,
|
|
8
8
|
} from "@opengeni/contracts";
|
|
9
9
|
import { PersonalGitHubConnectionMetadata } from "@opengeni/contracts/personal-github";
|
|
10
|
+
import { withOrganizationIntegrationAcquisition } from "@opengeni/db/organization-integration-policy";
|
|
10
11
|
import {
|
|
11
12
|
ListGitHubRepositoryBranchesQuery,
|
|
12
13
|
VerifyPublicGitHubRepositoryRefRequest,
|
|
@@ -72,6 +73,10 @@ import {
|
|
|
72
73
|
requireLegacyOAuthActor,
|
|
73
74
|
} from "../connection-ownership";
|
|
74
75
|
import { listPersonalGitHubConnections } from "../integrations/personal-github";
|
|
76
|
+
import {
|
|
77
|
+
integrationCommitGrant,
|
|
78
|
+
type IntegrationCommitGrant,
|
|
79
|
+
} from "../integrations/integration-commit-authority";
|
|
75
80
|
import {
|
|
76
81
|
isConsistentGitHubBindingCandidates,
|
|
77
82
|
isConsistentGitHubBindingProof,
|
|
@@ -598,6 +603,7 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
598
603
|
message: "GitHub OAuth state does not match this workspace",
|
|
599
604
|
});
|
|
600
605
|
}
|
|
606
|
+
await withOrganizationIntegrationAcquisition(db, grant, ["github-app"], async () => {});
|
|
601
607
|
let candidates: GitHubInstallationBindingCandidate[] | null;
|
|
602
608
|
try {
|
|
603
609
|
candidates = deps.githubAppApi?.discoverInstallationBindingCandidates
|
|
@@ -652,6 +658,7 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
652
658
|
message: "GitHub OAuth state does not match this workspace",
|
|
653
659
|
});
|
|
654
660
|
}
|
|
661
|
+
await withOrganizationIntegrationAcquisition(db, grant, ["github-app"], async () => {});
|
|
655
662
|
let proof;
|
|
656
663
|
try {
|
|
657
664
|
proof = deps.githubAppApi?.authorizeInstallationBinding
|
|
@@ -681,22 +688,30 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
681
688
|
const expiresAt = new Date((statePayload.iat + githubBindingStateMaxAgeSeconds) * 1_000);
|
|
682
689
|
let bound;
|
|
683
690
|
try {
|
|
684
|
-
bound = await
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
691
|
+
bound = await withOrganizationIntegrationAcquisition(
|
|
692
|
+
db,
|
|
693
|
+
grant,
|
|
694
|
+
["github-app"],
|
|
695
|
+
async (tx) => {
|
|
696
|
+
await grant.authorizeCommit(tx);
|
|
697
|
+
return bindAuthorizedGitHubInstallationRepositories(tx, {
|
|
698
|
+
accountId: grant.accountId,
|
|
699
|
+
workspaceId: grant.workspaceId,
|
|
700
|
+
installationId,
|
|
701
|
+
githubAccountId: proof.installation.accountId,
|
|
702
|
+
accountLogin: proof.installation.accountLogin,
|
|
703
|
+
accountType: proof.installation.accountType,
|
|
704
|
+
linkedBySubjectId: grant.subjectId,
|
|
705
|
+
githubActorId: proof.actorId,
|
|
706
|
+
githubActorLogin: proof.actorLogin,
|
|
707
|
+
authorityKind: proof.authorityKind,
|
|
708
|
+
authorityCheckedAt,
|
|
709
|
+
authorityExpiresAt: expiresAt,
|
|
710
|
+
authorityNonce: statePayload.nonce,
|
|
711
|
+
repositoryIds,
|
|
712
|
+
});
|
|
713
|
+
},
|
|
714
|
+
);
|
|
700
715
|
} catch (error) {
|
|
701
716
|
if (error instanceof GitHubInstallationAuthorityCommitError) {
|
|
702
717
|
throw new HTTPException(409, { message: error.message });
|
|
@@ -913,18 +928,32 @@ async function requireGitHubManageGrant(
|
|
|
913
928
|
deps: ApiRouteDeps,
|
|
914
929
|
workspaceId: string,
|
|
915
930
|
expectedState: GitHubSignedStatePayload,
|
|
916
|
-
): Promise<
|
|
931
|
+
): Promise<IntegrationCommitGrant> {
|
|
917
932
|
try {
|
|
918
933
|
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "github:manage");
|
|
919
934
|
requireLegacyOAuthActor(access);
|
|
920
|
-
return access
|
|
935
|
+
return integrationCommitGrant(access, ["github:manage"], {
|
|
936
|
+
settings: deps.settings,
|
|
937
|
+
authorizationHeader: c.req.header("authorization"),
|
|
938
|
+
});
|
|
921
939
|
} catch (error) {
|
|
922
940
|
if (!(error instanceof HTTPException) || error.status !== 401) {
|
|
923
941
|
throw error;
|
|
924
942
|
}
|
|
925
943
|
const grant = githubBrowserGrantFromState(deps.settings, expectedState, workspaceId);
|
|
926
944
|
if (grant) {
|
|
927
|
-
return
|
|
945
|
+
return {
|
|
946
|
+
...grant,
|
|
947
|
+
authorizeCommit: async () => {
|
|
948
|
+
const current = githubBrowserGrantFromState(deps.settings, expectedState, workspaceId);
|
|
949
|
+
if (
|
|
950
|
+
!current ||
|
|
951
|
+
current.accountId !== grant.accountId ||
|
|
952
|
+
current.subjectId !== grant.subjectId
|
|
953
|
+
)
|
|
954
|
+
throw new HTTPException(403, { message: "GitHub browser handoff expired or changed" });
|
|
955
|
+
},
|
|
956
|
+
};
|
|
928
957
|
}
|
|
929
958
|
throw error;
|
|
930
959
|
}
|
package/src/routes/insights.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { InsightsRange, WorkspaceInsightsResponse } from "@opengeni/contracts";
|
|
2
2
|
import {
|
|
3
3
|
getWorkspaceInsights,
|
|
4
|
+
measureInsightsPhase,
|
|
5
|
+
workspaceInsightsPhaseMetricObserver,
|
|
4
6
|
normalizeWorkspaceInsightsFilter,
|
|
5
7
|
requireAccessGrant,
|
|
6
8
|
WorkspaceInsightsFilterValidationError,
|
|
@@ -27,6 +29,7 @@ export function normalizeWorkspaceInsightsQueryFilter(
|
|
|
27
29
|
|
|
28
30
|
export function registerInsightsRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
29
31
|
const observeRequest = workspaceInsightsMetricObserver(deps.observability);
|
|
32
|
+
const observePhase = workspaceInsightsPhaseMetricObserver(deps.observability);
|
|
30
33
|
app.get("/v1/workspaces/:workspaceId/insights", async (c) => {
|
|
31
34
|
const startedAtMs = performance.now();
|
|
32
35
|
const rangeRaw = c.req.query("range") ?? "week";
|
|
@@ -38,7 +41,9 @@ export function registerInsightsRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
38
41
|
|
|
39
42
|
try {
|
|
40
43
|
const workspaceId = c.req.param("workspaceId");
|
|
41
|
-
await
|
|
44
|
+
await measureInsightsPhase(observePhase, "auth", () =>
|
|
45
|
+
requireAccessGrant(c, deps, workspaceId, "workspace:admin"),
|
|
46
|
+
);
|
|
42
47
|
|
|
43
48
|
const rangeParsed = InsightsRange.safeParse(rangeRaw);
|
|
44
49
|
if (!rangeParsed.success) {
|
|
@@ -49,12 +54,17 @@ export function registerInsightsRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
49
54
|
provider = normalizeWorkspaceInsightsQueryFilter(providerRaw, "provider");
|
|
50
55
|
model = normalizeWorkspaceInsightsQueryFilter(modelRaw, "model");
|
|
51
56
|
|
|
52
|
-
const response = await getWorkspaceInsights(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
const response = await getWorkspaceInsights(
|
|
58
|
+
deps.db,
|
|
59
|
+
deps.settings,
|
|
60
|
+
{
|
|
61
|
+
workspaceId,
|
|
62
|
+
range: rangeParsed.data,
|
|
63
|
+
provider,
|
|
64
|
+
model,
|
|
65
|
+
},
|
|
66
|
+
observePhase,
|
|
67
|
+
);
|
|
58
68
|
c.header("cache-control", "private, no-store");
|
|
59
69
|
const result = c.json(WorkspaceInsightsResponse.parse(response));
|
|
60
70
|
outcome = "completed";
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assertOrganizationIntegrationAllowed,
|
|
2
3
|
IntegrationFacetMutationResult,
|
|
3
4
|
IntegrationFacetRemovalResult,
|
|
4
5
|
IntegrationInstanceFacetsResponse,
|
|
5
6
|
MutateIntegrationFacetRequest,
|
|
6
7
|
UpsertIntegrationFacetRequest,
|
|
7
8
|
} from "@opengeni/contracts";
|
|
9
|
+
import { withOrganizationIntegrationPolicyFence } from "@opengeni/db/organization-integration-policy";
|
|
10
|
+
import { CORE_INTEGRATION_DEFINITIONS } from "@opengeni/capabilities";
|
|
8
11
|
import {
|
|
9
12
|
hasPermission,
|
|
10
13
|
requireAccessGrant,
|
|
@@ -25,6 +28,7 @@ import {
|
|
|
25
28
|
removeIntegrationFacet,
|
|
26
29
|
replayCompletedIntegrationFacetOperation,
|
|
27
30
|
setIntegrationFacetLifecycle,
|
|
31
|
+
type IntegrationFacetAcquisitionAuthorizer,
|
|
28
32
|
} from "@opengeni/db";
|
|
29
33
|
import type { Hono } from "hono";
|
|
30
34
|
import { HTTPException } from "hono/http-exception";
|
|
@@ -179,20 +183,30 @@ export function registerIntegrationFacetRoutes(app: Hono, deps: ApiRouteDeps): v
|
|
|
179
183
|
}
|
|
180
184
|
return c.json(
|
|
181
185
|
IntegrationFacetMutationResult.parse(
|
|
182
|
-
await
|
|
183
|
-
|
|
184
|
-
workspaceId,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
186
|
+
await withOrganizationIntegrationPolicyFence(
|
|
187
|
+
deps.db,
|
|
188
|
+
{ accountId: grant.accountId, workspaceId },
|
|
189
|
+
(tx, policy) =>
|
|
190
|
+
configureIntegrationFacet(tx, {
|
|
191
|
+
accountId: grant.accountId,
|
|
192
|
+
workspaceId,
|
|
193
|
+
subjectId: grant.subjectId,
|
|
194
|
+
capabilityId,
|
|
195
|
+
instanceKey,
|
|
196
|
+
facetKey,
|
|
197
|
+
displayName: payload.displayName,
|
|
198
|
+
config: payload.config,
|
|
199
|
+
...(payload.expectedVersion !== undefined
|
|
200
|
+
? { expectedVersion: payload.expectedVersion }
|
|
201
|
+
: {}),
|
|
202
|
+
idempotencyKey: payload.idempotencyKey,
|
|
203
|
+
beforeAcquire: async (_db, context) =>
|
|
204
|
+
assertOrganizationIntegrationAllowed(
|
|
205
|
+
policy,
|
|
206
|
+
installedFacetIntegrationKey(context),
|
|
207
|
+
),
|
|
208
|
+
}),
|
|
209
|
+
),
|
|
196
210
|
),
|
|
197
211
|
payload.expectedVersion === undefined ? 201 : 200,
|
|
198
212
|
);
|
|
@@ -212,17 +226,27 @@ export function registerIntegrationFacetRoutes(app: Hono, deps: ApiRouteDeps): v
|
|
|
212
226
|
try {
|
|
213
227
|
return c.json(
|
|
214
228
|
IntegrationFacetMutationResult.parse(
|
|
215
|
-
await
|
|
216
|
-
|
|
217
|
-
workspaceId,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
await withOrganizationIntegrationPolicyFence(
|
|
230
|
+
deps.db,
|
|
231
|
+
{ accountId: grant.accountId, workspaceId },
|
|
232
|
+
(tx, policy) =>
|
|
233
|
+
setIntegrationFacetLifecycle(tx, {
|
|
234
|
+
accountId: grant.accountId,
|
|
235
|
+
workspaceId,
|
|
236
|
+
subjectId: grant.subjectId,
|
|
237
|
+
capabilityId: decoded(c.req.param("capabilityId")),
|
|
238
|
+
instanceKey: decoded(c.req.param("instanceKey")),
|
|
239
|
+
facetKey: decoded(c.req.param("facetKey")),
|
|
240
|
+
action,
|
|
241
|
+
expectedVersion: payload.expectedVersion,
|
|
242
|
+
idempotencyKey: payload.idempotencyKey,
|
|
243
|
+
beforeAcquire: async (_db, context) =>
|
|
244
|
+
assertOrganizationIntegrationAllowed(
|
|
245
|
+
policy,
|
|
246
|
+
installedFacetIntegrationKey(context),
|
|
247
|
+
),
|
|
248
|
+
}),
|
|
249
|
+
),
|
|
226
250
|
),
|
|
227
251
|
);
|
|
228
252
|
} catch (error) {
|
|
@@ -264,6 +288,23 @@ function decoded(value: string): string {
|
|
|
264
288
|
return decodeURIComponent(value);
|
|
265
289
|
}
|
|
266
290
|
|
|
291
|
+
/** The DB context comes from the matching installed API facet and immutable
|
|
292
|
+
* server-authored version manifest, never request metadata or a provider domain.
|
|
293
|
+
*/
|
|
294
|
+
function installedFacetIntegrationKey(
|
|
295
|
+
context: Parameters<IntegrationFacetAcquisitionAuthorizer>[1],
|
|
296
|
+
): string | null {
|
|
297
|
+
if (context.apiProtocol !== "openapi" && context.apiProtocol !== "graphql") return null;
|
|
298
|
+
if (context.definitionProvenance === "workspace") return `custom:${context.apiProtocol}`;
|
|
299
|
+
if (
|
|
300
|
+
context.definitionProvenance === "curated" &&
|
|
301
|
+
CORE_INTEGRATION_DEFINITIONS.some((definition) => definition.id === context.definitionId)
|
|
302
|
+
) {
|
|
303
|
+
return context.definitionId;
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
267
308
|
function facetHttpError(error: unknown): HTTPException {
|
|
268
309
|
if (error instanceof HTTPException) return error;
|
|
269
310
|
if (error instanceof IntegrationFacetNotFoundError) {
|
package/src/routes/knowledge.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
AgentLearningContext,
|
|
6
6
|
AgentLearningOverridePatch,
|
|
7
7
|
KnowledgeEntryListRequest,
|
|
8
|
+
KnowledgeSavePreparationRequest,
|
|
8
9
|
KnowledgeReviewBatchListRequest,
|
|
9
10
|
KnowledgeEntryBatchReviewRequest,
|
|
10
11
|
AgentInstructionReviewRequest,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
} from "@opengeni/contracts";
|
|
16
17
|
import {
|
|
17
18
|
prepareKnowledgeFile,
|
|
19
|
+
prepareKnowledgeSave,
|
|
18
20
|
searchKnowledgeEntries,
|
|
19
21
|
hasPermission,
|
|
20
22
|
knowledgeContextForAccess,
|
|
@@ -125,7 +127,20 @@ export function registerKnowledgeRoutes(app: Hono, deps: ApiRouteDeps) {
|
|
|
125
127
|
throw new HTTPException(403, {
|
|
126
128
|
message: "Source preparation is unavailable for this task",
|
|
127
129
|
});
|
|
128
|
-
|
|
130
|
+
const body = await c.req.text();
|
|
131
|
+
let input: unknown = {};
|
|
132
|
+
try {
|
|
133
|
+
input = body ? JSON.parse(body) : {};
|
|
134
|
+
} catch {
|
|
135
|
+
throw new HTTPException(400, { message: "Invalid source preparation JSON" });
|
|
136
|
+
}
|
|
137
|
+
const { purpose } = z
|
|
138
|
+
.object({ purpose: z.enum(["evidence", "reference"]).default("evidence") })
|
|
139
|
+
.strict()
|
|
140
|
+
.parse(input);
|
|
141
|
+
return c.json(
|
|
142
|
+
await prepareKnowledgeFile(deps, context, Id.parse(c.req.param("fileId")), purpose),
|
|
143
|
+
);
|
|
129
144
|
}),
|
|
130
145
|
);
|
|
131
146
|
app.post(`${base}/:entryId/file/download-url`, (c) =>
|
|
@@ -173,6 +188,38 @@ export function registerKnowledgeRoutes(app: Hono, deps: ApiRouteDeps) {
|
|
|
173
188
|
);
|
|
174
189
|
}),
|
|
175
190
|
);
|
|
191
|
+
app.post(`${base}/prepare-save`, (c) =>
|
|
192
|
+
run(c, false, async (context) => {
|
|
193
|
+
if (context.actor.kind === "agent") {
|
|
194
|
+
const access = await requireAccessGrantAuthorization(
|
|
195
|
+
c,
|
|
196
|
+
deps,
|
|
197
|
+
context.workspaceId,
|
|
198
|
+
"documents:search",
|
|
199
|
+
);
|
|
200
|
+
const selected = access.grant.metadata?.firstPartyMcpTools as
|
|
201
|
+
| FirstPartyMcpToolName[]
|
|
202
|
+
| undefined;
|
|
203
|
+
if (
|
|
204
|
+
!selected ||
|
|
205
|
+
!allowedFirstPartyMcpToolsForSession(deps.settings, selected).includes(
|
|
206
|
+
"knowledge_prepare_save",
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
throw new HTTPException(403, {
|
|
210
|
+
message: "Knowledge preparation is unavailable for this task",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return c.json(
|
|
214
|
+
await prepareKnowledgeSave(
|
|
215
|
+
deps.db,
|
|
216
|
+
context,
|
|
217
|
+
KnowledgeSavePreparationRequest.parse(await c.req.json()),
|
|
218
|
+
() => deps.getDocumentServices().embedder,
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
176
223
|
app.post(`${base}/search`, (c) =>
|
|
177
224
|
run(c, false, async (context) =>
|
|
178
225
|
c.json(
|