@opengeni/api-router 2.6.4 → 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.d.ts +2 -1
- package/dist/app.js +3 -1
- package/dist/{chunk-XTLI3CBH.js → chunk-HHKQMVY7.js} +3009 -1699
- package/dist/chunk-HHKQMVY7.js.map +1 -0
- package/dist/github-access.d.ts +25 -2
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/personal-github-repositories.d.ts +41 -2
- package/dist/mcp/scheduled-task-view.d.ts +3 -3
- package/dist/model-catalog.d.ts +5 -31
- 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 +55 -9
- 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/index.ts +13 -1
- package/src/integrations/personal-github-repositories.ts +238 -9
- package/src/integrations/slack-app-home.ts +2 -2
- package/src/integrations/slack-interactions.ts +77 -37
- 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/mcp/server.ts +11 -2
- package/src/model-catalog.ts +45 -337
- package/src/routes/automations.ts +178 -19
- package/src/routes/codex.ts +136 -32
- package/src/routes/connections.ts +271 -16
- package/src/routes/github.ts +116 -15
- 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 +72 -4
- package/src/routes/scheduled-tasks.ts +40 -13
- package/src/routes/sessions.ts +88 -52
- package/src/routes/supergrok.ts +3 -2
- package/src/routes/workspaces.ts +405 -62
- package/src/workspace-deletion.ts +64 -0
- package/dist/chunk-XTLI3CBH.js.map +0 -1
|
@@ -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 {};
|
|
@@ -82,8 +82,8 @@ export declare function scheduledTaskMcpSummary(task: ScheduledTask): {
|
|
|
82
82
|
runMode: "existing_session" | "new_session_per_run" | "reusable_session";
|
|
83
83
|
overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
|
|
84
84
|
action: {
|
|
85
|
+
readonly sourceId?: never;
|
|
85
86
|
kind: "agent_turn";
|
|
86
|
-
sourceId?: never;
|
|
87
87
|
sourceGeneration?: never;
|
|
88
88
|
sourceLifecycleGeneration?: never;
|
|
89
89
|
destination?: never;
|
|
@@ -205,8 +205,8 @@ export declare function boundScheduledTaskDetailMcp(task: ScheduledTask): {
|
|
|
205
205
|
runMode: "existing_session" | "new_session_per_run" | "reusable_session";
|
|
206
206
|
overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
|
|
207
207
|
action: {
|
|
208
|
+
readonly sourceId?: never;
|
|
208
209
|
kind: "agent_turn";
|
|
209
|
-
sourceId?: never;
|
|
210
210
|
sourceGeneration?: never;
|
|
211
211
|
sourceLifecycleGeneration?: never;
|
|
212
212
|
destination?: never;
|
|
@@ -389,8 +389,8 @@ export declare function boundScheduledTaskMcpPage(input: {
|
|
|
389
389
|
runMode: "existing_session" | "new_session_per_run" | "reusable_session";
|
|
390
390
|
overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
|
|
391
391
|
action: {
|
|
392
|
+
readonly sourceId?: never;
|
|
392
393
|
kind: "agent_turn";
|
|
393
|
-
sourceId?: never;
|
|
394
394
|
sourceGeneration?: never;
|
|
395
395
|
sourceLifecycleGeneration?: never;
|
|
396
396
|
destination?: never;
|
package/dist/model-catalog.d.ts
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
|
-
import { type ConfiguredModel
|
|
2
|
-
import { ClientModel, type WorkspaceModelCatalogResponse as WorkspaceModelCatalogResponseType
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
reason: "not_entitled" | "provider_unhealthy" | null;
|
|
6
|
-
checkedAt: string;
|
|
7
|
-
};
|
|
8
|
-
export type ModelCredentialReadinessObservation = {
|
|
9
|
-
status: "ready";
|
|
10
|
-
checkedAt: string;
|
|
11
|
-
} | {
|
|
12
|
-
status: "not_ready";
|
|
13
|
-
reason: "prerequisites_missing" | "needs_reauth";
|
|
14
|
-
checkedAt: string;
|
|
15
|
-
} | {
|
|
16
|
-
status: "error";
|
|
17
|
-
reason: "resolver_error";
|
|
18
|
-
checkedAt: string;
|
|
19
|
-
};
|
|
20
|
-
export declare const MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS: number;
|
|
1
|
+
import { type ConfiguredModel } from "@opengeni/config";
|
|
2
|
+
import { ClientModel, type WorkspaceModelCatalogResponse as WorkspaceModelCatalogResponseType } from "@opengeni/contracts";
|
|
3
|
+
import { type WorkspaceModelSelectionInput } from "@opengeni/core";
|
|
4
|
+
export { MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS, type ModelAvailabilityObservation, type ModelCredentialReadinessObservation, } from "@opengeni/core";
|
|
21
5
|
/** Static, client-safe definition projection. No provider secret is reachable. */
|
|
22
6
|
export declare function projectClientModel(model: ConfiguredModel): ClientModel;
|
|
23
7
|
/**
|
|
@@ -28,14 +12,4 @@ export declare function projectClientModel(model: ConfiguredModel): ClientModel;
|
|
|
28
12
|
* successful typed resolver observation; credential health and provider health
|
|
29
13
|
* are separate inputs and neither is fabricated.
|
|
30
14
|
*/
|
|
31
|
-
export declare function buildWorkspaceModelCatalog(input:
|
|
32
|
-
settings: Settings;
|
|
33
|
-
policy: WorkspaceModelPolicyContract | null;
|
|
34
|
-
codexSubscriptionActive: boolean;
|
|
35
|
-
xaiSubscriptionActive?: boolean;
|
|
36
|
-
workspaceGatewayConnectionActive?: boolean;
|
|
37
|
-
credentialReadinessObservations?: Readonly<Record<string, ModelCredentialReadinessObservation>> | undefined;
|
|
38
|
-
observations?: Readonly<Record<string, ModelAvailabilityObservation>> | undefined;
|
|
39
|
-
now?: Date | undefined;
|
|
40
|
-
credentialReadinessMaxAgeMs?: number | undefined;
|
|
41
|
-
}): WorkspaceModelCatalogResponseType;
|
|
15
|
+
export declare function buildWorkspaceModelCatalog(input: WorkspaceModelSelectionInput): WorkspaceModelCatalogResponseType;
|
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",
|
|
47
|
-
"@opengeni/artifact-tool": "^0.3.
|
|
48
|
-
"@opengeni/capabilities": "^0.3.1",
|
|
49
|
-
"@opengeni/codemode": "^0.4.
|
|
50
|
-
"@opengeni/codex": "^0.2.20",
|
|
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",
|
|
59
|
-
"@opengeni/observability": "^0.8.
|
|
60
|
-
"@opengeni/runtime": "^2.0
|
|
61
|
-
"@opengeni/storage": "^0.2.
|
|
62
|
-
"@opengeni/xai-subscription": "^0.1.2",
|
|
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
|
@@ -66,6 +66,8 @@ import {
|
|
|
66
66
|
requireLiveAgentAttemptAuthorization,
|
|
67
67
|
requirePermission,
|
|
68
68
|
releaseManagedAuthRequestActorLease,
|
|
69
|
+
resolveCatalogSettings,
|
|
70
|
+
resolveWorkspaceCatalogSettings,
|
|
69
71
|
validateManagedAuthRequestActorLease,
|
|
70
72
|
requireSessionAuthorization,
|
|
71
73
|
SessionAuthorizationDeniedError,
|
|
@@ -116,6 +118,7 @@ import { allowedCorsOrigin } from "./http/cors";
|
|
|
116
118
|
import { registerCapabilityRoutes } from "./routes/capabilities";
|
|
117
119
|
import { registerCatalogAssetRoutes } from "./routes/catalog-assets";
|
|
118
120
|
import { registerCodexRoutes } from "./routes/codex";
|
|
121
|
+
import { registerOrganizationModelProviderRoutes } from "./routes/organization-model-providers";
|
|
119
122
|
import { registerSuperGrokRoutes } from "./routes/supergrok";
|
|
120
123
|
import { registerConnectionRoutes } from "./routes/connections";
|
|
121
124
|
import { registerDocumentRoutes } from "./routes/documents";
|
|
@@ -222,6 +225,22 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
222
225
|
return createAppComposition(deps).app;
|
|
223
226
|
}
|
|
224
227
|
|
|
228
|
+
export async function resolveWorkspaceMcpRouteDeps(
|
|
229
|
+
routeDeps: ApiRouteDeps,
|
|
230
|
+
grant: AccessGrant,
|
|
231
|
+
): Promise<ApiRouteDeps> {
|
|
232
|
+
const catalogSourceSettings = routeDeps.catalogSourceSettings ?? routeDeps.settings;
|
|
233
|
+
const resolvedCatalog = await resolveWorkspaceCatalogSettings(
|
|
234
|
+
routeDeps.db,
|
|
235
|
+
catalogSourceSettings,
|
|
236
|
+
{
|
|
237
|
+
accountId: grant.accountId,
|
|
238
|
+
workspaceId: grant.workspaceId,
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
return { ...routeDeps, catalogSourceSettings, settings: resolvedCatalog.settings };
|
|
242
|
+
}
|
|
243
|
+
|
|
225
244
|
export function createAppComposition(deps: AppDependencies): {
|
|
226
245
|
app: Hono;
|
|
227
246
|
routeDeps: ApiRouteDeps;
|
|
@@ -358,6 +377,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
358
377
|
: deps.transcriptionSegmenter;
|
|
359
378
|
const routeDeps: ApiRouteDeps = {
|
|
360
379
|
...deps,
|
|
380
|
+
resolveCatalogSettings: () => resolveCatalogSettings(deps.db, deps.settings),
|
|
361
381
|
observability,
|
|
362
382
|
githubStateSecret:
|
|
363
383
|
deps.githubStateSecret ?? deps.settings.githubAppManifestStateSecret ?? crypto.randomUUID(),
|
|
@@ -403,6 +423,21 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
403
423
|
"X-OpenGeni-Actor-Epoch",
|
|
404
424
|
"X-OpenGeni-Actor-State",
|
|
405
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",
|
|
406
441
|
],
|
|
407
442
|
};
|
|
408
443
|
const publicApiCors = cors({
|
|
@@ -766,10 +801,12 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
766
801
|
|
|
767
802
|
app.get("/v1/config/client", async (c) => {
|
|
768
803
|
c.header("cache-control", "no-store");
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
804
|
+
const resolvedCatalog = await resolveCatalogSettings(deps.db, deps.settings);
|
|
805
|
+
const baseCatalogSettings = resolvedCatalog.settings;
|
|
806
|
+
const codexCatalogSettings = baseCatalogSettings.codexSubscriptionEnabled
|
|
807
|
+
? withCodexCatalogProvider(baseCatalogSettings)
|
|
808
|
+
: baseCatalogSettings;
|
|
809
|
+
const catalogSettings = baseCatalogSettings.supergrokSubscriptionEnabled
|
|
773
810
|
? withXaiSubscriptionCatalogProvider(codexCatalogSettings)
|
|
774
811
|
: codexCatalogSettings;
|
|
775
812
|
return c.json(
|
|
@@ -889,7 +926,8 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
889
926
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
890
927
|
enableJsonResponse: true,
|
|
891
928
|
});
|
|
892
|
-
const
|
|
929
|
+
const mcpDeps = await resolveWorkspaceMcpRouteDeps(routeDeps, grant);
|
|
930
|
+
const mcp = buildOpenGeniMcpServer(mcpDeps, grant, {
|
|
893
931
|
requestOrigin: new URL(c.req.url).origin,
|
|
894
932
|
workspaceMemoryEnabled,
|
|
895
933
|
workspaceMemoryPromptMode,
|
|
@@ -996,6 +1034,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
996
1034
|
registerSessionRoutes(app, routeDeps);
|
|
997
1035
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
998
1036
|
registerCodexRoutes(app, routeDeps);
|
|
1037
|
+
registerOrganizationModelProviderRoutes(app, routeDeps);
|
|
999
1038
|
registerSuperGrokRoutes(app, routeDeps);
|
|
1000
1039
|
registerTranscriptionRoutes(app, routeDeps);
|
|
1001
1040
|
registerEditableArtifactRoutes(app, routeDeps);
|
|
@@ -1093,6 +1132,10 @@ const publicWorkspaceBrowserRoutePatterns = [
|
|
|
1093
1132
|
] as const;
|
|
1094
1133
|
|
|
1095
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;
|
|
1096
1139
|
if (/^\/v1\/workspaces\/[^/]+\/mcp$/.test(pathname)) return true;
|
|
1097
1140
|
if (
|
|
1098
1141
|
method === "GET" &&
|
|
@@ -1379,11 +1422,14 @@ type ReadinessCheckResult = { ok: boolean; error?: string };
|
|
|
1379
1422
|
function readinessChecks(deps: AppDependencies): ReadinessChecks {
|
|
1380
1423
|
const configuredNatsCheck = deps.readinessChecks?.nats;
|
|
1381
1424
|
return {
|
|
1382
|
-
db:
|
|
1383
|
-
deps.readinessChecks?.db
|
|
1384
|
-
|
|
1425
|
+
db: async () => {
|
|
1426
|
+
if (deps.readinessChecks?.db) {
|
|
1427
|
+
await deps.readinessChecks.db();
|
|
1428
|
+
} else {
|
|
1385
1429
|
await deps.db.execute(dbSql`select 1`);
|
|
1386
|
-
}
|
|
1430
|
+
}
|
|
1431
|
+
await resolveCatalogSettings(deps.db, deps.settings);
|
|
1432
|
+
},
|
|
1387
1433
|
nats: async () => {
|
|
1388
1434
|
requireSessionEventDurableFanoutCapability(deps.bus);
|
|
1389
1435
|
if (configuredNatsCheck) {
|
|
@@ -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
|
};
|
package/src/index.ts
CHANGED
|
@@ -28,7 +28,10 @@ import {
|
|
|
28
28
|
} from "@opengeni/observability";
|
|
29
29
|
import { createObjectStorage } from "@opengeni/storage";
|
|
30
30
|
import { isArtifactRuntimeConfigured } from "@opengeni/artifact-tool/runtime/development";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
resolveCatalogSettings,
|
|
33
|
+
SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID,
|
|
34
|
+
} from "@opengeni/core";
|
|
32
35
|
import {
|
|
33
36
|
Connection,
|
|
34
37
|
Client as TemporalClient,
|
|
@@ -357,6 +360,15 @@ export async function startApi(
|
|
|
357
360
|
() => assertRuntimeDatabasePosture(dbClient.db, databasePosture),
|
|
358
361
|
{ ...retryOptions, onRetry },
|
|
359
362
|
);
|
|
363
|
+
const resolvedCatalog = await retryStartupDependency(
|
|
364
|
+
"model catalog",
|
|
365
|
+
() => resolveCatalogSettings(dbClient.db, settings),
|
|
366
|
+
{ ...retryOptions, onRetry },
|
|
367
|
+
);
|
|
368
|
+
observability.info("OpenGeni model catalog resolved", {
|
|
369
|
+
catalogSource: resolvedCatalog.source,
|
|
370
|
+
catalogVersion: resolvedCatalog.version,
|
|
371
|
+
});
|
|
360
372
|
bus = await retryStartupDependency(
|
|
361
373
|
"NATS",
|
|
362
374
|
() =>
|