@nylorun/runtime 0.15.1-beta → 0.16.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 +82 -0
- package/README.md +33 -6
- package/dist/adapters/execution/restate.d.ts +9 -1
- package/dist/adapters/execution/restate.js +64 -5
- package/dist/adapters/replication/pgoutput.js +3 -1
- 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 +37 -3
- package/dist/api/a2a/tasks.js +6 -1
- package/dist/api/ag-ui/history.js +6 -1
- package/dist/api/ag-ui/routes.js +5 -1
- package/dist/api/components.d.ts +2673 -655
- package/dist/api/components.js +17 -1
- 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 +4 -0
- package/dist/api/http/define.d.ts +10 -0
- package/dist/api/http/define.js +2 -1
- package/dist/api/http/routes/artifacts.d.ts +11 -0
- package/dist/api/http/routes/artifacts.js +518 -0
- package/dist/api/http/routes/sandboxes.d.ts +9 -0
- package/dist/api/http/routes/sandboxes.js +152 -0
- package/dist/api/http/routes/sessions.js +3 -1
- package/dist/api/http/routes/tenant.js +36 -2
- 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/model-gate.d.ts +6 -2
- 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/app.js +23 -2
- package/dist/host/gates.d.ts +27 -2
- package/dist/host/gates.js +46 -1
- package/dist/host/http.d.ts +4 -1
- package/dist/host/http.js +6 -1
- package/dist/host/main.js +135 -27
- package/dist/host/stack-config.d.ts +101 -1
- package/dist/host/stack-config.js +199 -8
- 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 +15245 -8850
- 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/meta/_journal.json +28 -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 +894 -0
- package/dist/store/postgres/schema.js +130 -2
- package/dist/store/postgres/store.js +289 -4
- package/dist/store/types.d.ts +143 -6
- package/dist/streams/control.d.ts +2 -0
- package/dist/streams/control.js +5 -0
- package/dist/streams/types.d.ts +10 -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/commands.js +30 -5
- package/dist/tenant/context.d.ts +64 -5
- package/dist/tenant/effects.d.ts +19 -13
- package/dist/tenant/effects.js +5 -382
- 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/module.js +1 -0
- package/dist/tenant/paths.d.ts +2 -1
- package/dist/tenant/paths.js +3 -1
- 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 +38 -0
- package/dist/tenant/runtime.js +120 -32
- package/dist/tenant/sandboxes.d.ts +119 -0
- package/dist/tenant/sandboxes.js +551 -0
- package/dist/tenant/session.d.ts +2 -13
- package/dist/tenant/session.js +4 -28
- package/dist/tenant/sessions.d.ts +5 -0
- package/dist/tenant/sessions.js +28 -1
- package/dist/tenant/status.d.ts +8 -3
- package/dist/tenant/status.js +5 -0
- package/dist/tenant/streams.d.ts +7 -0
- package/dist/tenant/streams.js +24 -1
- package/dist/tenant/sweep.js +37 -4
- package/dist/tenant/tokens.js +9 -2
- package/dist/tenant/types.d.ts +18 -1
- package/dist/tenant/worker.d.ts +11 -5
- package/dist/tenant/worker.js +12 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -10
- package/dist/adapters/media.d.ts +0 -24
- package/dist/adapters/media.js +0 -111
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `BlobStore` seam (blueprint D35, F8.1): the only way the Runtime reaches the Object
|
|
3
|
+
* store. Bytes live here; what they mean lives in Postgres, which is the commit point: an
|
|
4
|
+
* object counts only once a committed row references its key, so an orphaned upload is
|
|
5
|
+
* garbage, never data. Keys are chosen by the Runtime (fresh random ids), so the seam needs no
|
|
6
|
+
* conditional writes.
|
|
7
|
+
*
|
|
8
|
+
* Two adapters, held to one conformance suite (`test/blob/conformance.ts`):
|
|
9
|
+
* - `s3` (`./s3.ts`): the plain S3 API, against RustFS in the local stack and S3 or any S3
|
|
10
|
+
* server elsewhere;
|
|
11
|
+
* - `fs` (`./fs.ts`): files under a directory, for embedding and tests.
|
|
12
|
+
*/
|
|
13
|
+
/** A body to store: bytes, a web stream, or any async iterable of chunks (a Node stream). */
|
|
14
|
+
export type BlobBody = Uint8Array | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
|
|
15
|
+
export interface BlobPutOptions {
|
|
16
|
+
/** Stored with the object and returned by `head` and `get`. Default `application/octet-stream`. */
|
|
17
|
+
contentType?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Refuse a body longer than this many bytes with `BlobTooLargeError`; nothing is stored. The
|
|
20
|
+
* body is counted as it streams, so a caller need not know its length up front.
|
|
21
|
+
*/
|
|
22
|
+
maxBytes?: number;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
export interface BlobPutResult {
|
|
26
|
+
key: string;
|
|
27
|
+
/** Bytes stored. */
|
|
28
|
+
size: number;
|
|
29
|
+
/** SHA-256 of the bytes stored, lowercase hex. */
|
|
30
|
+
sha256: string;
|
|
31
|
+
}
|
|
32
|
+
export interface BlobMeta {
|
|
33
|
+
key: string;
|
|
34
|
+
/** The whole object's size in bytes. */
|
|
35
|
+
size: number;
|
|
36
|
+
contentType: string;
|
|
37
|
+
lastModified: Date;
|
|
38
|
+
}
|
|
39
|
+
/** Bytes `start` to `end`, both inclusive, as in an HTTP `Range` header. */
|
|
40
|
+
export interface BlobRange {
|
|
41
|
+
start: number;
|
|
42
|
+
/** Inclusive; past the end of the object means "to the end". Default: to the end. */
|
|
43
|
+
end?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface BlobGetOptions {
|
|
46
|
+
/** Only these bytes. A range that starts at or past the end throws `BlobRangeError`. */
|
|
47
|
+
range?: BlobRange;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
}
|
|
50
|
+
export interface BlobGetResult extends BlobMeta {
|
|
51
|
+
/** The bytes asked for: the whole object, or the range. Read it or cancel it. */
|
|
52
|
+
body: ReadableStream<Uint8Array>;
|
|
53
|
+
/** Present when a range was asked for: the bytes `body` holds, both inclusive. */
|
|
54
|
+
range?: {
|
|
55
|
+
start: number;
|
|
56
|
+
end: number;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** An entry of `list`: the object's key, size and when it was last written. */
|
|
60
|
+
export interface BlobListEntry {
|
|
61
|
+
key: string;
|
|
62
|
+
size: number;
|
|
63
|
+
lastModified: Date;
|
|
64
|
+
}
|
|
65
|
+
export interface BlobStore {
|
|
66
|
+
/** Which adapter this is, for logs and `/ready`. */
|
|
67
|
+
readonly kind: "fs" | "s3";
|
|
68
|
+
/** Store `body` at `key`, replacing what is there. Streams; counts and hashes as it goes. */
|
|
69
|
+
put(key: string, body: BlobBody, options?: BlobPutOptions): Promise<BlobPutResult>;
|
|
70
|
+
/** The object's bytes and metadata, or `undefined` when there is none at `key`. */
|
|
71
|
+
get(key: string, options?: BlobGetOptions): Promise<BlobGetResult | undefined>;
|
|
72
|
+
/** The object's metadata, or `undefined` when there is none at `key`. */
|
|
73
|
+
head(key: string): Promise<BlobMeta | undefined>;
|
|
74
|
+
/** Remove the object; removing one that is not there succeeds. */
|
|
75
|
+
delete(key: string): Promise<void>;
|
|
76
|
+
/** Every object whose key starts with `prefix`, in key order. */
|
|
77
|
+
list(prefix: string): AsyncIterable<BlobListEntry>;
|
|
78
|
+
}
|
|
79
|
+
/** The Object store failed or refused a request; `status` is the HTTP status when it has one. */
|
|
80
|
+
export declare class BlobStoreError extends Error {
|
|
81
|
+
readonly status?: number | undefined;
|
|
82
|
+
readonly code?: string | undefined;
|
|
83
|
+
constructor(message: string, status?: number | undefined, code?: string | undefined);
|
|
84
|
+
}
|
|
85
|
+
/** A `put` body grew past `maxBytes`. Nothing was stored. */
|
|
86
|
+
export declare class BlobTooLargeError extends Error {
|
|
87
|
+
readonly maxBytes: number;
|
|
88
|
+
constructor(maxBytes: number);
|
|
89
|
+
}
|
|
90
|
+
/** A `get` range starts at or past the end of the object (HTTP 416). */
|
|
91
|
+
export declare class BlobRangeError extends Error {
|
|
92
|
+
readonly range: BlobRange;
|
|
93
|
+
readonly size: number;
|
|
94
|
+
constructor(range: BlobRange, size: number);
|
|
95
|
+
}
|
|
96
|
+
export declare const DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
97
|
+
export declare const MAX_KEY_BYTES = 1024;
|
|
98
|
+
/**
|
|
99
|
+
* Keys are `/`-separated segments of `A-Z a-z 0-9 . _ -`, neither `.` nor `..`, at most 1024
|
|
100
|
+
* bytes: safe as S3 keys without encoding surprises and as relative paths for `fs`.
|
|
101
|
+
*/
|
|
102
|
+
export declare function assertBlobKey(key: string): void;
|
|
103
|
+
/** A `list` prefix: empty, a key, or a key followed by `/` (or the start of a segment). */
|
|
104
|
+
export declare function assertBlobPrefix(prefix: string): void;
|
|
105
|
+
export declare function assertRange(range: BlobRange): void;
|
|
106
|
+
export declare function assertMaxBytes(maxBytes: number | undefined): void;
|
|
107
|
+
/** The chunks of a `BlobBody`, as `Uint8Array`s. */
|
|
108
|
+
export declare function chunksOf(body: BlobBody): AsyncGenerator<Uint8Array>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `BlobStore` seam (blueprint D35, F8.1): the only way the Runtime reaches the Object
|
|
3
|
+
* store. Bytes live here; what they mean lives in Postgres, which is the commit point: an
|
|
4
|
+
* object counts only once a committed row references its key, so an orphaned upload is
|
|
5
|
+
* garbage, never data. Keys are chosen by the Runtime (fresh random ids), so the seam needs no
|
|
6
|
+
* conditional writes.
|
|
7
|
+
*
|
|
8
|
+
* Two adapters, held to one conformance suite (`test/blob/conformance.ts`):
|
|
9
|
+
* - `s3` (`./s3.ts`): the plain S3 API, against RustFS in the local stack and S3 or any S3
|
|
10
|
+
* server elsewhere;
|
|
11
|
+
* - `fs` (`./fs.ts`): files under a directory, for embedding and tests.
|
|
12
|
+
*/
|
|
13
|
+
/** The Object store failed or refused a request; `status` is the HTTP status when it has one. */
|
|
14
|
+
export class BlobStoreError extends Error {
|
|
15
|
+
status;
|
|
16
|
+
code;
|
|
17
|
+
constructor(message, status, code) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.name = "BlobStoreError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** A `put` body grew past `maxBytes`. Nothing was stored. */
|
|
25
|
+
export class BlobTooLargeError extends Error {
|
|
26
|
+
maxBytes;
|
|
27
|
+
constructor(maxBytes) {
|
|
28
|
+
super(`The body is larger than ${maxBytes} bytes`);
|
|
29
|
+
this.maxBytes = maxBytes;
|
|
30
|
+
this.name = "BlobTooLargeError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** A `get` range starts at or past the end of the object (HTTP 416). */
|
|
34
|
+
export class BlobRangeError extends Error {
|
|
35
|
+
range;
|
|
36
|
+
size;
|
|
37
|
+
constructor(range, size) {
|
|
38
|
+
super(`Range ${range.start}-${range.end ?? ""} is outside an object of ${size} bytes`);
|
|
39
|
+
this.range = range;
|
|
40
|
+
this.size = size;
|
|
41
|
+
this.name = "BlobRangeError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export const DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
45
|
+
export const MAX_KEY_BYTES = 1024;
|
|
46
|
+
const SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
47
|
+
/**
|
|
48
|
+
* Keys are `/`-separated segments of `A-Z a-z 0-9 . _ -`, neither `.` nor `..`, at most 1024
|
|
49
|
+
* bytes: safe as S3 keys without encoding surprises and as relative paths for `fs`.
|
|
50
|
+
*/
|
|
51
|
+
export function assertBlobKey(key) {
|
|
52
|
+
if (typeof key !== "string" || key === "" || key.length > MAX_KEY_BYTES)
|
|
53
|
+
throw new TypeError(`Blob key must be 1 to ${MAX_KEY_BYTES} characters`);
|
|
54
|
+
for (const segment of key.split("/"))
|
|
55
|
+
if (!SEGMENT.test(segment) || segment === "." || segment === "..")
|
|
56
|
+
throw new TypeError(`Blob key ${JSON.stringify(key)} must be /-separated segments of A-Z a-z 0-9 . _ -, none . or ..`);
|
|
57
|
+
}
|
|
58
|
+
/** A `list` prefix: empty, a key, or a key followed by `/` (or the start of a segment). */
|
|
59
|
+
export function assertBlobPrefix(prefix) {
|
|
60
|
+
if (prefix === "")
|
|
61
|
+
return;
|
|
62
|
+
assertBlobKey(prefix.endsWith("/") ? prefix.slice(0, -1) : prefix);
|
|
63
|
+
}
|
|
64
|
+
export function assertRange(range) {
|
|
65
|
+
if (!Number.isSafeInteger(range.start) || range.start < 0)
|
|
66
|
+
throw new TypeError("Range start must be a non-negative integer");
|
|
67
|
+
if (range.end !== undefined && (!Number.isSafeInteger(range.end) || range.end < range.start))
|
|
68
|
+
throw new TypeError("Range end must be an integer no smaller than start");
|
|
69
|
+
}
|
|
70
|
+
export function assertMaxBytes(maxBytes) {
|
|
71
|
+
if (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes < 0))
|
|
72
|
+
throw new TypeError("maxBytes must be a non-negative integer");
|
|
73
|
+
}
|
|
74
|
+
/** The chunks of a `BlobBody`, as `Uint8Array`s. */
|
|
75
|
+
export async function* chunksOf(body) {
|
|
76
|
+
if (body instanceof Uint8Array) {
|
|
77
|
+
yield body;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
for await (const chunk of body) {
|
|
81
|
+
if (!(chunk instanceof Uint8Array))
|
|
82
|
+
throw new TypeError("A blob body must yield Uint8Array chunks");
|
|
83
|
+
yield chunk;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Action } from "@nylorun/core/contracts";
|
|
2
|
-
import type {
|
|
2
|
+
import type { WorkspacePort } from "../harness-api/workspace.js";
|
|
3
3
|
import type { SandboxToolOutcome } from "../sandbox/tools.js";
|
|
4
4
|
import { type SessionSandboxRef } from "../sandbox/share.js";
|
|
5
5
|
export declare class SandboxRouteError extends Error {
|
|
@@ -16,7 +16,7 @@ export type SandboxRouteRead = {
|
|
|
16
16
|
readonly lookup: (id: string) => SessionSandboxRef | undefined;
|
|
17
17
|
};
|
|
18
18
|
export type SandboxRouteDeps = {
|
|
19
|
-
readonly sandbox:
|
|
19
|
+
readonly sandbox: Pick<WorkspacePort, "run">;
|
|
20
20
|
/** The session (rejects when missing) and its owner chain, read in one transaction. */
|
|
21
21
|
readonly session: (id: string) => Promise<SandboxRouteRead>;
|
|
22
22
|
readonly getAction: (id: string) => Promise<Action | undefined>;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Wired from TenantRuntime; keeps route bodies out of the hot runtime file.
|
|
4
4
|
*/
|
|
5
5
|
import { isSandboxToolName, } from "@nylorun/core/define";
|
|
6
|
-
import { capabilityForSandbox, owningSandboxSessionId, sandboxSpecOf, sandboxSpecsEqual, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
6
|
+
import { capabilityForSandbox, owningSandboxSessionId, sandboxSpecOf, sandboxWorkspaceOf, sandboxSpecsEqual, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
7
7
|
export class SandboxRouteError extends Error {
|
|
8
8
|
status;
|
|
9
9
|
constructor(status, message) {
|
|
@@ -36,11 +36,12 @@ function toolInput(body) {
|
|
|
36
36
|
}
|
|
37
37
|
async function runTool(deps, { session, lookup }, tool, input, signal) {
|
|
38
38
|
const spec = resolveSpec(session, lookup);
|
|
39
|
-
const
|
|
39
|
+
const workspace = sandboxWorkspaceOf(session, lookup);
|
|
40
40
|
const ref = {
|
|
41
|
-
id: ownerId,
|
|
41
|
+
id: workspace.ownerId,
|
|
42
42
|
activeTurnId: session.activeTurnId,
|
|
43
43
|
manifest: session.manifest,
|
|
44
|
+
...(workspace.sandboxId === undefined ? {} : { sandboxId: workspace.sandboxId }),
|
|
44
45
|
};
|
|
45
46
|
return deps.sandbox.run(ref, capabilityForSandbox(spec), tool, input, signal);
|
|
46
47
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DurableExecution, type Wake, type WorkerHandlers } from "./types.js";
|
|
1
|
+
import { type DurableExecution, type SandboxSignal, type Wake, type WorkerHandlers } from "./types.js";
|
|
2
2
|
export interface MemoryExecutionOptions {
|
|
3
3
|
/** Delay between sweep passes of one Tenant. Default 5000. */
|
|
4
4
|
sweepIntervalMs?: number;
|
|
@@ -8,6 +8,11 @@ export interface MemoryExecutionOptions {
|
|
|
8
8
|
maxAttempts?: number;
|
|
9
9
|
/** How long a dedupe key merges repeated wakes. Default 24 hours. */
|
|
10
10
|
dedupeRetentionMs?: number;
|
|
11
|
+
/**
|
|
12
|
+
* How long `stop` waits for running handlers after aborting them; the ones still running
|
|
13
|
+
* then are abandoned. Default `DEFAULT_STOP_GRACE_MS`.
|
|
14
|
+
*/
|
|
15
|
+
stopGraceMs?: number;
|
|
11
16
|
/** Receives handler failures that are not retried. */
|
|
12
17
|
onError?: (error: unknown) => void;
|
|
13
18
|
}
|
|
@@ -23,16 +28,21 @@ export declare class MemoryExecution implements DurableExecution {
|
|
|
23
28
|
private readonly seen;
|
|
24
29
|
private readonly timers;
|
|
25
30
|
private readonly sweeps;
|
|
31
|
+
private readonly sandboxes;
|
|
26
32
|
private readonly delayed;
|
|
27
33
|
private readonly inflight;
|
|
28
34
|
private readonly sweepIntervalMs;
|
|
29
35
|
private readonly retryDelayMs;
|
|
30
36
|
private readonly maxAttempts;
|
|
31
37
|
private readonly dedupeRetentionMs;
|
|
38
|
+
private readonly stopGraceMs;
|
|
32
39
|
private readonly onError;
|
|
33
40
|
constructor(options?: MemoryExecutionOptions);
|
|
34
41
|
wake(tenantId: string, sessionId: string, wake: Wake): Promise<void>;
|
|
35
42
|
deliver(tenantId: string, actionId: string): Promise<void>;
|
|
43
|
+
sandbox(tenantId: string, sandboxId: string, signal: SandboxSignal): Promise<void>;
|
|
44
|
+
private armSandbox;
|
|
45
|
+
private runSandbox;
|
|
36
46
|
timer(tenantId: string, key: string, at: Date): Promise<void>;
|
|
37
47
|
armSweep(tenantId: string): Promise<void>;
|
|
38
48
|
disarmSweep(tenantId: string): Promise<void>;
|
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({
|