@nylorun/runtime 0.25.0-beta → 0.27.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 +169 -0
- package/README.md +26 -16
- package/dist/adapters/execution/restate.d.ts +15 -18
- package/dist/adapters/execution/restate.js +31 -27
- package/dist/adapters/replication/pgoutput.d.ts +1 -1
- package/dist/adapters/replication/pgoutput.js +3 -2
- package/dist/adapters/sandbox/local.js +8 -5
- package/dist/adapters/sandbox/virtual.js +2 -2
- package/dist/adapters/streams/s2.d.ts +1 -1
- package/dist/adapters/streams/s2.js +18 -14
- package/dist/api/a2a/card.js +1 -1
- package/dist/api/a2a/endpoint.js +3 -4
- package/dist/api/a2a/protocol.js +4 -0
- package/dist/api/a2a/routes.d.ts +2 -6
- package/dist/api/a2a/routes.js +12 -18
- package/dist/api/ag-ui/endpoint.js +2 -3
- package/dist/api/ag-ui/routes.js +4 -4
- package/dist/api/components.d.ts +326 -45
- package/dist/api/components.js +5 -1
- package/dist/api/gate/keys.js +7 -4
- package/dist/api/gate/routes.d.ts +6 -2
- package/dist/api/gate/routes.js +15 -11
- package/dist/api/http/app.d.ts +2 -2
- package/dist/api/http/app.js +2 -2
- package/dist/api/http/body.d.ts +15 -3
- package/dist/api/http/body.js +15 -7
- package/dist/api/http/define.d.ts +1 -1
- package/dist/api/http/respond.js +7 -3
- package/dist/api/http/routes/access.js +5 -4
- package/dist/api/http/routes/artifacts.js +19 -7
- package/dist/api/http/routes/coverage.js +1 -1
- package/dist/api/http/routes/environment-manifests.js +1 -1
- package/dist/api/http/routes/environments.js +6 -6
- package/dist/api/http/routes/files.d.ts +2 -1
- package/dist/api/http/routes/files.js +31 -5
- package/dist/api/http/routes/mcp.js +1 -1
- package/dist/api/http/routes/reads.d.ts +35 -0
- package/dist/api/http/routes/reads.js +50 -22
- package/dist/api/http/routes/sessions.js +12 -11
- package/dist/api/http/routes/tenant.d.ts +2 -2
- package/dist/api/http/routes/tenant.js +69 -16
- package/dist/api/inbound-files.d.ts +3 -9
- package/dist/api/inbound-files.js +18 -24
- package/dist/api/openapi.js +6 -2
- package/dist/artifacts/disk.d.ts +0 -4
- package/dist/artifacts/disk.js +77 -73
- package/dist/artifacts/files.d.ts +13 -4
- package/dist/artifacts/files.js +27 -9
- package/dist/artifacts/folder-upload.d.ts +75 -0
- package/dist/artifacts/folder-upload.js +263 -0
- package/dist/artifacts/folders.d.ts +14 -8
- package/dist/artifacts/folders.js +19 -11
- package/dist/artifacts/links.js +7 -6
- package/dist/artifacts/parts.d.ts +9 -14
- package/dist/artifacts/parts.js +11 -9
- package/dist/artifacts/pipe.d.ts +15 -0
- package/dist/artifacts/pipe.js +71 -0
- package/dist/artifacts/quota.d.ts +36 -0
- package/dist/artifacts/quota.js +44 -0
- package/dist/artifacts/service.d.ts +25 -1
- package/dist/artifacts/service.js +70 -25
- package/dist/blob/fs.js +2 -1
- package/dist/blob/s3.d.ts +6 -2
- package/dist/blob/s3.js +9 -7
- package/dist/blob/types.d.ts +1 -1
- package/dist/blob/types.js +1 -1
- package/dist/core/limits.d.ts +11 -35
- package/dist/core/limits.js +5 -73
- package/dist/core/sandbox-routes.js +0 -1
- package/dist/core/turn-manifest.d.ts +1 -2
- package/dist/core/turn-manifest.js +4 -28
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +8 -6
- package/dist/execution/types.d.ts +1 -6
- package/dist/execution/types.js +0 -5
- package/dist/gates/contract.d.ts +3 -2
- package/dist/gates/contract.js +1 -1
- package/dist/gates/egress-credentials.js +4 -3
- package/dist/gates/egress.d.ts +9 -9
- package/dist/gates/egress.js +19 -19
- package/dist/gates/fixture-model.js +13 -2
- package/dist/gates/handler.d.ts +1 -3
- package/dist/gates/handler.js +4 -3
- package/dist/gates/http-client.d.ts +4 -3
- package/dist/gates/http-client.js +5 -7
- package/dist/gates/http-tool.d.ts +24 -4
- package/dist/gates/http-tool.js +53 -17
- package/dist/gates/in-process.d.ts +2 -1
- package/dist/gates/in-process.js +17 -9
- package/dist/gates/inflight.d.ts +2 -2
- package/dist/gates/inflight.js +3 -2
- package/dist/gates/journal.js +5 -7
- package/dist/gates/mcp-handler.d.ts +4 -3
- package/dist/gates/mcp-handler.js +15 -5
- package/dist/gates/model-gate.d.ts +11 -2
- package/dist/gates/policy.d.ts +28 -5
- package/dist/gates/policy.js +60 -17
- package/dist/gates/provider-gate.d.ts +22 -0
- package/dist/gates/provider-gate.js +30 -0
- package/dist/gates/tool-calls.d.ts +1 -3
- package/dist/gates/tool-calls.js +2 -3
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +5 -6
- package/dist/gates/tool-contract.d.ts +8 -5
- package/dist/gates/tool-contract.js +1 -1
- package/dist/gates/tool-gate.d.ts +18 -7
- package/dist/gates/tool-gate.js +49 -7
- package/dist/harness/calls.d.ts +27 -25
- package/dist/harness/calls.js +40 -46
- package/dist/harness/executors.d.ts +10 -3
- package/dist/harness/executors.js +53 -23
- package/dist/harness/main.js +2 -2
- package/dist/harness/pod.d.ts +0 -2
- package/dist/harness/pod.js +7 -6
- package/dist/harness/run-tokens.d.ts +4 -1
- package/dist/harness/run-tokens.js +6 -2
- package/dist/harness/service.d.ts +17 -14
- package/dist/harness/service.js +21 -35
- package/dist/harness/setup.d.ts +0 -2
- package/dist/harness/setup.js +4 -9
- package/dist/harness/ws-client.d.ts +2 -2
- package/dist/harness/ws-client.js +7 -5
- package/dist/harness/ws-port.d.ts +3 -4
- package/dist/harness/ws-port.js +3 -4
- package/dist/harness-api/artifacts.d.ts +20 -0
- package/dist/harness-api/artifacts.js +478 -0
- package/dist/harness-api/in-process.d.ts +2 -2
- package/dist/harness-api/in-process.js +18 -16
- package/dist/harness-api/record.d.ts +10 -9
- package/dist/harness-api/record.js +102 -157
- package/dist/harness-api/renew.d.ts +1 -1
- package/dist/harness-api/renew.js +2 -2
- package/dist/harness-api/server.d.ts +1 -16
- package/dist/harness-api/server.js +82 -73
- package/dist/harness-api/start.d.ts +20 -2
- package/dist/harness-api/start.js +57 -18
- package/dist/harness-api/tool-journal.d.ts +15 -10
- package/dist/harness-api/tool-journal.js +26 -24
- package/dist/harness-api/workspace.d.ts +0 -4
- package/dist/harness-api/workspace.js +2 -45
- package/dist/harness-api/ws-server.js +10 -10
- package/dist/host/create-host.js +2 -3
- package/dist/host/execution.js +2 -3
- package/dist/host/gates.d.ts +2 -9
- package/dist/host/gates.js +15 -12
- package/dist/host/http.d.ts +0 -2
- package/dist/host/http.js +0 -27
- package/dist/host/main.js +10 -3
- package/dist/host/operate.js +12 -3
- package/dist/host/stack-config.d.ts +11 -0
- package/dist/host/stack-config.js +21 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/index.js +2 -1
- package/dist/infra/readiness.d.ts +0 -2
- package/dist/infra/readiness.js +4 -3
- package/dist/keys/client.d.ts +1 -0
- package/dist/keys/client.js +2 -3
- package/dist/keys/contract.d.ts +1 -3
- package/dist/keys/contract.js +1 -3
- package/dist/keys/egress-ca.d.ts +0 -2
- package/dist/keys/egress-ca.js +2 -3
- package/dist/keys/keys.js +6 -1
- package/dist/keys/x509.d.ts +8 -4
- package/dist/keys/x509.js +14 -5
- package/dist/management-openapi.json +1961 -994
- package/dist/mcp/connect.d.ts +35 -26
- package/dist/mcp/connect.js +112 -141
- package/dist/mcp/pool.d.ts +27 -22
- package/dist/mcp/pool.js +34 -48
- package/dist/mcp/preview.d.ts +13 -6
- package/dist/mcp/preview.js +37 -20
- package/dist/mcp/snapshot.d.ts +35 -86
- package/dist/mcp/snapshot.js +93 -135
- package/dist/model/auth-store.js +5 -3
- package/dist/model/capabilities.d.ts +1 -1
- package/dist/model/catalog.d.ts +9 -36
- package/dist/model/catalog.js +6 -33
- package/dist/model/descriptor.d.ts +51 -0
- package/dist/model/descriptor.js +83 -0
- package/dist/model/models.d.ts +0 -3
- package/dist/model/models.js +3 -5
- package/dist/model/native-blocks.js +1 -1
- package/dist/model/pi-model.d.ts +16 -5
- package/dist/model/pi-model.js +86 -105
- package/dist/openapi.json +866 -1218
- package/dist/reads/cursor.js +3 -1
- package/dist/record/append.d.ts +34 -2
- package/dist/record/append.js +50 -1
- package/dist/record/environment.d.ts +2 -0
- package/dist/record/environment.js +2 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/redact.d.ts +1 -1
- package/dist/redact.js +3 -2
- package/dist/sandbox/egress-token.d.ts +3 -3
- package/dist/sandbox/egress-token.js +3 -3
- package/dist/sandbox/join.js +3 -2
- package/dist/sandbox/manager.d.ts +41 -41
- package/dist/sandbox/manager.js +112 -44
- package/dist/sandbox/placement.d.ts +1 -1
- package/dist/sandbox/placement.js +5 -8
- package/dist/sandbox/pods/client.d.ts +1 -10
- package/dist/sandbox/pods/client.js +8 -4
- package/dist/sandbox/pods/lifecycle.d.ts +19 -11
- package/dist/sandbox/pods/lifecycle.js +45 -23
- package/dist/sandbox/pods/network-gate.d.ts +4 -5
- package/dist/sandbox/pods/network-gate.js +7 -7
- package/dist/sandbox/pods/reconcile.js +4 -1
- package/dist/sandbox/pods/spec.js +8 -5
- package/dist/sandbox/policy.d.ts +7 -1
- package/dist/sandbox/policy.js +10 -3
- package/dist/sandbox/resolve.d.ts +8 -3
- package/dist/sandbox/resolve.js +12 -2
- package/dist/sandbox/setup.d.ts +3 -1
- package/dist/sandbox/setup.js +34 -11
- package/dist/sandbox/share.d.ts +5 -14
- package/dist/sandbox/share.js +5 -34
- package/dist/sandbox/skills.d.ts +7 -13
- package/dist/sandbox/skills.js +16 -31
- package/dist/sandbox/tenant-config.d.ts +2 -2
- package/dist/sandbox/tenant-config.js +4 -3
- package/dist/sandbox/tools.d.ts +7 -1
- package/dist/sandbox/tools.js +26 -26
- package/dist/sandbox/types.d.ts +10 -3
- package/dist/sandbox/types.js +9 -0
- package/dist/store/postgres/connect.d.ts +6 -5
- package/dist/store/postgres/connect.js +14 -11
- package/dist/store/postgres/control.js +13 -13
- package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
- package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
- package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
- package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/migrate.js +1 -1
- package/dist/store/postgres/reads.js +10 -2
- package/dist/store/postgres/schema.d.ts +19 -0
- package/dist/store/postgres/schema.js +7 -0
- package/dist/store/postgres/store.js +55 -26
- package/dist/store/types.d.ts +17 -7
- package/dist/streams/relay/core.d.ts +3 -3
- package/dist/streams/relay/core.js +9 -10
- package/dist/tenant/advance.js +21 -31
- package/dist/tenant/commands.js +9 -16
- package/dist/tenant/context.d.ts +14 -17
- package/dist/tenant/definition-files.d.ts +25 -4
- package/dist/tenant/definition-files.js +85 -47
- package/dist/tenant/effects.js +9 -36
- package/dist/tenant/environment-manifests.js +9 -1
- package/dist/tenant/environments.d.ts +0 -2
- package/dist/tenant/environments.js +54 -30
- package/dist/tenant/ephemeral.d.ts +1 -1
- package/dist/tenant/ephemeral.js +2 -1
- package/dist/tenant/history.d.ts +1 -1
- package/dist/tenant/history.js +1 -1
- package/dist/tenant/host-token.d.ts +3 -3
- package/dist/tenant/host-token.js +12 -11
- package/dist/tenant/http.d.ts +5 -8
- package/dist/tenant/http.js +9 -0
- package/dist/tenant/identity-file.d.ts +24 -1
- package/dist/tenant/identity-file.js +35 -7
- package/dist/tenant/issuers.d.ts +5 -5
- package/dist/tenant/issuers.js +31 -23
- package/dist/tenant/jwt.d.ts +8 -2
- package/dist/tenant/jwt.js +10 -2
- package/dist/tenant/limits.d.ts +90 -0
- package/dist/tenant/limits.js +175 -0
- package/dist/tenant/module.d.ts +4 -1
- package/dist/tenant/module.js +9 -8
- package/dist/tenant/outbound.d.ts +17 -19
- package/dist/tenant/outbound.js +18 -21
- package/dist/tenant/run-grants.js +3 -2
- package/dist/tenant/run-token.d.ts +0 -4
- package/dist/tenant/run-token.js +6 -11
- package/dist/tenant/runtime-limits.d.ts +24 -0
- package/dist/tenant/runtime-limits.js +27 -0
- package/dist/tenant/runtime.js +30 -43
- package/dist/tenant/session-streams.js +7 -6
- package/dist/tenant/session.d.ts +1 -1
- package/dist/tenant/session.js +1 -1
- package/dist/tenant/sessions.js +32 -29
- package/dist/tenant/signing-keys.d.ts +1 -1
- package/dist/tenant/signing-keys.js +5 -6
- package/dist/tenant/status.js +4 -3
- package/dist/tenant/streams.js +18 -20
- package/dist/tenant/sweep.d.ts +1 -21
- package/dist/tenant/sweep.js +39 -48
- package/dist/tenant/transcript.d.ts +70 -23
- package/dist/tenant/transcript.js +28 -22
- package/dist/tenant/types.d.ts +11 -16
- package/dist/tenant/worker.d.ts +2 -10
- package/dist/tenant/worker.js +5 -14
- package/dist/traces/ids.js +5 -1
- package/dist/traces/map.d.ts +9 -6
- package/dist/traces/map.js +9 -11
- package/dist/traces/otlp.d.ts +3 -3
- package/dist/traces/otlp.js +5 -3
- package/dist/traces/projector.d.ts +9 -4
- package/dist/traces/projector.js +13 -7
- package/dist/traces/read.js +6 -1
- package/dist/tuning.d.ts +383 -0
- package/dist/tuning.js +658 -0
- package/dist/vault/headers.js +4 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/artifacts/export.d.ts +0 -8
- package/dist/artifacts/export.js +0 -204
- package/dist/artifacts/media-types.d.ts +0 -30
- package/dist/artifacts/media-types.js +0 -137
- package/dist/artifacts/workspace.d.ts +0 -60
- package/dist/artifacts/workspace.js +0 -45
- package/dist/media.d.ts +0 -10
- package/dist/media.js +0 -58
- package/dist/sandbox/runtime-tools.d.ts +0 -9
- package/dist/sandbox/runtime-tools.js +0 -16
- package/dist/sandbox/session-sandbox.d.ts +0 -16
- package/dist/sandbox/session-sandbox.js +0 -77
- package/dist/tenant/artifact-tool.d.ts +0 -13
- package/dist/tenant/artifact-tool.js +0 -243
- package/dist/tenant/skill-tool.d.ts +0 -20
- package/dist/tenant/skill-tool.js +0 -67
- package/dist/tenant/tool-results.d.ts +0 -57
- package/dist/tenant/tool-results.js +0 -298
- package/dist/tenant/tool-search.d.ts +0 -39
- package/dist/tenant/tool-search.js +0 -103
package/dist/gates/inflight.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hostTuning } from "../tuning.js";
|
|
1
2
|
/** A re-send whose request differs from the call already running under its key. */
|
|
2
3
|
export class InflightConflict extends Error {
|
|
3
4
|
name = "InflightConflict";
|
|
@@ -7,8 +8,8 @@ export class InflightStale extends Error {
|
|
|
7
8
|
name = "InflightStale";
|
|
8
9
|
}
|
|
9
10
|
export function createInflightCalls(options = {}) {
|
|
10
|
-
const ttlMs = options.ttlMs ??
|
|
11
|
-
const max = options.max ??
|
|
11
|
+
const ttlMs = options.ttlMs ?? hostTuning().gates.inflightTtlMs;
|
|
12
|
+
const max = options.max ?? hostTuning().gates.inflightMax;
|
|
12
13
|
const now = options.now ?? Date.now;
|
|
13
14
|
const entries = new Map();
|
|
14
15
|
/** Drops expired outcomes, then the oldest settled ones while over `max`. */
|
package/dist/gates/journal.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { mcpToolOf } from "../mcp/snapshot.js";
|
|
2
1
|
import { canonical } from "../store/canonical.js";
|
|
3
2
|
import { httpToolRefOf } from "./http-tool.js";
|
|
4
3
|
export function journalCheck(vaults) {
|
|
@@ -25,12 +24,11 @@ export function journalCheck(vaults) {
|
|
|
25
24
|
return `Effect '${key}' was journaled with other input`;
|
|
26
25
|
return held(row);
|
|
27
26
|
}
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
tool
|
|
32
|
-
(
|
|
33
|
-
tool.serverToolName !== call.name)
|
|
27
|
+
const target = request.target;
|
|
28
|
+
if (target?.kind !== "mcp" ||
|
|
29
|
+
target.server !== call.server.serverName ||
|
|
30
|
+
target.tool !== call.name ||
|
|
31
|
+
(request.agent?.id ?? null) !== (call.server.agentId ?? null))
|
|
34
32
|
return `Effect '${key}' calls another tool`;
|
|
35
33
|
if (canonical(argumentsOf(request.input)) !== canonical(call.arguments))
|
|
36
34
|
return `Effect '${key}' was journaled with other input`;
|
|
@@ -4,12 +4,13 @@ import type { OutboundPolicy } from "../tenant/outbound.js";
|
|
|
4
4
|
import type { Logger } from "../tenant/types.js";
|
|
5
5
|
import type { McpAnswer } from "./tool-contract.js";
|
|
6
6
|
import { type TenantVaults } from "./tenant-vaults.js";
|
|
7
|
-
/** A connection unused this long is closed; the next request opens it again. */
|
|
8
|
-
export declare const GATE_MCP_IDLE_MS: number;
|
|
9
7
|
export interface McpHandlerOptions {
|
|
10
8
|
readonly vaults: TenantVaults;
|
|
11
9
|
readonly logger: Logger;
|
|
12
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* A connection unused this long is closed; the next request opens it again. Default
|
|
12
|
+
* `gates.mcpIdleMs` (Host tuning, 15 min).
|
|
13
|
+
*/
|
|
13
14
|
readonly idleMs?: number;
|
|
14
15
|
readonly now?: () => number;
|
|
15
16
|
/** Tests replace how a server is opened. */
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { McpCallFailed, McpConnectionStale, openMcpServer, unreachable, } from "../mcp/connect.js";
|
|
2
2
|
import { findServer } from "../mcp/pool.js";
|
|
3
|
+
import { readRuntimeLimits } from "../tenant/runtime-limits.js";
|
|
3
4
|
import { GateRefusal } from "./tenant-vaults.js";
|
|
4
|
-
|
|
5
|
-
export const GATE_MCP_IDLE_MS = 15 * 60_000;
|
|
5
|
+
import { hostTuning } from "../tuning.js";
|
|
6
6
|
/** The server is not one the session may reach through the gate. */
|
|
7
7
|
class NotDeclared extends Error {
|
|
8
8
|
}
|
|
9
9
|
export function createMcpHandler(options) {
|
|
10
10
|
const { vaults, logger } = options;
|
|
11
|
-
const idleMs = options.idleMs ??
|
|
11
|
+
const idleMs = options.idleMs ?? hostTuning().gates.mcpIdleMs;
|
|
12
12
|
const now = options.now ?? Date.now;
|
|
13
13
|
const open = options.open ?? openMcpServer;
|
|
14
14
|
const live = new Map();
|
|
@@ -42,6 +42,7 @@ export function createMcpHandler(options) {
|
|
|
42
42
|
...(server.agentId === undefined ? {} : { agentId: server.agentId }),
|
|
43
43
|
}),
|
|
44
44
|
policy: options.delivery ?? {},
|
|
45
|
+
errorExcerptChars: (await vault.store.tx((t) => readRuntimeLimits(t))).errorExcerptChars,
|
|
45
46
|
});
|
|
46
47
|
const entry = { connection, lastUsedAt: now(), active: 0 };
|
|
47
48
|
live.set(key, entry);
|
|
@@ -80,7 +81,11 @@ export function createMcpHandler(options) {
|
|
|
80
81
|
// the session is not the server's failure, and stays as it was.
|
|
81
82
|
if (!call || error instanceof GateRefusal || error instanceof NotDeclared)
|
|
82
83
|
throw error;
|
|
83
|
-
|
|
84
|
+
const limits = await vaults
|
|
85
|
+
.open(tenantId)
|
|
86
|
+
.then((vault) => vault.store.tx((t) => readRuntimeLimits(t)))
|
|
87
|
+
.catch(() => undefined);
|
|
88
|
+
throw unreachable(server.serverName, error, limits?.errorExcerptChars);
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
async function use(tenantId, server, run, call = false) {
|
|
@@ -112,10 +117,15 @@ export function createMcpHandler(options) {
|
|
|
112
117
|
* the call sent once more (R2b C7).
|
|
113
118
|
*/
|
|
114
119
|
async function callOnce(tenantId, request, signal) {
|
|
120
|
+
// The Tenant's Runtime limits, read for this call: the largest answer the gate keeps, and how
|
|
121
|
+
// much of a failed one its error quotes.
|
|
122
|
+
const { toolAnswerBytes, errorExcerptChars } = await (await vaults.open(tenantId)).store.tx((t) => readRuntimeLimits(t));
|
|
115
123
|
for (let attempt = 0;; attempt += 1) {
|
|
116
124
|
try {
|
|
117
125
|
return await use(tenantId, request.server, (entry) => entry.connection.client.callTool({ name: request.name, arguments: request.arguments }, {
|
|
118
126
|
signal,
|
|
127
|
+
maxResultBytes: toolAnswerBytes,
|
|
128
|
+
errorExcerptChars,
|
|
119
129
|
...(request.key === undefined ? {} : { key: request.key }),
|
|
120
130
|
...(request.traceparent === undefined ? {} : { traceparent: request.traceparent }),
|
|
121
131
|
}), true);
|
|
@@ -188,7 +198,7 @@ function errorOf(error) {
|
|
|
188
198
|
...(error.code === "credential_rejected" ? { code: 401 } : {}),
|
|
189
199
|
failure: error.failure,
|
|
190
200
|
};
|
|
191
|
-
const message = error instanceof Error ? error.message.slice(0,
|
|
201
|
+
const message = error instanceof Error ? error.message.slice(0, hostTuning().gates.errorMessageChars) : String(error);
|
|
192
202
|
const code = error && typeof error === "object" && "code" in error && typeof error.code === "number"
|
|
193
203
|
? error.code
|
|
194
204
|
: undefined;
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* call needs that the loop must not hold: the provider credential, pi-ai, its retries and
|
|
5
5
|
* idle watchdog, failure classification and scrubbing.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Three implementations: `inProcessModelGate` (embedding, the ephemeral Runtime, tests), the
|
|
8
8
|
* HTTP client of the gates service (the `gateway` container), which keeps the credential out
|
|
9
|
-
* of the loop process
|
|
9
|
+
* of the loop process, and `providerModelGate`, which serves a Runtime's scripted model (tests,
|
|
10
|
+
* `model: {kind: "scripted"}`) the same way, metered. Both return what `piModel` returns, so the effect journal is the same
|
|
10
11
|
* either way.
|
|
11
12
|
*/
|
|
13
|
+
import type { AgentRef } from "@nylorun/core/define";
|
|
12
14
|
import type { RuntimeModelAdapter, RuntimeModelCall } from "../contracts.js";
|
|
13
15
|
export type { ModelCallSettings } from "../model/pi-model.js";
|
|
14
16
|
/**
|
|
@@ -25,6 +27,13 @@ export interface ModelGateRequest {
|
|
|
25
27
|
/** The effect's journal id; sent as `Idempotency-Key` (acted on from P1.2). */
|
|
26
28
|
readonly effectId: string;
|
|
27
29
|
readonly invocationId: string;
|
|
30
|
+
/**
|
|
31
|
+
* The agent used as a tool the call is for; absent for the session's root agent. In process
|
|
32
|
+
* only: a scripted provider reads it (`providerModelGate`).
|
|
33
|
+
*/
|
|
34
|
+
readonly agent?: AgentRef;
|
|
35
|
+
/** A call that summarizes history for a compaction (protocol 15); in process only, like `agent`. */
|
|
36
|
+
readonly purpose?: "summary";
|
|
28
37
|
readonly call: RuntimeModelCall;
|
|
29
38
|
}
|
|
30
39
|
/** A model candidate or a failure outcome: exactly what `piModel` returns. */
|
package/dist/gates/policy.d.ts
CHANGED
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
* (`deny`, then `ask`, then `allow`), and a call no rule matches runs. A session pinned to a
|
|
11
11
|
* manifest saved before R5 step 2 keeps its `approval: "always"` as an ask: no session loses
|
|
12
12
|
* the approval it was opened with. A policy that cannot be read refuses every call.
|
|
13
|
+
*
|
|
14
|
+
* A rule's `tool` matches the tool's identity (protocol 15): an MCP tool's own name on its
|
|
15
|
+
* server (`server` names the server), an HTTP tool's declared name, a flow stage's key. The name
|
|
16
|
+
* a harness gave the model never matches. A stored rule whose `tool` has `__` in it (an MCP tool
|
|
17
|
+
* named by its old model-facing `server__tool` name) makes the policy unreadable, so every call
|
|
18
|
+
* is refused until the policy names it by `server` and `tool`: a rule never silently stops
|
|
19
|
+
* matching.
|
|
13
20
|
*/
|
|
14
21
|
import { type ToolPolicy, type ToolRule } from "@nylorun/core/contracts";
|
|
15
22
|
import type { AgentManifest } from "@nylorun/core/define";
|
|
16
23
|
import type { HostEffect } from "@nylorun/harness/run";
|
|
17
|
-
import { type McpSnapshot } from "../mcp/snapshot.js";
|
|
18
24
|
import type { Tx } from "../store/types.js";
|
|
19
25
|
import type { Session } from "../tenant/context.js";
|
|
20
26
|
/** The `tenant_settings` key the policy is kept under. */
|
|
@@ -22,13 +28,18 @@ export declare const TOOL_POLICY_SETTING = "tool-policy";
|
|
|
22
28
|
/** A call as the policy reads it. */
|
|
23
29
|
export interface PolicyCall {
|
|
24
30
|
readonly kind: "mcp" | "http";
|
|
25
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The tool's identity: an MCP tool's own name on its server, an HTTP tool's declared name, a
|
|
33
|
+
* flow stage's key.
|
|
34
|
+
*/
|
|
26
35
|
readonly tool: string;
|
|
27
36
|
/** The MCP server's name. */
|
|
28
37
|
readonly server?: string;
|
|
29
38
|
/** The agent that calls it. */
|
|
30
39
|
readonly agent: string;
|
|
31
40
|
readonly input: unknown;
|
|
41
|
+
/** The name the model called it by, for display only: no rule matches it. */
|
|
42
|
+
readonly name?: string;
|
|
32
43
|
}
|
|
33
44
|
export type PolicyDecision = {
|
|
34
45
|
readonly effect: "allow";
|
|
@@ -36,14 +47,26 @@ export type PolicyDecision = {
|
|
|
36
47
|
readonly effect: "ask" | "deny";
|
|
37
48
|
readonly reason: string;
|
|
38
49
|
};
|
|
39
|
-
/**
|
|
50
|
+
/** A stored policy this Runtime cannot apply: every call is refused, with `message` as why. */
|
|
51
|
+
export declare class ToolPolicyUnreadable extends Error {
|
|
52
|
+
constructor(message: string);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The Tenant's policy; an empty one when none was set. Throws when it cannot be read, a
|
|
56
|
+
* `ToolPolicyUnreadable` saying which rule when one names an MCP tool by a model-facing name.
|
|
57
|
+
*/
|
|
40
58
|
export declare function readToolPolicy(t: Pick<Tx, "getSetting">): Promise<ToolPolicy>;
|
|
41
59
|
/**
|
|
42
60
|
* The decision for `request`, a session's MCP or HTTP tool call: the Tenant's rules and the
|
|
43
61
|
* pinned manifest's `approval`. A policy that cannot be read refuses the call.
|
|
44
62
|
*/
|
|
45
63
|
export declare function toolCallDecision(t: Pick<Tx, "getSetting">, s: Session, request: HostEffect, kind: "mcp" | "http"): Promise<PolicyDecision>;
|
|
64
|
+
/** `request` as the policy reads it: by its target's identity (protocol 15). */
|
|
65
|
+
export declare function policyCallOf(s: Pick<Session, "agentId">, request: HostEffect, kind: "mcp" | "http"): PolicyCall;
|
|
46
66
|
/** Decides `call` by `rules`; `pinnedAsk` adds the ask of a manifest saved before R5 step 2. */
|
|
47
67
|
export declare function decide(rules: readonly ToolRule[], call: PolicyCall, pinnedAsk?: boolean): PolicyDecision;
|
|
48
|
-
/**
|
|
49
|
-
|
|
68
|
+
/**
|
|
69
|
+
* The pinned `approval: "always"` of the call's tool, in a manifest saved before R5 step 2: an
|
|
70
|
+
* MCP tool's settings by its own name, an HTTP tool's declaration by its declared name.
|
|
71
|
+
*/
|
|
72
|
+
export declare function pinnedApproval(manifest: AgentManifest, request: HostEffect): boolean;
|
package/dist/gates/policy.js
CHANGED
|
@@ -10,21 +10,44 @@
|
|
|
10
10
|
* (`deny`, then `ask`, then `allow`), and a call no rule matches runs. A session pinned to a
|
|
11
11
|
* manifest saved before R5 step 2 keeps its `approval: "always"` as an ask: no session loses
|
|
12
12
|
* the approval it was opened with. A policy that cannot be read refuses every call.
|
|
13
|
+
*
|
|
14
|
+
* A rule's `tool` matches the tool's identity (protocol 15): an MCP tool's own name on its
|
|
15
|
+
* server (`server` names the server), an HTTP tool's declared name, a flow stage's key. The name
|
|
16
|
+
* a harness gave the model never matches. A stored rule whose `tool` has `__` in it (an MCP tool
|
|
17
|
+
* named by its old model-facing `server__tool` name) makes the policy unreadable, so every call
|
|
18
|
+
* is refused until the policy names it by `server` and `tool`: a rule never silently stops
|
|
19
|
+
* matching.
|
|
13
20
|
*/
|
|
14
21
|
import { ToolPolicySchema } from "@nylorun/core/contracts";
|
|
15
|
-
import {
|
|
22
|
+
import { mcpToolSettingsIn } from "../mcp/snapshot.js";
|
|
16
23
|
import { canonical } from "../store/canonical.js";
|
|
17
24
|
import { turnManifestOf } from "../tenant/session.js";
|
|
18
25
|
import { declaredHttpTool } from "./http-tool.js";
|
|
19
26
|
/** The `tenant_settings` key the policy is kept under. */
|
|
20
27
|
export const TOOL_POLICY_SETTING = "tool-policy";
|
|
21
28
|
const STRICTNESS = { allow: 0, ask: 1, deny: 2 };
|
|
22
|
-
/**
|
|
29
|
+
/** A stored policy this Runtime cannot apply: every call is refused, with `message` as why. */
|
|
30
|
+
export class ToolPolicyUnreadable extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "ToolPolicyUnreadable";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The Tenant's policy; an empty one when none was set. Throws when it cannot be read, a
|
|
38
|
+
* `ToolPolicyUnreadable` saying which rule when one names an MCP tool by a model-facing name.
|
|
39
|
+
*/
|
|
23
40
|
export async function readToolPolicy(t) {
|
|
24
41
|
const raw = await t.getSetting(TOOL_POLICY_SETTING);
|
|
25
42
|
if (raw === undefined)
|
|
26
43
|
return { rules: [], updatedAt: null };
|
|
27
|
-
|
|
44
|
+
const parsed = ToolPolicySchema.safeParse(JSON.parse(raw));
|
|
45
|
+
if (parsed.success)
|
|
46
|
+
return parsed.data;
|
|
47
|
+
const issue = parsed.error.issues[0];
|
|
48
|
+
throw new ToolPolicyUnreadable(issue?.path.at(-1) === "tool"
|
|
49
|
+
? `Rule ${Number(issue.path[1]) + 1} of the Tenant's tool policy names a tool by a model-facing name: ${issue.message}. Set the policy again (PUT /v1/tenant/tool-policy)`
|
|
50
|
+
: `The Tenant's tool policy could not be read: ${issue?.message ?? "malformed"}`);
|
|
28
51
|
}
|
|
29
52
|
/**
|
|
30
53
|
* The decision for `request`, a session's MCP or HTTP tool call: the Tenant's rules and the
|
|
@@ -35,22 +58,37 @@ export async function toolCallDecision(t, s, request, kind) {
|
|
|
35
58
|
try {
|
|
36
59
|
rules = (await readToolPolicy(t)).rules;
|
|
37
60
|
}
|
|
38
|
-
catch {
|
|
39
|
-
return {
|
|
61
|
+
catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
effect: "deny",
|
|
64
|
+
reason: error instanceof ToolPolicyUnreadable
|
|
65
|
+
? `${error.message}, so no tool call runs`
|
|
66
|
+
: "The Tenant's tool policy could not be read, so no tool call runs",
|
|
67
|
+
};
|
|
40
68
|
}
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
return decide(rules, policyCallOf(s, request, kind), pinnedApproval(turnManifestOf(s), request));
|
|
70
|
+
}
|
|
71
|
+
/** `request` as the policy reads it: by its target's identity (protocol 15). */
|
|
72
|
+
export function policyCallOf(s, request, kind) {
|
|
73
|
+
const target = request.target;
|
|
74
|
+
return {
|
|
43
75
|
kind,
|
|
44
|
-
tool:
|
|
45
|
-
|
|
76
|
+
tool: target?.kind === "mcp" || target?.kind === "http"
|
|
77
|
+
? target.tool
|
|
78
|
+
: (request.key ?? request.effectId),
|
|
79
|
+
...(target?.kind === "mcp" ? { server: target.server } : {}),
|
|
46
80
|
agent: request.agent?.id ?? s.agentId,
|
|
47
81
|
input: request.input,
|
|
48
|
-
|
|
82
|
+
...(request.toolName === undefined ? {} : { name: request.toolName }),
|
|
83
|
+
};
|
|
49
84
|
}
|
|
50
85
|
/** Decides `call` by `rules`; `pinnedAsk` adds the ask of a manifest saved before R5 step 2. */
|
|
51
86
|
export function decide(rules, call, pinnedAsk = false) {
|
|
52
87
|
let strictest = pinnedAsk
|
|
53
|
-
? {
|
|
88
|
+
? {
|
|
89
|
+
effect: "ask",
|
|
90
|
+
reason: `The agent's manifest asks for approval of '${call.server === undefined ? call.tool : `${call.server} / ${call.tool}`}'`,
|
|
91
|
+
}
|
|
54
92
|
: undefined;
|
|
55
93
|
for (const rule of rules) {
|
|
56
94
|
if (!matches(rule, call))
|
|
@@ -63,12 +101,17 @@ export function decide(rules, call, pinnedAsk = false) {
|
|
|
63
101
|
return { effect: "allow" };
|
|
64
102
|
return { effect: strictest.effect, reason: strictest.reason };
|
|
65
103
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
104
|
+
/**
|
|
105
|
+
* The pinned `approval: "always"` of the call's tool, in a manifest saved before R5 step 2: an
|
|
106
|
+
* MCP tool's settings by its own name, an HTTP tool's declaration by its declared name.
|
|
107
|
+
*/
|
|
108
|
+
export function pinnedApproval(manifest, request) {
|
|
109
|
+
const target = request.target;
|
|
110
|
+
if (target?.kind === "mcp")
|
|
111
|
+
return mcpToolSettingsIn(manifest, request.agent?.id, target.server, target.tool).approval === "always";
|
|
112
|
+
if (target?.kind === "http")
|
|
113
|
+
return declaredHttpTool(manifest, request.agent?.id, target.tool)?.approval === "always";
|
|
114
|
+
return false;
|
|
72
115
|
}
|
|
73
116
|
function matches(rule, call) {
|
|
74
117
|
if (rule.kind !== undefined && rule.kind !== call.kind)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Model Gate over a `ModelProvider` (protocol 15): how a Runtime whose model is scripted (tests,
|
|
3
|
+
* `model: {kind: "scripted"}` or `"fixture"`, an embedder's provider) serves its harness's model
|
|
4
|
+
* calls. The harness calls the gate as it calls any (`invokeModel`), in process or not: no harness
|
|
5
|
+
* calls a provider directly.
|
|
6
|
+
*
|
|
7
|
+
* The provider gets the call as an effect (`HostEffect`), rebuilt from the request: the ids, the
|
|
8
|
+
* agent used as a tool, the call's purpose, and the call as its `input`. With a store, each call
|
|
9
|
+
* is metered in the usage ledger like the Tenant's own gate's, and the Tenant's fixture model
|
|
10
|
+
* setting is answered first (R4.3). It dies with its process: it never `recovers`.
|
|
11
|
+
*/
|
|
12
|
+
import type { HostEffect } from "@nylorun/harness/run";
|
|
13
|
+
import type { ModelProvider } from "../core/provider.js";
|
|
14
|
+
import type { SessionStore } from "../store/types.js";
|
|
15
|
+
import type { Logger } from "../tenant/types.js";
|
|
16
|
+
import type { ModelGate, ModelGateRequest } from "./model-gate.js";
|
|
17
|
+
/** The effect a provider sees for `request`. */
|
|
18
|
+
export declare function providerEffect(request: ModelGateRequest): HostEffect;
|
|
19
|
+
export declare function providerModelGate(provider: ModelProvider, options?: {
|
|
20
|
+
readonly store?: SessionStore;
|
|
21
|
+
readonly logger?: Logger;
|
|
22
|
+
}): ModelGate;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { serve } from "./in-process.js";
|
|
2
|
+
import { createMeter } from "./meter.js";
|
|
3
|
+
/** The effect a provider sees for `request`. */
|
|
4
|
+
export function providerEffect(request) {
|
|
5
|
+
return {
|
|
6
|
+
effectId: request.effectId,
|
|
7
|
+
sessionId: request.sessionId,
|
|
8
|
+
turnId: request.turnId,
|
|
9
|
+
agentId: request.agentId,
|
|
10
|
+
// The gate is told no definition hash; a provider routes by the call, never by the hash.
|
|
11
|
+
manifestHash: "",
|
|
12
|
+
kind: "model",
|
|
13
|
+
...(request.agent === undefined ? {} : { agent: request.agent }),
|
|
14
|
+
...(request.purpose === undefined ? {} : { purpose: request.purpose }),
|
|
15
|
+
input: request.call,
|
|
16
|
+
context: { invocationId: request.invocationId },
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function providerModelGate(provider, options = {}) {
|
|
20
|
+
const direct = {
|
|
21
|
+
call: async (request, signal) => (await provider(providerEffect(request), signal)),
|
|
22
|
+
};
|
|
23
|
+
const { store, logger } = options;
|
|
24
|
+
if (!store || !logger)
|
|
25
|
+
return direct;
|
|
26
|
+
const meter = createMeter({ logger });
|
|
27
|
+
return {
|
|
28
|
+
call: (request, signal) => meter.call(store, request, () => serve(store, direct, request, signal)),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -21,8 +21,6 @@ import type { Logger } from "../tenant/types.js";
|
|
|
21
21
|
import { type InflightCallsOptions, type InflightOwner } from "./inflight.js";
|
|
22
22
|
import type { TenantVaults } from "./tenant-vaults.js";
|
|
23
23
|
import type { McpAnswer } from "./tool-contract.js";
|
|
24
|
-
/** How long a settled crossing is kept. */
|
|
25
|
-
export declare const TOOL_CROSSING_TTL_MS: number;
|
|
26
24
|
/** A tool call's answer: an MCP `CallToolResult`, or an HTTP tool's outcome. */
|
|
27
25
|
export type ToolCallAnswer = McpAnswer<Record<string, unknown>>;
|
|
28
26
|
export interface ToolCalls {
|
|
@@ -40,7 +38,7 @@ export interface ToolCalls {
|
|
|
40
38
|
cancel(key: string, sessionId?: string): boolean;
|
|
41
39
|
/** Aborts every running call (gateway shutdown). */
|
|
42
40
|
close(): void;
|
|
43
|
-
/** Deletes crossings settled more than `
|
|
41
|
+
/** Deletes crossings settled more than `gates.toolCrossingTtlMs` (Host tuning, 24 h) ago. */
|
|
44
42
|
prune(): Promise<void>;
|
|
45
43
|
}
|
|
46
44
|
export interface ToolCallsOptions {
|
package/dist/gates/tool-calls.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createInflightCalls, InflightConflict, } from "./inflight.js";
|
|
2
|
-
|
|
3
|
-
export const TOOL_CROSSING_TTL_MS = 24 * 60 * 60_000;
|
|
2
|
+
import { hostTuning } from "../tuning.js";
|
|
4
3
|
const CANCELLED = {
|
|
5
4
|
ok: false,
|
|
6
5
|
error: { message: "The call was cancelled while it ran; it may have reached its server" },
|
|
@@ -71,7 +70,7 @@ export function createToolCalls(options) {
|
|
|
71
70
|
async prune() {
|
|
72
71
|
try {
|
|
73
72
|
const vault = await vaults.open(undefined);
|
|
74
|
-
const before = new Date(now() -
|
|
73
|
+
const before = new Date(now() - hostTuning().gates.toolCrossingTtlMs).toISOString();
|
|
75
74
|
await vault.store.tx((t) => t.pruneToolCrossings(before));
|
|
76
75
|
}
|
|
77
76
|
catch {
|
|
@@ -21,7 +21,7 @@ export interface HttpToolGateOptions {
|
|
|
21
21
|
readonly runTokens?: RunTokens;
|
|
22
22
|
/** Sent as `Nylorun-Tenant`; the gate checks it against its database's Tenant. */
|
|
23
23
|
readonly tenantId?: string;
|
|
24
|
-
/** How long an MCP request may stay silent. Default `
|
|
24
|
+
/** How long an MCP request may stay silent. Default `gates.clientTimeoutMs` (Host tuning). */
|
|
25
25
|
readonly timeoutMs?: number;
|
|
26
26
|
}
|
|
27
27
|
export declare function httpToolGate(options: HttpToolGateOptions): ToolGate;
|
|
@@ -26,7 +26,7 @@ import { request as httpRequest } from "node:http";
|
|
|
26
26
|
import { request as httpsRequest } from "node:https";
|
|
27
27
|
import { McpCallFailed, } from "../mcp/connect.js";
|
|
28
28
|
import { TENANT_HEADER } from "./contract.js";
|
|
29
|
-
import {
|
|
29
|
+
import { hostTuning } from "../tuning.js";
|
|
30
30
|
import { HTTP_CALLS_PATH, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, TOOL_CALLS_PATH, } from "./tool-contract.js";
|
|
31
31
|
/**
|
|
32
32
|
* A keyed call whose answer the hop lost (R5.3): the gate never got it, the connection broke,
|
|
@@ -36,11 +36,9 @@ import { HTTP_CALLS_PATH, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, TOOL_
|
|
|
36
36
|
export class GateCallLost extends Error {
|
|
37
37
|
name = "GateCallLost";
|
|
38
38
|
}
|
|
39
|
-
/** How long a close or a cancel may take; neither changes an outcome. */
|
|
40
|
-
const SHORT_TIMEOUT_MS = 2_000;
|
|
41
39
|
export function httpToolGate(options) {
|
|
42
40
|
const where = new URL(options.url).origin;
|
|
43
|
-
const timeoutMs = options.timeoutMs ??
|
|
41
|
+
const timeoutMs = options.timeoutMs ?? hostTuning().gates.clientTimeoutMs;
|
|
44
42
|
function exchange(path, body, request) {
|
|
45
43
|
const url = new URL(path, options.url);
|
|
46
44
|
const send = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
@@ -88,7 +86,7 @@ export function httpToolGate(options) {
|
|
|
88
86
|
responded = true;
|
|
89
87
|
readBody(response).then((text) => settle({ kind: "answer", status: response.statusCode ?? 0, text }), (error) => failed(error));
|
|
90
88
|
});
|
|
91
|
-
outgoing.on("socket", (socket) => socket.setKeepAlive(true,
|
|
89
|
+
outgoing.on("socket", (socket) => socket.setKeepAlive(true, hostTuning().gates.keepAliveMs));
|
|
92
90
|
outgoing.on("finish", () => {
|
|
93
91
|
sent = true;
|
|
94
92
|
});
|
|
@@ -147,7 +145,8 @@ export function httpToolGate(options) {
|
|
|
147
145
|
}
|
|
148
146
|
async function fireAndForget(path, scope) {
|
|
149
147
|
await exchange(path, scope.body, {
|
|
150
|
-
|
|
148
|
+
// A close or a cancel is short (`gates.shortTimeoutMs`); neither changes an outcome.
|
|
149
|
+
timeoutMs: hostTuning().gates.shortTimeoutMs,
|
|
151
150
|
...(scope.bearer ? { bearer: scope.bearer } : {}),
|
|
152
151
|
});
|
|
153
152
|
}
|
|
@@ -23,14 +23,17 @@
|
|
|
23
23
|
* - `POST /nylorun/v1/mcp/close` `{server}`: closes the server's connection; `204`.
|
|
24
24
|
* Each answers `200 {ok: true, result}` or `200 {ok: false, error}`: an MCP failure is an
|
|
25
25
|
* answer, not a gate error. A tool call's failure is coded (`error.failure`, R2b C7), and neither
|
|
26
|
-
* a result nor a failure carries a credential value the gate sent (C8). An answer past
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
26
|
+
* a result nor a failure carries a credential value the gate sent (C8). An answer past the
|
|
27
|
+
* Tenant's Runtime limit `toolAnswerBytes` (default 8 MiB), which also caps the bodies the gate
|
|
28
|
+
* reads, is `mcp.too-large` (C11), as an HTTP tool's is `http.too-large`; the harness shapes
|
|
29
|
+
* what fits into a result the model can take (`shapeToolOutcome`, protocol 15). The tool-calls
|
|
30
|
+
* body names the tool by its identity: the server (`{agentId?, serverName}`) and the server's
|
|
31
|
+
* own name for the tool (`name`). A keyed call whose earlier attempt was
|
|
29
32
|
* lost with the gateway runs again under the same key (R5.3); one another gateway process holds
|
|
30
33
|
* answers `{ok: false, error: {uncertain: true}}`, and the caller sends it again.
|
|
31
34
|
*
|
|
32
35
|
* HTTP tools (R2 M3). `POST /nylorun/v1/http-calls` `{tool, effectId, turnId, input}`: one call
|
|
33
|
-
* of an HTTP tool. The loop names the tool (`{sessionId?, agentId?, toolName}
|
|
36
|
+
* of an HTTP tool. The loop names the tool (`{sessionId?, agentId?, toolName}`, its declared name), or a
|
|
34
37
|
* flow's HTTP stage or HTTP verifier by its stage key (`{sessionId?, stage}`); the gate finds its
|
|
35
38
|
* URL, method and credential in the session's pinned manifest
|
|
36
39
|
* (`gates/http-tool.ts`). A run token's turn is the call's; `turnId` counts only under core's
|
|
@@ -39,7 +42,7 @@
|
|
|
39
42
|
* model sees), or `{ok: false, error: {uncertain: true}}` for a call another gateway process holds.
|
|
40
43
|
*/
|
|
41
44
|
import type { McpCallFailure } from "../mcp/connect.js";
|
|
42
|
-
export { HTTP_CALLS_PATH, HttpCallBodySchema,
|
|
45
|
+
export { HTTP_CALLS_PATH, HttpCallBodySchema, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, McpServerRefSchema, TOOL_CALLS_PATH, ToolCallBodySchema, type HttpCallBody, type ToolCallBody, } from "@nylorun/core/harness-api";
|
|
43
46
|
/** Why an MCP request failed, as the loop's diagnostics read it (`mcp/connect.ts`). */
|
|
44
47
|
export interface McpGateError {
|
|
45
48
|
readonly message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { HTTP_CALLS_PATH, HttpCallBodySchema,
|
|
1
|
+
export { HTTP_CALLS_PATH, HttpCallBodySchema, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, McpServerRefSchema, TOOL_CALLS_PATH, ToolCallBodySchema, } from "@nylorun/core/harness-api";
|
|
@@ -6,17 +6,20 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Two implementations: `inProcessToolGate` (embedding, the ephemeral Runtime, tests), which
|
|
8
8
|
* opens remote servers and calls HTTP tools in this process, and the HTTP client of the gates
|
|
9
|
-
* service (`tool-client.ts`, the `gateway` container). The sandbox tools never cross it.
|
|
9
|
+
* service (`tool-client.ts`, the `gateway` container). The sandbox tools never cross it. A
|
|
10
|
+
* harness's MCP pool opens every server through a gate (`openMcp`), in process too (protocol
|
|
11
|
+
* 15): the vault authorizes a connection only here.
|
|
10
12
|
*/
|
|
11
|
-
import type
|
|
12
|
-
import type
|
|
13
|
+
import { openMcpServer, type LiveConnection } from "../mcp/connect.js";
|
|
14
|
+
import { type McpServerRef } from "../mcp/pool.js";
|
|
13
15
|
import type { OutboundPolicy } from "../tenant/outbound.js";
|
|
14
16
|
import type { Logger } from "../tenant/types.js";
|
|
15
17
|
import { type HttpOutcome, type HttpToolCall, type HttpToolTenant } from "./http-tool.js";
|
|
16
18
|
export interface ToolGate {
|
|
17
19
|
/**
|
|
18
|
-
* Opens a remote (`streamable-http` or `sse`) MCP server of a session
|
|
19
|
-
*
|
|
20
|
+
* Opens a remote (`streamable-http` or `sse`) MCP server of a session: the one its pinned
|
|
21
|
+
* manifest declares, authorized with the Tenant vault. Absent: this Runtime opens no MCP
|
|
22
|
+
* server.
|
|
20
23
|
*/
|
|
21
24
|
openMcp?(server: McpServerRef): Promise<LiveConnection>;
|
|
22
25
|
/**
|
|
@@ -39,9 +42,17 @@ export interface ToolGate {
|
|
|
39
42
|
}): Promise<void>;
|
|
40
43
|
}
|
|
41
44
|
/**
|
|
42
|
-
* The Tool Gate in the caller's process: remote MCP
|
|
43
|
-
* `
|
|
45
|
+
* The Tool Gate in the caller's process: remote MCP servers and HTTP tools with `tenant`'s
|
|
46
|
+
* sessions and vault. `openMcp` finds the server in the session's pinned manifest (as the gates
|
|
47
|
+
* service does), so a caller names a server, never a URL or a credential. `open` replaces how a
|
|
48
|
+
* server is opened (tests).
|
|
44
49
|
*/
|
|
45
50
|
export declare function inProcessToolGate(policy?: OutboundPolicy, tenant?: HttpToolTenant & {
|
|
46
51
|
readonly logger?: Logger;
|
|
52
|
+
readonly open?: typeof openMcpServer;
|
|
47
53
|
}): ToolGate;
|
|
54
|
+
/**
|
|
55
|
+
* How a harness's MCP pool opens a server: through `gate`, or, for a Runtime whose gate opens
|
|
56
|
+
* none, never (the call fails to the model as `mcp.unreachable`).
|
|
57
|
+
*/
|
|
58
|
+
export declare function openMcpThrough(gate: Pick<ToolGate, "openMcp">): (server: McpServerRef) => Promise<LiveConnection>;
|
package/dist/gates/tool-gate.js
CHANGED
|
@@ -1,14 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Tool Gate seam (blueprint §12, F4.1). Every tool call that leaves the loop crosses one
|
|
3
|
+
* `ToolGate`: a remote MCP server's connection and calls, and an HTTP tool's request (R2 M3).
|
|
4
|
+
* The gate owns what those need that the loop must not hold: the MCP and HTTP tool credentials,
|
|
5
|
+
* and the outbound route.
|
|
6
|
+
*
|
|
7
|
+
* Two implementations: `inProcessToolGate` (embedding, the ephemeral Runtime, tests), which
|
|
8
|
+
* opens remote servers and calls HTTP tools in this process, and the HTTP client of the gates
|
|
9
|
+
* service (`tool-client.ts`, the `gateway` container). The sandbox tools never cross it. A
|
|
10
|
+
* harness's MCP pool opens every server through a gate (`openMcp`), in process too (protocol
|
|
11
|
+
* 15): the vault authorizes a connection only here.
|
|
12
|
+
*/
|
|
13
|
+
import { openMcpServer } from "../mcp/connect.js";
|
|
14
|
+
import { findServer } from "../mcp/pool.js";
|
|
1
15
|
import { runHttpTool } from "./http-tool.js";
|
|
2
16
|
/**
|
|
3
|
-
* The Tool Gate in the caller's process: remote MCP
|
|
4
|
-
* `
|
|
17
|
+
* The Tool Gate in the caller's process: remote MCP servers and HTTP tools with `tenant`'s
|
|
18
|
+
* sessions and vault. `openMcp` finds the server in the session's pinned manifest (as the gates
|
|
19
|
+
* service does), so a caller names a server, never a URL or a credential. `open` replaces how a
|
|
20
|
+
* server is opened (tests).
|
|
5
21
|
*/
|
|
6
22
|
export function inProcessToolGate(policy = {}, tenant) {
|
|
23
|
+
if (!tenant)
|
|
24
|
+
return {};
|
|
25
|
+
const open = tenant.open ?? openMcpServer;
|
|
7
26
|
return {
|
|
8
|
-
...(tenant
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
callHttp: (call, signal) => runHttpTool(tenant, call, { policy, signal, ...(tenant.logger ? { logger: tenant.logger } : {}) }),
|
|
28
|
+
async openMcp(server) {
|
|
29
|
+
const session = await tenant.session(server.sessionId);
|
|
30
|
+
if (!session)
|
|
31
|
+
throw new Error(`Session ${server.sessionId} not found`);
|
|
32
|
+
const declared = findServer(session.manifest, server.agentId, server.serverName);
|
|
33
|
+
if (!declared)
|
|
34
|
+
throw new Error(`MCP server '${server.serverName}' is not declared`);
|
|
35
|
+
return open({
|
|
36
|
+
server: declared.server,
|
|
37
|
+
policy,
|
|
38
|
+
errorExcerptChars: (await tenant.limits()).errorExcerptChars,
|
|
39
|
+
authorize: (url) => tenant.authorize(server.sessionId, {
|
|
40
|
+
url,
|
|
41
|
+
serverName: server.serverName,
|
|
42
|
+
...(server.agentId === undefined ? {} : { agentId: server.agentId }),
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
},
|
|
13
46
|
};
|
|
14
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* How a harness's MCP pool opens a server: through `gate`, or, for a Runtime whose gate opens
|
|
50
|
+
* none, never (the call fails to the model as `mcp.unreachable`).
|
|
51
|
+
*/
|
|
52
|
+
export function openMcpThrough(gate) {
|
|
53
|
+
return (server) => gate.openMcp
|
|
54
|
+
? gate.openMcp(server)
|
|
55
|
+
: Promise.reject(new Error("This Runtime opens MCP servers only through a Tool Gate, and has none"));
|
|
56
|
+
}
|