@opengeni/api-router 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +1 -1
- package/dist/{chunk-24C5LPNE.js → chunk-2SSU5KWV.js} +715 -279
- package/dist/{chunk-24C5LPNE.js.map → chunk-2SSU5KWV.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/github-lens-connect.d.ts +1 -1
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/mcp/documents.d.ts +2 -0
- package/dist/mcp/first-party-tool-permissions.d.ts +8 -0
- package/dist/routes/browser-sessions.d.ts +3 -0
- package/dist/routes/github-browser-pages.d.ts +5 -0
- package/dist/routes/insights.d.ts +26 -0
- package/dist/sandbox-file-artifacts.d.ts +17 -1
- package/package.json +15 -15
- package/src/mcp/documents.ts +9 -2
- package/src/mcp/first-party-tool-permissions.ts +2 -0
- package/src/mcp/knowledge-entries.ts +15 -2
- package/src/mcp/server.ts +51 -2
- package/src/routes/browser-sessions.ts +140 -0
- package/src/routes/computer-sessions.ts +18 -1
- package/src/routes/documents.ts +1 -0
- package/src/routes/files.ts +4 -0
- package/src/routes/github-browser-pages.ts +102 -0
- package/src/routes/github.ts +6 -47
- package/src/routes/insights.ts +70 -6
- package/src/routes/knowledge.ts +19 -0
- package/src/routes/managed-onboarding.ts +3 -0
- package/src/routes/organization-memberships.ts +3 -0
- package/src/routes/sessions.ts +89 -0
- package/src/sandbox/channel-a.ts +8 -0
- package/src/sandbox/viewer.ts +16 -3
- package/src/sandbox-file-artifacts.ts +37 -26
- package/src/workspace-tool-gateway.ts +1 -0
package/src/sandbox/viewer.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
hasGitHubRepositorySelection,
|
|
27
27
|
resolveStreamTokenSecret,
|
|
28
28
|
sandboxLifecycleTransitionWaitMs,
|
|
29
|
+
sandboxWarmRateMicrosPerSecond,
|
|
29
30
|
stableSandboxEnvironmentForRun,
|
|
30
31
|
} from "@opengeni/config";
|
|
31
32
|
import type { Settings } from "@opengeni/config";
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
getSandbox,
|
|
38
39
|
getScheduledScopedRigVersionMetadata,
|
|
39
40
|
getSandboxSessionEnvelope,
|
|
40
|
-
|
|
41
|
+
heartbeatLeaseHolderStatus,
|
|
41
42
|
loadWorkspaceEnvironmentForRun,
|
|
42
43
|
markSandboxProviderReady,
|
|
43
44
|
markWarmLeaseInstanceLost,
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
releaseLeaseHolder,
|
|
48
49
|
SandboxLeaseSupersededError,
|
|
49
50
|
SandboxViewerAdmissionBlockedError,
|
|
51
|
+
SandboxPaidComputeAdmissionError,
|
|
50
52
|
type Database,
|
|
51
53
|
type LeaseSnapshot,
|
|
52
54
|
type SandboxRecord,
|
|
@@ -300,6 +302,10 @@ export async function attachViewer(
|
|
|
300
302
|
...(attachSubjectId ? { viewerSubjectId: attachSubjectId } : {}),
|
|
301
303
|
...(attachAuthorityEpoch !== null ? { viewerAuthorityEpoch: attachAuthorityEpoch } : {}),
|
|
302
304
|
backend: session.sandboxBackend,
|
|
305
|
+
warmBilling: {
|
|
306
|
+
mode: settings.sandboxWarmBillingMode,
|
|
307
|
+
rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond(settings, session.sandboxBackend),
|
|
308
|
+
},
|
|
303
309
|
os: session.sandboxOs,
|
|
304
310
|
image: sandboxRuntime.image,
|
|
305
311
|
rigVersionId: session.rigVersionId,
|
|
@@ -309,6 +315,9 @@ export async function attachViewer(
|
|
|
309
315
|
...(input.waitSignal ? { waitSignal: input.waitSignal } : {}),
|
|
310
316
|
});
|
|
311
317
|
} catch (error) {
|
|
318
|
+
if (error instanceof SandboxPaidComputeAdmissionError) {
|
|
319
|
+
throw new HTTPException(402, { message: error.message, cause: error });
|
|
320
|
+
}
|
|
312
321
|
if (error instanceof SandboxViewerAdmissionBlockedError) {
|
|
313
322
|
throw new HTTPException(error.reason === "balance" ? 402 : 429, {
|
|
314
323
|
message:
|
|
@@ -592,7 +601,7 @@ export async function heartbeatViewer(
|
|
|
592
601
|
expectedEpoch: number;
|
|
593
602
|
},
|
|
594
603
|
): Promise<boolean> {
|
|
595
|
-
const
|
|
604
|
+
const status = await heartbeatLeaseHolderStatus(services.db, {
|
|
596
605
|
accountId: input.accountId,
|
|
597
606
|
workspaceId: input.workspaceId,
|
|
598
607
|
sandboxGroupId: input.sandboxGroupId,
|
|
@@ -600,8 +609,12 @@ export async function heartbeatViewer(
|
|
|
600
609
|
holderId: input.viewerId,
|
|
601
610
|
leaseTtlMs: services.settings.sandboxLeaseTtlMs,
|
|
602
611
|
expectedEpoch: input.expectedEpoch,
|
|
612
|
+
billingMode: services.settings.sandboxWarmBillingMode,
|
|
603
613
|
});
|
|
604
|
-
|
|
614
|
+
// A funding fence stops renewing paid provider time, but the holder is
|
|
615
|
+
// deliberately kept alive while the viewer winds down or adds funds. Other
|
|
616
|
+
// fences (epoch/rotation/drain) still require the client to re-attach.
|
|
617
|
+
if (!status.leaseExtended) return status.holderAlive && status.fence === "funding";
|
|
605
618
|
const lease = await readLease(services.db, input.workspaceId, input.sandboxGroupId);
|
|
606
619
|
if (lease?.liveness === "warm" && lease.leaseEpoch === input.expectedEpoch && lease.instanceId) {
|
|
607
620
|
await renewSandboxProviderExpiration({
|
|
@@ -27,6 +27,12 @@ import {
|
|
|
27
27
|
} from "@opengeni/db";
|
|
28
28
|
import { retryWhileMissing, type ObjectHead, type ObjectStorage } from "@opengeni/storage";
|
|
29
29
|
import { HTTPException } from "hono/http-exception";
|
|
30
|
+
import {
|
|
31
|
+
isConnectedMachineAbsolutePath,
|
|
32
|
+
relativeConnectedMachinePath,
|
|
33
|
+
resolveConnectedMachinePath,
|
|
34
|
+
type SandboxChannelAService,
|
|
35
|
+
} from "@opengeni/runtime/sandbox";
|
|
30
36
|
|
|
31
37
|
import { withChannelARead } from "./sandbox/channel-a";
|
|
32
38
|
import { sanitizeFilename } from "./routes/files";
|
|
@@ -98,13 +104,12 @@ async function publishSandboxFileArtifactInScope(
|
|
|
98
104
|
});
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
const path = sandboxArtifactRelativePath(input.path);
|
|
102
107
|
const maxArtifactBytes = Math.min(SANDBOX_FILE_ARTIFACT_MAX_BYTES, storage.maxSinglePutSizeBytes);
|
|
103
108
|
if (maxArtifactBytes < 1) {
|
|
104
109
|
throw new HTTPException(503, { message: "object storage cannot accept artifact files" });
|
|
105
110
|
}
|
|
106
111
|
const readLimit = Math.min(25 * 1024 * 1024, maxArtifactBytes + FILE_READ_SENTINEL_BYTES);
|
|
107
|
-
const read = await withChannelARead(
|
|
112
|
+
const { path, sandboxPath, read } = await withChannelARead(
|
|
108
113
|
{
|
|
109
114
|
db: deps.db,
|
|
110
115
|
settings: deps.settings,
|
|
@@ -119,7 +124,7 @@ async function publishSandboxFileArtifactInScope(
|
|
|
119
124
|
...(input.signal ? { waitSignal: input.signal } : {}),
|
|
120
125
|
operation: "artifact.publish",
|
|
121
126
|
},
|
|
122
|
-
({ service }) => service.
|
|
127
|
+
({ service }) => readSandboxArtifactFile(service, input.path, readLimit),
|
|
123
128
|
);
|
|
124
129
|
if (read.truncated || read.sizeBytes > maxArtifactBytes) {
|
|
125
130
|
throw new HTTPException(413, {
|
|
@@ -234,48 +239,54 @@ async function publishSandboxFileArtifactInScope(
|
|
|
234
239
|
});
|
|
235
240
|
return SandboxFileArtifactReceipt.parse({
|
|
236
241
|
type: "sandbox_file",
|
|
237
|
-
sandboxPath
|
|
242
|
+
sandboxPath,
|
|
238
243
|
filename,
|
|
239
244
|
artifact,
|
|
240
245
|
});
|
|
241
246
|
}
|
|
242
247
|
|
|
243
|
-
|
|
248
|
+
/** Use the root captured by the same fenced Channel-A service that reads bytes,
|
|
249
|
+
* not the session's placement-home label or a caller-supplied root. */
|
|
250
|
+
export async function readSandboxArtifactFile(
|
|
251
|
+
service: Pick<SandboxChannelAService, "capabilities" | "fsRead">,
|
|
252
|
+
value: string,
|
|
253
|
+
maxBytes: number,
|
|
254
|
+
) {
|
|
255
|
+
const root = service.capabilities().FileSystem.root;
|
|
256
|
+
const path = sandboxArtifactRelativePath(value, root);
|
|
257
|
+
const sandboxPath = resolveConnectedMachinePath(root, path);
|
|
258
|
+
const read = await service.fsRead({ path, encoding: "base64", maxBytes });
|
|
259
|
+
return { path, sandboxPath, read };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function sandboxArtifactRelativePath(value: string, workspaceRoot = "/workspace"): string {
|
|
244
263
|
let path = value.trim();
|
|
245
264
|
if (path.startsWith("sandbox:")) {
|
|
246
265
|
path = path.slice("sandbox:".length);
|
|
247
266
|
}
|
|
248
|
-
if (path
|
|
267
|
+
if (!path || /[\\/]$/.test(path)) {
|
|
249
268
|
throw new HTTPException(400, { message: "sandbox artifact path must name a file" });
|
|
250
269
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
270
|
+
let absolute: string;
|
|
271
|
+
let relative: string | null;
|
|
272
|
+
try {
|
|
273
|
+
if (!isConnectedMachineAbsolutePath(workspaceRoot)) throw new Error("Missing workspace root");
|
|
274
|
+
absolute = resolveConnectedMachinePath(workspaceRoot, path);
|
|
275
|
+
relative = relativeConnectedMachinePath(workspaceRoot, absolute);
|
|
276
|
+
} catch {
|
|
254
277
|
throw new HTTPException(400, {
|
|
255
|
-
message: "sandbox artifact path
|
|
278
|
+
message: "sandbox artifact path is invalid for this workspace",
|
|
256
279
|
});
|
|
257
280
|
}
|
|
258
|
-
if (!
|
|
259
|
-
throw new HTTPException(400, { message: "sandbox artifact path must name a file" });
|
|
260
|
-
}
|
|
261
|
-
if (path.includes("\0")) {
|
|
262
|
-
throw new HTTPException(400, { message: "sandbox artifact path is invalid" });
|
|
263
|
-
}
|
|
264
|
-
const normalized = posix.normalize(path);
|
|
265
|
-
if (
|
|
266
|
-
normalized === "." ||
|
|
267
|
-
normalized === ".." ||
|
|
268
|
-
normalized.startsWith("../") ||
|
|
269
|
-
normalized.endsWith("/")
|
|
270
|
-
) {
|
|
281
|
+
if (!relative) {
|
|
271
282
|
throw new HTTPException(400, {
|
|
272
|
-
message: "sandbox artifact path must
|
|
283
|
+
message: "sandbox artifact path must name a file inside the active workspace root",
|
|
273
284
|
});
|
|
274
285
|
}
|
|
275
|
-
if (
|
|
286
|
+
if (absolute.length > SANDBOX_ARTIFACT_ABSOLUTE_PATH_MAX_CHARS) {
|
|
276
287
|
throw new HTTPException(400, { message: "sandbox artifact path is too long" });
|
|
277
288
|
}
|
|
278
|
-
return
|
|
289
|
+
return relative;
|
|
279
290
|
}
|
|
280
291
|
|
|
281
292
|
export function sandboxArtifactSafeFilename(filename: string): string {
|