@nylorun/runtime 0.15.2-beta → 0.17.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 +134 -0
- package/README.md +68 -29
- package/dist/adapters/execution/restate.d.ts +9 -1
- package/dist/adapters/execution/restate.js +64 -5
- package/dist/adapters/sandbox/local.d.ts +10 -0
- package/dist/adapters/sandbox/local.js +195 -0
- package/dist/adapters/sandbox/virtual.js +36 -1
- package/dist/admin-openapi.json +341 -7
- package/dist/api/a2a/tasks.js +6 -1
- package/dist/api/ag-ui/endpoint.js +1 -2
- package/dist/api/ag-ui/history.js +6 -1
- package/dist/api/ag-ui/routes.d.ts +1 -1
- package/dist/api/ag-ui/routes.js +15 -13
- package/dist/api/components.d.ts +2667 -711
- package/dist/api/components.js +24 -11
- package/dist/api/gate/keys.d.ts +4 -3
- package/dist/api/gate/routes.d.ts +19 -3
- package/dist/api/gate/routes.js +162 -53
- package/dist/api/http/app.js +6 -0
- package/dist/api/http/define.d.ts +19 -12
- package/dist/api/http/define.js +13 -27
- package/dist/api/http/routes/access.d.ts +3 -19
- package/dist/api/http/routes/access.js +12 -172
- package/dist/api/http/routes/actions.js +0 -1
- package/dist/api/http/routes/artifacts.d.ts +11 -0
- package/dist/api/http/routes/artifacts.js +516 -0
- package/dist/api/http/routes/me.d.ts +13 -0
- package/dist/api/http/routes/me.js +45 -0
- package/dist/api/http/routes/sandboxes.d.ts +9 -0
- package/dist/api/http/routes/sandboxes.js +150 -0
- package/dist/api/http/routes/sessions.js +9 -13
- package/dist/api/http/routes/tenant.js +36 -2
- package/dist/api/http/routes/vaults.d.ts +12 -3
- package/dist/api/http/routes/vaults.js +122 -54
- package/dist/api/openapi.js +4 -10
- package/dist/artifacts/config.d.ts +23 -0
- package/dist/artifacts/config.js +27 -0
- package/dist/artifacts/export.d.ts +8 -0
- package/dist/artifacts/export.js +205 -0
- package/dist/artifacts/files.d.ts +36 -0
- package/dist/artifacts/files.js +45 -0
- package/dist/artifacts/folders.d.ts +60 -0
- package/dist/artifacts/folders.js +209 -0
- package/dist/artifacts/links.d.ts +28 -0
- package/dist/artifacts/links.js +92 -0
- package/dist/artifacts/media-types.d.ts +13 -0
- package/dist/artifacts/media-types.js +80 -0
- package/dist/artifacts/parts.d.ts +34 -0
- package/dist/artifacts/parts.js +38 -0
- package/dist/artifacts/service.d.ts +74 -0
- package/dist/artifacts/service.js +301 -0
- package/dist/artifacts/workspace.d.ts +60 -0
- package/dist/artifacts/workspace.js +57 -0
- package/dist/artifacts/zip.d.ts +10 -0
- package/dist/artifacts/zip.js +126 -0
- package/dist/blob/fs.d.ts +13 -0
- package/dist/blob/fs.js +194 -0
- package/dist/blob/index.d.ts +9 -0
- package/dist/blob/index.js +9 -0
- package/dist/blob/s3.d.ts +42 -0
- package/dist/blob/s3.js +366 -0
- package/dist/blob/types.d.ts +108 -0
- package/dist/blob/types.js +85 -0
- package/dist/core/sandbox-routes.d.ts +2 -2
- package/dist/core/sandbox-routes.js +4 -3
- package/dist/execution/memory.d.ts +11 -1
- package/dist/execution/memory.js +77 -3
- package/dist/execution/types.d.ts +54 -1
- package/dist/execution/types.js +31 -0
- package/dist/gates/contract.d.ts +14 -13
- package/dist/gates/contract.js +13 -12
- package/dist/gates/egress.d.ts +72 -0
- package/dist/gates/egress.js +319 -0
- package/dist/gates/handler.d.ts +3 -0
- package/dist/gates/handler.js +3 -2
- package/dist/gates/http-client.d.ts +3 -2
- package/dist/gates/http-client.js +17 -6
- package/dist/gates/in-process.d.ts +13 -1
- package/dist/gates/in-process.js +11 -0
- package/dist/gates/inflight.d.ts +23 -4
- package/dist/gates/inflight.js +18 -4
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/model-gate.d.ts +6 -2
- package/dist/gates/tenant-vaults.d.ts +18 -9
- package/dist/gates/tenant-vaults.js +17 -12
- package/dist/gates/tool-calls.d.ts +9 -5
- package/dist/gates/tool-calls.js +6 -6
- package/dist/gates/tool-client.d.ts +9 -2
- package/dist/gates/tool-client.js +56 -22
- package/dist/gates/tool-contract.d.ts +12 -7
- package/dist/gates/tool-contract.js +10 -4
- package/dist/gates/tool-gate.d.ts +1 -0
- package/dist/harness/calls.d.ts +52 -0
- package/dist/harness/calls.js +113 -0
- package/dist/harness/executors.d.ts +22 -0
- package/dist/harness/executors.js +96 -0
- package/dist/harness/main.d.ts +6 -0
- package/dist/harness/main.js +103 -0
- package/dist/harness/pod.d.ts +21 -0
- package/dist/harness/pod.js +119 -0
- package/dist/harness/run-tokens.d.ts +18 -0
- package/dist/harness/run-tokens.js +15 -0
- package/dist/harness/service.d.ts +46 -0
- package/dist/harness/service.js +179 -0
- package/dist/harness/ws-client.d.ts +43 -0
- package/dist/harness/ws-client.js +122 -0
- package/dist/harness/ws-port.d.ts +22 -0
- package/dist/harness/ws-port.js +78 -0
- package/dist/harness-api/in-process.d.ts +24 -0
- package/dist/harness-api/in-process.js +119 -0
- package/dist/harness-api/record.d.ts +44 -0
- package/dist/harness-api/record.js +250 -0
- package/dist/harness-api/renew.d.ts +37 -0
- package/dist/harness-api/renew.js +77 -0
- package/dist/harness-api/server.d.ts +104 -0
- package/dist/harness-api/server.js +408 -0
- package/dist/harness-api/start.d.ts +36 -0
- package/dist/harness-api/start.js +118 -0
- package/dist/harness-api/workspace.d.ts +53 -0
- package/dist/harness-api/workspace.js +201 -0
- package/dist/harness-api/ws-server.d.ts +34 -0
- package/dist/harness-api/ws-server.js +227 -0
- package/dist/host/admin-api.d.ts +12 -5
- package/dist/host/admin-api.js +72 -6
- package/dist/host/app.d.ts +0 -1
- package/dist/host/app.js +35 -24
- package/dist/host/config-for.d.ts +4 -0
- package/dist/host/config-for.js +2 -0
- package/dist/host/config.d.ts +0 -6
- package/dist/host/create-host.d.ts +3 -10
- package/dist/host/create-host.js +4 -1
- package/dist/host/gates.d.ts +27 -2
- package/dist/host/gates.js +52 -2
- package/dist/host/http.d.ts +4 -1
- package/dist/host/http.js +6 -1
- package/dist/host/main.js +167 -37
- package/dist/host/stack-config.d.ts +112 -9
- package/dist/host/stack-config.js +222 -20
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/keys/client.js +9 -1
- package/dist/keys/keys.d.ts +12 -2
- package/dist/keys/keys.js +8 -0
- package/dist/mcp/connect.js +2 -1
- package/dist/mcp/pool.d.ts +1 -0
- package/dist/mcp/pool.js +1 -0
- package/dist/mcp/snapshot.d.ts +8 -1
- package/dist/mcp/snapshot.js +28 -0
- package/dist/media.d.ts +5 -24
- package/dist/media.js +5 -0
- package/dist/model/pi-model.d.ts +6 -2
- package/dist/model/pi-model.js +39 -22
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +1 -1
- package/dist/openapi.json +14084 -9426
- package/dist/record/index.d.ts +1 -0
- package/dist/record/index.js +1 -0
- package/dist/record/sandbox.d.ts +25 -0
- package/dist/record/sandbox.js +43 -0
- package/dist/sandbox/capability.d.ts +3 -0
- package/dist/sandbox/capability.js +10 -0
- package/dist/sandbox/egress-token.d.ts +26 -0
- package/dist/sandbox/egress-token.js +8 -0
- package/dist/sandbox/join.d.ts +37 -0
- package/dist/sandbox/join.js +165 -0
- package/dist/sandbox/manager.d.ts +99 -34
- package/dist/sandbox/manager.js +216 -84
- package/dist/sandbox/placement.d.ts +27 -0
- package/dist/sandbox/placement.js +44 -0
- package/dist/sandbox/pods/client.d.ts +90 -0
- package/dist/sandbox/pods/client.js +79 -0
- package/dist/sandbox/pods/lifecycle.d.ts +91 -0
- package/dist/sandbox/pods/lifecycle.js +178 -0
- package/dist/sandbox/pods/name.d.ts +1 -0
- package/dist/sandbox/pods/name.js +28 -0
- package/dist/sandbox/pods/network-gate.d.ts +25 -0
- package/dist/sandbox/pods/network-gate.js +71 -0
- package/dist/sandbox/pods/reconcile.d.ts +11 -0
- package/dist/sandbox/pods/reconcile.js +152 -0
- package/dist/sandbox/pods/spec.d.ts +45 -0
- package/dist/sandbox/pods/spec.js +62 -0
- package/dist/sandbox/records.d.ts +29 -0
- package/dist/sandbox/records.js +79 -0
- package/dist/sandbox/resolve.d.ts +5 -0
- package/dist/sandbox/resolve.js +3 -2
- package/dist/sandbox/session-sandbox.js +7 -5
- package/dist/sandbox/share.d.ts +11 -0
- package/dist/sandbox/share.js +10 -0
- package/dist/sandbox/tenant-config.d.ts +16 -1
- package/dist/sandbox/tenant-config.js +9 -0
- package/dist/sandbox/types.d.ts +29 -1
- package/dist/sandbox/types.js +13 -1
- package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
- package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
- package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
- package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
- package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
- package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
- package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
- package/dist/store/postgres/record-writer.d.ts +6 -1
- package/dist/store/postgres/record-writer.js +20 -2
- package/dist/store/postgres/schema.d.ts +1168 -250
- package/dist/store/postgres/schema.js +174 -28
- package/dist/store/postgres/store.js +339 -103
- package/dist/store/postgres/tenant.d.ts +1 -1
- package/dist/store/postgres/tenant.js +2 -2
- package/dist/store/types.d.ts +163 -32
- package/dist/streams/control.d.ts +2 -5
- package/dist/streams/control.js +5 -8
- package/dist/streams/types.d.ts +8 -8
- package/dist/streams/types.js +1 -1
- package/dist/tenant/advance.js +218 -160
- package/dist/tenant/artifact-tool.d.ts +5 -0
- package/dist/tenant/artifact-tool.js +119 -0
- package/dist/tenant/auth.d.ts +13 -9
- package/dist/tenant/auth.js +25 -32
- package/dist/tenant/commands.js +31 -10
- package/dist/tenant/context.d.ts +83 -26
- package/dist/tenant/delivery-token.js +4 -4
- package/dist/tenant/effects.d.ts +24 -14
- package/dist/tenant/effects.js +10 -398
- package/dist/tenant/ephemeral.d.ts +14 -2
- package/dist/tenant/ephemeral.js +8 -1
- package/dist/tenant/history.d.ts +13 -23
- package/dist/tenant/history.js +14 -62
- package/dist/tenant/host-token.d.ts +92 -0
- package/dist/tenant/host-token.js +179 -0
- package/dist/tenant/identity-file.d.ts +75 -0
- package/dist/tenant/identity-file.js +202 -0
- package/dist/tenant/issuers.d.ts +48 -0
- package/dist/tenant/issuers.js +311 -0
- package/dist/tenant/jwt.js +3 -3
- package/dist/tenant/module.js +1 -0
- package/dist/tenant/operator-keys.d.ts +18 -0
- package/dist/tenant/operator-keys.js +55 -0
- package/dist/tenant/outbound.d.ts +30 -0
- package/dist/tenant/outbound.js +142 -10
- package/dist/tenant/paths.d.ts +2 -1
- package/dist/tenant/paths.js +3 -1
- package/dist/tenant/principals.d.ts +2 -11
- package/dist/tenant/principals.js +10 -30
- package/dist/tenant/reset.d.ts +5 -2
- package/dist/tenant/reset.js +22 -2
- package/dist/tenant/run-grants.d.ts +49 -0
- package/dist/tenant/run-grants.js +62 -0
- package/dist/tenant/run-token.d.ts +70 -0
- package/dist/tenant/run-token.js +138 -0
- package/dist/tenant/runtime.d.ts +42 -0
- package/dist/tenant/runtime.js +134 -33
- package/dist/tenant/sandboxes.d.ts +119 -0
- package/dist/tenant/sandboxes.js +551 -0
- package/dist/tenant/session-streams.d.ts +6 -10
- package/dist/tenant/session-streams.js +9 -27
- package/dist/tenant/session.d.ts +2 -13
- package/dist/tenant/session.js +4 -28
- package/dist/tenant/sessions.d.ts +6 -1
- package/dist/tenant/sessions.js +29 -2
- package/dist/tenant/signing-keys.d.ts +2 -1
- package/dist/tenant/signing-keys.js +2 -2
- package/dist/tenant/status.d.ts +8 -3
- package/dist/tenant/status.js +5 -0
- package/dist/tenant/streams.d.ts +8 -2
- package/dist/tenant/streams.js +25 -6
- package/dist/tenant/sweep.js +37 -4
- package/dist/tenant/types.d.ts +39 -1
- package/dist/tenant/worker.d.ts +11 -5
- package/dist/tenant/worker.js +12 -6
- package/dist/vault/crypto.d.ts +7 -0
- package/dist/vault/crypto.js +16 -0
- package/dist/vault/oauth.d.ts +55 -0
- package/dist/vault/oauth.js +186 -0
- package/dist/vault/service.d.ts +54 -7
- package/dist/vault/service.js +261 -17
- package/dist/vault/sources.d.ts +54 -0
- package/dist/vault/sources.js +185 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +14 -10
- package/dist/adapters/media.d.ts +0 -24
- package/dist/adapters/media.js +0 -111
- package/dist/host/cors.d.ts +0 -27
- package/dist/host/cors.js +0 -64
- package/dist/tenant/access-policy.d.ts +0 -30
- package/dist/tenant/access-policy.js +0 -52
- package/dist/tenant/browser.d.ts +0 -15
- package/dist/tenant/browser.js +0 -39
- package/dist/tenant/subject-limits.d.ts +0 -16
- package/dist/tenant/subject-limits.js +0 -45
- package/dist/tenant/tokens.d.ts +0 -14
- package/dist/tenant/tokens.js +0 -180
package/dist/execution/memory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WAKE_REASONS, sessionKey, } from "./types.js";
|
|
1
|
+
import { DEFAULT_STOP_GRACE_MS, WAKE_REASONS, sessionKey, settleWithin, } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* In-process `DurableExecution`. Not durable: wakes, timers and sweeps live in
|
|
4
4
|
* memory. It keeps the seam's ordering guarantees within one process, which is
|
|
@@ -11,18 +11,21 @@ export class MemoryExecution {
|
|
|
11
11
|
seen = new Map();
|
|
12
12
|
timers = new Map();
|
|
13
13
|
sweeps = new Map();
|
|
14
|
+
sandboxes = new Map();
|
|
14
15
|
delayed = new Set();
|
|
15
16
|
inflight = new Set();
|
|
16
17
|
sweepIntervalMs;
|
|
17
18
|
retryDelayMs;
|
|
18
19
|
maxAttempts;
|
|
19
20
|
dedupeRetentionMs;
|
|
21
|
+
stopGraceMs;
|
|
20
22
|
onError;
|
|
21
23
|
constructor(options = {}) {
|
|
22
24
|
this.sweepIntervalMs = options.sweepIntervalMs ?? 5000;
|
|
23
25
|
this.retryDelayMs = options.retryDelayMs ?? 50;
|
|
24
26
|
this.maxAttempts = options.maxAttempts ?? 5;
|
|
25
27
|
this.dedupeRetentionMs = options.dedupeRetentionMs ?? 24 * 60 * 60_000;
|
|
28
|
+
this.stopGraceMs = options.stopGraceMs ?? DEFAULT_STOP_GRACE_MS;
|
|
26
29
|
this.onError =
|
|
27
30
|
options.onError ??
|
|
28
31
|
((error) => queueMicrotask(() => {
|
|
@@ -46,6 +49,66 @@ export class MemoryExecution {
|
|
|
46
49
|
async deliver(tenantId, actionId) {
|
|
47
50
|
this.enqueue("deliver", tenantId, actionId);
|
|
48
51
|
}
|
|
52
|
+
async sandbox(tenantId, sandboxId, signal) {
|
|
53
|
+
const key = `${tenantId}\u0000${sandboxId}`;
|
|
54
|
+
let state = this.sandboxes.get(key);
|
|
55
|
+
if (!state)
|
|
56
|
+
this.sandboxes.set(key, (state = { tenantId, sandboxId, tail: Promise.resolve(), pending: [], attempts: 0, timers: new Map() }));
|
|
57
|
+
if (signal.kind === "arm")
|
|
58
|
+
this.armSandbox(state, signal.timer, signal.at);
|
|
59
|
+
else
|
|
60
|
+
this.runSandbox(state, "reconcile");
|
|
61
|
+
}
|
|
62
|
+
armSandbox(state, timer, at) {
|
|
63
|
+
const existing = state.timers.get(timer);
|
|
64
|
+
if (existing?.handle)
|
|
65
|
+
clearTimeout(existing.handle);
|
|
66
|
+
const entry = { at };
|
|
67
|
+
state.timers.set(timer, entry);
|
|
68
|
+
if (!this.handlers)
|
|
69
|
+
return;
|
|
70
|
+
entry.handle = setTimeout(() => {
|
|
71
|
+
if (state.timers.get(timer) !== entry)
|
|
72
|
+
return;
|
|
73
|
+
state.timers.delete(timer);
|
|
74
|
+
this.runSandbox(state, timer === "retry" ? "reconcile" : timer);
|
|
75
|
+
}, Math.max(0, at - Date.now()));
|
|
76
|
+
}
|
|
77
|
+
runSandbox(state, trigger) {
|
|
78
|
+
const handlers = this.handlers;
|
|
79
|
+
if (!handlers) {
|
|
80
|
+
state.pending.push(trigger);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const run = state.tail.then(async () => {
|
|
84
|
+
if (!this.handlers)
|
|
85
|
+
return;
|
|
86
|
+
if (!handlers.sandbox) {
|
|
87
|
+
this.onError(new Error("WorkerHandlers.sandbox is required for sandbox reconciles"));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
try {
|
|
92
|
+
const result = await handlers.sandbox(state.tenantId, state.sandboxId, trigger, controller.signal);
|
|
93
|
+
state.attempts = 0;
|
|
94
|
+
for (const item of result.arm ?? [])
|
|
95
|
+
this.armSandbox(state, item.timer, item.at);
|
|
96
|
+
if (result.retryAfterMs !== undefined)
|
|
97
|
+
this.armSandbox(state, "retry", Date.now() + Math.max(0, result.retryAfterMs));
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
state.attempts += 1;
|
|
101
|
+
if (state.attempts >= this.maxAttempts) {
|
|
102
|
+
state.attempts = 0;
|
|
103
|
+
this.onError(error);
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
this.armSandbox(state, "retry", Date.now() + this.retryDelayMs * 2 ** (state.attempts - 1));
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
state.tail = run.catch(() => undefined);
|
|
110
|
+
this.track(state.tail);
|
|
111
|
+
}
|
|
49
112
|
async timer(tenantId, key, at) {
|
|
50
113
|
const id = `${tenantId}\u0000${key}`;
|
|
51
114
|
const existing = this.timers.get(id);
|
|
@@ -81,6 +144,12 @@ export class MemoryExecution {
|
|
|
81
144
|
this.armTimer(id, timer);
|
|
82
145
|
for (const [tenantId, sweep] of this.sweeps)
|
|
83
146
|
this.scheduleSweep(tenantId, sweep, 0);
|
|
147
|
+
for (const state of this.sandboxes.values()) {
|
|
148
|
+
for (const [timer, entry] of state.timers)
|
|
149
|
+
this.armSandbox(state, timer, entry.at);
|
|
150
|
+
for (const trigger of state.pending.splice(0))
|
|
151
|
+
this.runSandbox(state, trigger);
|
|
152
|
+
}
|
|
84
153
|
}
|
|
85
154
|
async stop() {
|
|
86
155
|
this.handlers = undefined;
|
|
@@ -93,10 +162,15 @@ export class MemoryExecution {
|
|
|
93
162
|
for (const sweep of this.sweeps.values())
|
|
94
163
|
if (sweep.handle)
|
|
95
164
|
clearTimeout(sweep.handle);
|
|
165
|
+
for (const state of this.sandboxes.values())
|
|
166
|
+
for (const entry of state.timers.values())
|
|
167
|
+
if (entry.handle)
|
|
168
|
+
clearTimeout(entry.handle);
|
|
96
169
|
for (const state of this.keys.values())
|
|
97
170
|
state.controller?.abort();
|
|
98
|
-
|
|
99
|
-
|
|
171
|
+
// Bounded: a handler that ignores its abort is abandoned and runs on unwaited. An
|
|
172
|
+
// abandoned advance's lease lapses and the next advance takes the session over (§11.4).
|
|
173
|
+
await settleWithin(this.inflight, this.stopGraceMs);
|
|
100
174
|
}
|
|
101
175
|
/**
|
|
102
176
|
* Resolves once no advance or delivery is running or queued and no busy or
|
|
@@ -37,6 +37,13 @@
|
|
|
37
37
|
* - **Retry after `retry`.** A delivery that returns `retry` is run again for
|
|
38
38
|
* the same key after `retryAfterMs`. This is how an Action endpoint that is
|
|
39
39
|
* down or busy is retried; the handler throws only on infrastructure errors.
|
|
40
|
+
* - **One reconcile per sandbox at a time** (F7.2). `sandbox` runs
|
|
41
|
+
* `WorkerHandlers.sandbox` for the key `<tenantId>:<sandboxId>` at least once
|
|
42
|
+
* after a `reconcile` resolves, never overlapping another run for the key. A
|
|
43
|
+
* run that answers `retryAfterMs` runs again after it (one pending retry per
|
|
44
|
+
* key: a later one replaces it), and each timer it answers is armed. `arm`
|
|
45
|
+
* sets one of the sandbox's timers (`idle`, `ttl`): setting it again replaces
|
|
46
|
+
* the earlier time, and the run it causes is told which timer fired.
|
|
40
47
|
*/
|
|
41
48
|
/** Why a session is woken (§12.3, "Where wakes come from"). */
|
|
42
49
|
export type WakeReason =
|
|
@@ -69,6 +76,28 @@ export type AdvanceResult = {
|
|
|
69
76
|
status: "busy";
|
|
70
77
|
retryAfterMs: number;
|
|
71
78
|
};
|
|
79
|
+
/** A pod sandbox's timers. */
|
|
80
|
+
export type SandboxTimer = "idle" | "ttl";
|
|
81
|
+
/** What `DurableExecution.sandbox` asks for: a reconcile, or a timer. */
|
|
82
|
+
export type SandboxSignal = {
|
|
83
|
+
kind: "reconcile";
|
|
84
|
+
} | {
|
|
85
|
+
kind: "arm";
|
|
86
|
+
timer: SandboxTimer;
|
|
87
|
+
at: number;
|
|
88
|
+
};
|
|
89
|
+
/** Why `WorkerHandlers.sandbox` runs. */
|
|
90
|
+
export type SandboxTrigger = "reconcile" | SandboxTimer;
|
|
91
|
+
/** What a sandbox reconcile asks for next. */
|
|
92
|
+
export interface SandboxResult {
|
|
93
|
+
/** Run again this soon. */
|
|
94
|
+
retryAfterMs?: number;
|
|
95
|
+
/** Timers to set, at ms since the epoch. */
|
|
96
|
+
arm?: readonly {
|
|
97
|
+
timer: SandboxTimer;
|
|
98
|
+
at: number;
|
|
99
|
+
}[];
|
|
100
|
+
}
|
|
72
101
|
export type DeliverResult = {
|
|
73
102
|
status: "done";
|
|
74
103
|
}
|
|
@@ -94,6 +123,11 @@ export interface WorkerHandlers {
|
|
|
94
123
|
* stops. Throw only on infrastructure errors. Required when `deliver` is used.
|
|
95
124
|
*/
|
|
96
125
|
deliver?(tenantId: string, actionId: string, signal: AbortSignal): Promise<DeliverResult>;
|
|
126
|
+
/**
|
|
127
|
+
* Reconciles one pod sandbox (F7.2), serialized per sandbox. Throw only on infrastructure
|
|
128
|
+
* errors. Required when `sandbox` is used.
|
|
129
|
+
*/
|
|
130
|
+
sandbox?(tenantId: string, sandboxId: string, trigger: SandboxTrigger, signal: AbortSignal): Promise<SandboxResult>;
|
|
97
131
|
}
|
|
98
132
|
export interface DurableExecution {
|
|
99
133
|
/** At-least-once. `dedupeKey` merges repeated wakes for the same cause. */
|
|
@@ -110,13 +144,22 @@ export interface DurableExecution {
|
|
|
110
144
|
* after the transaction that made the Action pending commits.
|
|
111
145
|
*/
|
|
112
146
|
deliver(tenantId: string, actionId: string): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* A pod sandbox's reconcile, or one of its timers (F7.2): see "One reconcile per sandbox
|
|
149
|
+
* at a time". Absent where pods are not supported.
|
|
150
|
+
*/
|
|
151
|
+
sandbox?(tenantId: string, sandboxId: string, signal: SandboxSignal): Promise<void>;
|
|
113
152
|
/** Arms the Tenant's self-re-arming sweep. Idempotent. */
|
|
114
153
|
armSweep(tenantId: string): Promise<void>;
|
|
115
154
|
/** Stops re-arming the Tenant's sweep (Tenant deleted). A pass already running finishes. */
|
|
116
155
|
disarmSweep(tenantId: string): Promise<void>;
|
|
117
156
|
/** Starts delivering to `handlers` (serving the Worker endpoint, for Restate). */
|
|
118
157
|
start(handlers: WorkerHandlers): Promise<void>;
|
|
119
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* Stops delivering, aborts running advances' signals and waits for them, up to the
|
|
160
|
+
* implementation's stop grace (`DEFAULT_STOP_GRACE_MS`). Handlers still running then are
|
|
161
|
+
* abandoned: their sessions' leases lapse and the next advance takes over (§11.4).
|
|
162
|
+
*/
|
|
120
163
|
stop(): Promise<void>;
|
|
121
164
|
/**
|
|
122
165
|
* Resolves when the backing service answers, rejects otherwise (readiness,
|
|
@@ -155,3 +198,13 @@ export declare function parseSessionKey(key: string): {
|
|
|
155
198
|
tenantId: string;
|
|
156
199
|
sessionId: string;
|
|
157
200
|
};
|
|
201
|
+
/**
|
|
202
|
+
* Default time `DurableExecution.stop` waits for running handlers after aborting them: the
|
|
203
|
+
* advance grace period (`DEFAULT_ADVANCE_GRACE_MS` in `tenant/worker.ts`).
|
|
204
|
+
*/
|
|
205
|
+
export declare const DEFAULT_STOP_GRACE_MS = 30000;
|
|
206
|
+
/**
|
|
207
|
+
* Waits until `inflight` is empty (it shrinks as its promises settle and may grow meanwhile),
|
|
208
|
+
* for at most `graceMs`. Resolves to how many were still running when the grace ran out.
|
|
209
|
+
*/
|
|
210
|
+
export declare function settleWithin(inflight: ReadonlySet<Promise<unknown>>, graceMs: number): Promise<number>;
|
package/dist/execution/types.js
CHANGED
|
@@ -37,6 +37,13 @@
|
|
|
37
37
|
* - **Retry after `retry`.** A delivery that returns `retry` is run again for
|
|
38
38
|
* the same key after `retryAfterMs`. This is how an Action endpoint that is
|
|
39
39
|
* down or busy is retried; the handler throws only on infrastructure errors.
|
|
40
|
+
* - **One reconcile per sandbox at a time** (F7.2). `sandbox` runs
|
|
41
|
+
* `WorkerHandlers.sandbox` for the key `<tenantId>:<sandboxId>` at least once
|
|
42
|
+
* after a `reconcile` resolves, never overlapping another run for the key. A
|
|
43
|
+
* run that answers `retryAfterMs` runs again after it (one pending retry per
|
|
44
|
+
* key: a later one replaces it), and each timer it answers is armed. `arm`
|
|
45
|
+
* sets one of the sandbox's timers (`idle`, `ttl`): setting it again replaces
|
|
46
|
+
* the earlier time, and the run it causes is told which timer fired.
|
|
40
47
|
*/
|
|
41
48
|
export const WAKE_REASONS = [
|
|
42
49
|
"message",
|
|
@@ -59,3 +66,27 @@ export function parseSessionKey(key) {
|
|
|
59
66
|
throw new Error(`Invalid session key: ${key}`);
|
|
60
67
|
return { tenantId: key.slice(0, at), sessionId: key.slice(at + 1) };
|
|
61
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Default time `DurableExecution.stop` waits for running handlers after aborting them: the
|
|
71
|
+
* advance grace period (`DEFAULT_ADVANCE_GRACE_MS` in `tenant/worker.ts`).
|
|
72
|
+
*/
|
|
73
|
+
export const DEFAULT_STOP_GRACE_MS = 30_000;
|
|
74
|
+
/**
|
|
75
|
+
* Waits until `inflight` is empty (it shrinks as its promises settle and may grow meanwhile),
|
|
76
|
+
* for at most `graceMs`. Resolves to how many were still running when the grace ran out.
|
|
77
|
+
*/
|
|
78
|
+
export async function settleWithin(inflight, graceMs) {
|
|
79
|
+
let timer;
|
|
80
|
+
const expired = new Promise((resolve) => {
|
|
81
|
+
timer = setTimeout(() => resolve("expired"), Math.max(0, graceMs));
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
while (inflight.size > 0)
|
|
85
|
+
if ((await Promise.race([Promise.allSettled(inflight), expired])) === "expired")
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
}
|
|
91
|
+
return inflight.size;
|
|
92
|
+
}
|
package/dist/gates/contract.d.ts
CHANGED
|
@@ -3,20 +3,24 @@
|
|
|
3
3
|
* it is not in the published OpenAPI documents.
|
|
4
4
|
*
|
|
5
5
|
* `POST /nylorun/v1/model-calls`
|
|
6
|
-
* - Headers: `Authorization: Bearer <
|
|
7
|
-
* and optionally `Nylorun-Tenant: <tenant id>`, which must then be the
|
|
8
|
-
* Tenant (a
|
|
9
|
-
* - Body: `ModelCallBody`, at most `MAX_MODEL_CALL_BYTES`.
|
|
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.
|
|
10
11
|
* - `200 {outcome}` once the call has finished, whether the outcome is a candidate or a
|
|
11
|
-
* failure; nothing is sent while it runs. `401 gate_unauthorized` for a missing
|
|
12
|
-
* token, `
|
|
13
|
-
*
|
|
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.
|
|
14
17
|
* - With an `Idempotency-Key` the call outlives its client (P1.2): a re-send with the same key
|
|
15
18
|
* and body joins it, or gets its outcome for 30 minutes. Without one, a client that goes
|
|
16
19
|
* away aborts the call.
|
|
17
20
|
*
|
|
18
21
|
* `POST /nylorun/v1/model-calls/{key}/cancel` (same headers): aborts the keyed call; `204`
|
|
19
|
-
* whether or not it exists.
|
|
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.
|
|
20
24
|
*/
|
|
21
25
|
import { z } from "zod";
|
|
22
26
|
import type { ModelGateOutcome } from "./model-gate.js";
|
|
@@ -26,9 +30,6 @@ export declare const TENANT_HEADER = "nylorun-tenant";
|
|
|
26
30
|
/** Largest request body the gate reads: one `ModelCall` with its whole projected history. */
|
|
27
31
|
export declare const MAX_MODEL_CALL_BYTES: number;
|
|
28
32
|
export declare const ModelCallBodySchema: z.ZodObject<{
|
|
29
|
-
sessionId: z.ZodString;
|
|
30
|
-
turnId: z.ZodString;
|
|
31
|
-
agentId: z.ZodString;
|
|
32
33
|
effectId: z.ZodString;
|
|
33
34
|
invocationId: z.ZodString;
|
|
34
35
|
call: z.ZodObject<{
|
|
@@ -37,7 +38,7 @@ export declare const ModelCallBodySchema: z.ZodObject<{
|
|
|
37
38
|
name: z.ZodString;
|
|
38
39
|
}, z.core.$loose>>;
|
|
39
40
|
}, z.core.$loose>;
|
|
40
|
-
}, z.core.$
|
|
41
|
+
}, z.core.$strict>;
|
|
41
42
|
export type ModelCallBody = z.infer<typeof ModelCallBodySchema>;
|
|
42
43
|
export interface ModelCallResponse {
|
|
43
44
|
readonly outcome: ModelGateOutcome;
|
|
@@ -45,7 +46,7 @@ export interface ModelCallResponse {
|
|
|
45
46
|
/** An error answer: `{error: {code, message}}`. */
|
|
46
47
|
export interface GateErrorBody {
|
|
47
48
|
readonly error: {
|
|
48
|
-
readonly code: "gate_unauthorized" | "invalid_request" | "gate_conflict";
|
|
49
|
+
readonly code: "gate_unauthorized" | "gate_forbidden" | "run_stale" | "invalid_request" | "gate_conflict";
|
|
49
50
|
readonly message: string;
|
|
50
51
|
};
|
|
51
52
|
}
|
package/dist/gates/contract.js
CHANGED
|
@@ -3,20 +3,24 @@
|
|
|
3
3
|
* it is not in the published OpenAPI documents.
|
|
4
4
|
*
|
|
5
5
|
* `POST /nylorun/v1/model-calls`
|
|
6
|
-
* - Headers: `Authorization: Bearer <
|
|
7
|
-
* and optionally `Nylorun-Tenant: <tenant id>`, which must then be the
|
|
8
|
-
* Tenant (a
|
|
9
|
-
* - Body: `ModelCallBody`, at most `MAX_MODEL_CALL_BYTES`.
|
|
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.
|
|
10
11
|
* - `200 {outcome}` once the call has finished, whether the outcome is a candidate or a
|
|
11
|
-
* failure; nothing is sent while it runs. `401 gate_unauthorized` for a missing
|
|
12
|
-
* token, `
|
|
13
|
-
*
|
|
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.
|
|
14
17
|
* - With an `Idempotency-Key` the call outlives its client (P1.2): a re-send with the same key
|
|
15
18
|
* and body joins it, or gets its outcome for 30 minutes. Without one, a client that goes
|
|
16
19
|
* away aborts the call.
|
|
17
20
|
*
|
|
18
21
|
* `POST /nylorun/v1/model-calls/{key}/cancel` (same headers): aborts the keyed call; `204`
|
|
19
|
-
* whether or not it exists.
|
|
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.
|
|
20
24
|
*/
|
|
21
25
|
import { z } from "zod";
|
|
22
26
|
export const MODEL_CALLS_PATH = "/nylorun/v1/model-calls";
|
|
@@ -24,10 +28,7 @@ export const MODEL_CALLS_PATH = "/nylorun/v1/model-calls";
|
|
|
24
28
|
export const TENANT_HEADER = "nylorun-tenant";
|
|
25
29
|
/** Largest request body the gate reads: one `ModelCall` with its whole projected history. */
|
|
26
30
|
export const MAX_MODEL_CALL_BYTES = 32 * 1024 * 1024;
|
|
27
|
-
export const ModelCallBodySchema = z.
|
|
28
|
-
sessionId: z.string().min(1),
|
|
29
|
-
turnId: z.string().min(1),
|
|
30
|
-
agentId: z.string().min(1),
|
|
31
|
+
export const ModelCallBodySchema = z.strictObject({
|
|
31
32
|
effectId: z.string().min(1),
|
|
32
33
|
invocationId: z.string().min(1),
|
|
33
34
|
call: z.looseObject({
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type Server } from "node:http";
|
|
2
|
+
import { type Socket } from "node:net";
|
|
3
|
+
import type { EgressTokenVerdict } from "../sandbox/egress-token.js";
|
|
4
|
+
import type { SessionStore } from "../store/types.js";
|
|
5
|
+
import type { Logger } from "../tenant/types.js";
|
|
6
|
+
/**
|
|
7
|
+
* What egress-gate reads of a sandbox: its current host epoch, the pod that joined at it, and
|
|
8
|
+
* its spec's `network.allow`; `undefined` when it is gone, deleted, lost, expired or not a pod.
|
|
9
|
+
*/
|
|
10
|
+
export interface EgressSandboxes {
|
|
11
|
+
live(sandboxId: string): Promise<{
|
|
12
|
+
readonly epoch: number;
|
|
13
|
+
readonly podUid?: string;
|
|
14
|
+
readonly allow: readonly string[];
|
|
15
|
+
} | undefined>;
|
|
16
|
+
}
|
|
17
|
+
/** Ports a tunnel may open. */
|
|
18
|
+
export declare const EGRESS_PORTS: readonly number[];
|
|
19
|
+
/** Tunnels one sandbox may hold open at once. */
|
|
20
|
+
export declare const EGRESS_MAX_PER_SANDBOX = 64;
|
|
21
|
+
/** A tunnel with no traffic either way for this long is closed. */
|
|
22
|
+
export declare const EGRESS_IDLE_MS: number;
|
|
23
|
+
/** How long a sandbox's epoch and allowlist are reused before they are read again. */
|
|
24
|
+
export declare const EGRESS_CACHE_MS = 5000;
|
|
25
|
+
/** How long the gate waits for the upstream connection. */
|
|
26
|
+
export declare const EGRESS_CONNECT_TIMEOUT_MS = 10000;
|
|
27
|
+
export interface StartEgressGateOptions {
|
|
28
|
+
readonly listen: {
|
|
29
|
+
readonly host: string;
|
|
30
|
+
readonly port: number;
|
|
31
|
+
};
|
|
32
|
+
readonly logger: Logger;
|
|
33
|
+
/** Verifies a presented egress token (`verifyEgressToken` over the Tenant's keys). */
|
|
34
|
+
verify(raw: string): Promise<EgressTokenVerdict>;
|
|
35
|
+
readonly sandboxes: EgressSandboxes;
|
|
36
|
+
readonly idleMs?: number;
|
|
37
|
+
readonly maxPerSandbox?: number;
|
|
38
|
+
readonly cacheMs?: number;
|
|
39
|
+
readonly connectTimeoutMs?: number;
|
|
40
|
+
/** Tests replace name resolution. */
|
|
41
|
+
readonly lookup?: (host: string) => Promise<readonly {
|
|
42
|
+
address: string;
|
|
43
|
+
family: number;
|
|
44
|
+
}[]>;
|
|
45
|
+
/** Tests replace which resolved addresses are refused. Default `isEgressBlocked`. */
|
|
46
|
+
readonly blocked?: (address: string) => boolean;
|
|
47
|
+
/** Tests replace how the checked address is dialled. */
|
|
48
|
+
readonly dial?: (address: string, port: number) => Socket;
|
|
49
|
+
}
|
|
50
|
+
export interface EgressGate {
|
|
51
|
+
/** Where the gate listens, e.g. `http://0.0.0.0:4200`. */
|
|
52
|
+
readonly url: string;
|
|
53
|
+
readonly server: Server;
|
|
54
|
+
close(): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* True when a tunnel must not reach `address`: private, loopback, link-local (169.254.169.254, the
|
|
58
|
+
* cloud metadata address, included), unspecified, multicast, reserved, or an IPv6 form that
|
|
59
|
+
* embeds an IPv4 address.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isEgressBlocked(address: string): boolean;
|
|
62
|
+
/** Whether `host` (a lowercase name) matches one of `allow`'s names or `*.suffix` patterns. */
|
|
63
|
+
export declare function egressAllows(host: string, allow: readonly string[]): boolean;
|
|
64
|
+
/** The egress token in a `Proxy-Authorization` header, or `undefined`. */
|
|
65
|
+
export declare function proxyToken(header: string | undefined): string | undefined;
|
|
66
|
+
export declare function startEgressGate(options: StartEgressGateOptions): Promise<EgressGate>;
|
|
67
|
+
/**
|
|
68
|
+
* `EgressSandboxes` over the Tenant's `sandbox_resources` (one indexed read): a pod sandbox's
|
|
69
|
+
* host epoch, the pod that joined at it and its spec's allowlist; nothing for a virtual
|
|
70
|
+
* sandbox, or one deleted, lost or expired, as `staleHost` judges a host token.
|
|
71
|
+
*/
|
|
72
|
+
export declare function storeEgressSandboxes(store: SessionStore): EgressSandboxes;
|