@opengeni/api-router 0.5.3 → 0.5.5
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 +9 -1
- package/dist/app.js +7 -1
- package/dist/{chunk-3HIA43CC.js → chunk-HBEJMWD3.js} +5470 -2223
- package/dist/chunk-HBEJMWD3.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +279 -55
- package/dist/index.js.map +1 -1
- package/package.json +20 -20
- package/src/app.ts +583 -166
- package/src/auth/managed-auth.ts +32 -16
- package/src/http/auth.ts +8 -1
- package/src/http/common.ts +6 -2
- package/src/http/sse.ts +84 -8
- package/src/index.ts +178 -75
- package/src/integrations/oauth-client.ts +403 -120
- package/src/integrations/provider-domain.ts +4 -1
- package/src/mcp/documents.ts +173 -94
- package/src/mcp/server.ts +1600 -693
- package/src/mcp/session-view.ts +8 -2
- package/src/mcp/toolspace.ts +175 -84
- package/src/observability.ts +7 -1
- package/src/routes/api-keys.ts +39 -23
- package/src/routes/billing.ts +180 -65
- package/src/routes/capabilities.ts +17 -8
- package/src/routes/catalog-assets.ts +5 -2
- package/src/routes/codex.ts +244 -63
- package/src/routes/connections.ts +71 -33
- package/src/routes/documents.ts +242 -92
- package/src/routes/enrollments.ts +100 -70
- package/src/routes/environments.ts +205 -136
- package/src/routes/files.ts +164 -39
- package/src/routes/github.ts +123 -50
- package/src/routes/install.ts +9 -2
- package/src/routes/machines.ts +9 -8
- package/src/routes/packs.ts +141 -89
- package/src/routes/rigs.ts +189 -0
- package/src/routes/scheduled-tasks.ts +51 -9
- package/src/routes/sessions.ts +870 -328
- package/src/routes/social.ts +50 -38
- package/src/routes/workspace-capture.ts +238 -0
- package/src/routes/workspaces.ts +146 -13
- package/src/sandbox/access.ts +11 -3
- package/src/sandbox/auth-callout.ts +5 -1
- package/src/sandbox/channel-a.ts +104 -27
- package/src/sandbox/enrollment.ts +13 -3
- package/src/sandbox/machines.ts +68 -59
- package/src/sandbox/metrics-ingestion.ts +238 -17
- package/src/sandbox/viewer.ts +172 -46
- package/dist/chunk-3HIA43CC.js.map +0 -1
package/src/app.ts
CHANGED
|
@@ -3,16 +3,27 @@ import {
|
|
|
3
3
|
configuredAllowedReasoningEfforts,
|
|
4
4
|
configuredModels,
|
|
5
5
|
} from "@opengeni/config";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import {
|
|
7
|
+
ClientConfig,
|
|
8
|
+
OPENGENI_API_CONTRACT_HEADER,
|
|
9
|
+
OPENGENI_API_CONTRACT_REVISION,
|
|
10
|
+
resolveWorkspaceMemoryEnabled,
|
|
11
|
+
type AccessGrant,
|
|
12
|
+
} from "@opengeni/contracts";
|
|
13
|
+
import {
|
|
14
|
+
createDocumentServices,
|
|
15
|
+
indexDocumentNow,
|
|
16
|
+
type DocumentServices,
|
|
17
|
+
} from "@opengeni/documents";
|
|
18
|
+
import { dbSql, getWorkspace } from "@opengeni/db";
|
|
9
19
|
import { createObservability } from "@opengeni/observability";
|
|
10
20
|
import { createObjectStorage } from "@opengeni/storage";
|
|
11
21
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
12
22
|
import { Hono } from "hono";
|
|
23
|
+
import { bodyLimit } from "hono/body-limit";
|
|
13
24
|
import { cors } from "hono/cors";
|
|
14
25
|
import { HTTPException } from "hono/http-exception";
|
|
15
|
-
import type { ApiRouteDeps, AppDependencies
|
|
26
|
+
import type { ApiRouteDeps, AppDependencies } from "@opengeni/core";
|
|
16
27
|
import { hasPermission, requireAccessGrant, requirePermission } from "@opengeni/core";
|
|
17
28
|
import { createManagedAuth } from "./auth/managed-auth";
|
|
18
29
|
import { createApiSandboxClient, makeResumeBoxById } from "./sandbox/access";
|
|
@@ -34,6 +45,7 @@ import { registerBillingRoutes } from "./routes/billing";
|
|
|
34
45
|
import { registerGitHubRoutes } from "./routes/github";
|
|
35
46
|
import { registerInstallRoutes } from "./routes/install";
|
|
36
47
|
import { registerPackRoutes } from "./routes/packs";
|
|
48
|
+
import { registerRigRoutes } from "./routes/rigs";
|
|
37
49
|
import { registerScheduledTaskRoutes } from "./routes/scheduled-tasks";
|
|
38
50
|
import { registerSessionRoutes } from "./routes/sessions";
|
|
39
51
|
import { registerSocialRoutes } from "./routes/social";
|
|
@@ -57,26 +69,51 @@ export {
|
|
|
57
69
|
withDefaultEnabledCapabilityMcpTools,
|
|
58
70
|
} from "@opengeni/core";
|
|
59
71
|
export { workflowIdForSession } from "@opengeni/core";
|
|
60
|
-
export { replaySessionEvents, sseSessionStream } from "./http/sse";
|
|
72
|
+
export { replaySessionEvents, sseSessionStream, sseWorkspaceControlStream } from "./http/sse";
|
|
73
|
+
|
|
74
|
+
export const API_MAX_REQUEST_BODY_BYTES = 8 * 1024 * 1024;
|
|
61
75
|
|
|
62
76
|
export function createApp(deps: AppDependencies): Hono {
|
|
63
77
|
const managedAuth = deps.managedAuth ?? createManagedAuth(deps.settings, deps.db);
|
|
64
|
-
const objectStorage =
|
|
78
|
+
const objectStorage =
|
|
79
|
+
deps.objectStorage === undefined ? createObjectStorage(deps.settings) : deps.objectStorage;
|
|
65
80
|
let documentServices: DocumentServices | null = deps.documentServices ?? null;
|
|
66
81
|
const getDocumentServices = () => {
|
|
67
82
|
documentServices ??= createDocumentServices(deps.settings);
|
|
68
83
|
return documentServices;
|
|
69
84
|
};
|
|
70
85
|
const documentIndexer = deps.documentIndexer ?? {
|
|
71
|
-
indexDocument: async ({
|
|
86
|
+
indexDocument: async ({
|
|
87
|
+
accountId,
|
|
88
|
+
workspaceId,
|
|
89
|
+
documentId,
|
|
90
|
+
}: {
|
|
91
|
+
accountId: string;
|
|
92
|
+
workspaceId: string;
|
|
93
|
+
documentId: string;
|
|
94
|
+
}) => {
|
|
72
95
|
if (!objectStorage) {
|
|
73
|
-
throw new HTTPException(503, {
|
|
96
|
+
throw new HTTPException(503, {
|
|
97
|
+
message: "object storage is not configured",
|
|
98
|
+
});
|
|
74
99
|
}
|
|
75
|
-
return await indexDocumentNow(
|
|
76
|
-
|
|
77
|
-
|
|
100
|
+
return await indexDocumentNow(
|
|
101
|
+
deps.db,
|
|
102
|
+
objectStorage,
|
|
103
|
+
workspaceId,
|
|
104
|
+
documentId,
|
|
105
|
+
getDocumentServices(),
|
|
106
|
+
{
|
|
107
|
+
beforeEmbed: async ({ chunkCount }) => {
|
|
108
|
+
await requireLimit(routeDeps, {
|
|
109
|
+
accountId,
|
|
110
|
+
workspaceId,
|
|
111
|
+
action: "document:index",
|
|
112
|
+
quantity: chunkCount,
|
|
113
|
+
});
|
|
114
|
+
},
|
|
78
115
|
},
|
|
79
|
-
|
|
116
|
+
);
|
|
80
117
|
},
|
|
81
118
|
};
|
|
82
119
|
// The API process's own agent-loop-free sandbox client — the API-direct
|
|
@@ -87,7 +124,8 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
87
124
|
const resumeBoxById = deps.resumeBoxById ?? makeResumeBoxById(sandboxClient);
|
|
88
125
|
const routeDeps: ApiRouteDeps = {
|
|
89
126
|
...deps,
|
|
90
|
-
githubStateSecret:
|
|
127
|
+
githubStateSecret:
|
|
128
|
+
deps.githubStateSecret ?? deps.settings.githubAppManifestStateSecret ?? crypto.randomUUID(),
|
|
91
129
|
managedAuth,
|
|
92
130
|
objectStorage,
|
|
93
131
|
documentIndexer,
|
|
@@ -96,17 +134,39 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
96
134
|
resumeBoxById,
|
|
97
135
|
};
|
|
98
136
|
const app = new Hono();
|
|
99
|
-
const observability =
|
|
137
|
+
const observability =
|
|
138
|
+
deps.observability ?? createObservability(deps.settings, { component: "api" });
|
|
100
139
|
|
|
101
|
-
app.use(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
140
|
+
app.use(
|
|
141
|
+
"*",
|
|
142
|
+
cors({
|
|
143
|
+
credentials: true,
|
|
144
|
+
allowHeaders: [
|
|
145
|
+
"Accept",
|
|
146
|
+
"Authorization",
|
|
147
|
+
"Content-Type",
|
|
148
|
+
"X-OpenGeni-Access-Key",
|
|
149
|
+
"X-OpenGeni-Api-Contract",
|
|
150
|
+
"X-OpenGeni-Subject",
|
|
151
|
+
],
|
|
152
|
+
exposeHeaders: ["X-OpenGeni-Api-Contract"],
|
|
153
|
+
origin: (origin) => {
|
|
154
|
+
if (!origin) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin) ? origin : null;
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
app.use(
|
|
163
|
+
"*",
|
|
164
|
+
bodyLimit({
|
|
165
|
+
maxSize: API_MAX_REQUEST_BODY_BYTES,
|
|
166
|
+
onError: (c) =>
|
|
167
|
+
c.json({ code: "PAYLOAD_TOO_LARGE", message: "Request body is too large." }, 413),
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
110
170
|
|
|
111
171
|
app.use("*", async (c, next) => {
|
|
112
172
|
const url = new URL(c.req.url);
|
|
@@ -121,7 +181,12 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
121
181
|
await next();
|
|
122
182
|
const status = c.res.status || 200;
|
|
123
183
|
const durationSeconds = (performance.now() - start) / 1000;
|
|
124
|
-
observability.recordHttpRequest({
|
|
184
|
+
observability.recordHttpRequest({
|
|
185
|
+
method: c.req.method,
|
|
186
|
+
route,
|
|
187
|
+
status,
|
|
188
|
+
durationSeconds,
|
|
189
|
+
});
|
|
125
190
|
span.end({
|
|
126
191
|
attributes: {
|
|
127
192
|
"http.response.status_code": status,
|
|
@@ -139,7 +204,12 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
139
204
|
} catch (error) {
|
|
140
205
|
const status = httpStatusForError(error);
|
|
141
206
|
const durationSeconds = (performance.now() - start) / 1000;
|
|
142
|
-
observability.recordHttpRequest({
|
|
207
|
+
observability.recordHttpRequest({
|
|
208
|
+
method: c.req.method,
|
|
209
|
+
route,
|
|
210
|
+
status,
|
|
211
|
+
durationSeconds,
|
|
212
|
+
});
|
|
143
213
|
span.end({
|
|
144
214
|
attributes: {
|
|
145
215
|
"http.response.status_code": status,
|
|
@@ -162,61 +232,92 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
162
232
|
|
|
163
233
|
app.use("*", requireAccessKey(deps.settings));
|
|
164
234
|
|
|
235
|
+
app.use("/v1/*", async (c, next) => {
|
|
236
|
+
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
237
|
+
if (
|
|
238
|
+
deps.settings.environment !== "test" &&
|
|
239
|
+
isApiContractProtectedMutation(c.req.method, new URL(c.req.url).pathname) &&
|
|
240
|
+
c.req.header(OPENGENI_API_CONTRACT_HEADER) !== OPENGENI_API_CONTRACT_REVISION
|
|
241
|
+
) {
|
|
242
|
+
return c.json(
|
|
243
|
+
{
|
|
244
|
+
code: "API_CONTRACT_CHANGED",
|
|
245
|
+
message: "OpenGeni updated. Reload this client before changing state.",
|
|
246
|
+
apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
|
|
247
|
+
},
|
|
248
|
+
409,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
await next();
|
|
252
|
+
});
|
|
253
|
+
|
|
165
254
|
if (managedAuth) {
|
|
166
255
|
app.on(["GET", "POST"], "/v1/auth/*", (c) => managedAuth.handler(c.req.raw));
|
|
167
256
|
}
|
|
168
257
|
|
|
169
|
-
app.get("/healthz", (c) =>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
258
|
+
app.get("/healthz", (c) =>
|
|
259
|
+
c.json({
|
|
260
|
+
service: deps.settings.serviceName,
|
|
261
|
+
environment: deps.settings.environment,
|
|
262
|
+
deploymentRevision: deps.settings.deploymentRevision,
|
|
263
|
+
...(deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {}),
|
|
264
|
+
ok: true,
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
176
267
|
|
|
177
268
|
app.get("/readyz", async (c) => {
|
|
178
269
|
const result = await runReadinessChecks(readinessChecks(deps), 2_000);
|
|
179
270
|
return c.json(result, result.ok ? 200 : 503);
|
|
180
271
|
});
|
|
181
272
|
|
|
182
|
-
app.get("/metrics", async (c) =>
|
|
183
|
-
|
|
184
|
-
|
|
273
|
+
app.get("/metrics", async (c) =>
|
|
274
|
+
c.text(await observability.prometheusMetrics(), 200, {
|
|
275
|
+
"content-type": "text/plain; version=0.0.4; charset=utf-8",
|
|
276
|
+
}),
|
|
277
|
+
);
|
|
185
278
|
|
|
186
|
-
app.get("/v1/config/client", (c) =>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
279
|
+
app.get("/v1/config/client", (c) => {
|
|
280
|
+
c.header("cache-control", "no-store");
|
|
281
|
+
return c.json(
|
|
282
|
+
ClientConfig.parse({
|
|
283
|
+
deploymentRevision: deps.settings.deploymentRevision,
|
|
284
|
+
apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
|
|
285
|
+
...(deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {}),
|
|
286
|
+
defaultModel: deps.settings.openaiModel,
|
|
287
|
+
allowedModels: configuredAllowedModels(deps.settings),
|
|
288
|
+
// Provider-grouped model list for the picker. configuredModels() carries the
|
|
289
|
+
// union of the built-in allow-list and every registry provider's models, in
|
|
290
|
+
// selection order (default model first); project each to the client-safe
|
|
291
|
+
// ClientModel shape (ConfiguredModel.providerId → ClientModel.provider).
|
|
292
|
+
models: configuredModels(deps.settings).map((model) => ({
|
|
293
|
+
id: model.id,
|
|
294
|
+
label: model.label,
|
|
295
|
+
provider: model.providerId,
|
|
296
|
+
providerLabel: model.providerLabel,
|
|
297
|
+
api: model.api,
|
|
298
|
+
...(model.contextWindowTokens === undefined
|
|
299
|
+
? {}
|
|
300
|
+
: { contextWindowTokens: model.contextWindowTokens }),
|
|
301
|
+
})),
|
|
302
|
+
defaultReasoningEffort: deps.settings.openaiReasoningEffort,
|
|
303
|
+
allowedReasoningEfforts: configuredAllowedReasoningEfforts(deps.settings),
|
|
304
|
+
mcpServers: deps.settings.mcpServers.map((server) => ({
|
|
305
|
+
id: server.id,
|
|
306
|
+
name: server.name ?? server.id,
|
|
307
|
+
})),
|
|
308
|
+
fileUploads: {
|
|
309
|
+
enabled: objectStorage !== null,
|
|
310
|
+
maxSizeBytes: objectStorage?.maxSinglePutSizeBytes ?? 5_000_000_000,
|
|
311
|
+
},
|
|
312
|
+
productAccessMode: deps.settings.productAccessMode,
|
|
313
|
+
auth: clientAuthConfig(deps.settings),
|
|
314
|
+
// Channel-A structured services (P4.4) ride exec/readFile/createEditor,
|
|
315
|
+
// available on every real backend; `none` has no box so they are all off.
|
|
316
|
+
// Per-session availability is still negotiated on /stream-capabilities.
|
|
317
|
+
structuredServices: structuredServicesHint(deps.settings.sandboxBackend),
|
|
318
|
+
}),
|
|
319
|
+
);
|
|
320
|
+
});
|
|
220
321
|
|
|
221
322
|
app.all("/v1/workspaces/:workspaceId/mcp", async (c) => {
|
|
222
323
|
const workspaceId = c.req.param("workspaceId");
|
|
@@ -224,10 +325,15 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
224
325
|
const toolspace = isToolspaceGrant(routeDeps.settings, grant)
|
|
225
326
|
? await prepareToolspaceMcpSurface({ deps: routeDeps, grant })
|
|
226
327
|
: null;
|
|
227
|
-
const
|
|
328
|
+
const workspace = await getWorkspace(routeDeps.db, workspaceId);
|
|
329
|
+
const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
|
|
330
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
331
|
+
enableJsonResponse: true,
|
|
332
|
+
});
|
|
228
333
|
const mcp = buildOpenGeniMcpServer(routeDeps, grant, {
|
|
229
334
|
requestOrigin: new URL(c.req.url).origin,
|
|
230
335
|
toolspace,
|
|
336
|
+
workspaceMemoryEnabled,
|
|
231
337
|
});
|
|
232
338
|
try {
|
|
233
339
|
await mcp.connect(transport);
|
|
@@ -251,6 +357,7 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
251
357
|
registerEnrollmentRoutes(app, routeDeps);
|
|
252
358
|
registerMachineRoutes(app, routeDeps);
|
|
253
359
|
registerEnvironmentRoutes(app, routeDeps);
|
|
360
|
+
registerRigRoutes(app, routeDeps);
|
|
254
361
|
registerPackRoutes(app, routeDeps);
|
|
255
362
|
registerSessionRoutes(app, routeDeps);
|
|
256
363
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
@@ -259,7 +366,11 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
259
366
|
return app;
|
|
260
367
|
}
|
|
261
368
|
|
|
262
|
-
async function requireMcpAccessGrant(
|
|
369
|
+
async function requireMcpAccessGrant(
|
|
370
|
+
c: Parameters<typeof requireAccessGrant>[0],
|
|
371
|
+
deps: ApiRouteDeps,
|
|
372
|
+
workspaceId: string,
|
|
373
|
+
): Promise<AccessGrant> {
|
|
263
374
|
const grant = await requireAccessGrant(c, deps, workspaceId);
|
|
264
375
|
if (hasPermission(grant.permissions, "workspace:read")) {
|
|
265
376
|
return grant;
|
|
@@ -276,15 +387,26 @@ function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
|
276
387
|
return { mode: "managedSession" as const, session: "cookie" as const };
|
|
277
388
|
}
|
|
278
389
|
if (settings.productAccessMode === "configured") {
|
|
279
|
-
return {
|
|
390
|
+
return {
|
|
391
|
+
mode: "configuredToken" as const,
|
|
392
|
+
headerName: "authorization" as const,
|
|
393
|
+
scheme: "bearer" as const,
|
|
394
|
+
};
|
|
280
395
|
}
|
|
281
396
|
if (settings.authRequired) {
|
|
282
|
-
return {
|
|
397
|
+
return {
|
|
398
|
+
mode: "deploymentKey" as const,
|
|
399
|
+
headerName: "x-opengeni-access-key" as const,
|
|
400
|
+
};
|
|
283
401
|
}
|
|
284
402
|
return { mode: "none" as const };
|
|
285
403
|
}
|
|
286
404
|
|
|
287
|
-
function structuredServicesHint(backend: string): {
|
|
405
|
+
function structuredServicesHint(backend: string): {
|
|
406
|
+
fileSystem: boolean;
|
|
407
|
+
git: boolean;
|
|
408
|
+
terminalEvents: boolean;
|
|
409
|
+
} {
|
|
288
410
|
const hasBox = backend !== "none";
|
|
289
411
|
return { fileSystem: hasBox, git: hasBox, terminalEvents: hasBox };
|
|
290
412
|
}
|
|
@@ -305,36 +427,56 @@ type ReadinessChecks = Record<ReadinessCheckName, () => Promise<void> | void>;
|
|
|
305
427
|
|
|
306
428
|
function readinessChecks(deps: AppDependencies): ReadinessChecks {
|
|
307
429
|
return {
|
|
308
|
-
db:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
430
|
+
db:
|
|
431
|
+
deps.readinessChecks?.db ??
|
|
432
|
+
(async () => {
|
|
433
|
+
await deps.db.execute(dbSql`select 1`);
|
|
434
|
+
}),
|
|
435
|
+
nats:
|
|
436
|
+
deps.readinessChecks?.nats ??
|
|
437
|
+
(() => {
|
|
438
|
+
if (deps.bus.isConnected && !deps.bus.isConnected()) {
|
|
439
|
+
throw new Error("NATS is not connected");
|
|
440
|
+
}
|
|
441
|
+
}),
|
|
442
|
+
temporal:
|
|
443
|
+
deps.readinessChecks?.temporal ??
|
|
444
|
+
deps.workflowClient.check ??
|
|
445
|
+
(() => {
|
|
446
|
+
throw new Error("Temporal readiness check unavailable");
|
|
447
|
+
}),
|
|
319
448
|
};
|
|
320
449
|
}
|
|
321
450
|
|
|
322
|
-
async function runReadinessChecks(
|
|
451
|
+
async function runReadinessChecks(
|
|
452
|
+
checks: ReadinessChecks,
|
|
453
|
+
timeoutMs: number,
|
|
454
|
+
): Promise<{
|
|
323
455
|
ok: boolean;
|
|
324
456
|
checks: Record<ReadinessCheckName, { ok: boolean; error?: string }>;
|
|
325
457
|
}> {
|
|
326
458
|
const entries = await Promise.all(
|
|
327
|
-
(Object.entries(checks) as Array<[ReadinessCheckName, () => Promise<void> | void]>)
|
|
328
|
-
|
|
459
|
+
(Object.entries(checks) as Array<[ReadinessCheckName, () => Promise<void> | void]>).map(
|
|
460
|
+
async ([name, check]) => {
|
|
329
461
|
try {
|
|
330
462
|
await withTimeout(Promise.resolve().then(check), timeoutMs);
|
|
331
463
|
return [name, { ok: true }] as const;
|
|
332
464
|
} catch (error) {
|
|
333
|
-
return [
|
|
465
|
+
return [
|
|
466
|
+
name,
|
|
467
|
+
{
|
|
468
|
+
ok: false,
|
|
469
|
+
error: error instanceof Error ? error.message : String(error),
|
|
470
|
+
},
|
|
471
|
+
] as const;
|
|
334
472
|
}
|
|
335
|
-
}
|
|
473
|
+
},
|
|
474
|
+
),
|
|
336
475
|
);
|
|
337
|
-
const result = Object.fromEntries(entries) as Record<
|
|
476
|
+
const result = Object.fromEntries(entries) as Record<
|
|
477
|
+
ReadinessCheckName,
|
|
478
|
+
{ ok: boolean; error?: string }
|
|
479
|
+
>;
|
|
338
480
|
return {
|
|
339
481
|
ok: Object.values(result).every((check) => check.ok),
|
|
340
482
|
checks: result,
|
|
@@ -347,7 +489,10 @@ async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T
|
|
|
347
489
|
return await Promise.race([
|
|
348
490
|
promise,
|
|
349
491
|
new Promise<never>((_, reject) => {
|
|
350
|
-
timer = setTimeout(
|
|
492
|
+
timer = setTimeout(
|
|
493
|
+
() => reject(new Error(`readiness check timed out after ${timeoutMs}ms`)),
|
|
494
|
+
timeoutMs,
|
|
495
|
+
);
|
|
351
496
|
}),
|
|
352
497
|
]);
|
|
353
498
|
} finally {
|
|
@@ -360,88 +505,334 @@ async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T
|
|
|
360
505
|
const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
361
506
|
{ pattern: /^\/healthz$/, label: "/healthz" },
|
|
362
507
|
{ pattern: /^\/readyz$/, label: "/readyz" },
|
|
363
|
-
{
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
{
|
|
508
|
+
{
|
|
509
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/connect\/start$/,
|
|
510
|
+
label: "/v1/workspaces/:workspaceId/codex/connect/start",
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/connect\/poll$/,
|
|
514
|
+
label: "/v1/workspaces/:workspaceId/codex/connect/poll",
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/status$/,
|
|
518
|
+
label: "/v1/workspaces/:workspaceId/codex/status",
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/usage$/,
|
|
522
|
+
label: "/v1/workspaces/:workspaceId/codex/usage",
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/codex$/,
|
|
526
|
+
label: "/v1/workspaces/:workspaceId/codex",
|
|
527
|
+
},
|
|
368
528
|
{ pattern: /^\/metrics$/, label: "/metrics" },
|
|
369
529
|
{ pattern: /^\/v1\/config\/client$/, label: "/v1/config/client" },
|
|
370
530
|
{ pattern: /^\/v1\/billing$/, label: "/v1/billing" },
|
|
371
531
|
{ pattern: /^\/v1\/billing\/checkout$/, label: "/v1/billing/checkout" },
|
|
372
532
|
{ pattern: /^\/v1\/billing\/usage$/, label: "/v1/billing/usage" },
|
|
373
|
-
{
|
|
533
|
+
{
|
|
534
|
+
pattern: /^\/v1\/billing\/entitlements$/,
|
|
535
|
+
label: "/v1/billing/entitlements",
|
|
536
|
+
},
|
|
374
537
|
{ pattern: /^\/v1\/webhooks\/stripe$/, label: "/v1/webhooks/stripe" },
|
|
375
|
-
{
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
{
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
{
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
{
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
{
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
{
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
{
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
{
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
{
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
{
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
{
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
{
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
{
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
{
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
538
|
+
{
|
|
539
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/mcp$/,
|
|
540
|
+
label: "/v1/workspaces/:workspaceId/mcp",
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/mcp\/docs$/,
|
|
544
|
+
label: "/v1/workspaces/:workspaceId/mcp/docs",
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/default-rig$/,
|
|
548
|
+
label: "/v1/workspaces/:workspaceId/default-rig",
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions$/,
|
|
552
|
+
label: "/v1/workspaces/:workspaceId/sessions",
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/control-events\/stream$/,
|
|
556
|
+
label: "/v1/workspaces/:workspaceId/control-events/stream",
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/control-events$/,
|
|
560
|
+
label: "/v1/workspaces/:workspaceId/control-events",
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/inference-control$/,
|
|
564
|
+
label: "/v1/workspaces/:workspaceId/inference-control",
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events\/stream$/,
|
|
568
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/events/stream",
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/lineage$/,
|
|
572
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/lineage",
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events$/,
|
|
576
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/events",
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue\/[^/]+\/(move|edit|steer|delete)$/,
|
|
580
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/queue/:turnId/:action",
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue$/,
|
|
584
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/queue",
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft$/,
|
|
588
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft",
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/(control|steer)$/,
|
|
592
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/:controlAction",
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/stream-capabilities$/,
|
|
596
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/stream-capabilities",
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/viewers\/[^/]+\/heartbeat$/,
|
|
600
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/viewers/:viewerId/heartbeat",
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/viewers\/[^/]+$/,
|
|
604
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/viewers/:viewerId",
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/viewers$/,
|
|
608
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/viewers",
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/goal$/,
|
|
612
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/goal",
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+$/,
|
|
616
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id",
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/files\/uploads$/,
|
|
620
|
+
label: "/v1/workspaces/:workspaceId/files/uploads",
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/files\/uploads\/[^/]+\/complete$/,
|
|
624
|
+
label: "/v1/workspaces/:workspaceId/files/uploads/:id/complete",
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/files\/[^/]+\/download-url$/,
|
|
628
|
+
label: "/v1/workspaces/:workspaceId/files/:id/download-url",
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/files\/[^/]+$/,
|
|
632
|
+
label: "/v1/workspaces/:workspaceId/files/:id",
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/api-keys$/,
|
|
636
|
+
label: "/v1/workspaces/:workspaceId/api-keys",
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/api-keys\/[^/]+$/,
|
|
640
|
+
label: "/v1/workspaces/:workspaceId/api-keys/:id",
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks$/,
|
|
644
|
+
label: "/v1/workspaces/:workspaceId/scheduled-tasks",
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks\/[^/]+\/pause$/,
|
|
648
|
+
label: "/v1/workspaces/:workspaceId/scheduled-tasks/:id/pause",
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks\/[^/]+\/resume$/,
|
|
652
|
+
label: "/v1/workspaces/:workspaceId/scheduled-tasks/:id/resume",
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks\/[^/]+\/trigger$/,
|
|
656
|
+
label: "/v1/workspaces/:workspaceId/scheduled-tasks/:id/trigger",
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks\/[^/]+\/runs$/,
|
|
660
|
+
label: "/v1/workspaces/:workspaceId/scheduled-tasks/:id/runs",
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks\/[^/]+$/,
|
|
664
|
+
label: "/v1/workspaces/:workspaceId/scheduled-tasks/:id",
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/document-bases$/,
|
|
668
|
+
label: "/v1/workspaces/:workspaceId/document-bases",
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/document-bases\/[^/]+\/documents\/[^/]+\/reindex$/,
|
|
672
|
+
label: "/v1/workspaces/:workspaceId/document-bases/:id/documents/:documentId/reindex",
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/document-bases\/[^/]+\/documents\/[^/]+$/,
|
|
676
|
+
label: "/v1/workspaces/:workspaceId/document-bases/:id/documents/:documentId",
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/document-bases\/[^/]+\/documents$/,
|
|
680
|
+
label: "/v1/workspaces/:workspaceId/document-bases/:id/documents",
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/document-bases\/[^/]+\/search$/,
|
|
684
|
+
label: "/v1/workspaces/:workspaceId/document-bases/:id/search",
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/document-bases\/[^/]+$/,
|
|
688
|
+
label: "/v1/workspaces/:workspaceId/document-bases/:id",
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/knowledge\/search$/,
|
|
692
|
+
label: "/v1/workspaces/:workspaceId/knowledge/search",
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/knowledge\/memories\/[^/]+$/,
|
|
696
|
+
label: "/v1/workspaces/:workspaceId/knowledge/memories/:id",
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/knowledge\/memories$/,
|
|
700
|
+
label: "/v1/workspaces/:workspaceId/knowledge/memories",
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/github\/app$/,
|
|
704
|
+
label: "/v1/workspaces/:workspaceId/github/app",
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/github\/repositories$/,
|
|
708
|
+
label: "/v1/workspaces/:workspaceId/github/repositories",
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/github\/repositories\/sync$/,
|
|
712
|
+
label: "/v1/workspaces/:workspaceId/github/repositories/sync",
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/github\/app-manifest$/,
|
|
716
|
+
label: "/v1/workspaces/:workspaceId/github/app-manifest",
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/capabilities$/,
|
|
720
|
+
label: "/v1/workspaces/:workspaceId/capabilities",
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/capabilities\/discovery\/mcp-registry$/,
|
|
724
|
+
label: "/v1/workspaces/:workspaceId/capabilities/discovery/mcp-registry",
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/capabilities\/[^/]+\/enable$/,
|
|
728
|
+
label: "/v1/workspaces/:workspaceId/capabilities/:id/enable",
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/capabilities\/[^/]+\/disable$/,
|
|
732
|
+
label: "/v1/workspaces/:workspaceId/capabilities/:id/disable",
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/environments$/,
|
|
736
|
+
label: "/v1/workspaces/:workspaceId/environments",
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/environments\/[^/]+\/variables\/[^/]+$/,
|
|
740
|
+
label: "/v1/workspaces/:workspaceId/environments/:id/variables/:name",
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/environments\/[^/]+$/,
|
|
744
|
+
label: "/v1/workspaces/:workspaceId/environments/:id",
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/packs$/,
|
|
748
|
+
label: "/v1/workspaces/:workspaceId/packs",
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/installations$/,
|
|
752
|
+
label: "/v1/workspaces/:workspaceId/packs/installations",
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/marketing-social-daily-analysis\/scheduled-tasks$/,
|
|
756
|
+
label: "/v1/workspaces/:workspaceId/packs/marketing-social-daily-analysis/scheduled-tasks",
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+\/enable$/,
|
|
760
|
+
label: "/v1/workspaces/:workspaceId/packs/:id/enable",
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+$/,
|
|
764
|
+
label: "/v1/workspaces/:workspaceId/packs/:id",
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/social\/connections$/,
|
|
768
|
+
label: "/v1/workspaces/:workspaceId/social/connections",
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/social\/posts$/,
|
|
772
|
+
label: "/v1/workspaces/:workspaceId/social/posts",
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections$/,
|
|
776
|
+
label: "/v1/workspaces/:workspaceId/connections",
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/oauth\/start$/,
|
|
780
|
+
label: "/v1/workspaces/:workspaceId/connections/oauth/start",
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+$/,
|
|
784
|
+
label: "/v1/workspaces/:workspaceId/connections/:connectionId",
|
|
785
|
+
},
|
|
431
786
|
{ pattern: /^\/v1\/catalog-assets\/.+$/, label: "/v1/catalog-assets/*" },
|
|
432
|
-
{
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
{
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
{
|
|
441
|
-
|
|
787
|
+
{
|
|
788
|
+
pattern: /^\/v1\/integrations\/oauth\/callback$/,
|
|
789
|
+
label: "/v1/integrations/oauth/callback",
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
pattern: /^\/v1\/integrations\/oauth\/client-metadata\.json$/,
|
|
793
|
+
label: "/v1/integrations/oauth/client-metadata.json",
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
pattern: /^\/v1\/enrollments\/device\/start$/,
|
|
797
|
+
label: "/v1/enrollments/device/start",
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
pattern: /^\/v1\/enrollments\/device\/poll$/,
|
|
801
|
+
label: "/v1/enrollments/device/poll",
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/enrollments\/device\/approve$/,
|
|
805
|
+
label: "/v1/workspaces/:workspaceId/enrollments/device/approve",
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/enrollments\/[^/]+\/revoke$/,
|
|
809
|
+
label: "/v1/workspaces/:workspaceId/enrollments/:id/revoke",
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/enrollments$/,
|
|
813
|
+
label: "/v1/workspaces/:workspaceId/enrollments",
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/metrics\/series$/,
|
|
817
|
+
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/metrics/series",
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/machines$/,
|
|
821
|
+
label: "/v1/workspaces/:workspaceId/machines",
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
pattern: /^\/v1\/github\/app-manifest\/callback$/,
|
|
825
|
+
label: "/v1/github/app-manifest/callback",
|
|
826
|
+
},
|
|
442
827
|
{ pattern: /^\/v1\/github\/setup$/, label: "/v1/github/setup" },
|
|
443
|
-
{
|
|
444
|
-
|
|
828
|
+
{
|
|
829
|
+
pattern: /^\/v1\/github\/install\/callback$/,
|
|
830
|
+
label: "/v1/github/install/callback",
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
pattern: /^\/v1\/github\/oauth\/callback$/,
|
|
834
|
+
label: "/v1/github/oauth/callback",
|
|
835
|
+
},
|
|
445
836
|
];
|
|
446
837
|
|
|
447
838
|
export function routeLabel(pathname: string): string {
|
|
@@ -451,3 +842,29 @@ export function routeLabel(pathname: string): string {
|
|
|
451
842
|
}
|
|
452
843
|
return pathname.startsWith("/v1/") ? "/v1/unknown" : "/unknown";
|
|
453
844
|
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* State-changing OpenGeni HTTP calls must never cross an incompatible rollout
|
|
848
|
+
* boundary. Standard third-party protocols and externally initiated callbacks
|
|
849
|
+
* are intentionally outside this product API contract.
|
|
850
|
+
*/
|
|
851
|
+
export function isApiContractProtectedMutation(method: string, pathname: string): boolean {
|
|
852
|
+
if (!new Set(["POST", "PUT", "PATCH", "DELETE"]).has(method.toUpperCase())) {
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
if (!pathname.startsWith("/v1/")) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
if (
|
|
859
|
+
pathname.startsWith("/v1/auth/") ||
|
|
860
|
+
pathname.startsWith("/v1/webhooks/") ||
|
|
861
|
+
pathname.startsWith("/v1/integrations/oauth/") ||
|
|
862
|
+
pathname.startsWith("/v1/github/") ||
|
|
863
|
+
pathname === "/v1/enrollments/device/start" ||
|
|
864
|
+
pathname === "/v1/enrollments/device/poll" ||
|
|
865
|
+
pathname === "/v1/enrollments/token/exchange"
|
|
866
|
+
) {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
return !pathname.split("/").includes("mcp");
|
|
870
|
+
}
|