@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.
@@ -1,6 +1,22 @@
1
- import type { GitHubBindingStatus, GitHubInstallationBinding, GitHubRepository } from "@opengeni/contracts";
2
- import { listGitHubInstallationAccessForWorkspace } from "@opengeni/db";
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
@@ -6,7 +6,7 @@ import {
6
6
  createOpenGeniSlackBotInteractionClient,
7
7
  startSlackInteractionPump,
8
8
  startTemporalScheduleCleanupPump
9
- } from "./chunk-UOI7KAP3.js";
9
+ } from "./chunk-HHKQMVY7.js";
10
10
 
11
11
  // src/index.ts
12
12
  import {
@@ -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
- export type PersonalGitHubRepositoryProviderErrorCode = "connection_changed" | "connection_inactive" | "invalid_provider_response" | "provider_denied" | "provider_rate_limited" | "provider_unavailable" | "repository_archived" | "repository_not_found" | "repository_read_denied" | "repository_write_denied";
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 {};
@@ -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 {};
@@ -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;
@@ -0,0 +1,3 @@
1
+ import { type ApiRouteDeps } from "@opengeni/core";
2
+ import type { Hono } from "hono";
3
+ export declare function registerOrganizationModelProviderRoutes(app: Hono, deps: ApiRouteDeps): void;
@@ -0,0 +1,6 @@
1
+ import type { ApiRouteDeps } from "@opengeni/core";
2
+ export declare function deleteWorkspaceForRequest(deps: ApiRouteDeps, input: {
3
+ accountId: string;
4
+ workspaceId: string;
5
+ organizationAdministratorSubjectId?: string;
6
+ }): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "2.6.4-canary.0",
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.7",
47
- "@opengeni/artifact-tool": "^0.3.14-canary.1",
48
- "@opengeni/capabilities": "^0.3.1-canary.6",
49
- "@opengeni/codemode": "^0.4.22-canary.1",
50
- "@opengeni/codex": "^0.2.20-canary.1",
51
- "@opengeni/config": "^0.22.5-canary.1",
52
- "@opengeni/contracts": "^2.9.2-canary.1",
53
- "@opengeni/core": "^2.6.4-canary.0",
54
- "@opengeni/db": "^3.7.4-canary.0",
55
- "@opengeni/documents": "^0.8.14-canary.0",
56
- "@opengeni/events": "^0.4.12-canary.0",
57
- "@opengeni/github": "^0.6.7-canary.1",
58
- "@opengeni/network": "^0.2.3-canary.6",
59
- "@opengeni/observability": "^0.8.14-canary.1",
60
- "@opengeni/runtime": "^2.0.1-canary.1",
61
- "@opengeni/storage": "^0.2.115-canary.1",
62
- "@opengeni/xai-subscription": "^0.1.2-canary.6",
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: `Join ${input.organizationName} on OpenGeni`,
111
- text: `${greeting}\n\nYou have been invited to ${input.organizationName} 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\nSet up your account: ${input.setupUrl}\n\nIf you already have an OpenGeni account, sign in and accept the invitation instead.`,
112
- html: `<p>${escapeHtml(greeting)}</p><p>You have been invited to <strong>${escapeHtml(input.organizationName)}</strong> 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)}">Set up your account</a></p><p>If you already have an OpenGeni account, sign in and accept the invitation instead.</p>`,
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
 
@@ -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 { listGitHubAppInstallationSummaries, listGitHubAppRepositories } from "@opengeni/github";
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
- coalesceSessionEventDeltas,
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
- coalesceSessionEventDeltas(events).map(formatSessionEventSse),
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
- for (const projected of coalesceSessionEventDeltas(eligible)) {
382
- await writeFrame(formatSessionEventSse(projected));
383
- lastSent = sessionEventResumeSequence(projected);
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 = sessionEventResumeSequence(event);
443
+ const targetSequence = event.sequence;
436
444
  if (targetSequence <= lastSent) return;
437
445
  await reconcileDurableThrough(targetSequence);
438
446
  };