@nylorun/runtime 0.23.0-beta → 0.24.0-beta
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/CHANGELOG.md +148 -0
- package/README.md +79 -28
- package/dist/adapters/execution/restate.js +1 -1
- package/dist/adapters/replication/pgoutput.d.ts +2 -0
- package/dist/adapters/replication/pgoutput.js +6 -3
- package/dist/adapters/sandbox/local.d.ts +9 -1
- package/dist/adapters/sandbox/local.js +16 -4
- package/dist/adapters/sandbox/virtual.js +4 -1
- package/dist/api/a2a/endpoint.js +5 -1
- package/dist/api/a2a/routes.d.ts +2 -0
- package/dist/api/a2a/routes.js +2 -2
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +4 -2
- package/dist/api/ag-ui/history.js +4 -1
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +11 -10
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +4953 -1204
- package/dist/api/components.js +25 -16
- package/dist/api/gate/keys.js +7 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +39 -3
- package/dist/api/http/app.js +4 -4
- package/dist/api/http/define.d.ts +6 -0
- package/dist/api/http/define.js +13 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/artifacts.js +35 -8
- package/dist/api/http/routes/coverage.d.ts +9 -0
- package/dist/api/http/routes/coverage.js +34 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +283 -0
- package/dist/api/http/routes/mcp.d.ts +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.js +1 -1
- package/dist/api/http/routes/reads.d.ts +45 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +11 -6
- package/dist/api/http/routes/tenant.js +32 -10
- package/dist/api/openapi.js +31 -26
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +563 -0
- package/dist/artifacts/export.d.ts +1 -1
- package/dist/artifacts/export.js +2 -3
- package/dist/artifacts/folders.d.ts +6 -3
- package/dist/artifacts/folders.js +17 -12
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +1 -1
- package/dist/artifacts/workspace.js +1 -1
- package/dist/core/flow-host.d.ts +2 -3
- package/dist/core/flow-host.js +3 -0
- package/dist/core/provider.d.ts +3 -2
- package/dist/core/provider.js +5 -37
- package/dist/core/sandbox-routes.d.ts +2 -21
- package/dist/core/sandbox-routes.js +10 -43
- package/dist/execution/types.d.ts +1 -1
- package/dist/gates/contract.d.ts +3 -24
- package/dist/gates/contract.js +1 -36
- package/dist/gates/egress-credentials.d.ts +9 -0
- package/dist/gates/egress-credentials.js +11 -4
- package/dist/gates/egress.d.ts +28 -1
- package/dist/gates/egress.js +115 -10
- package/dist/gates/fixture-model.d.ts +28 -0
- package/dist/gates/fixture-model.js +92 -0
- package/dist/gates/handler.js +4 -4
- package/dist/gates/http-tool.d.ts +6 -2
- package/dist/gates/http-tool.js +3 -5
- package/dist/gates/in-process.d.ts +5 -0
- package/dist/gates/in-process.js +12 -1
- package/dist/gates/journal.d.ts +30 -0
- package/dist/gates/journal.js +52 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/meter.d.ts +10 -2
- package/dist/gates/meter.js +20 -5
- package/dist/gates/policy.d.ts +49 -0
- package/dist/gates/policy.js +134 -0
- package/dist/gates/tenant-vaults.d.ts +7 -7
- package/dist/gates/tenant-vaults.js +5 -5
- package/dist/gates/tool-calls.d.ts +10 -5
- package/dist/gates/tool-calls.js +28 -10
- package/dist/gates/tool-client.d.ts +8 -0
- package/dist/gates/tool-client.js +27 -11
- package/dist/gates/tool-contract.d.ts +10 -68
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +12 -5
- package/dist/harness/calls.js +21 -12
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +18 -5
- package/dist/harness/pod.d.ts +34 -0
- package/dist/harness/pod.js +41 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +13 -5
- package/dist/harness/setup.d.ts +27 -0
- package/dist/harness/setup.js +150 -0
- package/dist/harness/ws-client.js +3 -2
- package/dist/harness/ws-port.d.ts +1 -5
- package/dist/harness/ws-port.js +1 -5
- package/dist/harness-api/in-process.js +3 -3
- package/dist/harness-api/record.d.ts +15 -1
- package/dist/harness-api/record.js +174 -44
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +8 -0
- package/dist/harness-api/server.js +93 -13
- package/dist/harness-api/start.d.ts +10 -3
- package/dist/harness-api/start.js +40 -10
- package/dist/harness-api/tool-journal.d.ts +96 -0
- package/dist/harness-api/tool-journal.js +102 -0
- package/dist/harness-api/tool-status.d.ts +13 -0
- package/dist/harness-api/tool-status.js +26 -0
- package/dist/harness-api/workspace.d.ts +6 -3
- package/dist/harness-api/workspace.js +27 -11
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +54 -5
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +64 -0
- package/dist/host/stack-config.js +61 -1
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +5 -5
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +999 -2211
- package/dist/mcp/connect.d.ts +25 -20
- package/dist/mcp/connect.js +29 -30
- package/dist/mcp/pool.d.ts +2 -2
- package/dist/mcp/pool.js +1 -1
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -20
- package/dist/mcp/snapshot.d.ts +5 -11
- package/dist/mcp/snapshot.js +6 -7
- package/dist/model/pi-model.js +8 -0
- package/dist/openapi.json +6955 -2526
- package/dist/reads/types.d.ts +16 -3
- package/dist/record/append.d.ts +3 -1
- package/dist/record/append.js +1 -0
- package/dist/record/envelope.d.ts +5 -1
- package/dist/record/envelope.js +7 -3
- package/dist/record/environment.d.ts +25 -0
- package/dist/record/environment.js +44 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/sandbox/disk-fs.d.ts +95 -0
- package/dist/sandbox/disk-fs.js +206 -0
- package/dist/sandbox/join.d.ts +17 -18
- package/dist/sandbox/join.js +42 -15
- package/dist/sandbox/manager.d.ts +16 -8
- package/dist/sandbox/manager.js +33 -17
- package/dist/sandbox/placement.d.ts +13 -12
- package/dist/sandbox/placement.js +29 -12
- package/dist/sandbox/pods/client.d.ts +6 -1
- package/dist/sandbox/pods/lifecycle.d.ts +28 -27
- package/dist/sandbox/pods/lifecycle.js +14 -26
- package/dist/sandbox/pods/reconcile.js +18 -12
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +16 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +35 -27
- package/dist/sandbox/resolve.js +66 -41
- package/dist/sandbox/session-sandbox.js +1 -1
- package/dist/sandbox/setup.d.ts +61 -0
- package/dist/sandbox/setup.js +123 -0
- package/dist/sandbox/share.d.ts +11 -21
- package/dist/sandbox/share.js +12 -41
- package/dist/sandbox/tenant-config.d.ts +12 -14
- package/dist/sandbox/tenant-config.js +12 -17
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
- package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
- package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/reads.js +117 -17
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/record.d.ts +7 -0
- package/dist/store/postgres/record.js +23 -2
- package/dist/store/postgres/schema.d.ts +934 -761
- package/dist/store/postgres/schema.js +102 -73
- package/dist/store/postgres/store.js +119 -66
- package/dist/store/types.d.ts +109 -48
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +115 -42
- package/dist/tenant/artifact-tool.js +3 -4
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +93 -12
- package/dist/tenant/context.d.ts +48 -22
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +37 -20
- package/dist/tenant/environments.d.ts +167 -0
- package/dist/tenant/environments.js +823 -0
- package/dist/tenant/harness-tokens.d.ts +21 -0
- package/dist/tenant/harness-tokens.js +61 -0
- package/dist/tenant/host-token.d.ts +1 -1
- package/dist/tenant/host-token.js +2 -4
- package/dist/tenant/identity-file.d.ts +4 -4
- package/dist/tenant/identity-file.js +17 -8
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/principals.js +1 -1
- package/dist/tenant/reset.js +3 -3
- package/dist/tenant/runtime.d.ts +6 -1
- package/dist/tenant/runtime.js +7 -1
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +13 -13
- package/dist/tenant/sessions.js +61 -138
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/sweep.js +12 -6
- package/dist/tenant/transcript.d.ts +210 -20
- package/dist/tenant/transcript.js +62 -11
- package/dist/tenant/turn-timing.d.ts +43 -0
- package/dist/tenant/turn-timing.js +39 -0
- package/dist/tenant/types.d.ts +2 -0
- package/dist/traces/ids.d.ts +42 -0
- package/dist/traces/ids.js +85 -0
- package/dist/traces/key.d.ts +6 -0
- package/dist/traces/key.js +37 -0
- package/dist/traces/map.d.ts +113 -0
- package/dist/traces/map.js +317 -0
- package/dist/traces/options.d.ts +7 -0
- package/dist/traces/options.js +1 -0
- package/dist/traces/otlp.d.ts +42 -0
- package/dist/traces/otlp.js +84 -0
- package/dist/traces/projector.d.ts +58 -0
- package/dist/traces/projector.js +200 -0
- package/dist/traces/read.d.ts +23 -0
- package/dist/traces/read.js +16 -0
- package/dist/vault/coverage.d.ts +26 -0
- package/dist/vault/coverage.js +64 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/service.d.ts +47 -48
- package/dist/vault/service.js +321 -382
- package/dist/vault/sources.d.ts +5 -16
- package/dist/vault/sources.js +6 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +13 -13
- package/dist/api/http/routes/sandboxes.d.ts +0 -9
- package/dist/api/http/routes/sandboxes.js +0 -153
- package/dist/api/http/routes/vaults.d.ts +0 -10
- package/dist/api/http/routes/vaults.js +0 -139
- package/dist/record/sandbox.d.ts +0 -25
- package/dist/record/sandbox.js +0 -43
- package/dist/tenant/sandboxes.d.ts +0 -123
- package/dist/tenant/sandboxes.js +0 -566
|
@@ -66,7 +66,8 @@ export declare function folderContent(ctx: TenantContext, id: string, version: n
|
|
|
66
66
|
/**
|
|
67
67
|
* Deletes the artifact and every version: its rows and, for a session's artifact, its
|
|
68
68
|
* `artifact.deleted` event in one transaction, then its bytes. A folder's content-addressed
|
|
69
|
-
* files go too, those no other version names, under the quota lock (`folders.ts`).
|
|
69
|
+
* files go too, those no other version names, under the quota lock (`folders.ts`). An
|
|
70
|
+
* Environment's disk artifact is refused: its own routes delete its versions.
|
|
70
71
|
*/
|
|
71
72
|
export declare function deleteArtifact(ctx: TenantContext, id: string, access: ArtifactAccess): Promise<{
|
|
72
73
|
artifactId: string;
|
|
@@ -52,6 +52,7 @@ export function artifactView(row, versions) {
|
|
|
52
52
|
name: row.name,
|
|
53
53
|
contentType: row.contentType,
|
|
54
54
|
...(row.sessionId === null ? {} : { sessionId: row.sessionId }),
|
|
55
|
+
...(row.environmentId === null ? {} : { environmentId: row.environmentId }),
|
|
55
56
|
latestVersion: row.latestVersion,
|
|
56
57
|
...(row.labelsJson === null ? {} : { labels: JSON.parse(row.labelsJson) }),
|
|
57
58
|
createdAt: row.createdAt,
|
|
@@ -194,6 +195,7 @@ export async function uploadArtifact(ctx, input, body, access, signal) {
|
|
|
194
195
|
: JSON.stringify(input.labels),
|
|
195
196
|
createdAt,
|
|
196
197
|
updatedAt: createdAt,
|
|
198
|
+
environmentId: null,
|
|
197
199
|
};
|
|
198
200
|
version = {
|
|
199
201
|
artifactId,
|
|
@@ -278,11 +280,14 @@ export async function folderContent(ctx, id, version, access) {
|
|
|
278
280
|
/**
|
|
279
281
|
* Deletes the artifact and every version: its rows and, for a session's artifact, its
|
|
280
282
|
* `artifact.deleted` event in one transaction, then its bytes. A folder's content-addressed
|
|
281
|
-
* files go too, those no other version names, under the quota lock (`folders.ts`).
|
|
283
|
+
* files go too, those no other version names, under the quota lock (`folders.ts`). An
|
|
284
|
+
* Environment's disk artifact is refused: its own routes delete its versions.
|
|
282
285
|
*/
|
|
283
286
|
export async function deleteArtifact(ctx, id, access) {
|
|
284
287
|
const keys = await ctx.store.tx(async (t) => {
|
|
285
288
|
const found = await readableArtifact(t, id, access);
|
|
289
|
+
if (found.environmentId !== null)
|
|
290
|
+
fail(409, `Artifact ${id} holds Environment ${found.environmentId}'s disk versions: delete them with DELETE /v1/environments/{id}/versions/{version}, or delete the Environment.`);
|
|
286
291
|
if (found.sessionId !== null)
|
|
287
292
|
await lockedSession(t, found.sessionId, access);
|
|
288
293
|
if (found.kind === "folder")
|
|
@@ -22,7 +22,7 @@ export interface WorkspaceSession {
|
|
|
22
22
|
/** The session that owns the workspace (one hop through `sandboxOwnerId`). */
|
|
23
23
|
readonly ownerId: string;
|
|
24
24
|
/** The sandbox resource the workspace belongs to, when the owner is attached to one. */
|
|
25
|
-
readonly
|
|
25
|
+
readonly environmentId?: string;
|
|
26
26
|
/** The session's pinned manifest: its sandbox capability carries the sandbox's spec. */
|
|
27
27
|
readonly manifest: AgentManifest;
|
|
28
28
|
}
|
|
@@ -24,7 +24,7 @@ export function sandboxWorkspaceReader(sandbox) {
|
|
|
24
24
|
id: session.ownerId,
|
|
25
25
|
activeTurnId: session.turnId,
|
|
26
26
|
manifest: session.manifest,
|
|
27
|
-
...(session.
|
|
27
|
+
...(session.environmentId === undefined ? {} : { environmentId: session.environmentId }),
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
};
|
package/dist/core/flow-host.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { EffectOutcome } from "@nylorun/core/contracts";
|
|
|
2
2
|
import { type JsonValue, type WorkflowManifest } from "@nylorun/core/define";
|
|
3
3
|
import type { HostEffect } from "@nylorun/harness/run";
|
|
4
4
|
import type { EffectDoc, Tx } from "../store/types.js";
|
|
5
|
+
import { type TurnTiming } from "../tenant/turn-timing.js";
|
|
5
6
|
/** Deterministic agent session id: derive(workflowSessionId, path, …parts). */
|
|
6
7
|
export declare function deriveSessionId(workflowSessionId: string, path: string, ...parts: string[]): string;
|
|
7
8
|
/**
|
|
@@ -59,15 +60,13 @@ export type FlowHostSession = {
|
|
|
59
60
|
status: string;
|
|
60
61
|
activeTurnId: string | null;
|
|
61
62
|
info?: any;
|
|
62
|
-
vaultIds?: readonly string[];
|
|
63
|
-
credentialSelections?: readonly unknown[];
|
|
64
63
|
creation?: unknown;
|
|
65
64
|
waits?: unknown;
|
|
66
65
|
lastOutput?: JsonValue;
|
|
67
66
|
/** The turn `status`, `lastOutput` and `error` describe, once it ended. */
|
|
68
67
|
lastTurnId?: string;
|
|
69
68
|
error?: string;
|
|
70
|
-
};
|
|
69
|
+
} & TurnTiming;
|
|
71
70
|
/** How a linked agent turn ended. */
|
|
72
71
|
export type LinkedTurnEnd = {
|
|
73
72
|
readonly turnId: string;
|
package/dist/core/flow-host.js
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
*/
|
|
51
51
|
import { createHash } from "node:crypto";
|
|
52
52
|
import { isVerdict } from "@nylorun/core/define";
|
|
53
|
+
import { resumeTurn } from "../tenant/turn-timing.js";
|
|
53
54
|
/** Deterministic agent session id: derive(workflowSessionId, path, …parts). */
|
|
54
55
|
export function deriveSessionId(workflowSessionId, path, ...parts) {
|
|
55
56
|
const digest = createHash("sha256")
|
|
@@ -237,6 +238,8 @@ export async function wakeLinkedWorkflow(input) {
|
|
|
237
238
|
}
|
|
238
239
|
: { value: input.output ?? null };
|
|
239
240
|
await settleAgentEffect({ t, effect, outcome });
|
|
241
|
+
// A workflow paused on its agents' interactions goes on (R7.1, `tenant/turn-timing.ts`).
|
|
242
|
+
resumeTurn(workflow);
|
|
240
243
|
workflow.status = "runnable";
|
|
241
244
|
await t.put("sessions", workflow.id, workflow);
|
|
242
245
|
await t.wake(workflow.id, {
|
package/dist/core/provider.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ export declare function gatewayModel(options: {
|
|
|
9
9
|
model: string;
|
|
10
10
|
}): ModelProvider;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* The fixture model as a provider (`model: {kind: "fixture"}` in the Runtime's config). The Tenant
|
|
13
|
+
* setting `model.fixture` is answered by the Model Gate instead (R4.3); both answer the same way
|
|
14
|
+
* (`gates/fixture-model.ts`).
|
|
14
15
|
*/
|
|
15
16
|
export declare function toolFixtureModel(): ModelProvider;
|
package/dist/core/provider.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fixtureAnswer } from "../gates/fixture-model.js";
|
|
1
2
|
export function scriptedModel(output = "Scripted development response") {
|
|
2
3
|
return async () => ({ output: [{ type: "text", text: output }] });
|
|
3
4
|
}
|
|
@@ -23,43 +24,10 @@ export function gatewayModel(options) {
|
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
27
|
+
* The fixture model as a provider (`model: {kind: "fixture"}` in the Runtime's config). The Tenant
|
|
28
|
+
* setting `model.fixture` is answered by the Model Gate instead (R4.3); both answer the same way
|
|
29
|
+
* (`gates/fixture-model.ts`).
|
|
28
30
|
*/
|
|
29
31
|
export function toolFixtureModel() {
|
|
30
|
-
return async (effect) =>
|
|
31
|
-
const call = effect.input;
|
|
32
|
-
const last = call.prompt.at(-1);
|
|
33
|
-
if (last?.kind === "tool-result")
|
|
34
|
-
return {
|
|
35
|
-
output: [
|
|
36
|
-
{
|
|
37
|
-
type: "text",
|
|
38
|
-
text: `Order lookup complete: ${last.content.map((part) => (part.type === "text" ? part.text : JSON.stringify(part))).join(" ")}`,
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
};
|
|
42
|
-
const users = call.prompt.filter((p) => p.kind === "message" && p.role === "user");
|
|
43
|
-
if (users.length > 1)
|
|
44
|
-
return {
|
|
45
|
-
output: [
|
|
46
|
-
{
|
|
47
|
-
type: "text",
|
|
48
|
-
text: `I remember: ${users[0].content.map((part) => (part.type === "text" ? part.text : "")).join(" ")}`,
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
};
|
|
52
|
-
if (!call.tools.some((tool) => tool.name === "lookup_order"))
|
|
53
|
-
return { output: [{ type: "text", text: "Hello from the fixture model." }] };
|
|
54
|
-
return {
|
|
55
|
-
output: [
|
|
56
|
-
{
|
|
57
|
-
type: "tool-call",
|
|
58
|
-
id: "fixture_lookup",
|
|
59
|
-
name: "lookup_order",
|
|
60
|
-
args: { orderId: "demo-123" },
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
};
|
|
64
|
-
};
|
|
32
|
+
return async (effect, signal) => fixtureAnswer(effect.input, signal);
|
|
65
33
|
}
|
|
@@ -9,32 +9,13 @@ export type SandboxRouteSession = SessionSandboxRef & {
|
|
|
9
9
|
readonly activeTurnId: string | null;
|
|
10
10
|
readonly status: string;
|
|
11
11
|
};
|
|
12
|
-
/** A session together with a synchronous lookup over its sandbox owner chain. */
|
|
13
|
-
export type SandboxRouteRead = {
|
|
14
|
-
readonly session: SandboxRouteSession;
|
|
15
|
-
readonly lookup: (id: string) => SessionSandboxRef | undefined;
|
|
16
|
-
};
|
|
17
12
|
export type SandboxRouteDeps = {
|
|
18
13
|
readonly sandbox: Pick<WorkspacePort, "run">;
|
|
19
|
-
/** The session
|
|
20
|
-
readonly session: (id: string) => Promise<
|
|
14
|
+
/** The session; rejects when it is missing. */
|
|
15
|
+
readonly session: (id: string) => Promise<SandboxRouteSession>;
|
|
21
16
|
};
|
|
22
17
|
/**
|
|
23
18
|
* `POST /v1/sessions/:id/sandbox/:tool` — application principal.
|
|
24
19
|
* Refused while the session has an active agent turn.
|
|
25
20
|
*/
|
|
26
21
|
export declare function handleSessionSandboxTool(deps: SandboxRouteDeps, sessionId: string, toolName: string, body: unknown, signal: AbortSignal): Promise<SandboxToolOutcome>;
|
|
27
|
-
/**
|
|
28
|
-
* Validate PutSession.sandbox attach: same owner, and a sandbox to share. A session whose
|
|
29
|
-
* definition declares a sandbox must declare the owner's spec; one whose definition declares
|
|
30
|
-
* none inherits it (`inherit`).
|
|
31
|
-
*/
|
|
32
|
-
export declare function validateSandboxAttach(body: {
|
|
33
|
-
ownerUserId: string;
|
|
34
|
-
sandbox?: unknown;
|
|
35
|
-
}, newManifest: SessionSandboxRef["manifest"], lookup: (id: string) => SessionSandboxRef | undefined,
|
|
36
|
-
/** Acting for a subject: another owner's session is the 404 of a missing one. */
|
|
37
|
-
options?: {
|
|
38
|
-
opaque?: boolean;
|
|
39
|
-
inherit?: boolean;
|
|
40
|
-
}): string | undefined;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP handlers for the session-scoped sandbox tool route (`POST /v1/sessions/:id/sandbox/:tool`)
|
|
3
|
-
*
|
|
2
|
+
* HTTP handlers for the session-scoped sandbox tool route (`POST /v1/sessions/:id/sandbox/:tool`):
|
|
3
|
+
* a tool run in the sandbox of the session's Environment.
|
|
4
4
|
*/
|
|
5
5
|
import { isSandboxToolName, } from "@nylorun/core/define";
|
|
6
|
-
import { capabilityForSandbox,
|
|
6
|
+
import { capabilityForSandbox, sandboxWorkspaceOf, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
7
7
|
export class SandboxRouteError extends Error {
|
|
8
8
|
status;
|
|
9
9
|
constructor(status, message) {
|
|
@@ -12,10 +12,8 @@ export class SandboxRouteError extends Error {
|
|
|
12
12
|
this.name = "SandboxRouteError";
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
function resolveSpec(session
|
|
16
|
-
const
|
|
17
|
-
const owner = ownerId === session.id ? session : lookup(ownerId) ?? session;
|
|
18
|
-
const spec = sessionSandboxSpec(owner) ?? sessionSandboxSpec(session);
|
|
15
|
+
function resolveSpec(session) {
|
|
16
|
+
const spec = sessionSandboxSpec(session);
|
|
19
17
|
if (!spec)
|
|
20
18
|
throw new SandboxRouteError(404, "Session has no sandbox");
|
|
21
19
|
return spec;
|
|
@@ -34,14 +32,14 @@ function toolInput(body) {
|
|
|
34
32
|
const { requestId: _r, ...input } = record;
|
|
35
33
|
return input;
|
|
36
34
|
}
|
|
37
|
-
async function runTool(deps,
|
|
38
|
-
const spec = resolveSpec(session
|
|
39
|
-
const workspace = sandboxWorkspaceOf(session
|
|
35
|
+
async function runTool(deps, session, tool, input, signal) {
|
|
36
|
+
const spec = resolveSpec(session);
|
|
37
|
+
const workspace = sandboxWorkspaceOf(session);
|
|
40
38
|
const ref = {
|
|
41
39
|
id: workspace.ownerId,
|
|
42
40
|
activeTurnId: session.activeTurnId,
|
|
43
41
|
manifest: session.manifest,
|
|
44
|
-
...(workspace.
|
|
42
|
+
...(workspace.environmentId === undefined ? {} : { environmentId: workspace.environmentId }),
|
|
45
43
|
};
|
|
46
44
|
return deps.sandbox.run(ref, capabilityForSandbox(spec), tool, input, signal);
|
|
47
45
|
}
|
|
@@ -51,39 +49,8 @@ async function runTool(deps, { session, lookup }, tool, input, signal) {
|
|
|
51
49
|
*/
|
|
52
50
|
export async function handleSessionSandboxTool(deps, sessionId, toolName, body, signal) {
|
|
53
51
|
const read = await deps.session(sessionId);
|
|
54
|
-
if (read.
|
|
52
|
+
if (read.activeTurnId)
|
|
55
53
|
throw new SandboxRouteError(409, "Sandbox is unavailable while the session has an active agent turn");
|
|
56
54
|
const tool = parseTool(toolName);
|
|
57
55
|
return runTool(deps, read, tool, toolInput(body), signal);
|
|
58
56
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Validate PutSession.sandbox attach: same owner, and a sandbox to share. A session whose
|
|
61
|
-
* definition declares a sandbox must declare the owner's spec; one whose definition declares
|
|
62
|
-
* none inherits it (`inherit`).
|
|
63
|
-
*/
|
|
64
|
-
export function validateSandboxAttach(body, newManifest, lookup,
|
|
65
|
-
/** Acting for a subject: another owner's session is the 404 of a missing one. */
|
|
66
|
-
options = {}) {
|
|
67
|
-
const request = body.sandbox;
|
|
68
|
-
const share = typeof request === "object" && request !== null && "session" in request
|
|
69
|
-
? String(request.session)
|
|
70
|
-
: undefined;
|
|
71
|
-
if (!share)
|
|
72
|
-
return undefined;
|
|
73
|
-
const owner = lookup(share);
|
|
74
|
-
if (!owner || (options.opaque && owner.ownerUserId !== body.ownerUserId))
|
|
75
|
-
throw new SandboxRouteError(404, "Sandbox session not found");
|
|
76
|
-
if (owner.ownerUserId !== body.ownerUserId)
|
|
77
|
-
throw new SandboxRouteError(403, "Sandbox session must belong to the same owner");
|
|
78
|
-
const ownerSpec = sessionSandboxSpec(owner);
|
|
79
|
-
if (!ownerSpec)
|
|
80
|
-
throw new SandboxRouteError(400, "Target session has no sandbox to share");
|
|
81
|
-
if (options.inherit)
|
|
82
|
-
return owningSandboxSessionId(owner, lookup);
|
|
83
|
-
const newSpec = sandboxSpecOf(newManifest);
|
|
84
|
-
if (!newSpec)
|
|
85
|
-
throw new SandboxRouteError(400, "Session must declare a sandbox identical to the shared session");
|
|
86
|
-
if (!sandboxSpecsEqual(ownerSpec, newSpec))
|
|
87
|
-
throw new SandboxRouteError(409, "Sandbox specs must be identical to share a sandbox");
|
|
88
|
-
return owningSandboxSessionId(owner, lookup);
|
|
89
|
-
}
|
|
@@ -72,7 +72,7 @@ export type AdvanceResult = {
|
|
|
72
72
|
retryAfterMs: number;
|
|
73
73
|
};
|
|
74
74
|
/** A pod sandbox's timers. */
|
|
75
|
-
export type SandboxTimer = "idle"
|
|
75
|
+
export type SandboxTimer = "idle";
|
|
76
76
|
/**
|
|
77
77
|
* What `DurableExecution.sandbox` asks for: a reconcile, or a timer. A reconcile's `key` merges
|
|
78
78
|
* repeated sends of one request (the outbox's `signal:<row id>`); without one, every send runs.
|
package/dist/gates/contract.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The gates service's wire format (blueprint §15, P1.1).
|
|
3
|
-
* it
|
|
2
|
+
* The gates service's wire format (blueprint §15, P1.1). Not in the published OpenAPI documents:
|
|
3
|
+
* harnesses call it, and its bodies are published in `@nylorun/core/harness-api` (R4.5).
|
|
4
4
|
*
|
|
5
5
|
* `POST /nylorun/v1/model-calls`
|
|
6
6
|
* - Headers: `Authorization: Bearer <run token>` (F5, `tenant/run-token.ts`), `Idempotency-Key:
|
|
@@ -22,31 +22,10 @@
|
|
|
22
22
|
* whether or not it exists, `403 gate_forbidden` when it is another session's call. The token
|
|
23
23
|
* need not be live: a user cancel makes it stale, and still stops its call.
|
|
24
24
|
*/
|
|
25
|
-
import { z } from "zod";
|
|
26
25
|
import type { ModelGateOutcome } from "./model-gate.js";
|
|
27
|
-
export
|
|
26
|
+
export { MAX_MODEL_CALL_BYTES, MODEL_CALLS_PATH, ModelCallBodySchema, type GateErrorBody, type ModelCallBody, } from "@nylorun/core/harness-api";
|
|
28
27
|
/** Header naming the Tenant a call is for: optional, and checked against the gate's Tenant. */
|
|
29
28
|
export declare const TENANT_HEADER = "nylorun-tenant";
|
|
30
|
-
/** Largest request body the gate reads: one `ModelCall` with its whole projected history. */
|
|
31
|
-
export declare const MAX_MODEL_CALL_BYTES: number;
|
|
32
|
-
export declare const ModelCallBodySchema: z.ZodObject<{
|
|
33
|
-
effectId: z.ZodString;
|
|
34
|
-
invocationId: z.ZodString;
|
|
35
|
-
call: z.ZodObject<{
|
|
36
|
-
prompt: z.ZodArray<z.ZodUnknown>;
|
|
37
|
-
tools: z.ZodArray<z.ZodObject<{
|
|
38
|
-
name: z.ZodString;
|
|
39
|
-
}, z.core.$loose>>;
|
|
40
|
-
}, z.core.$loose>;
|
|
41
|
-
}, z.core.$strict>;
|
|
42
|
-
export type ModelCallBody = z.infer<typeof ModelCallBodySchema>;
|
|
43
29
|
export interface ModelCallResponse {
|
|
44
30
|
readonly outcome: ModelGateOutcome;
|
|
45
31
|
}
|
|
46
|
-
/** An error answer: `{error: {code, message}}`. */
|
|
47
|
-
export interface GateErrorBody {
|
|
48
|
-
readonly error: {
|
|
49
|
-
readonly code: "gate_unauthorized" | "gate_forbidden" | "run_stale" | "invalid_request" | "gate_conflict";
|
|
50
|
-
readonly message: string;
|
|
51
|
-
};
|
|
52
|
-
}
|
package/dist/gates/contract.js
CHANGED
|
@@ -1,38 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
* The gates service's wire format (blueprint §15, P1.1). Internal: only the loop calls it, so
|
|
3
|
-
* it is not in the published OpenAPI documents.
|
|
4
|
-
*
|
|
5
|
-
* `POST /nylorun/v1/model-calls`
|
|
6
|
-
* - Headers: `Authorization: Bearer <run token>` (F5, `tenant/run-token.ts`), `Idempotency-Key:
|
|
7
|
-
* <effect id>`, and optionally `Nylorun-Tenant: <tenant id>`, which must then be the token's
|
|
8
|
-
* Tenant. Core's credential (`NYLORUN_GATES_TOKEN`) is refused: only a run calls the model.
|
|
9
|
-
* - Body: `ModelCallBody`, at most `MAX_MODEL_CALL_BYTES`. It names no session, turn or agent:
|
|
10
|
-
* the gate takes them from the token, and refuses a body that names them.
|
|
11
|
-
* - `200 {outcome}` once the call has finished, whether the outcome is a candidate or a
|
|
12
|
-
* failure; nothing is sent while it runs. `401 gate_unauthorized` for a missing, malformed,
|
|
13
|
-
* expired or foreign token, `409 run_stale` when the token's lease is no longer the
|
|
14
|
-
* session's (a cancel, a new turn, a takeover), `400 invalid_request` for a malformed Tenant
|
|
15
|
-
* header or a bad body, `409 gate_conflict` when a different request already runs under the
|
|
16
|
-
* same key.
|
|
17
|
-
* - With an `Idempotency-Key` the call outlives its client (P1.2): a re-send with the same key
|
|
18
|
-
* and body joins it, or gets its outcome for 30 minutes. Without one, a client that goes
|
|
19
|
-
* away aborts the call.
|
|
20
|
-
*
|
|
21
|
-
* `POST /nylorun/v1/model-calls/{key}/cancel` (same headers): aborts the keyed call; `204`
|
|
22
|
-
* whether or not it exists, `403 gate_forbidden` when it is another session's call. The token
|
|
23
|
-
* need not be live: a user cancel makes it stale, and still stops its call.
|
|
24
|
-
*/
|
|
25
|
-
import { z } from "zod";
|
|
26
|
-
export const MODEL_CALLS_PATH = "/nylorun/v1/model-calls";
|
|
1
|
+
export { MAX_MODEL_CALL_BYTES, MODEL_CALLS_PATH, ModelCallBodySchema, } from "@nylorun/core/harness-api";
|
|
27
2
|
/** Header naming the Tenant a call is for: optional, and checked against the gate's Tenant. */
|
|
28
3
|
export const TENANT_HEADER = "nylorun-tenant";
|
|
29
|
-
/** Largest request body the gate reads: one `ModelCall` with its whole projected history. */
|
|
30
|
-
export const MAX_MODEL_CALL_BYTES = 32 * 1024 * 1024;
|
|
31
|
-
export const ModelCallBodySchema = z.strictObject({
|
|
32
|
-
effectId: z.string().min(1),
|
|
33
|
-
invocationId: z.string().min(1),
|
|
34
|
-
call: z.looseObject({
|
|
35
|
-
prompt: z.array(z.unknown()),
|
|
36
|
-
tools: z.array(z.looseObject({ name: z.string() })),
|
|
37
|
-
}),
|
|
38
|
-
});
|
|
@@ -27,6 +27,13 @@ export interface EgressCredentials {
|
|
|
27
27
|
/** Tests: the port requests are sent to, instead of 443. */
|
|
28
28
|
readonly upstreamPort?: number;
|
|
29
29
|
}
|
|
30
|
+
/** The answer to a CONNECT the gate serves; a tunnel's byte counts leave it out. */
|
|
31
|
+
export declare const CONNECT_ESTABLISHED = "HTTP/1.1 200 Connection Established\r\n\r\n";
|
|
32
|
+
/** What one terminated connection did so far (R7.3): requests read, credentials set (ids). */
|
|
33
|
+
export interface EgressTally {
|
|
34
|
+
requests: number;
|
|
35
|
+
readonly credentialIds: Set<string>;
|
|
36
|
+
}
|
|
30
37
|
export interface CredentialTerminator {
|
|
31
38
|
/**
|
|
32
39
|
* Answers the CONNECT, terminates TLS for `host` and serves its requests, sending each to one
|
|
@@ -39,6 +46,8 @@ export interface CredentialTerminator {
|
|
|
39
46
|
address: string;
|
|
40
47
|
family: number;
|
|
41
48
|
}[];
|
|
49
|
+
/** Counted as requests are served, read when the connection closes. */
|
|
50
|
+
readonly tally: EgressTally;
|
|
42
51
|
}): Promise<void>;
|
|
43
52
|
}
|
|
44
53
|
export declare function credentialTerminator(options: {
|
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
* Nothing here knows a tool: the header name and its format are the credential's. A release the
|
|
13
13
|
* vault refuses (two secrets for one host, an unreadable value) answers 502 and is logged, never
|
|
14
14
|
* as an event; a value never is. Upgrades (WebSocket) and HTTP/2 are not served on this path.
|
|
15
|
+
* Each connection keeps a tally for its `egress.connection` event (R7.3): how many requests it
|
|
16
|
+
* read and which credentials it set, by id.
|
|
15
17
|
*/
|
|
16
18
|
import { createServer } from "node:http";
|
|
17
19
|
import { Agent, request } from "node:https";
|
|
18
20
|
import { createSecureContext, TLSSocket } from "node:tls";
|
|
21
|
+
/** The answer to a CONNECT the gate serves; a tunnel's byte counts leave it out. */
|
|
22
|
+
export const CONNECT_ESTABLISHED = "HTTP/1.1 200 Connection Established\r\n\r\n";
|
|
19
23
|
/** Headers that describe one hop and are never forwarded. */
|
|
20
24
|
const HOP_BY_HOP = new Set([
|
|
21
25
|
"connection",
|
|
@@ -74,6 +78,7 @@ export function credentialTerminator(options) {
|
|
|
74
78
|
return;
|
|
75
79
|
}
|
|
76
80
|
const { sandboxId, host } = at;
|
|
81
|
+
at.tally.requests += 1;
|
|
77
82
|
const release = await credentials.release(sandboxId, host);
|
|
78
83
|
if (release.status === "refused") {
|
|
79
84
|
logger.warn("egress_credential_refused", { status: 502, reason: release.reason, sandboxId, host });
|
|
@@ -82,8 +87,10 @@ export function credentialTerminator(options) {
|
|
|
82
87
|
}
|
|
83
88
|
const header = release.status === "released" ? release.header.toLowerCase() : undefined;
|
|
84
89
|
const headers = forwardable(req.rawHeaders, header);
|
|
85
|
-
if (release.status === "released")
|
|
90
|
+
if (release.status === "released") {
|
|
86
91
|
headers[header] = [release.value];
|
|
92
|
+
at.tally.credentialIds.add(release.credentialId);
|
|
93
|
+
}
|
|
87
94
|
const upstream = request({
|
|
88
95
|
host,
|
|
89
96
|
port: credentials.upstreamPort ?? 443,
|
|
@@ -111,7 +118,7 @@ export function credentialTerminator(options) {
|
|
|
111
118
|
}
|
|
112
119
|
return {
|
|
113
120
|
async terminate(client, target) {
|
|
114
|
-
const { sandboxId, host, addresses } = target;
|
|
121
|
+
const { sandboxId, host, addresses, tally } = target;
|
|
115
122
|
const leaf = await credentials.leaf(host);
|
|
116
123
|
if (client.destroyed)
|
|
117
124
|
return;
|
|
@@ -128,13 +135,13 @@ export function credentialTerminator(options) {
|
|
|
128
135
|
lookup,
|
|
129
136
|
...(credentials.upstreamCa ? { ca: credentials.upstreamCa } : {}),
|
|
130
137
|
});
|
|
131
|
-
client.write(
|
|
138
|
+
client.write(CONNECT_ESTABLISHED);
|
|
132
139
|
const tls = new TLSSocket(client, {
|
|
133
140
|
isServer: true,
|
|
134
141
|
secureContext: createSecureContext({ key: leaf.key, cert: leaf.cert }),
|
|
135
142
|
ALPNProtocols: ["http/1.1"],
|
|
136
143
|
});
|
|
137
|
-
terminated.set(tls, { sandboxId, host, agent });
|
|
144
|
+
terminated.set(tls, { sandboxId, host, agent, tally });
|
|
138
145
|
tls.on("error", () => tls.destroy());
|
|
139
146
|
tls.on("close", () => {
|
|
140
147
|
agent.destroy();
|
package/dist/gates/egress.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Server } from "node:http";
|
|
2
2
|
import { type Socket } from "node:net";
|
|
3
|
+
import type { EventPayload, EventWriter } from "@nylorun/core/contracts";
|
|
3
4
|
import type { EgressTokenVerdict } from "../sandbox/egress-token.js";
|
|
4
5
|
import type { SessionStore } from "../store/types.js";
|
|
5
6
|
import type { Logger } from "../tenant/types.js";
|
|
@@ -15,6 +16,22 @@ export interface EgressSandboxes {
|
|
|
15
16
|
readonly allow: readonly string[];
|
|
16
17
|
} | undefined>;
|
|
17
18
|
}
|
|
19
|
+
/** The session and turn holding an Environment: turns are serial per Environment, so one at most. */
|
|
20
|
+
export interface EgressHolder {
|
|
21
|
+
readonly sessionId: string;
|
|
22
|
+
readonly turnId: string;
|
|
23
|
+
}
|
|
24
|
+
/** One connection's record (R7.3): `egress.connection`'s payload, `environment.egress`'s too. */
|
|
25
|
+
export type EgressConnection = EventPayload<"egress.connection">;
|
|
26
|
+
/** Where egress-gate records connections (R7.3). */
|
|
27
|
+
export interface EgressRecord {
|
|
28
|
+
/** The session whose turn holds the Environment now; `undefined` between turns. */
|
|
29
|
+
holder(environmentId: string): Promise<EgressHolder | undefined>;
|
|
30
|
+
/** Writes `connection` to `holder`'s session log, or to the Environment's stream without one. */
|
|
31
|
+
write(holder: EgressHolder | undefined, connection: EgressConnection): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/** egress-gate as the writer of its events (R4.9): the gate saw the connection itself. */
|
|
34
|
+
export declare const EGRESS_WRITER: EventWriter;
|
|
18
35
|
/** Ports a tunnel may open. */
|
|
19
36
|
export declare const EGRESS_PORTS: readonly number[];
|
|
20
37
|
/** Tunnels one sandbox may hold open at once. */
|
|
@@ -49,6 +66,8 @@ export interface StartEgressGateOptions {
|
|
|
49
66
|
readonly dial?: (address: string, port: number) => Socket;
|
|
50
67
|
/** The vault and keys, for hosts an `environment_secret` is bound to (R2c). None: tunnels only. */
|
|
51
68
|
readonly credentials?: EgressCredentials;
|
|
69
|
+
/** Where connections are recorded (R7.3). None: they are only logged. */
|
|
70
|
+
readonly record?: EgressRecord;
|
|
52
71
|
}
|
|
53
72
|
export interface EgressGate {
|
|
54
73
|
/** Where the gate listens, e.g. `http://0.0.0.0:4200`. */
|
|
@@ -70,6 +89,14 @@ export declare function startEgressGate(options: StartEgressGateOptions): Promis
|
|
|
70
89
|
/**
|
|
71
90
|
* `EgressSandboxes` over the Tenant's `sandbox_resources` (one indexed read): a pod sandbox's
|
|
72
91
|
* host epoch, the pod that joined at it and its spec's allowlist; nothing for a virtual
|
|
73
|
-
*
|
|
92
|
+
* Environment, or one deleted or lost, as `staleHost` judges a host token.
|
|
74
93
|
*/
|
|
75
94
|
export declare function storeEgressSandboxes(store: SessionStore): EgressSandboxes;
|
|
95
|
+
/**
|
|
96
|
+
* `EgressRecord` over the Tenant's record (R7.3). The holder is the session on the Environment
|
|
97
|
+
* whose body has an `activeTurnId` (turns are serial per Environment). With a holder, the write
|
|
98
|
+
* is `egress.connection` on its session log, at that turn, under the session row's lock; without
|
|
99
|
+
* one, `environment.egress` on the Environment's stream, under its row's lock. A deleted
|
|
100
|
+
* Environment gets nothing.
|
|
101
|
+
*/
|
|
102
|
+
export declare function storeEgressRecord(store: SessionStore): EgressRecord;
|