@opengeni/api-router 2.6.4-canary.0 → 2.8.1-canary.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/app.js +1 -1
- package/dist/{chunk-UOI7KAP3.js → chunk-HHKQMVY7.js} +2056 -1346
- package/dist/chunk-HHKQMVY7.js.map +1 -0
- package/dist/github-access.d.ts +25 -2
- package/dist/index.js +1 -1
- package/dist/integrations/personal-github-repositories.d.ts +41 -2
- package/dist/routes/codex.d.ts +8 -1
- package/dist/routes/github.d.ts +2 -0
- package/dist/routes/organization-model-providers.d.ts +3 -0
- package/dist/workspace-deletion.d.ts +6 -0
- package/package.json +18 -18
- package/src/app.ts +21 -0
- package/src/auth/organization-user-setup.ts +3 -3
- package/src/github-access.ts +117 -1
- package/src/http/sse.ts +15 -7
- package/src/integrations/personal-github-repositories.ts +238 -9
- package/src/integrations/slack-app-home.ts +2 -2
- package/src/mcp/company-brain-governed-writes.ts +2 -2
- package/src/mcp/company-profile-agent-admin.ts +1 -1
- package/src/mcp/remember.ts +1 -1
- package/src/routes/automations.ts +6 -14
- package/src/routes/codex.ts +136 -32
- package/src/routes/github.ts +116 -7
- package/src/routes/organization-memberships.ts +50 -3
- package/src/routes/organization-model-providers.ts +231 -0
- package/src/routes/packs.ts +1 -0
- package/src/routes/personal-github.ts +39 -0
- package/src/routes/pr-review.ts +6 -14
- package/src/routes/sessions.ts +16 -5
- package/src/routes/workspaces.ts +32 -59
- package/src/workspace-deletion.ts +64 -0
- package/dist/chunk-UOI7KAP3.js.map +0 -1
package/dist/github-access.d.ts
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import type { GitHubBindingStatus, GitHubInstallationBinding, GitHubRepository } from "@opengeni/contracts";
|
|
2
|
-
import {
|
|
1
|
+
import type { GitHubAppRepositoryBranchPage, GitHubBindingStatus, GitHubInstallationBinding, GitHubRepository } from "@opengeni/contracts";
|
|
2
|
+
import { type GitHubRepositoryBranchesResponse, type ListGitHubRepositoryBranchesQuery } from "@opengeni/contracts/github-repository-contracts";
|
|
3
|
+
import { areGitHubRepositoriesAllowedForWorkspace, listGitHubInstallationAccessForWorkspace } from "@opengeni/db";
|
|
3
4
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
5
|
+
export type GitHubRepositoryBranchAuthorityErrorCode = "changed" | "not_authorized";
|
|
6
|
+
export declare class GitHubRepositoryBranchAuthorityError extends Error {
|
|
7
|
+
readonly code: GitHubRepositoryBranchAuthorityErrorCode;
|
|
8
|
+
constructor(code: GitHubRepositoryBranchAuthorityErrorCode);
|
|
9
|
+
}
|
|
10
|
+
export type WorkspaceGitHubRepositoryBranchServices = {
|
|
11
|
+
listInstallationAccess: typeof listGitHubInstallationAccessForWorkspace;
|
|
12
|
+
areRepositoriesAllowed: typeof areGitHubRepositoriesAllowedForWorkspace;
|
|
13
|
+
listProviderBranches: (deps: ApiRouteDeps, input: {
|
|
14
|
+
installationId: number;
|
|
15
|
+
repositoryId: number;
|
|
16
|
+
page: number;
|
|
17
|
+
limit: number;
|
|
18
|
+
}) => Promise<GitHubAppRepositoryBranchPage | null>;
|
|
19
|
+
};
|
|
4
20
|
export declare function listWorkspaceGitHubInstallationBindings(deps: ApiRouteDeps, workspaceId: string): Promise<GitHubInstallationBinding[]>;
|
|
5
21
|
export declare function githubBindingStatus(configured: boolean, installations: GitHubInstallationBinding[]): GitHubBindingStatus;
|
|
6
22
|
export type LiveGitHubInstallation = {
|
|
@@ -10,3 +26,10 @@ export type LiveGitHubInstallation = {
|
|
|
10
26
|
};
|
|
11
27
|
export declare function githubInstallationBindingLifecycle(stored: Awaited<ReturnType<typeof listGitHubInstallationAccessForWorkspace>>[number], liveById: Map<number, LiveGitHubInstallation | null>, installationId: number, lifecycleVerified: boolean): GitHubInstallationBinding["lifecycle"];
|
|
12
28
|
export declare function listWorkspaceGitHubRepositories(deps: ApiRouteDeps, workspaceId: string): Promise<GitHubRepository[]>;
|
|
29
|
+
export declare function listWorkspaceGitHubRepositoryBranches(deps: ApiRouteDeps, input: {
|
|
30
|
+
accountId: string;
|
|
31
|
+
workspaceId: string;
|
|
32
|
+
installationId: number;
|
|
33
|
+
repositoryId: number;
|
|
34
|
+
query: ListGitHubRepositoryBranchesQuery;
|
|
35
|
+
}, services?: WorkspaceGitHubRepositoryBranchServices): Promise<GitHubRepositoryBranchesResponse>;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,39 @@
|
|
|
1
1
|
import { type PersonalGitHubRepository as PersonalGitHubRepositoryContract, type PersonalGitHubRepositorySelectionInput } from "@opengeni/contracts/personal-github";
|
|
2
|
+
import { type GitHubRepositoryBranchesResponse as GitHubRepositoryBranchesResponseContract, type ListGitHubRepositoryBranchesQuery } from "@opengeni/contracts/github-repository-contracts";
|
|
2
3
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
3
|
-
import { getConnectionMetadata } from "@opengeni/db";
|
|
4
|
+
import { getConnectionMetadata, type PersonalGitHubRepositorySelectionState as DbPersonalGitHubRepositorySelectionState } from "@opengeni/db";
|
|
4
5
|
import { HTTPException } from "hono/http-exception";
|
|
5
6
|
export type PersonalGitHubRepositoryConnection = NonNullable<Awaited<ReturnType<typeof getConnectionMetadata>>>;
|
|
6
|
-
|
|
7
|
+
type PersonalGitHubRepositoryProviderOperation = "repositories_list" | "repository_branches_list" | "repository_verify";
|
|
8
|
+
type ExpectedPersonalGitHubRepositoryAuthority = {
|
|
9
|
+
selectionGeneration: number;
|
|
10
|
+
repositoryId: string;
|
|
11
|
+
fullName: string;
|
|
12
|
+
};
|
|
13
|
+
type PersonalGitHubProviderJsonOptions = {
|
|
14
|
+
operation: PersonalGitHubRepositoryProviderOperation;
|
|
15
|
+
expectedRepositoryAuthority?: ExpectedPersonalGitHubRepositoryAuthority;
|
|
16
|
+
responseLabel?: string;
|
|
17
|
+
responseMaxBytes?: number;
|
|
18
|
+
};
|
|
19
|
+
export type PersonalGitHubRepositoryBranchServices = {
|
|
20
|
+
requireConnection: typeof requirePersonalGitHubRepositoryConnection;
|
|
21
|
+
getSelectionState: (deps: ApiRouteDeps, input: {
|
|
22
|
+
accountId: string;
|
|
23
|
+
originWorkspaceId: string;
|
|
24
|
+
subjectId: string;
|
|
25
|
+
connectionId: string;
|
|
26
|
+
}) => Promise<DbPersonalGitHubRepositorySelectionState | null>;
|
|
27
|
+
providerJson: (input: {
|
|
28
|
+
deps: ApiRouteDeps;
|
|
29
|
+
connection: PersonalGitHubRepositoryConnection;
|
|
30
|
+
subjectId: string;
|
|
31
|
+
url: URL;
|
|
32
|
+
expectedConnectionAuthorityGeneration: number;
|
|
33
|
+
options: PersonalGitHubProviderJsonOptions;
|
|
34
|
+
}) => Promise<unknown>;
|
|
35
|
+
};
|
|
36
|
+
export type PersonalGitHubRepositoryProviderErrorCode = "connection_changed" | "connection_inactive" | "invalid_provider_response" | "provider_denied" | "provider_rate_limited" | "provider_unavailable" | "repository_archived" | "repository_not_found" | "repository_not_selected" | "repository_read_denied" | "repository_selection_changed" | "repository_write_denied";
|
|
7
37
|
export declare class PersonalGitHubRepositoryProviderError extends Error {
|
|
8
38
|
readonly code: PersonalGitHubRepositoryProviderErrorCode;
|
|
9
39
|
constructor(code: PersonalGitHubRepositoryProviderErrorCode);
|
|
@@ -26,6 +56,14 @@ export declare function listLivePersonalGitHubRepositories(deps: ApiRouteDeps, i
|
|
|
26
56
|
repositories: PersonalGitHubRepositoryContract[];
|
|
27
57
|
nextPage: number | null;
|
|
28
58
|
}>;
|
|
59
|
+
export declare function listLivePersonalGitHubRepositoryBranches(deps: ApiRouteDeps, input: {
|
|
60
|
+
workspaceId: string;
|
|
61
|
+
accountId: string;
|
|
62
|
+
subjectId: string;
|
|
63
|
+
connectionId: string;
|
|
64
|
+
repositoryId: string;
|
|
65
|
+
query: ListGitHubRepositoryBranchesQuery;
|
|
66
|
+
}, services?: PersonalGitHubRepositoryBranchServices): Promise<GitHubRepositoryBranchesResponseContract>;
|
|
29
67
|
/** Serial by design: one bounded provider request and one authority check at a time. */
|
|
30
68
|
export declare function verifyLivePersonalGitHubRepositories(deps: ApiRouteDeps, input: {
|
|
31
69
|
workspaceId: string;
|
|
@@ -43,3 +81,4 @@ export declare function parsePersonalGitHubProviderJson(value: string): unknown;
|
|
|
43
81
|
export declare function personalGitHubRepositoryCanRead(permissions: PersonalGitHubRepositoryContract["permissions"]): boolean;
|
|
44
82
|
export declare function personalGitHubRepositoryCanWrite(permissions: PersonalGitHubRepositoryContract["permissions"]): boolean;
|
|
45
83
|
export declare function parsePersonalGitHubRepository(value: unknown): PersonalGitHubRepositoryContract;
|
|
84
|
+
export {};
|
package/dist/routes/codex.d.ts
CHANGED
|
@@ -6,5 +6,12 @@ export declare function codexModelsForPicker(liveSlugs: readonly string[]): Arra
|
|
|
6
6
|
api: "responses";
|
|
7
7
|
}>;
|
|
8
8
|
import { type ApiRouteDeps } from "@opengeni/core";
|
|
9
|
-
import type { Hono } from "hono";
|
|
9
|
+
import type { Context, Hono } from "hono";
|
|
10
|
+
type ManagedCookieHuman = {
|
|
11
|
+
subjectId: string;
|
|
12
|
+
browserSessionHash: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function requireOrganizationCodexHuman(c: Context, deps: ApiRouteDeps, organizationId: string): Promise<ManagedCookieHuman>;
|
|
15
|
+
export declare function requireSameOriginBrowserMutation(c: Context, deps: ApiRouteDeps): void;
|
|
10
16
|
export declare function registerCodexRoutes(app: Hono, deps: ApiRouteDeps): void;
|
|
17
|
+
export {};
|
package/dist/routes/github.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type AccessGrant } from "@opengeni/contracts";
|
|
1
2
|
import type { Hono } from "hono";
|
|
2
3
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
3
4
|
export declare function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void;
|
|
5
|
+
export declare function requirePublicGitHubRepositoryVerificationPermission(grant: Pick<AccessGrant, "permissions">): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/api-router",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.1-canary.0",
|
|
4
4
|
"description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -43,23 +43,23 @@
|
|
|
43
43
|
"@hono/zod-validator": "^0.7.6",
|
|
44
44
|
"@llamaindex/liteparse": "2.14.2",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
|
-
"@opengeni/agent-proto": "^0.5.1-canary.
|
|
47
|
-
"@opengeni/artifact-tool": "^0.3.
|
|
48
|
-
"@opengeni/capabilities": "^0.3.1-canary.
|
|
49
|
-
"@opengeni/codemode": "^0.4.
|
|
50
|
-
"@opengeni/codex": "^0.2.20-canary.
|
|
51
|
-
"@opengeni/config": "^0.
|
|
52
|
-
"@opengeni/contracts": "^2.
|
|
53
|
-
"@opengeni/core": "^2.
|
|
54
|
-
"@opengeni/db": "^3.
|
|
55
|
-
"@opengeni/documents": "^0.8.
|
|
56
|
-
"@opengeni/events": "^0.4.
|
|
57
|
-
"@opengeni/github": "^0.
|
|
58
|
-
"@opengeni/network": "^0.2.3-canary.
|
|
59
|
-
"@opengeni/observability": "^0.8.
|
|
60
|
-
"@opengeni/runtime": "^2.
|
|
61
|
-
"@opengeni/storage": "^0.2.
|
|
62
|
-
"@opengeni/xai-subscription": "^0.1.2-canary.
|
|
46
|
+
"@opengeni/agent-proto": "^0.5.1-canary.8",
|
|
47
|
+
"@opengeni/artifact-tool": "^0.3.17-canary.0",
|
|
48
|
+
"@opengeni/capabilities": "^0.3.1-canary.7",
|
|
49
|
+
"@opengeni/codemode": "^0.4.25-canary.0",
|
|
50
|
+
"@opengeni/codex": "^0.2.20-canary.2",
|
|
51
|
+
"@opengeni/config": "^0.23.2-canary.0",
|
|
52
|
+
"@opengeni/contracts": "^2.11.1-canary.0",
|
|
53
|
+
"@opengeni/core": "^2.7.2-canary.0",
|
|
54
|
+
"@opengeni/db": "^3.8.2-canary.0",
|
|
55
|
+
"@opengeni/documents": "^0.8.17-canary.0",
|
|
56
|
+
"@opengeni/events": "^0.4.15-canary.0",
|
|
57
|
+
"@opengeni/github": "^0.7.1-canary.0",
|
|
58
|
+
"@opengeni/network": "^0.2.3-canary.7",
|
|
59
|
+
"@opengeni/observability": "^0.8.17-canary.0",
|
|
60
|
+
"@opengeni/runtime": "^2.1.2-canary.0",
|
|
61
|
+
"@opengeni/storage": "^0.2.118-canary.0",
|
|
62
|
+
"@opengeni/xai-subscription": "^0.1.2-canary.7",
|
|
63
63
|
"@temporalio/client": "^1.17.0",
|
|
64
64
|
"better-auth": "^1.6.14",
|
|
65
65
|
"hono": "^4.12.18",
|
package/src/app.ts
CHANGED
|
@@ -118,6 +118,7 @@ import { allowedCorsOrigin } from "./http/cors";
|
|
|
118
118
|
import { registerCapabilityRoutes } from "./routes/capabilities";
|
|
119
119
|
import { registerCatalogAssetRoutes } from "./routes/catalog-assets";
|
|
120
120
|
import { registerCodexRoutes } from "./routes/codex";
|
|
121
|
+
import { registerOrganizationModelProviderRoutes } from "./routes/organization-model-providers";
|
|
121
122
|
import { registerSuperGrokRoutes } from "./routes/supergrok";
|
|
122
123
|
import { registerConnectionRoutes } from "./routes/connections";
|
|
123
124
|
import { registerDocumentRoutes } from "./routes/documents";
|
|
@@ -422,6 +423,21 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
422
423
|
"X-OpenGeni-Actor-Epoch",
|
|
423
424
|
"X-OpenGeni-Actor-State",
|
|
424
425
|
"X-OpenGeni-Correlation-Id",
|
|
426
|
+
"X-OpenGeni-Covered-First",
|
|
427
|
+
"X-OpenGeni-Covered-Last",
|
|
428
|
+
"X-OpenGeni-Event-Direction",
|
|
429
|
+
"X-OpenGeni-Event-Mode",
|
|
430
|
+
"X-OpenGeni-Event-Result",
|
|
431
|
+
"X-OpenGeni-Event-Result-Mode",
|
|
432
|
+
"X-OpenGeni-Forensic-Exact",
|
|
433
|
+
"X-OpenGeni-Has-More",
|
|
434
|
+
"X-OpenGeni-Next-After",
|
|
435
|
+
"X-OpenGeni-Next-Before",
|
|
436
|
+
"X-OpenGeni-Page-Bytes",
|
|
437
|
+
"X-OpenGeni-Page-Max-Bytes",
|
|
438
|
+
"X-OpenGeni-Page-Truncated",
|
|
439
|
+
"X-OpenGeni-Payload-Mode",
|
|
440
|
+
"X-OpenGeni-Truncated-By",
|
|
425
441
|
],
|
|
426
442
|
};
|
|
427
443
|
const publicApiCors = cors({
|
|
@@ -1018,6 +1034,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1018
1034
|
registerSessionRoutes(app, routeDeps);
|
|
1019
1035
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
1020
1036
|
registerCodexRoutes(app, routeDeps);
|
|
1037
|
+
registerOrganizationModelProviderRoutes(app, routeDeps);
|
|
1021
1038
|
registerSuperGrokRoutes(app, routeDeps);
|
|
1022
1039
|
registerTranscriptionRoutes(app, routeDeps);
|
|
1023
1040
|
registerEditableArtifactRoutes(app, routeDeps);
|
|
@@ -1115,6 +1132,10 @@ const publicWorkspaceBrowserRoutePatterns = [
|
|
|
1115
1132
|
] as const;
|
|
1116
1133
|
|
|
1117
1134
|
export function workspaceActorContextExempt(method: string, pathname: string): boolean {
|
|
1135
|
+
// The account-scoped provisioning route shares the workspace collection prefix.
|
|
1136
|
+
// Hono's trailing wildcard also matches it, but "external" is not a workspace UUID;
|
|
1137
|
+
// the route performs its own organization API-key authorization.
|
|
1138
|
+
if (method === "PUT" && pathname === "/v1/workspaces/external") return true;
|
|
1118
1139
|
if (/^\/v1\/workspaces\/[^/]+\/mcp$/.test(pathname)) return true;
|
|
1119
1140
|
if (
|
|
1120
1141
|
method === "GET" &&
|
|
@@ -107,9 +107,9 @@ export function renderOrganizationUserSetupEmail(input: OrganizationUserSetupEma
|
|
|
107
107
|
return {
|
|
108
108
|
from: input.senderEmail,
|
|
109
109
|
to: input.recipientEmail,
|
|
110
|
-
subject: `
|
|
111
|
-
text: `${greeting}\n\nYou
|
|
112
|
-
html: `<p>${escapeHtml(greeting)}</p><p>You
|
|
110
|
+
subject: `You're invited to join ${input.organizationName} on OpenGeni`,
|
|
111
|
+
text: `${greeting}\n\nYou've been invited to join ${input.organizationName} on OpenGeni as ${role}.\n\n${workspaceSummary}\n\nThis invitation grants only the organization role and shared workspace access listed above. It never shares anyone's Personal workspace.\n\nAccept invitation to ${input.organizationName}: ${input.setupUrl}\n\nYou'll sign in or create an account before joining. Already use OpenGeni? Open this invitation and choose Sign in as ${input.recipientEmail}. OpenGeni will show the invitation immediately after you sign in.`,
|
|
112
|
+
html: `<p>${escapeHtml(greeting)}</p><p>You've been invited to join <strong>${escapeHtml(input.organizationName)}</strong> on OpenGeni as ${escapeHtml(role)}.</p>${workspaceHtml}<p>This invitation grants only the organization role and shared workspace access listed above. It never shares anyone's Personal workspace.</p><p><a href="${escapeHtml(input.setupUrl)}">Accept invitation to ${escapeHtml(input.organizationName)}</a></p><p>You'll sign in or create an account before joining. Already use OpenGeni? Open this invitation and choose <strong>Sign in as ${escapeHtml(input.recipientEmail)}</strong>. OpenGeni will show the invitation immediately after you sign in.</p>`,
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
|
package/src/github-access.ts
CHANGED
|
@@ -1,15 +1,61 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
GitHubAppRepositoryBranchPage,
|
|
2
3
|
GitHubBindingStatus,
|
|
3
4
|
GitHubInstallationBinding,
|
|
4
5
|
GitHubRepository,
|
|
5
6
|
} from "@opengeni/contracts";
|
|
6
7
|
import {
|
|
8
|
+
GitHubRepositoryBranchesResponse as GitHubRepositoryBranchesResponseSchema,
|
|
9
|
+
type GitHubRepositoryBranchesResponse,
|
|
10
|
+
type ListGitHubRepositoryBranchesQuery,
|
|
11
|
+
} from "@opengeni/contracts/github-repository-contracts";
|
|
12
|
+
import {
|
|
13
|
+
areGitHubRepositoriesAllowedForWorkspace,
|
|
7
14
|
hasAuditableGitHubInstallationAuthority,
|
|
8
15
|
listGitHubInstallationAccessForWorkspace,
|
|
9
16
|
} from "@opengeni/db";
|
|
10
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
GitHubAppApiError,
|
|
19
|
+
listGitHubAppInstallationSummaries,
|
|
20
|
+
listGitHubAppRepositories,
|
|
21
|
+
listGitHubAppRepositoryBranches,
|
|
22
|
+
} from "@opengeni/github";
|
|
11
23
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
12
24
|
|
|
25
|
+
export type GitHubRepositoryBranchAuthorityErrorCode = "changed" | "not_authorized";
|
|
26
|
+
|
|
27
|
+
export class GitHubRepositoryBranchAuthorityError extends Error {
|
|
28
|
+
constructor(readonly code: GitHubRepositoryBranchAuthorityErrorCode) {
|
|
29
|
+
super(code);
|
|
30
|
+
this.name = "GitHubRepositoryBranchAuthorityError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type WorkspaceGitHubRepositoryBranchServices = {
|
|
35
|
+
listInstallationAccess: typeof listGitHubInstallationAccessForWorkspace;
|
|
36
|
+
areRepositoriesAllowed: typeof areGitHubRepositoriesAllowedForWorkspace;
|
|
37
|
+
listProviderBranches: (
|
|
38
|
+
deps: ApiRouteDeps,
|
|
39
|
+
input: {
|
|
40
|
+
installationId: number;
|
|
41
|
+
repositoryId: number;
|
|
42
|
+
page: number;
|
|
43
|
+
limit: number;
|
|
44
|
+
},
|
|
45
|
+
) => Promise<GitHubAppRepositoryBranchPage | null>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const workspaceGitHubRepositoryBranchServices: WorkspaceGitHubRepositoryBranchServices = {
|
|
49
|
+
listInstallationAccess: listGitHubInstallationAccessForWorkspace,
|
|
50
|
+
areRepositoriesAllowed: areGitHubRepositoriesAllowedForWorkspace,
|
|
51
|
+
listProviderBranches: async (deps, input) =>
|
|
52
|
+
deps.githubAppApi?.listRepositoryBranches
|
|
53
|
+
? await deps.githubAppApi.listRepositoryBranches(input)
|
|
54
|
+
: deps.githubAppApi
|
|
55
|
+
? null
|
|
56
|
+
: await listGitHubAppRepositoryBranches(deps.settings, input),
|
|
57
|
+
};
|
|
58
|
+
|
|
13
59
|
export async function listWorkspaceGitHubInstallationBindings(
|
|
14
60
|
deps: ApiRouteDeps,
|
|
15
61
|
workspaceId: string,
|
|
@@ -153,3 +199,73 @@ export async function listWorkspaceGitHubRepositories(
|
|
|
153
199
|
return installation.repositoryIds.includes(repository.id);
|
|
154
200
|
});
|
|
155
201
|
}
|
|
202
|
+
|
|
203
|
+
export async function listWorkspaceGitHubRepositoryBranches(
|
|
204
|
+
deps: ApiRouteDeps,
|
|
205
|
+
input: {
|
|
206
|
+
accountId: string;
|
|
207
|
+
workspaceId: string;
|
|
208
|
+
installationId: number;
|
|
209
|
+
repositoryId: number;
|
|
210
|
+
query: ListGitHubRepositoryBranchesQuery;
|
|
211
|
+
},
|
|
212
|
+
services: WorkspaceGitHubRepositoryBranchServices = workspaceGitHubRepositoryBranchServices,
|
|
213
|
+
): Promise<GitHubRepositoryBranchesResponse> {
|
|
214
|
+
const installations = await services.listInstallationAccess(deps.db, input.workspaceId);
|
|
215
|
+
const installation = installations.find(
|
|
216
|
+
(candidate) => candidate.installationId === input.installationId,
|
|
217
|
+
);
|
|
218
|
+
if (
|
|
219
|
+
!installation ||
|
|
220
|
+
installation.accountId !== input.accountId ||
|
|
221
|
+
!hasAuditableGitHubInstallationAuthority(installation) ||
|
|
222
|
+
!installation.repositoryIds.includes(input.repositoryId)
|
|
223
|
+
) {
|
|
224
|
+
throw new GitHubRepositoryBranchAuthorityError("not_authorized");
|
|
225
|
+
}
|
|
226
|
+
if (
|
|
227
|
+
!(await services.areRepositoriesAllowed(deps.db, input.workspaceId, input.installationId, [
|
|
228
|
+
input.repositoryId,
|
|
229
|
+
]))
|
|
230
|
+
) {
|
|
231
|
+
throw new GitHubRepositoryBranchAuthorityError("changed");
|
|
232
|
+
}
|
|
233
|
+
const page = await services.listProviderBranches(deps, {
|
|
234
|
+
installationId: input.installationId,
|
|
235
|
+
repositoryId: input.repositoryId,
|
|
236
|
+
page: input.query.cursor,
|
|
237
|
+
limit: input.query.limit,
|
|
238
|
+
});
|
|
239
|
+
if (!page) {
|
|
240
|
+
throw new GitHubAppApiError(
|
|
241
|
+
"The configured GitHub provider cannot list exact repository branches",
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (
|
|
245
|
+
page.installationId !== input.installationId ||
|
|
246
|
+
page.repositoryId !== input.repositoryId ||
|
|
247
|
+
page.branches.length > input.query.limit ||
|
|
248
|
+
(page.nextPage !== null && page.nextPage !== input.query.cursor + 1) ||
|
|
249
|
+
(page.branches.length < input.query.limit && page.nextPage !== null)
|
|
250
|
+
) {
|
|
251
|
+
throw new GitHubAppApiError("GitHub returned an invalid repository branches page");
|
|
252
|
+
}
|
|
253
|
+
const response = GitHubRepositoryBranchesResponseSchema.safeParse({
|
|
254
|
+
branches: page.branches.map((name) => ({
|
|
255
|
+
name,
|
|
256
|
+
isDefault: name === page.defaultBranch,
|
|
257
|
+
})),
|
|
258
|
+
nextCursor: page.nextPage,
|
|
259
|
+
});
|
|
260
|
+
if (!response.success) {
|
|
261
|
+
throw new GitHubAppApiError("GitHub returned an invalid repository branches page");
|
|
262
|
+
}
|
|
263
|
+
if (
|
|
264
|
+
!(await services.areRepositoriesAllowed(deps.db, input.workspaceId, input.installationId, [
|
|
265
|
+
input.repositoryId,
|
|
266
|
+
]))
|
|
267
|
+
) {
|
|
268
|
+
throw new GitHubRepositoryBranchAuthorityError("changed");
|
|
269
|
+
}
|
|
270
|
+
return response.data;
|
|
271
|
+
}
|
package/src/http/sse.ts
CHANGED
|
@@ -10,12 +10,11 @@ import {
|
|
|
10
10
|
type Database,
|
|
11
11
|
} from "@opengeni/db";
|
|
12
12
|
import {
|
|
13
|
-
|
|
13
|
+
coalesceSessionEventDeltasWithCoverage,
|
|
14
14
|
formatSessionEventSse,
|
|
15
15
|
formatWorkspaceControlEventSse,
|
|
16
16
|
requireSessionEventDurableFanoutCapability,
|
|
17
17
|
SESSION_EVENT_SSE_FRAME_MAX_BYTES,
|
|
18
|
-
sessionEventResumeSequence,
|
|
19
18
|
type EventBus,
|
|
20
19
|
} from "@opengeni/events";
|
|
21
20
|
import type { Observability } from "@opengeni/observability";
|
|
@@ -295,8 +294,14 @@ export async function sseSessionStream(
|
|
|
295
294
|
limit: SESSION_REPLAY_PAGE_SIZE,
|
|
296
295
|
});
|
|
297
296
|
await options.reauthorize?.();
|
|
297
|
+
const compactProjection = coalesceSessionEventDeltasWithCoverage(events);
|
|
298
298
|
return finiteSseBatchResponse(
|
|
299
|
-
|
|
299
|
+
compactProjection.events.map((event) =>
|
|
300
|
+
formatSessionEventSse(
|
|
301
|
+
event,
|
|
302
|
+
compactProjection.coveredThroughBySequence.get(event.sequence) ?? event.sequence,
|
|
303
|
+
),
|
|
304
|
+
),
|
|
300
305
|
options,
|
|
301
306
|
);
|
|
302
307
|
}
|
|
@@ -378,9 +383,12 @@ export async function sseSessionStream(
|
|
|
378
383
|
// adjacent text deltas into bounded frames carrying `coalescedUntil`, so
|
|
379
384
|
// a long answer cannot create thousands of React renders and starve
|
|
380
385
|
// command acknowledgements behind its own token stream.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
386
|
+
const compactProjection = coalesceSessionEventDeltasWithCoverage(eligible);
|
|
387
|
+
for (const projected of compactProjection.events) {
|
|
388
|
+
const coveredThrough =
|
|
389
|
+
compactProjection.coveredThroughBySequence.get(projected.sequence) ?? projected.sequence;
|
|
390
|
+
await writeFrame(formatSessionEventSse(projected, coveredThrough));
|
|
391
|
+
lastSent = coveredThrough;
|
|
384
392
|
}
|
|
385
393
|
if (lastSent <= previousLastSent) {
|
|
386
394
|
throw new Error(`Session event replay made no progress after sequence ${lastSent}`);
|
|
@@ -432,7 +440,7 @@ export async function sseSessionStream(
|
|
|
432
440
|
drainReconnectReconciliation();
|
|
433
441
|
};
|
|
434
442
|
const send = async (event: SessionEvent) => {
|
|
435
|
-
const targetSequence =
|
|
443
|
+
const targetSequence = event.sequence;
|
|
436
444
|
if (targetSequence <= lastSent) return;
|
|
437
445
|
await reconcileDurableThrough(targetSequence);
|
|
438
446
|
};
|