@nylorun/runtime 0.13.0-beta → 0.15.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 +434 -0
- package/README.md +137 -41
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +40 -10
- package/dist/adapters/replication/pgoutput.d.ts +21 -0
- package/dist/adapters/replication/pgoutput.js +170 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +570 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +8065 -0
- package/dist/api/components.js +82 -0
- package/dist/api/gate/keys.d.ts +14 -0
- package/dist/api/gate/keys.js +59 -0
- package/dist/api/gate/routes.d.ts +34 -0
- package/dist/api/gate/routes.js +235 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +122 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +266 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +11 -0
- package/dist/api/http/routes/tenant.js +244 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +137 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +16 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/gates/contract.d.ts +51 -0
- package/dist/gates/contract.js +37 -0
- package/dist/gates/handler.d.ts +21 -0
- package/dist/gates/handler.js +55 -0
- package/dist/gates/http-client.d.ts +12 -0
- package/dist/gates/http-client.js +160 -0
- package/dist/gates/in-process.d.ts +32 -0
- package/dist/gates/in-process.js +42 -0
- package/dist/gates/inflight.d.ts +39 -0
- package/dist/gates/inflight.js +70 -0
- package/dist/gates/mcp-handler.d.ts +35 -0
- package/dist/gates/mcp-handler.js +142 -0
- package/dist/gates/meter.d.ts +24 -0
- package/dist/gates/meter.js +165 -0
- package/dist/gates/model-gate.d.ts +45 -0
- package/dist/gates/model-gate.js +1 -0
- package/dist/gates/tenant-vaults.d.ts +64 -0
- package/dist/gates/tenant-vaults.js +94 -0
- package/dist/gates/tool-calls.d.ts +51 -0
- package/dist/gates/tool-calls.js +64 -0
- package/dist/gates/tool-client.d.ts +12 -0
- package/dist/gates/tool-client.js +201 -0
- package/dist/gates/tool-contract.d.ts +116 -0
- package/dist/gates/tool-contract.js +64 -0
- package/dist/gates/tool-gate.d.ts +45 -0
- package/dist/gates/tool-gate.js +10 -0
- package/dist/host/admin-api.d.ts +23 -0
- package/dist/host/admin-api.js +74 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +181 -0
- package/dist/host/config-for.d.ts +3 -1
- package/dist/host/config-for.js +3 -2
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +36 -8
- package/dist/host/create-host.js +161 -330
- package/dist/host/execution.d.ts +20 -18
- package/dist/host/execution.js +2 -2
- package/dist/host/gates.d.ts +58 -0
- package/dist/host/gates.js +127 -0
- package/dist/host/http.d.ts +20 -4
- package/dist/host/http.js +81 -23
- package/dist/host/index.d.ts +1 -1
- package/dist/host/main.js +189 -58
- package/dist/host/stack-config.d.ts +91 -19
- package/dist/host/stack-config.js +246 -17
- package/dist/infra/database.d.ts +2 -2
- package/dist/infra/execution.d.ts +5 -5
- package/dist/infra/execution.js +8 -6
- package/dist/infra/index.d.ts +1 -1
- package/dist/infra/workers.d.ts +17 -17
- package/dist/infra/workers.js +9 -9
- package/dist/keys/client.d.ts +9 -0
- package/dist/keys/client.js +70 -0
- package/dist/keys/contract.d.ts +24 -0
- package/dist/keys/contract.js +12 -0
- package/dist/keys/keys.d.ts +41 -0
- package/dist/keys/keys.js +50 -0
- package/dist/mcp/connect.d.ts +43 -3
- package/dist/mcp/connect.js +12 -5
- package/dist/mcp/pool.d.ts +39 -3
- package/dist/mcp/pool.js +74 -11
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25889 -0
- package/dist/record/append.d.ts +51 -0
- package/dist/record/append.js +20 -0
- package/dist/record/envelope.d.ts +19 -0
- package/dist/record/envelope.js +49 -0
- package/dist/record/index.d.ts +11 -0
- package/dist/record/index.js +11 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/postgres/connect.d.ts +25 -9
- package/dist/store/postgres/connect.js +27 -10
- package/dist/store/postgres/db.d.ts +16 -0
- package/dist/store/postgres/db.js +28 -0
- package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
- package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
- package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
- package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
- package/dist/store/postgres/migrate.d.ts +68 -0
- package/dist/store/postgres/migrate.js +183 -0
- package/dist/store/postgres/record-writer.d.ts +4 -0
- package/dist/store/postgres/record-writer.js +34 -0
- package/dist/store/postgres/record.d.ts +5 -0
- package/dist/store/postgres/record.js +60 -0
- package/dist/store/postgres/schema.d.ts +3096 -0
- package/dist/store/postgres/schema.js +363 -0
- package/dist/store/postgres/store.d.ts +2 -4
- package/dist/store/postgres/store.js +590 -434
- package/dist/store/postgres/tenant.d.ts +56 -0
- package/dist/store/postgres/tenant.js +154 -0
- package/dist/store/types.d.ts +183 -132
- package/dist/store/types.js +0 -1
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +33 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/types.d.ts +78 -0
- package/dist/streams/relay/types.js +8 -0
- package/dist/streams/types.d.ts +71 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.d.ts +7 -2
- package/dist/tenant/advance.js +125 -14
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/cause.d.ts +25 -0
- package/dist/tenant/cause.js +45 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +111 -115
- package/dist/tenant/context.d.ts +101 -21
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +104 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +481 -0
- package/dist/tenant/effects.d.ts +20 -1
- package/dist/tenant/effects.js +159 -42
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +22 -7
- package/dist/tenant/ephemeral.js +83 -125
- package/dist/tenant/history.d.ts +64 -0
- package/dist/tenant/history.js +120 -0
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/model-setting.d.ts +2 -2
- package/dist/tenant/module.d.ts +17 -28
- package/dist/tenant/module.js +133 -279
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/paths.d.ts +11 -16
- package/dist/tenant/paths.js +24 -68
- package/dist/tenant/pool.d.ts +1 -3
- package/dist/tenant/pool.js +1 -19
- package/dist/tenant/principals.d.ts +23 -11
- package/dist/tenant/principals.js +61 -49
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +28 -20
- package/dist/tenant/runtime.js +95 -64
- package/dist/tenant/scheduler.d.ts +10 -3
- package/dist/tenant/scheduler.js +9 -5
- package/dist/tenant/session-streams.d.ts +127 -0
- package/dist/tenant/session-streams.js +404 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +3 -3
- package/dist/tenant/status.js +20 -13
- package/dist/tenant/store-pg.d.ts +9 -6
- package/dist/tenant/store-pg.js +60 -125
- package/dist/tenant/streams.d.ts +62 -85
- package/dist/tenant/streams.js +143 -226
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +180 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +69 -98
- package/dist/tenant/types.js +4 -15
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +13 -7
- package/dist/vault/host-model.d.ts +31 -0
- package/dist/vault/host-model.js +57 -0
- package/dist/vault/service.d.ts +44 -17
- package/dist/vault/service.js +126 -121
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +25 -9
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/store/memory.d.ts +0 -57
- package/dist/store/memory.js +0 -645
- package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
- package/dist/store/postgres/migrations/001_initial.js +0 -196
- package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
- package/dist/store/postgres/migrations/002_session_owner.js +0 -13
- package/dist/store/postgres/migrations/index.d.ts +0 -49
- package/dist/store/postgres/migrations/index.js +0 -76
- package/dist/store/postgres/names.d.ts +0 -11
- package/dist/store/postgres/names.js +0 -42
- package/dist/store/postgres/tenants.d.ts +0 -90
- package/dist/store/postgres/tenants.js +0 -214
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/envelope.d.ts +0 -7
- package/dist/tenant/envelope.js +0 -22
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/quarantine-error.d.ts +0 -6
- package/dist/tenant/quarantine-error.js +0 -9
- package/dist/tenant/quarantine.d.ts +0 -27
- package/dist/tenant/quarantine.js +0 -72
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
- package/dist/tenant/store-memory.d.ts +0 -16
- package/dist/tenant/store-memory.js +0 -111
- /package/dist/{store → record}/cursor.d.ts +0 -0
- /package/dist/{store → record}/cursor.js +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gates service's process (blueprint §15, §19): one listener serving the Model Gate's and
|
|
3
|
+
* the Tool Gate's routes (`api/gate/routes.ts`), behind the same `Host` check as the Runtime's
|
|
4
|
+
* listeners.
|
|
5
|
+
* It opens no Tenant runtime, no Restate endpoint and no stream relay, and runs no migration;
|
|
6
|
+
* it needs only the Postgres pool and the Host's tenant directory (read-only).
|
|
7
|
+
*
|
|
8
|
+
* Nothing is written while a model call runs, so the server's request timeout sits above the
|
|
9
|
+
* gate's longest call. Closing stops accepting calls, lets running ones finish for up to
|
|
10
|
+
* `drainMs`, then aborts them (their providers' requests with them).
|
|
11
|
+
*/
|
|
12
|
+
import { type Server } from "node:http";
|
|
13
|
+
import { type InflightCallsOptions } from "../gates/inflight.js";
|
|
14
|
+
import type { openMcpServer } from "../mcp/connect.js";
|
|
15
|
+
import type { OutboundPolicy } from "../tenant/outbound.js";
|
|
16
|
+
import type { ModelCallSettings } from "../gates/model-gate.js";
|
|
17
|
+
import { type TenantVaults } from "../gates/tenant-vaults.js";
|
|
18
|
+
import type { PostgresClient } from "../store/postgres/connect.js";
|
|
19
|
+
import type { Logger } from "../tenant/types.js";
|
|
20
|
+
import type { GatesConfig } from "./stack-config.js";
|
|
21
|
+
/** Above the gate's 600 s provider request timeout and the loop's 630 s client timeout. */
|
|
22
|
+
export declare const GATES_REQUEST_TIMEOUT_MS = 660000;
|
|
23
|
+
export interface StartGatesOptions {
|
|
24
|
+
readonly gates: GatesConfig;
|
|
25
|
+
readonly logger: Logger;
|
|
26
|
+
/** The Host root, for the Tenant's vault key. Required unless `vaults` is given. */
|
|
27
|
+
readonly hostRoot?: string;
|
|
28
|
+
/** The pool for the Tenant's vault and readiness. Required unless `vaults` is given. */
|
|
29
|
+
readonly database?: PostgresClient;
|
|
30
|
+
/** Replaces the Postgres-backed Tenant vault (tests). */
|
|
31
|
+
readonly vaults?: TenantVaults;
|
|
32
|
+
/** Retries and timeouts of model calls (tests). */
|
|
33
|
+
readonly settings?: ModelCallSettings;
|
|
34
|
+
/** Largest model call body (tests). */
|
|
35
|
+
readonly maxBodyBytes?: number;
|
|
36
|
+
/** How long `close` lets running calls finish. Default 10 s. */
|
|
37
|
+
readonly drainMs?: number;
|
|
38
|
+
/** How long keyed outcomes are kept, and how many (tests). */
|
|
39
|
+
readonly inflight?: InflightCallsOptions;
|
|
40
|
+
/** How the gate may call Action endpoints (`NYLORUN_ENDPOINT_*`). Default: no limits. */
|
|
41
|
+
readonly delivery?: OutboundPolicy;
|
|
42
|
+
/** How long an MCP connection may sit unused. Default `GATE_MCP_IDLE_MS`. */
|
|
43
|
+
readonly mcpIdleMs?: number;
|
|
44
|
+
/** Tests replace how a remote MCP server is opened. */
|
|
45
|
+
readonly openMcp?: typeof openMcpServer;
|
|
46
|
+
/**
|
|
47
|
+
* Serve the keys service too (`--service gates,keys`, F4.2): vault writes and token signing
|
|
48
|
+
* with the Tenant's vault key. The gateway is then not ready until the key file is there.
|
|
49
|
+
*/
|
|
50
|
+
readonly keys?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface GatesServer {
|
|
53
|
+
/** Where the gate listens, e.g. `http://0.0.0.0:4100`. */
|
|
54
|
+
readonly url: string;
|
|
55
|
+
readonly server: Server;
|
|
56
|
+
close(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
export declare function startGates(options: StartGatesOptions): Promise<GatesServer>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gates service's process (blueprint §15, §19): one listener serving the Model Gate's and
|
|
3
|
+
* the Tool Gate's routes (`api/gate/routes.ts`), behind the same `Host` check as the Runtime's
|
|
4
|
+
* listeners.
|
|
5
|
+
* It opens no Tenant runtime, no Restate endpoint and no stream relay, and runs no migration;
|
|
6
|
+
* it needs only the Postgres pool and the Host's tenant directory (read-only).
|
|
7
|
+
*
|
|
8
|
+
* Nothing is written while a model call runs, so the server's request timeout sits above the
|
|
9
|
+
* gate's longest call. Closing stops accepting calls, lets running ones finish for up to
|
|
10
|
+
* `drainMs`, then aborts them (their providers' requests with them).
|
|
11
|
+
*/
|
|
12
|
+
import { createServer } from "node:http";
|
|
13
|
+
import { getRequestListener } from "@hono/node-server";
|
|
14
|
+
import { createGatesApp } from "../api/gate/routes.js";
|
|
15
|
+
import { createModelCallHandler } from "../gates/handler.js";
|
|
16
|
+
import { createInflightCalls } from "../gates/inflight.js";
|
|
17
|
+
import { createMcpHandler, GATE_MCP_IDLE_MS } from "../gates/mcp-handler.js";
|
|
18
|
+
import { createToolCalls } from "../gates/tool-calls.js";
|
|
19
|
+
import { existsSync } from "node:fs";
|
|
20
|
+
import { tenantPaths } from "../tenant/paths.js";
|
|
21
|
+
import { createTenantVaults } from "../gates/tenant-vaults.js";
|
|
22
|
+
import { probeDatabase } from "../infra/database.js";
|
|
23
|
+
import { bindListener, headerValue, isAllowedRequestHost, sendRejected } from "./http.js";
|
|
24
|
+
/** Above the gate's 600 s provider request timeout and the loop's 630 s client timeout. */
|
|
25
|
+
export const GATES_REQUEST_TIMEOUT_MS = 660_000;
|
|
26
|
+
export async function startGates(options) {
|
|
27
|
+
const { gates, logger, database } = options;
|
|
28
|
+
let vaults = options.vaults;
|
|
29
|
+
if (!vaults) {
|
|
30
|
+
if (!database || options.hostRoot === undefined)
|
|
31
|
+
throw new Error("startGates needs the Postgres pool and the Host root");
|
|
32
|
+
vaults = createTenantVaults({ sql: database, hostRoot: options.hostRoot });
|
|
33
|
+
}
|
|
34
|
+
const inflight = createInflightCalls(options.inflight);
|
|
35
|
+
const mcpIdleMs = options.mcpIdleMs ?? GATE_MCP_IDLE_MS;
|
|
36
|
+
const mcp = createMcpHandler({
|
|
37
|
+
vaults,
|
|
38
|
+
logger,
|
|
39
|
+
idleMs: mcpIdleMs,
|
|
40
|
+
...(options.openMcp ? { open: options.openMcp } : {}),
|
|
41
|
+
});
|
|
42
|
+
const toolCalls = createToolCalls({
|
|
43
|
+
vaults,
|
|
44
|
+
mcp,
|
|
45
|
+
logger,
|
|
46
|
+
...(options.inflight ? { inflight: options.inflight } : {}),
|
|
47
|
+
});
|
|
48
|
+
const sweep = setInterval(() => void mcp.sweep(), Math.max(1_000, Math.min(mcpIdleMs, 60_000)));
|
|
49
|
+
sweep.unref();
|
|
50
|
+
const prune = setInterval(() => void toolCalls.prune(), 60 * 60_000);
|
|
51
|
+
prune.unref();
|
|
52
|
+
const app = createGatesApp({
|
|
53
|
+
token: gates.token,
|
|
54
|
+
inflight,
|
|
55
|
+
modelGate: createModelCallHandler({
|
|
56
|
+
vaults,
|
|
57
|
+
logger,
|
|
58
|
+
...(options.settings ? { settings: options.settings } : {}),
|
|
59
|
+
}),
|
|
60
|
+
ready: async () => {
|
|
61
|
+
// The keys service needs the vault key, which `nylorun start` writes; it never creates one.
|
|
62
|
+
if (options.keys && options.hostRoot !== undefined && !existsSync(tenantPaths(options.hostRoot).kek))
|
|
63
|
+
return false;
|
|
64
|
+
if (!database)
|
|
65
|
+
return true;
|
|
66
|
+
try {
|
|
67
|
+
await probeDatabase(database, AbortSignal.timeout(2000));
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
...(options.maxBodyBytes !== undefined ? { maxBodyBytes: options.maxBodyBytes } : {}),
|
|
75
|
+
mcp,
|
|
76
|
+
toolCalls,
|
|
77
|
+
delivery: options.delivery ?? {},
|
|
78
|
+
logger,
|
|
79
|
+
...(options.keys ? { keys: async () => (await vaults.open()).keys() } : {}),
|
|
80
|
+
});
|
|
81
|
+
const listener = getRequestListener(app.fetch);
|
|
82
|
+
const inFlight = new Set();
|
|
83
|
+
// The configured Host values, plus the loopback forms of the port actually bound (probes
|
|
84
|
+
// from inside the container; port 0 in tests).
|
|
85
|
+
let allowedHosts = gates.listen.allowedHosts;
|
|
86
|
+
const server = createServer((req, res) => {
|
|
87
|
+
if (!isAllowedRequestHost(headerValue(req, "host"), {
|
|
88
|
+
port: gates.listen.port,
|
|
89
|
+
host: gates.listen.host,
|
|
90
|
+
allowedHosts,
|
|
91
|
+
}))
|
|
92
|
+
return sendRejected(res, 421, "host_rejected", "Host header is not an allowed address of the gateway");
|
|
93
|
+
inFlight.add(res);
|
|
94
|
+
res.once("close", () => inFlight.delete(res));
|
|
95
|
+
void listener(req, res);
|
|
96
|
+
});
|
|
97
|
+
server.requestTimeout = GATES_REQUEST_TIMEOUT_MS;
|
|
98
|
+
await bindListener(server, gates.listen.port, gates.listen.host, (error) => logger.error("listener_error", { error: error.message }));
|
|
99
|
+
const port = server.address().port;
|
|
100
|
+
allowedHosts = [
|
|
101
|
+
...new Set([...allowedHosts, `localhost:${port}`, `127.0.0.1:${port}`, `[::1]:${port}`]),
|
|
102
|
+
];
|
|
103
|
+
const url = `http://${gates.listen.host.includes(":") ? `[${gates.listen.host}]` : gates.listen.host}:${port}`;
|
|
104
|
+
let closing;
|
|
105
|
+
return {
|
|
106
|
+
url,
|
|
107
|
+
server,
|
|
108
|
+
close() {
|
|
109
|
+
closing ??= (async () => {
|
|
110
|
+
const closed = new Promise((resolve) => server.close(() => resolve()));
|
|
111
|
+
server.closeIdleConnections();
|
|
112
|
+
const deadline = Date.now() + (options.drainMs ?? 10_000);
|
|
113
|
+
while (inFlight.size > 0 && Date.now() < deadline)
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
115
|
+
// Closing a running call's connection aborts an unkeyed one; keyed calls stop here.
|
|
116
|
+
server.closeAllConnections();
|
|
117
|
+
inflight.close();
|
|
118
|
+
toolCalls.close();
|
|
119
|
+
clearInterval(sweep);
|
|
120
|
+
clearInterval(prune);
|
|
121
|
+
await mcp.closeAll();
|
|
122
|
+
await closed;
|
|
123
|
+
})();
|
|
124
|
+
return closing;
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
package/dist/host/http.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
1
|
+
import type { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
2
|
+
import { jsonResponse } from "../api/http/respond.js";
|
|
2
3
|
import { type ErrorCode, type ProtocolRange } from "@nylorun/core/compatibility";
|
|
3
4
|
export declare const OPAQUE_NOT_FOUND: {
|
|
4
5
|
status: "rejected";
|
|
@@ -12,10 +13,18 @@ export declare class HostListenError extends Error {
|
|
|
12
13
|
readonly cause?: unknown;
|
|
13
14
|
constructor(message: string, exitCode: number, cause?: unknown);
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Binds `listening` on `host:port`. A port in use rejects with `HostListenError` (exit 98);
|
|
18
|
+
* once bound, a listener error goes to `onError` rather than being lost.
|
|
19
|
+
*/
|
|
20
|
+
export declare function bindListener(listening: Server, port: number, host: string, onError: (error: Error) => void): Promise<void>;
|
|
15
21
|
export declare function sendJson(response: ServerResponse, status: number, body: unknown): void;
|
|
22
|
+
/** `sendJson` as a Response, byte for byte (with any extra headers): the Host's answers. */
|
|
23
|
+
export { jsonResponse };
|
|
16
24
|
export declare function sendRejected(response: ServerResponse, status: number, code: ErrorCode, message: string, details?: unknown): void;
|
|
17
|
-
export declare function
|
|
18
|
-
export declare function
|
|
25
|
+
export declare function rejectedResponse(status: number, code: ErrorCode, message: string, details?: unknown): Response;
|
|
26
|
+
export declare function opaqueNotFoundResponse(): Response;
|
|
27
|
+
export declare function protocolRejectedResponse(protocol?: ProtocolRange): Response;
|
|
19
28
|
/**
|
|
20
29
|
* D§11 Host allowlist: loopback forms with the listening port, plus the
|
|
21
30
|
* configured host when `allowNonLoopback` is set.
|
|
@@ -32,8 +41,15 @@ export declare function isAllowedRequestHost(hostHeader: string | undefined, opt
|
|
|
32
41
|
export declare function isJsonContentType(value: string | undefined): boolean;
|
|
33
42
|
/** True when the request carries a body (Content-Length > 0 or chunked). */
|
|
34
43
|
export declare function requestHasBody(request: IncomingMessage): boolean;
|
|
44
|
+
/** `/health` and `/ready` are polled; the request log leaves them out. */
|
|
45
|
+
export declare function pathnameIsLogged(rawUrl: string | undefined): boolean;
|
|
35
46
|
/** Redact path parameters after `/v1/sessions/` for Host logs. */
|
|
36
47
|
export declare function redactRoutePath(pathname: string): string;
|
|
48
|
+
/** Constant-time comparison of SHA-256 digests of the presented and stored admin keys. */
|
|
49
|
+
export declare function adminKeyMatches(presented: string | undefined, adminKey: string): boolean;
|
|
50
|
+
/** A request header's first value. */
|
|
51
|
+
export declare function headerValue(incoming: IncomingMessage, name: string): string | undefined;
|
|
37
52
|
export declare function readBearer(authorization: string | undefined): string | undefined;
|
|
38
|
-
|
|
53
|
+
/** A JSON request body, capped at `limit` bytes (413); `undefined` when there is none. */
|
|
54
|
+
export declare function readJsonBody(request: Request, limit?: number): Promise<unknown>;
|
|
39
55
|
export declare function isLoopbackHost(host: string): boolean;
|
package/dist/host/http.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { jsonResponse } from "../api/http/respond.js";
|
|
1
3
|
import { HOST_PROTOCOL, } from "@nylorun/core/compatibility";
|
|
2
4
|
export const OPAQUE_NOT_FOUND = {
|
|
3
5
|
status: "rejected",
|
|
@@ -16,26 +18,45 @@ export class HostListenError extends Error {
|
|
|
16
18
|
this.name = "HostListenError";
|
|
17
19
|
}
|
|
18
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Binds `listening` on `host:port`. A port in use rejects with `HostListenError` (exit 98);
|
|
23
|
+
* once bound, a listener error goes to `onError` rather than being lost.
|
|
24
|
+
*/
|
|
25
|
+
export function bindListener(listening, port, host, onError) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const failed = (error) => reject(error.code === "EADDRINUSE"
|
|
28
|
+
? new HostListenError(`Port ${port} on ${host} is already in use`, EXIT_PORT_IN_USE, error)
|
|
29
|
+
: error);
|
|
30
|
+
listening.once("error", failed);
|
|
31
|
+
listening.listen(port, host, () => {
|
|
32
|
+
listening.off("error", failed);
|
|
33
|
+
listening.on("error", onError);
|
|
34
|
+
resolve();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
19
38
|
export function sendJson(response, status, body) {
|
|
20
39
|
const payload = JSON.stringify(body);
|
|
21
40
|
// No Access-Control-* headers (D§11).
|
|
22
|
-
response.writeHead(status,
|
|
23
|
-
"content-type": "application/json",
|
|
24
|
-
"content-length": Buffer.byteLength(payload),
|
|
25
|
-
});
|
|
41
|
+
response.writeHead(status, jsonHeaders(payload));
|
|
26
42
|
response.end(payload);
|
|
27
43
|
}
|
|
28
|
-
|
|
44
|
+
/** `sendJson` as a Response, byte for byte (with any extra headers): the Host's answers. */
|
|
45
|
+
export { jsonResponse };
|
|
46
|
+
function jsonHeaders(payload) {
|
|
47
|
+
return {
|
|
48
|
+
"content-type": "application/json",
|
|
49
|
+
"content-length": String(Buffer.byteLength(payload)),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function rejection(code, message, details) {
|
|
29
53
|
const body = { status: "rejected", code, message };
|
|
30
54
|
if (details !== undefined)
|
|
31
55
|
body.details = details;
|
|
32
|
-
|
|
56
|
+
return body;
|
|
33
57
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
export function sendProtocolRejected(response, protocol = HOST_PROTOCOL) {
|
|
38
|
-
sendJson(response, 426, {
|
|
58
|
+
function protocolRejection(protocol) {
|
|
59
|
+
return {
|
|
39
60
|
status: "rejected",
|
|
40
61
|
code: "protocol_unsupported",
|
|
41
62
|
protocol: {
|
|
@@ -43,7 +64,19 @@ export function sendProtocolRejected(response, protocol = HOST_PROTOCOL) {
|
|
|
43
64
|
max: protocol.max,
|
|
44
65
|
features: [...protocol.features],
|
|
45
66
|
},
|
|
46
|
-
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function sendRejected(response, status, code, message, details) {
|
|
70
|
+
sendJson(response, status, rejection(code, message, details));
|
|
71
|
+
}
|
|
72
|
+
export function rejectedResponse(status, code, message, details) {
|
|
73
|
+
return jsonResponse(status, rejection(code, message, details));
|
|
74
|
+
}
|
|
75
|
+
export function opaqueNotFoundResponse() {
|
|
76
|
+
return jsonResponse(404, OPAQUE_NOT_FOUND);
|
|
77
|
+
}
|
|
78
|
+
export function protocolRejectedResponse(protocol = HOST_PROTOCOL) {
|
|
79
|
+
return jsonResponse(426, protocolRejection(protocol));
|
|
47
80
|
}
|
|
48
81
|
/**
|
|
49
82
|
* D§11 Host allowlist: loopback forms with the listening port, plus the
|
|
@@ -95,6 +128,13 @@ export function requestHasBody(request) {
|
|
|
95
128
|
const n = Number(raw);
|
|
96
129
|
return Number.isFinite(n) && n > 0;
|
|
97
130
|
}
|
|
131
|
+
/** `/health` and `/ready` are polled; the request log leaves them out. */
|
|
132
|
+
export function pathnameIsLogged(rawUrl) {
|
|
133
|
+
if (!rawUrl)
|
|
134
|
+
return true;
|
|
135
|
+
const pathname = new URL(rawUrl, "http://runtime.local").pathname;
|
|
136
|
+
return pathname !== "/health" && pathname !== "/ready";
|
|
137
|
+
}
|
|
98
138
|
/** Redact path parameters after `/v1/sessions/` for Host logs. */
|
|
99
139
|
export function redactRoutePath(pathname) {
|
|
100
140
|
const parts = pathname.split("/");
|
|
@@ -104,34 +144,52 @@ export function redactRoutePath(pathname) {
|
|
|
104
144
|
}
|
|
105
145
|
return pathname;
|
|
106
146
|
}
|
|
147
|
+
/** Constant-time comparison of SHA-256 digests of the presented and stored admin keys. */
|
|
148
|
+
export function adminKeyMatches(presented, adminKey) {
|
|
149
|
+
if (presented === undefined)
|
|
150
|
+
return false;
|
|
151
|
+
const a = createHash("sha256").update(presented, "utf8").digest();
|
|
152
|
+
const b = createHash("sha256").update(adminKey, "utf8").digest();
|
|
153
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
154
|
+
}
|
|
155
|
+
/** A request header's first value. */
|
|
156
|
+
export function headerValue(incoming, name) {
|
|
157
|
+
const raw = incoming.headers[name.toLowerCase()];
|
|
158
|
+
if (Array.isArray(raw))
|
|
159
|
+
return raw[0];
|
|
160
|
+
return raw;
|
|
161
|
+
}
|
|
107
162
|
export function readBearer(authorization) {
|
|
108
163
|
if (!authorization)
|
|
109
164
|
return undefined;
|
|
110
165
|
const match = /^Bearer\s+(\S+)/i.exec(authorization.trim());
|
|
111
166
|
return match?.[1];
|
|
112
167
|
}
|
|
168
|
+
/** A JSON request body, capped at `limit` bytes (413); `undefined` when there is none. */
|
|
113
169
|
export async function readJsonBody(request, limit = 1024 * 1024) {
|
|
170
|
+
if (request.body === null)
|
|
171
|
+
return undefined;
|
|
114
172
|
const chunks = [];
|
|
115
173
|
let size = 0;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
174
|
+
const reader = request.body.getReader();
|
|
175
|
+
for (;;) {
|
|
176
|
+
const { done, value } = await reader.read();
|
|
177
|
+
if (done)
|
|
178
|
+
break;
|
|
179
|
+
size += value.byteLength;
|
|
119
180
|
if (size > limit) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
throw error;
|
|
181
|
+
await reader.cancel();
|
|
182
|
+
throw Object.assign(new Error("Request too large"), { status: 413 });
|
|
123
183
|
}
|
|
124
|
-
chunks.push(
|
|
184
|
+
chunks.push(value);
|
|
125
185
|
}
|
|
126
|
-
if (
|
|
186
|
+
if (size === 0)
|
|
127
187
|
return undefined;
|
|
128
188
|
try {
|
|
129
189
|
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
130
190
|
}
|
|
131
191
|
catch {
|
|
132
|
-
|
|
133
|
-
error.status = 400;
|
|
134
|
-
throw error;
|
|
192
|
+
throw Object.assign(new Error("Invalid JSON"), { status: 400 });
|
|
135
193
|
}
|
|
136
194
|
}
|
|
137
195
|
export function isLoopbackHost(host) {
|
package/dist/host/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { createHost, adminKeyMatches, type CreateHostOptions, type HostServer }
|
|
|
4
4
|
export { createHostLogger } from "./logger.js";
|
|
5
5
|
export { configForFactory } from "./config-for.js";
|
|
6
6
|
export { createHostExecution, type CreateHostExecutionOptions, type HostExecution, } from "./execution.js";
|
|
7
|
-
export { parseStackConfig, StackConfigError, type ContainerListen, type RuntimeRole, type StackConfig, type StackEndpoints, } from "./stack-config.js";
|
|
7
|
+
export { parseStackConfig, StackConfigError, type ContainerListen, type RuntimeRole, type RuntimeService, type RuntimeServices, type StackConfig, type StackEndpoints, } from "./stack-config.js";
|
|
8
8
|
export { EXIT_PORT_IN_USE, EXIT_NON_LOOPBACK, HostListenError, OPAQUE_NOT_FOUND, } from "./http.js";
|