@opengeni/api-router 2.6.4 → 2.10.0-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/access-grant-rls.d.ts +3 -0
- package/dist/app.d.ts +2 -1
- package/dist/app.js +3 -1
- package/dist/auth/organization-user-setup.d.ts +23 -0
- package/dist/{chunk-XTLI3CBH.js → chunk-XXH7DW34.js} +6866 -3554
- package/dist/chunk-XXH7DW34.js.map +1 -0
- package/dist/codemode.d.ts +6 -0
- package/dist/github-access.d.ts +25 -2
- package/dist/http/request-source.d.ts +14 -0
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/integrations/oauth-client.d.ts +2 -11
- package/dist/integrations/personal-github-repositories.d.ts +41 -2
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/mcp/server.d.ts +158 -1
- package/dist/mcp/session-view.d.ts +56 -2
- package/dist/mcp/session-wait.d.ts +1 -1
- package/dist/mcp-oauth.d.ts +19 -0
- package/dist/model-catalog.d.ts +5 -31
- package/dist/routes/codex.d.ts +27 -2
- package/dist/routes/github.d.ts +2 -0
- package/dist/routes/organization-model-providers.d.ts +3 -0
- package/dist/routes/workspace-artifacts.d.ts +2 -1
- package/dist/work-discovery-observability.d.ts +1 -1
- package/dist/workspace-artifact-content.d.ts +28 -0
- package/dist/workspace-artifact-provenance.d.ts +7 -0
- package/dist/workspace-deletion.d.ts +6 -0
- package/dist/workspace-tool-gateway-observability.d.ts +17 -0
- package/dist/workspace-tool-gateway.d.ts +118 -0
- package/package.json +19 -18
- package/src/access-grant-rls.ts +40 -0
- package/src/app.ts +296 -53
- package/src/auth/organization-user-setup.ts +95 -5
- package/src/codemode.ts +33 -4
- package/src/github-access.ts +117 -1
- package/src/http/auth.ts +11 -0
- package/src/http/request-source.ts +37 -0
- package/src/http/sse.ts +15 -7
- package/src/index.ts +18 -2
- package/src/integrations/oauth-client.ts +168 -203
- 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 +504 -133
- package/src/mcp/session-view.ts +20 -1
- package/src/mcp/session-wait.ts +3 -0
- package/src/mcp-oauth.ts +539 -0
- package/src/model-catalog.ts +45 -337
- package/src/routes/automations.ts +178 -19
- package/src/routes/codex.ts +242 -73
- package/src/routes/connections.ts +271 -16
- package/src/routes/documents.ts +67 -19
- package/src/routes/files.ts +54 -6
- package/src/routes/github.ts +116 -15
- package/src/routes/organization-memberships.ts +59 -16
- 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 +153 -65
- package/src/routes/supergrok.ts +3 -2
- package/src/routes/workspace-artifacts.ts +176 -67
- package/src/routes/workspaces.ts +405 -62
- package/src/sandbox/channel-a.ts +17 -4
- package/src/work-discovery-observability.ts +3 -3
- package/src/workspace-artifact-content.ts +163 -0
- package/src/workspace-artifact-provenance.ts +104 -0
- package/src/workspace-deletion.ts +64 -0
- package/src/workspace-tool-gateway-observability.ts +100 -0
- package/src/workspace-tool-gateway.ts +691 -0
- package/dist/chunk-XTLI3CBH.js.map +0 -1
package/src/codemode.ts
CHANGED
|
@@ -61,6 +61,15 @@ export class CodemodeCatalogNotReadyError extends Error {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export class CodemodeCatalogStaleError extends Error {
|
|
65
|
+
readonly code = "codemode_catalog_stale";
|
|
66
|
+
|
|
67
|
+
constructor() {
|
|
68
|
+
super("Codemode tool catalog is stale for the active execution attempt");
|
|
69
|
+
this.name = "CodemodeCatalogStaleError";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
export function codemodeAuthorityForGrant(grant: AccessGrant): CodemodeGrantAuthority | null {
|
|
65
74
|
const metadata = grant.metadata;
|
|
66
75
|
if (
|
|
@@ -153,6 +162,7 @@ export async function submitAndDispatchCodemodeCall(
|
|
|
153
162
|
): Promise<CodemodeCallSubmissionValue> {
|
|
154
163
|
const request = CodemodeCallRequest.parse(rawRequest);
|
|
155
164
|
const { authority, catalog } = await requireActiveCodemodeCatalog(deps, grant);
|
|
165
|
+
if (request.catalogDigest !== catalog.digest) throw new CodemodeCatalogStaleError();
|
|
156
166
|
const submitted = await submitCodemodeOperation(deps.db, {
|
|
157
167
|
...authority,
|
|
158
168
|
call: {
|
|
@@ -166,7 +176,7 @@ export async function submitAndDispatchCodemodeCall(
|
|
|
166
176
|
: operation.state === "running"
|
|
167
177
|
? "already_running"
|
|
168
178
|
: "unavailable";
|
|
169
|
-
if (operation
|
|
179
|
+
if (codemodeOperationNeedsDispatch(operation)) {
|
|
170
180
|
try {
|
|
171
181
|
const reply = await deps.bus.request(
|
|
172
182
|
codemodeDispatchSubject(authority.workspaceId, authority.attemptId),
|
|
@@ -181,13 +191,14 @@ export async function submitAndDispatchCodemodeCall(
|
|
|
181
191
|
} catch {
|
|
182
192
|
dispatch = "unavailable";
|
|
183
193
|
}
|
|
184
|
-
operation =
|
|
185
|
-
|
|
194
|
+
operation = await refreshAdmittedCodemodeOperation(operation, () =>
|
|
195
|
+
getCodemodeOperation(deps.db, {
|
|
186
196
|
accountId: authority.accountId,
|
|
187
197
|
workspaceId: authority.workspaceId,
|
|
188
198
|
attemptId: authority.attemptId,
|
|
189
199
|
operationId: operation.operationId,
|
|
190
|
-
})
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
191
202
|
if (terminal(operation)) dispatch = "terminal";
|
|
192
203
|
else if (operation.state === "running" && dispatch === "unavailable") {
|
|
193
204
|
dispatch = "already_running";
|
|
@@ -196,6 +207,10 @@ export async function submitAndDispatchCodemodeCall(
|
|
|
196
207
|
return CodemodeCallSubmission.parse({ operation, dispatch });
|
|
197
208
|
}
|
|
198
209
|
|
|
210
|
+
export function codemodeOperationNeedsDispatch(operation: CodemodeOperation): boolean {
|
|
211
|
+
return operation.state === "queued" || operation.state === "running";
|
|
212
|
+
}
|
|
213
|
+
|
|
199
214
|
export async function readCodemodeOperation(
|
|
200
215
|
deps: ApiRouteDeps,
|
|
201
216
|
grant: AccessGrant,
|
|
@@ -214,3 +229,17 @@ export async function readCodemodeOperation(
|
|
|
214
229
|
function terminal(operation: CodemodeOperation): boolean {
|
|
215
230
|
return ["completed", "failed", "outcome_unknown", "cancelled"].includes(operation.state);
|
|
216
231
|
}
|
|
232
|
+
|
|
233
|
+
export async function refreshAdmittedCodemodeOperation(
|
|
234
|
+
admitted: CodemodeOperation,
|
|
235
|
+
read: () => Promise<CodemodeOperation | null>,
|
|
236
|
+
): Promise<CodemodeOperation> {
|
|
237
|
+
try {
|
|
238
|
+
return (await read()) ?? admitted;
|
|
239
|
+
} catch {
|
|
240
|
+
// Admission is already durable. Returning the known row lets the client
|
|
241
|
+
// continue with the same operation id instead of turning a refresh outage
|
|
242
|
+
// into an unmarked post-commit failure.
|
|
243
|
+
return admitted;
|
|
244
|
+
}
|
|
245
|
+
}
|
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/auth.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { resolveFirstPartyDelegationSecret, type Settings } from "@opengeni/conf
|
|
|
2
2
|
import { verifyDelegatedAccessToken } from "@opengeni/contracts";
|
|
3
3
|
import type { Context, MiddlewareHandler } from "hono";
|
|
4
4
|
import { installExactPaths, isInstallRedirectPath } from "../routes/install";
|
|
5
|
+
import {
|
|
6
|
+
isMcpOAuthPublicProtocolPath,
|
|
7
|
+
isMcpOAuthResourcePath,
|
|
8
|
+
mcpOAuthBearerToken,
|
|
9
|
+
} from "../mcp-oauth";
|
|
5
10
|
|
|
6
11
|
const githubConnectPathPattern = /^\/v1\/workspaces\/[^/]+\/github\/connect$/;
|
|
7
12
|
const githubInstallationLinkPathPattern = /^\/v1\/workspaces\/[^/]+\/github\/installations$/;
|
|
@@ -33,6 +38,12 @@ function isAuthExempt(c: Context, settings: Settings): boolean {
|
|
|
33
38
|
return true;
|
|
34
39
|
}
|
|
35
40
|
const path = new URL(c.req.url).pathname;
|
|
41
|
+
if (settings.mcpOauthEnabled && isMcpOAuthPublicProtocolPath(path)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (settings.mcpOauthEnabled && isMcpOAuthResourcePath(path) && mcpOAuthBearerToken(c.req.raw)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
36
47
|
if (path === "/v1/config/client") {
|
|
37
48
|
return true;
|
|
38
49
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
|
|
3
|
+
const TRANSPORT_PEER_ADDRESS_BINDING = "opengeniTransportPeerAddress";
|
|
4
|
+
|
|
5
|
+
type ApiRequestBindings = {
|
|
6
|
+
[TRANSPORT_PEER_ADDRESS_BINDING]?: string | null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function apiRequestBindingsForTransportPeer(
|
|
10
|
+
address: string | null | undefined,
|
|
11
|
+
): ApiRequestBindings {
|
|
12
|
+
return { [TRANSPORT_PEER_ADDRESS_BINDING]: address ?? null };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a quota/audit source from the server-owned transport peer. Forwarded
|
|
17
|
+
* values are considered only when the operator declares an exact trusted proxy
|
|
18
|
+
* hop count; the chain is then walked from the server side so caller-prepended
|
|
19
|
+
* values cannot replace the address inserted by the trusted edge.
|
|
20
|
+
*/
|
|
21
|
+
export function trustedRequestSourceAddress(c: Context, trustedProxyHops: number): string {
|
|
22
|
+
const bindings = c.env as ApiRequestBindings | undefined;
|
|
23
|
+
const peer = normalizedAddress(bindings?.[TRANSPORT_PEER_ADDRESS_BINDING]) ?? "unknown";
|
|
24
|
+
if (trustedProxyHops <= 0 || peer === "unknown") return peer;
|
|
25
|
+
|
|
26
|
+
const forwarded = (c.req.header("x-forwarded-for") ?? "")
|
|
27
|
+
.split(",")
|
|
28
|
+
.map(normalizedAddress)
|
|
29
|
+
.filter((value): value is string => value !== null);
|
|
30
|
+
const sourceIndex = forwarded.length - trustedProxyHops;
|
|
31
|
+
return sourceIndex >= 0 ? forwarded[sourceIndex]! : peer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizedAddress(value: string | null | undefined): string | null {
|
|
35
|
+
const normalized = value?.trim();
|
|
36
|
+
return normalized ? normalized.slice(0, 128) : null;
|
|
37
|
+
}
|
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,
|
|
@@ -51,6 +54,7 @@ import {
|
|
|
51
54
|
} from "./editable-artifact-websocket";
|
|
52
55
|
import type { ApiWebSocketConnection } from "./api-websocket";
|
|
53
56
|
import { InteractionFrameProxyTransport } from "./interaction-frame-proxy";
|
|
57
|
+
import { apiRequestBindingsForTransportPeer } from "./http/request-source";
|
|
54
58
|
import {
|
|
55
59
|
createStandaloneEditableArtifactApplication,
|
|
56
60
|
type StandaloneEditableArtifactApplication,
|
|
@@ -357,6 +361,15 @@ export async function startApi(
|
|
|
357
361
|
() => assertRuntimeDatabasePosture(dbClient.db, databasePosture),
|
|
358
362
|
{ ...retryOptions, onRetry },
|
|
359
363
|
);
|
|
364
|
+
const resolvedCatalog = await retryStartupDependency(
|
|
365
|
+
"model catalog",
|
|
366
|
+
() => resolveCatalogSettings(dbClient.db, settings),
|
|
367
|
+
{ ...retryOptions, onRetry },
|
|
368
|
+
);
|
|
369
|
+
observability.info("OpenGeni model catalog resolved", {
|
|
370
|
+
catalogSource: resolvedCatalog.source,
|
|
371
|
+
catalogVersion: resolvedCatalog.version,
|
|
372
|
+
});
|
|
360
373
|
bus = await retryStartupDependency(
|
|
361
374
|
"NATS",
|
|
362
375
|
() =>
|
|
@@ -443,7 +456,10 @@ export async function startApi(
|
|
|
443
456
|
if (artifactWebSockets.handles(request)) {
|
|
444
457
|
return artifactWebSockets.upgrade(request, bunServer);
|
|
445
458
|
}
|
|
446
|
-
return app.fetch(
|
|
459
|
+
return app.fetch(
|
|
460
|
+
request,
|
|
461
|
+
apiRequestBindingsForTransportPeer(bunServer.requestIP(request)?.address),
|
|
462
|
+
);
|
|
447
463
|
},
|
|
448
464
|
websocket: {
|
|
449
465
|
maxPayloadLength: EDITABLE_ARTIFACT_LIVE_WEBSOCKET_MAX_MESSAGE_BYTES,
|