@nylorun/runtime 0.29.0-beta → 0.31.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 +36 -0
- package/dist/api/components.d.ts +240 -2
- package/dist/api/components.js +3 -1
- package/dist/api/http/routes/agent-manifests.d.ts +2 -1
- package/dist/api/http/routes/agent-manifests.js +62 -10
- package/dist/api/openapi.js +2 -0
- package/dist/gates/egress-credentials.js +2 -13
- package/dist/gates/handler.js +2 -1
- package/dist/gates/in-process.d.ts +9 -0
- package/dist/gates/in-process.js +21 -0
- package/dist/host/http.js +3 -1
- package/dist/host/main.js +2 -0
- package/dist/host/stack-config.d.ts +10 -0
- package/dist/host/stack-config.js +36 -0
- package/dist/management-openapi.json +361 -5
- package/dist/mcp/connect.d.ts +34 -5
- package/dist/mcp/connect.js +75 -14
- package/dist/mcp/gateway-outcomes.d.ts +14 -0
- package/dist/mcp/gateway-outcomes.js +46 -0
- package/dist/mcp/pool.js +5 -0
- package/dist/model/call-headers.d.ts +15 -0
- package/dist/model/call-headers.js +9 -0
- package/dist/model/models.d.ts +7 -0
- package/dist/model/models.js +3 -0
- package/dist/model/pi-model.d.ts +6 -0
- package/dist/model/pi-model.js +29 -3
- package/dist/openapi.json +654 -30
- package/dist/store/postgres/drizzle/0038_t6_agent_manifest_source.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +7 -0
- package/dist/store/postgres/schema.d.ts +21 -2
- package/dist/store/postgres/schema.js +7 -1
- package/dist/store/postgres/store.js +1 -0
- package/dist/store/types.d.ts +14 -0
- package/dist/tenant/agent-manifests.d.ts +18 -25
- package/dist/tenant/agent-manifests.js +136 -4
- package/dist/tenant/agents.js +7 -3
- package/dist/tenant/transcript.d.ts +8 -0
- package/dist/tenant/transcript.js +2 -0
- package/dist/vault/headers.d.ts +8 -0
- package/dist/vault/headers.js +25 -0
- package/dist/vault/host-model.js +8 -5
- package/dist/vault/service.d.ts +12 -5
- package/dist/vault/service.js +43 -6
- package/dist/vault/sources.js +22 -1
- package/dist/vault/workload-token.d.ts +16 -0
- package/dist/vault/workload-token.js +58 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -19,26 +19,15 @@ import { createServer } from "node:http";
|
|
|
19
19
|
import { Agent, request } from "node:https";
|
|
20
20
|
import { createSecureContext, TLSSocket } from "node:tls";
|
|
21
21
|
import { hostTuning } from "../tuning.js";
|
|
22
|
+
import { HOP_BY_HOP_HEADERS } from "../vault/headers.js";
|
|
22
23
|
/** The answer to a CONNECT the gate serves; a tunnel's byte counts leave it out. */
|
|
23
24
|
export const CONNECT_ESTABLISHED = "HTTP/1.1 200 Connection Established\r\n\r\n";
|
|
24
|
-
/** Headers that describe one hop and are never forwarded. */
|
|
25
|
-
const HOP_BY_HOP = new Set([
|
|
26
|
-
"connection",
|
|
27
|
-
"keep-alive",
|
|
28
|
-
"proxy-authenticate",
|
|
29
|
-
"proxy-authorization",
|
|
30
|
-
"proxy-connection",
|
|
31
|
-
"te",
|
|
32
|
-
"trailer",
|
|
33
|
-
"transfer-encoding",
|
|
34
|
-
"upgrade",
|
|
35
|
-
]);
|
|
36
25
|
/** `rawHeaders` without hop-by-hop headers and `drop`, as lowercase names to values. */
|
|
37
26
|
function forwardable(raw, drop) {
|
|
38
27
|
const headers = {};
|
|
39
28
|
for (let i = 0; i + 1 < raw.length; i += 2) {
|
|
40
29
|
const name = raw[i].toLowerCase();
|
|
41
|
-
if (
|
|
30
|
+
if (HOP_BY_HOP_HEADERS.has(name) || name === drop)
|
|
42
31
|
continue;
|
|
43
32
|
(headers[name] ??= []).push(raw[i + 1]);
|
|
44
33
|
}
|
package/dist/gates/handler.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the Tenant's ledger. Logs one line per call, never the prompt, the output or a credential.
|
|
7
7
|
*/
|
|
8
8
|
import { classifyThrown } from "../model/classify.js";
|
|
9
|
-
import { callFiles, inProcessModelGate, modelCallSettings, serve } from "./in-process.js";
|
|
9
|
+
import { callCaller, callFiles, inProcessModelGate, modelCallSettings, serve } from "./in-process.js";
|
|
10
10
|
import { createMeter } from "./meter.js";
|
|
11
11
|
import { GateRefusal } from "./tenant-vaults.js";
|
|
12
12
|
export function createModelCallHandler(options) {
|
|
@@ -28,6 +28,7 @@ export function createModelCallHandler(options) {
|
|
|
28
28
|
// The Tenant's Runtime limits `model*`, read for this call.
|
|
29
29
|
settings: await modelCallSettings(vault.store),
|
|
30
30
|
...(blobs ? { files: callFiles(vault.store, blobs) } : {}),
|
|
31
|
+
caller: callCaller(vault.store),
|
|
31
32
|
});
|
|
32
33
|
outcome = await meter.call(vault.store, call, () => serve(vault.store, gate, call, signal));
|
|
33
34
|
}
|
|
@@ -11,6 +11,7 @@ import type { HostModelSecret } from "../vault/service.js";
|
|
|
11
11
|
import type { ModelGate, ModelGateRequest } from "./model-gate.js";
|
|
12
12
|
import { type FileResolver } from "../artifacts/files.js";
|
|
13
13
|
import type { BlobStore } from "../blob/index.js";
|
|
14
|
+
import type { ModelCaller } from "../model/call-headers.js";
|
|
14
15
|
export interface InProcessModelGateOptions {
|
|
15
16
|
/** The Tenant's home: `piModel` redacts secrets it finds there from failure messages. */
|
|
16
17
|
readonly root?: string;
|
|
@@ -20,6 +21,8 @@ export interface InProcessModelGateOptions {
|
|
|
20
21
|
readonly settings?: ModelCallSettings;
|
|
21
22
|
/** Reads the files a call's prompt names (protocol 6): artifacts, for model-gate. */
|
|
22
23
|
readonly files?: (request: ModelGateRequest) => FileResolver;
|
|
24
|
+
/** Reads who makes a call, for a custom endpoint's per-call headers (#493). */
|
|
25
|
+
readonly caller?: (request: ModelGateRequest) => Promise<ModelCaller | undefined>;
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
25
28
|
* The files a call names, from the Tenant's artifacts, limited to the call's session: in
|
|
@@ -27,6 +30,12 @@ export interface InProcessModelGateOptions {
|
|
|
27
30
|
* gate's route puts on the request (F5). A request without a session reads no file at all.
|
|
28
31
|
*/
|
|
29
32
|
export declare function callFiles(store: SessionStore, blobs: BlobStore): (request: Pick<ModelGateRequest, "sessionId">) => FileResolver;
|
|
33
|
+
/**
|
|
34
|
+
* Who makes a call, from the Tenant's store: the Agent its session runs on, with its labels, and
|
|
35
|
+
* the session's owner when it is a person. The session is the loop's own in process, and the run
|
|
36
|
+
* token's over HTTP; the request's `agentId` names the manifest's agent, not the Agent.
|
|
37
|
+
*/
|
|
38
|
+
export declare function callCaller(store: SessionStore): (request: Pick<ModelGateRequest, "sessionId">) => Promise<ModelCaller | undefined>;
|
|
30
39
|
/**
|
|
31
40
|
* The in-process gate of an open Tenant: reads its host model from its own vault and records
|
|
32
41
|
* each call in its usage ledger. Only a Runtime without the gates service (embedding, the
|
package/dist/gates/in-process.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createMeter } from "./meter.js";
|
|
|
5
5
|
import { artifactFiles } from "../artifacts/files.js";
|
|
6
6
|
import { usesFixtureModel } from "../tenant/model-setting.js";
|
|
7
7
|
import { fixtureAnswer } from "./fixture-model.js";
|
|
8
|
+
import { isSubject } from "@nylorun/core/contracts";
|
|
8
9
|
/**
|
|
9
10
|
* The files a call names, from the Tenant's artifacts, limited to the call's session: in
|
|
10
11
|
* process, the loop's own request; over HTTP, the session of the run token's claims, which the
|
|
@@ -13,6 +14,24 @@ import { fixtureAnswer } from "./fixture-model.js";
|
|
|
13
14
|
export function callFiles(store, blobs) {
|
|
14
15
|
return (request) => artifactFiles({ store, blobs, sessionId: request.sessionId });
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Who makes a call, from the Tenant's store: the Agent its session runs on, with its labels, and
|
|
19
|
+
* the session's owner when it is a person. The session is the loop's own in process, and the run
|
|
20
|
+
* token's over HTTP; the request's `agentId` names the manifest's agent, not the Agent.
|
|
21
|
+
*/
|
|
22
|
+
export function callCaller(store) {
|
|
23
|
+
return (request) => store.tx(async (t) => {
|
|
24
|
+
const session = await t.get("sessions", request.sessionId);
|
|
25
|
+
const agent = session && (await t.environment(session.agentId));
|
|
26
|
+
if (!session || !agent)
|
|
27
|
+
return undefined;
|
|
28
|
+
return {
|
|
29
|
+
agentId: agent.id,
|
|
30
|
+
labels: agent.labels,
|
|
31
|
+
...(isSubject(session.ownerUserId) ? { owner: session.ownerUserId } : {}),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}
|
|
16
35
|
/**
|
|
17
36
|
* The in-process gate of an open Tenant: reads its host model from its own vault and records
|
|
18
37
|
* each call in its usage ledger. Only a Runtime without the gates service (embedding, the
|
|
@@ -30,6 +49,7 @@ export function tenantModelGate(options) {
|
|
|
30
49
|
writeHostCredential: (credential) => vault.updateHostCredential(credential),
|
|
31
50
|
settings: await modelCallSettings(options.store),
|
|
32
51
|
...(options.blobs ? { files: callFiles(options.store, options.blobs) } : {}),
|
|
52
|
+
caller: callCaller(options.store),
|
|
33
53
|
});
|
|
34
54
|
return serve(options.store, gate, request, signal);
|
|
35
55
|
}),
|
|
@@ -58,6 +78,7 @@ export function inProcessModelGate(options) {
|
|
|
58
78
|
writeHostCredential: options.writeHostCredential,
|
|
59
79
|
...(options.settings ? { settings: options.settings } : {}),
|
|
60
80
|
...(options.files ? { files: options.files(request) } : {}),
|
|
81
|
+
...(options.caller ? { caller: () => options.caller(request) } : {}),
|
|
61
82
|
});
|
|
62
83
|
return adapter(request.call, {
|
|
63
84
|
// The Runtime never journals the provider request (harness `durable.ts`), and
|
package/dist/host/http.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { jsonResponse } from "../api/http/respond.js";
|
|
3
3
|
import { HOST_PROTOCOL, } from "@nylorun/core/compatibility";
|
|
4
|
+
import { AGENT_FOLDER_MEDIA_TYPE } from "@nylorun/core/contracts";
|
|
4
5
|
export const OPAQUE_NOT_FOUND = {
|
|
5
6
|
status: "rejected",
|
|
6
7
|
code: "not_found",
|
|
@@ -113,7 +114,8 @@ export function isJsonContentType(value) {
|
|
|
113
114
|
if (value === undefined)
|
|
114
115
|
return false;
|
|
115
116
|
const media = value.split(";")[0].trim().toLowerCase();
|
|
116
|
-
|
|
117
|
+
// An agent folder by hash (track T6) is JSON too.
|
|
118
|
+
return media === "application/json" || media === AGENT_FOLDER_MEDIA_TYPE;
|
|
117
119
|
}
|
|
118
120
|
/** True when the request carries a body (Content-Length > 0 or chunked). */
|
|
119
121
|
export function requestHasBody(request) {
|
package/dist/host/main.js
CHANGED
|
@@ -65,6 +65,7 @@ import { startHarnessListener } from "../harness-api/ws-server.js";
|
|
|
65
65
|
import { httpSandboxesClient } from "../sandbox/pods/client.js";
|
|
66
66
|
import { retry } from "../retry.js";
|
|
67
67
|
import { hostTuning, useHostTuning } from "../tuning.js";
|
|
68
|
+
import { useWorkloadToken } from "../vault/workload-token.js";
|
|
68
69
|
const nodeRequire = createRequire(import.meta.url);
|
|
69
70
|
function coreVersion() {
|
|
70
71
|
try {
|
|
@@ -218,6 +219,7 @@ export async function main() {
|
|
|
218
219
|
// Read once, before any service starts: each reads the parts it uses where it uses them.
|
|
219
220
|
if (stack.tuning)
|
|
220
221
|
useHostTuning(stack.tuning.values, stack.tuning.overrides);
|
|
222
|
+
useWorkloadToken(stack.workloadToken ?? {});
|
|
221
223
|
if (stack.services.has("gates") || stack.services.has("keys") || stack.services.has("egress"))
|
|
222
224
|
return runGates(stack);
|
|
223
225
|
if (stack.services.has("harness"))
|
|
@@ -178,6 +178,16 @@ export interface HarnessServiceConfig {
|
|
|
178
178
|
export interface StackConfig {
|
|
179
179
|
/** The Runtime services this process runs. */
|
|
180
180
|
services: RuntimeServices;
|
|
181
|
+
/**
|
|
182
|
+
* `NYLORUN_WORKLOAD_TOKEN_FILE`, the file of the Runtime's workload token, which a platform
|
|
183
|
+
* rotates and the Runtime reads at each use, and `NYLORUN_WORKLOAD_TOKEN_ORIGINS`, the
|
|
184
|
+
* comma-separated origins a `workload` credential's `via` may send it to (#496,
|
|
185
|
+
* `vault/workload-token.ts`). Absent: no workload token.
|
|
186
|
+
*/
|
|
187
|
+
workloadToken?: {
|
|
188
|
+
file?: string;
|
|
189
|
+
origins?: string[];
|
|
190
|
+
};
|
|
181
191
|
/** Present when the process runs gates. */
|
|
182
192
|
gates?: GatesConfig;
|
|
183
193
|
/** Present when the process runs egress. */
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
import { DEFAULT_SERVICE_NAME } from "../traces/otlp.js";
|
|
48
48
|
import { TRACE_CONTENT_KINDS } from "../traces/map.js";
|
|
49
49
|
import { isTenantId } from "@nylorun/core/compatibility";
|
|
50
|
+
import { LOOPBACK_HOSTS } from "@nylorun/core/contracts";
|
|
50
51
|
import { blockedAddresses } from "../sandbox/pods/network-gate.js";
|
|
51
52
|
import { HostTuningError, parseHostTuning } from "../tuning.js";
|
|
52
53
|
export const RUNTIME_SERVICES = ["core", "loop", "gates", "keys", "egress", "harness"];
|
|
@@ -325,6 +326,7 @@ export function parseStackConfig(env, argv) {
|
|
|
325
326
|
throw new StackConfigError("NYLORUN_HARNESS_LISTEN_PORT must differ from NYLORUN_LISTEN_PORT");
|
|
326
327
|
const delivery = parseDelivery(env);
|
|
327
328
|
const tenant = servesApi ? parseTenant(env) : undefined;
|
|
329
|
+
const workloadToken = parseWorkloadToken(env);
|
|
328
330
|
const objectStore = parseObjectStore(env);
|
|
329
331
|
const traces = services.has("core") ? parseTraces(env) : undefined;
|
|
330
332
|
const removedResolverVariables = Object.keys(env)
|
|
@@ -347,6 +349,7 @@ export function parseStackConfig(env, argv) {
|
|
|
347
349
|
...(sandboxes ? { sandboxes } : {}),
|
|
348
350
|
...(delivery ? { delivery } : {}),
|
|
349
351
|
...(tenant ? { tenant } : {}),
|
|
352
|
+
...(workloadToken ? { workloadToken } : {}),
|
|
350
353
|
...(objectStore ? { objectStore } : {}),
|
|
351
354
|
...(traces ? { traces } : {}),
|
|
352
355
|
...(tuning ? { tuning } : {}),
|
|
@@ -454,6 +457,39 @@ function parseTenant(env) {
|
|
|
454
457
|
...(managementKeyFile ? { managementKeyFile } : {}),
|
|
455
458
|
};
|
|
456
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* `StackConfig.workloadToken` (#496): the Runtime's workload token, which the gates read for a
|
|
462
|
+
* custom model and for `workload` credentials, and the in-process Tenant does without them.
|
|
463
|
+
*/
|
|
464
|
+
function parseWorkloadToken(env) {
|
|
465
|
+
const file = read(env, "NYLORUN_WORKLOAD_TOKEN_FILE");
|
|
466
|
+
const origins = parseOrigins(read(env, "NYLORUN_WORKLOAD_TOKEN_ORIGINS"));
|
|
467
|
+
if (file === undefined && origins === undefined)
|
|
468
|
+
return undefined;
|
|
469
|
+
return { ...(file ? { file } : {}), ...(origins ? { origins } : {}) };
|
|
470
|
+
}
|
|
471
|
+
/** `NYLORUN_WORKLOAD_TOKEN_ORIGINS`: each an `https` origin (`http` only to a loopback host). */
|
|
472
|
+
function parseOrigins(raw) {
|
|
473
|
+
if (raw === undefined)
|
|
474
|
+
return undefined;
|
|
475
|
+
return raw
|
|
476
|
+
.split(",")
|
|
477
|
+
.map((item) => item.trim())
|
|
478
|
+
.filter((item) => item !== "")
|
|
479
|
+
.map((item) => {
|
|
480
|
+
let url;
|
|
481
|
+
try {
|
|
482
|
+
url = new URL(item);
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
throw new StackConfigError(`NYLORUN_WORKLOAD_TOKEN_ORIGINS: ${item} is not a URL`);
|
|
486
|
+
}
|
|
487
|
+
const loopback = url.protocol === "http:" && LOOPBACK_HOSTS.test(url.hostname);
|
|
488
|
+
if ((url.protocol !== "https:" && !loopback) || url.origin !== item.replace(/\/$/, ""))
|
|
489
|
+
throw new StackConfigError(`NYLORUN_WORKLOAD_TOKEN_ORIGINS: ${item} must be an https origin, such as https://gateway.example.com`);
|
|
490
|
+
return url.origin;
|
|
491
|
+
});
|
|
492
|
+
}
|
|
457
493
|
const GATES_TOKEN = /^[0-9a-f]{64,}$/i;
|
|
458
494
|
/** `NYLORUN_HARNESS`: `in-process` (the default when unset) or `remote`. */
|
|
459
495
|
function parseHarnessMode(env) {
|