@nylorun/runtime 0.23.0-beta → 0.24.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 +148 -0
- package/README.md +79 -28
- package/dist/adapters/execution/restate.js +1 -1
- package/dist/adapters/replication/pgoutput.d.ts +2 -0
- package/dist/adapters/replication/pgoutput.js +6 -3
- package/dist/adapters/sandbox/local.d.ts +9 -1
- package/dist/adapters/sandbox/local.js +16 -4
- package/dist/adapters/sandbox/virtual.js +4 -1
- package/dist/api/a2a/endpoint.js +5 -1
- package/dist/api/a2a/routes.d.ts +2 -0
- package/dist/api/a2a/routes.js +2 -2
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +4 -2
- package/dist/api/ag-ui/history.js +4 -1
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +11 -10
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +4953 -1204
- package/dist/api/components.js +25 -16
- package/dist/api/gate/keys.js +7 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +39 -3
- package/dist/api/http/app.js +4 -4
- package/dist/api/http/define.d.ts +6 -0
- package/dist/api/http/define.js +13 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/artifacts.js +35 -8
- package/dist/api/http/routes/coverage.d.ts +9 -0
- package/dist/api/http/routes/coverage.js +34 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +283 -0
- package/dist/api/http/routes/mcp.d.ts +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.js +1 -1
- package/dist/api/http/routes/reads.d.ts +45 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +11 -6
- package/dist/api/http/routes/tenant.js +32 -10
- package/dist/api/openapi.js +31 -26
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +563 -0
- package/dist/artifacts/export.d.ts +1 -1
- package/dist/artifacts/export.js +2 -3
- package/dist/artifacts/folders.d.ts +6 -3
- package/dist/artifacts/folders.js +17 -12
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +1 -1
- package/dist/artifacts/workspace.js +1 -1
- package/dist/core/flow-host.d.ts +2 -3
- package/dist/core/flow-host.js +3 -0
- package/dist/core/provider.d.ts +3 -2
- package/dist/core/provider.js +5 -37
- package/dist/core/sandbox-routes.d.ts +2 -21
- package/dist/core/sandbox-routes.js +10 -43
- package/dist/execution/types.d.ts +1 -1
- package/dist/gates/contract.d.ts +3 -24
- package/dist/gates/contract.js +1 -36
- package/dist/gates/egress-credentials.d.ts +9 -0
- package/dist/gates/egress-credentials.js +11 -4
- package/dist/gates/egress.d.ts +28 -1
- package/dist/gates/egress.js +115 -10
- package/dist/gates/fixture-model.d.ts +28 -0
- package/dist/gates/fixture-model.js +92 -0
- package/dist/gates/handler.js +4 -4
- package/dist/gates/http-tool.d.ts +6 -2
- package/dist/gates/http-tool.js +3 -5
- package/dist/gates/in-process.d.ts +5 -0
- package/dist/gates/in-process.js +12 -1
- package/dist/gates/journal.d.ts +30 -0
- package/dist/gates/journal.js +52 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/meter.d.ts +10 -2
- package/dist/gates/meter.js +20 -5
- package/dist/gates/policy.d.ts +49 -0
- package/dist/gates/policy.js +134 -0
- package/dist/gates/tenant-vaults.d.ts +7 -7
- package/dist/gates/tenant-vaults.js +5 -5
- package/dist/gates/tool-calls.d.ts +10 -5
- package/dist/gates/tool-calls.js +28 -10
- package/dist/gates/tool-client.d.ts +8 -0
- package/dist/gates/tool-client.js +27 -11
- package/dist/gates/tool-contract.d.ts +10 -68
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +12 -5
- package/dist/harness/calls.js +21 -12
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +18 -5
- package/dist/harness/pod.d.ts +34 -0
- package/dist/harness/pod.js +41 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +13 -5
- package/dist/harness/setup.d.ts +27 -0
- package/dist/harness/setup.js +150 -0
- package/dist/harness/ws-client.js +3 -2
- package/dist/harness/ws-port.d.ts +1 -5
- package/dist/harness/ws-port.js +1 -5
- package/dist/harness-api/in-process.js +3 -3
- package/dist/harness-api/record.d.ts +15 -1
- package/dist/harness-api/record.js +174 -44
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +8 -0
- package/dist/harness-api/server.js +93 -13
- package/dist/harness-api/start.d.ts +10 -3
- package/dist/harness-api/start.js +40 -10
- package/dist/harness-api/tool-journal.d.ts +96 -0
- package/dist/harness-api/tool-journal.js +102 -0
- package/dist/harness-api/tool-status.d.ts +13 -0
- package/dist/harness-api/tool-status.js +26 -0
- package/dist/harness-api/workspace.d.ts +6 -3
- package/dist/harness-api/workspace.js +27 -11
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +54 -5
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +64 -0
- package/dist/host/stack-config.js +61 -1
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +5 -5
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +999 -2211
- package/dist/mcp/connect.d.ts +25 -20
- package/dist/mcp/connect.js +29 -30
- package/dist/mcp/pool.d.ts +2 -2
- package/dist/mcp/pool.js +1 -1
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -20
- package/dist/mcp/snapshot.d.ts +5 -11
- package/dist/mcp/snapshot.js +6 -7
- package/dist/model/pi-model.js +8 -0
- package/dist/openapi.json +6955 -2526
- package/dist/reads/types.d.ts +16 -3
- package/dist/record/append.d.ts +3 -1
- package/dist/record/append.js +1 -0
- package/dist/record/envelope.d.ts +5 -1
- package/dist/record/envelope.js +7 -3
- package/dist/record/environment.d.ts +25 -0
- package/dist/record/environment.js +44 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/sandbox/disk-fs.d.ts +95 -0
- package/dist/sandbox/disk-fs.js +206 -0
- package/dist/sandbox/join.d.ts +17 -18
- package/dist/sandbox/join.js +42 -15
- package/dist/sandbox/manager.d.ts +16 -8
- package/dist/sandbox/manager.js +33 -17
- package/dist/sandbox/placement.d.ts +13 -12
- package/dist/sandbox/placement.js +29 -12
- package/dist/sandbox/pods/client.d.ts +6 -1
- package/dist/sandbox/pods/lifecycle.d.ts +28 -27
- package/dist/sandbox/pods/lifecycle.js +14 -26
- package/dist/sandbox/pods/reconcile.js +18 -12
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +16 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +35 -27
- package/dist/sandbox/resolve.js +66 -41
- package/dist/sandbox/session-sandbox.js +1 -1
- package/dist/sandbox/setup.d.ts +61 -0
- package/dist/sandbox/setup.js +123 -0
- package/dist/sandbox/share.d.ts +11 -21
- package/dist/sandbox/share.js +12 -41
- package/dist/sandbox/tenant-config.d.ts +12 -14
- package/dist/sandbox/tenant-config.js +12 -17
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
- package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
- package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/reads.js +117 -17
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/record.d.ts +7 -0
- package/dist/store/postgres/record.js +23 -2
- package/dist/store/postgres/schema.d.ts +934 -761
- package/dist/store/postgres/schema.js +102 -73
- package/dist/store/postgres/store.js +119 -66
- package/dist/store/types.d.ts +109 -48
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +115 -42
- package/dist/tenant/artifact-tool.js +3 -4
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +93 -12
- package/dist/tenant/context.d.ts +48 -22
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +37 -20
- package/dist/tenant/environments.d.ts +167 -0
- package/dist/tenant/environments.js +823 -0
- package/dist/tenant/harness-tokens.d.ts +21 -0
- package/dist/tenant/harness-tokens.js +61 -0
- package/dist/tenant/host-token.d.ts +1 -1
- package/dist/tenant/host-token.js +2 -4
- package/dist/tenant/identity-file.d.ts +4 -4
- package/dist/tenant/identity-file.js +17 -8
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/principals.js +1 -1
- package/dist/tenant/reset.js +3 -3
- package/dist/tenant/runtime.d.ts +6 -1
- package/dist/tenant/runtime.js +7 -1
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +13 -13
- package/dist/tenant/sessions.js +61 -138
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/sweep.js +12 -6
- package/dist/tenant/transcript.d.ts +210 -20
- package/dist/tenant/transcript.js +62 -11
- package/dist/tenant/turn-timing.d.ts +43 -0
- package/dist/tenant/turn-timing.js +39 -0
- package/dist/tenant/types.d.ts +2 -0
- package/dist/traces/ids.d.ts +42 -0
- package/dist/traces/ids.js +85 -0
- package/dist/traces/key.d.ts +6 -0
- package/dist/traces/key.js +37 -0
- package/dist/traces/map.d.ts +113 -0
- package/dist/traces/map.js +317 -0
- package/dist/traces/options.d.ts +7 -0
- package/dist/traces/options.js +1 -0
- package/dist/traces/otlp.d.ts +42 -0
- package/dist/traces/otlp.js +84 -0
- package/dist/traces/projector.d.ts +58 -0
- package/dist/traces/projector.js +200 -0
- package/dist/traces/read.d.ts +23 -0
- package/dist/traces/read.js +16 -0
- package/dist/vault/coverage.d.ts +26 -0
- package/dist/vault/coverage.js +64 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/service.d.ts +47 -48
- package/dist/vault/service.js +321 -382
- package/dist/vault/sources.d.ts +5 -16
- package/dist/vault/sources.js +6 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +13 -13
- package/dist/api/http/routes/sandboxes.d.ts +0 -9
- package/dist/api/http/routes/sandboxes.js +0 -153
- package/dist/api/http/routes/vaults.d.ts +0 -10
- package/dist/api/http/routes/vaults.js +0 -139
- package/dist/record/sandbox.d.ts +0 -25
- package/dist/record/sandbox.js +0 -43
- package/dist/tenant/sandboxes.d.ts +0 -123
- package/dist/tenant/sandboxes.js +0 -566
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool policy (R5 step 2). Every MCP and HTTP tool call of every harness is decided as core
|
|
3
|
+
* journals it (`harness-api/record.ts`), which each harness must do before it sends a call:
|
|
4
|
+
* `deny` fails it to the model as `tool.denied`, unsent; `ask` holds the row until a person
|
|
5
|
+
* approves it on the session; `allow` runs it. The Tool Gate sends only a call the journal holds
|
|
6
|
+
* running (`journal.ts`), so a held or refused call cannot run, whichever harness asks.
|
|
7
|
+
*
|
|
8
|
+
* The rules are the Tenant's (`PUT /v1/tenant/tool-policy`, the `tool-policy` setting). A rule
|
|
9
|
+
* matches a call when every selector it sets matches; the strictest matching rule decides
|
|
10
|
+
* (`deny`, then `ask`, then `allow`), and a call no rule matches runs. A session pinned to a
|
|
11
|
+
* manifest saved before R5 step 2 keeps its `approval: "always"` as an ask: no session loses
|
|
12
|
+
* the approval it was opened with. A policy that cannot be read refuses every call.
|
|
13
|
+
*/
|
|
14
|
+
import { type ToolPolicy, type ToolRule } from "@nylorun/core/contracts";
|
|
15
|
+
import type { AgentManifest } from "@nylorun/core/define";
|
|
16
|
+
import type { HostEffect } from "@nylorun/harness/run";
|
|
17
|
+
import { type McpSnapshot } from "../mcp/snapshot.js";
|
|
18
|
+
import type { Tx } from "../store/types.js";
|
|
19
|
+
import type { Session } from "../tenant/context.js";
|
|
20
|
+
/** The `tenant_settings` key the policy is kept under. */
|
|
21
|
+
export declare const TOOL_POLICY_SETTING = "tool-policy";
|
|
22
|
+
/** A call as the policy reads it. */
|
|
23
|
+
export interface PolicyCall {
|
|
24
|
+
readonly kind: "mcp" | "http";
|
|
25
|
+
/** The name the model calls the tool by; a flow stage's key. */
|
|
26
|
+
readonly tool: string;
|
|
27
|
+
/** The MCP server's name. */
|
|
28
|
+
readonly server?: string;
|
|
29
|
+
/** The agent that calls it. */
|
|
30
|
+
readonly agent: string;
|
|
31
|
+
readonly input: unknown;
|
|
32
|
+
}
|
|
33
|
+
export type PolicyDecision = {
|
|
34
|
+
readonly effect: "allow";
|
|
35
|
+
} | {
|
|
36
|
+
readonly effect: "ask" | "deny";
|
|
37
|
+
readonly reason: string;
|
|
38
|
+
};
|
|
39
|
+
/** The Tenant's policy; an empty one when none was set. Throws when it cannot be read. */
|
|
40
|
+
export declare function readToolPolicy(t: Pick<Tx, "getSetting">): Promise<ToolPolicy>;
|
|
41
|
+
/**
|
|
42
|
+
* The decision for `request`, a session's MCP or HTTP tool call: the Tenant's rules and the
|
|
43
|
+
* pinned manifest's `approval`. A policy that cannot be read refuses the call.
|
|
44
|
+
*/
|
|
45
|
+
export declare function toolCallDecision(t: Pick<Tx, "getSetting">, s: Session, request: HostEffect, kind: "mcp" | "http"): Promise<PolicyDecision>;
|
|
46
|
+
/** Decides `call` by `rules`; `pinnedAsk` adds the ask of a manifest saved before R5 step 2. */
|
|
47
|
+
export declare function decide(rules: readonly ToolRule[], call: PolicyCall, pinnedAsk?: boolean): PolicyDecision;
|
|
48
|
+
/** The pinned `approval: "always"` of the call's tool, in a manifest saved before R5 step 2. */
|
|
49
|
+
export declare function pinnedApproval(manifest: AgentManifest, snapshot: McpSnapshot | undefined, request: HostEffect): boolean;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool policy (R5 step 2). Every MCP and HTTP tool call of every harness is decided as core
|
|
3
|
+
* journals it (`harness-api/record.ts`), which each harness must do before it sends a call:
|
|
4
|
+
* `deny` fails it to the model as `tool.denied`, unsent; `ask` holds the row until a person
|
|
5
|
+
* approves it on the session; `allow` runs it. The Tool Gate sends only a call the journal holds
|
|
6
|
+
* running (`journal.ts`), so a held or refused call cannot run, whichever harness asks.
|
|
7
|
+
*
|
|
8
|
+
* The rules are the Tenant's (`PUT /v1/tenant/tool-policy`, the `tool-policy` setting). A rule
|
|
9
|
+
* matches a call when every selector it sets matches; the strictest matching rule decides
|
|
10
|
+
* (`deny`, then `ask`, then `allow`), and a call no rule matches runs. A session pinned to a
|
|
11
|
+
* manifest saved before R5 step 2 keeps its `approval: "always"` as an ask: no session loses
|
|
12
|
+
* the approval it was opened with. A policy that cannot be read refuses every call.
|
|
13
|
+
*/
|
|
14
|
+
import { ToolPolicySchema } from "@nylorun/core/contracts";
|
|
15
|
+
import { mcpToolOf, mcpToolSettingsIn } from "../mcp/snapshot.js";
|
|
16
|
+
import { canonical } from "../store/canonical.js";
|
|
17
|
+
import { turnManifestOf } from "../tenant/session.js";
|
|
18
|
+
import { declaredHttpTool } from "./http-tool.js";
|
|
19
|
+
/** The `tenant_settings` key the policy is kept under. */
|
|
20
|
+
export const TOOL_POLICY_SETTING = "tool-policy";
|
|
21
|
+
const STRICTNESS = { allow: 0, ask: 1, deny: 2 };
|
|
22
|
+
/** The Tenant's policy; an empty one when none was set. Throws when it cannot be read. */
|
|
23
|
+
export async function readToolPolicy(t) {
|
|
24
|
+
const raw = await t.getSetting(TOOL_POLICY_SETTING);
|
|
25
|
+
if (raw === undefined)
|
|
26
|
+
return { rules: [], updatedAt: null };
|
|
27
|
+
return ToolPolicySchema.parse(JSON.parse(raw));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The decision for `request`, a session's MCP or HTTP tool call: the Tenant's rules and the
|
|
31
|
+
* pinned manifest's `approval`. A policy that cannot be read refuses the call.
|
|
32
|
+
*/
|
|
33
|
+
export async function toolCallDecision(t, s, request, kind) {
|
|
34
|
+
let rules;
|
|
35
|
+
try {
|
|
36
|
+
rules = (await readToolPolicy(t)).rules;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { effect: "deny", reason: "The Tenant's tool policy could not be read, so no tool call runs" };
|
|
40
|
+
}
|
|
41
|
+
const mcp = mcpToolOf(s.mcpSnapshot, request);
|
|
42
|
+
return decide(rules, {
|
|
43
|
+
kind,
|
|
44
|
+
tool: request.toolName ?? request.key ?? request.effectId,
|
|
45
|
+
...(mcp ? { server: mcp.serverName } : {}),
|
|
46
|
+
agent: request.agent?.id ?? s.agentId,
|
|
47
|
+
input: request.input,
|
|
48
|
+
}, pinnedApproval(turnManifestOf(s), s.mcpSnapshot, request));
|
|
49
|
+
}
|
|
50
|
+
/** Decides `call` by `rules`; `pinnedAsk` adds the ask of a manifest saved before R5 step 2. */
|
|
51
|
+
export function decide(rules, call, pinnedAsk = false) {
|
|
52
|
+
let strictest = pinnedAsk
|
|
53
|
+
? { effect: "ask", reason: `The agent's manifest asks for approval of '${call.tool}'` }
|
|
54
|
+
: undefined;
|
|
55
|
+
for (const rule of rules) {
|
|
56
|
+
if (!matches(rule, call))
|
|
57
|
+
continue;
|
|
58
|
+
if (strictest && STRICTNESS[rule.effect] <= STRICTNESS[strictest.effect])
|
|
59
|
+
continue;
|
|
60
|
+
strictest = { effect: rule.effect, reason: rule.reason ?? describe(rule) };
|
|
61
|
+
}
|
|
62
|
+
if (!strictest || strictest.effect === "allow")
|
|
63
|
+
return { effect: "allow" };
|
|
64
|
+
return { effect: strictest.effect, reason: strictest.reason };
|
|
65
|
+
}
|
|
66
|
+
/** The pinned `approval: "always"` of the call's tool, in a manifest saved before R5 step 2. */
|
|
67
|
+
export function pinnedApproval(manifest, snapshot, request) {
|
|
68
|
+
const mcp = mcpToolOf(snapshot, request);
|
|
69
|
+
if (mcp)
|
|
70
|
+
return mcpToolSettingsIn(manifest, mcp).approval === "always";
|
|
71
|
+
if (request.capabilityId === undefined)
|
|
72
|
+
return false;
|
|
73
|
+
return declaredHttpTool(manifest, request.agent?.id, request.capabilityId, request.toolName)?.approval === "always";
|
|
74
|
+
}
|
|
75
|
+
function matches(rule, call) {
|
|
76
|
+
if (rule.kind !== undefined && rule.kind !== call.kind)
|
|
77
|
+
return false;
|
|
78
|
+
if (rule.tool !== undefined && !glob(rule.tool).test(call.tool))
|
|
79
|
+
return false;
|
|
80
|
+
if (rule.server !== undefined && (call.server === undefined || !glob(rule.server).test(call.server)))
|
|
81
|
+
return false;
|
|
82
|
+
if (rule.agent !== undefined && !glob(rule.agent).test(call.agent))
|
|
83
|
+
return false;
|
|
84
|
+
for (const [path, condition] of Object.entries(rule.when ?? {}))
|
|
85
|
+
if (!holds(condition, fieldOf(call.input, path)))
|
|
86
|
+
return false;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
/** A pattern where `*` is any run of characters, matched whole. */
|
|
90
|
+
function glob(pattern) {
|
|
91
|
+
return new RegExp(`^${pattern.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`);
|
|
92
|
+
}
|
|
93
|
+
/** The value at a dotted `path` of `input`, or undefined. */
|
|
94
|
+
function fieldOf(input, path) {
|
|
95
|
+
let value = input;
|
|
96
|
+
for (const key of path.split(".")) {
|
|
97
|
+
if (!value || typeof value !== "object")
|
|
98
|
+
return undefined;
|
|
99
|
+
value = value[key];
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
function holds(condition, value) {
|
|
104
|
+
if (value === undefined)
|
|
105
|
+
return false;
|
|
106
|
+
if (condition.equals !== undefined && canonical(value) !== canonical(condition.equals))
|
|
107
|
+
return false;
|
|
108
|
+
if (condition.in !== undefined && !condition.in.some((item) => canonical(item) === canonical(value)))
|
|
109
|
+
return false;
|
|
110
|
+
const number = typeof value === "number" ? value : undefined;
|
|
111
|
+
if (condition.gt !== undefined && !(number !== undefined && number > condition.gt))
|
|
112
|
+
return false;
|
|
113
|
+
if (condition.gte !== undefined && !(number !== undefined && number >= condition.gte))
|
|
114
|
+
return false;
|
|
115
|
+
if (condition.lt !== undefined && !(number !== undefined && number < condition.lt))
|
|
116
|
+
return false;
|
|
117
|
+
if (condition.lte !== undefined && !(number !== undefined && number <= condition.lte))
|
|
118
|
+
return false;
|
|
119
|
+
if (condition.matches !== undefined && !(typeof value === "string" && new RegExp(condition.matches).test(value)))
|
|
120
|
+
return false;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
/** What a rule with no `reason` says. */
|
|
124
|
+
function describe(rule) {
|
|
125
|
+
const what = [
|
|
126
|
+
rule.tool === undefined ? undefined : `tool '${rule.tool}'`,
|
|
127
|
+
rule.server === undefined ? undefined : `server '${rule.server}'`,
|
|
128
|
+
rule.kind === undefined ? undefined : `${rule.kind} tools`,
|
|
129
|
+
rule.agent === undefined ? undefined : `agent '${rule.agent}'`,
|
|
130
|
+
rule.when === undefined ? undefined : `input ${Object.keys(rule.when).join(", ")}`,
|
|
131
|
+
].filter((part) => part !== undefined);
|
|
132
|
+
const verb = rule.effect === "deny" ? "refuses" : "asks a person about";
|
|
133
|
+
return `The Tenant's tool policy ${verb} ${what.length === 0 ? "every tool call" : `calls of ${what.join(", ")}`}`;
|
|
134
|
+
}
|
|
@@ -29,15 +29,15 @@ export interface TenantVault {
|
|
|
29
29
|
session(sessionId: string): Promise<Session | undefined>;
|
|
30
30
|
/**
|
|
31
31
|
* The authorization of one request to a session's remote MCP server or HTTP tool: its
|
|
32
|
-
* credential from the session's
|
|
32
|
+
* credential from the vault of the session's Environment.
|
|
33
33
|
*/
|
|
34
34
|
authorizeMcp(sessionId: string, request: McpCredentialRequest): Promise<AuthorizeResult>;
|
|
35
35
|
/** Vault writes and token signing with the Tenant's vault key (the keys service, F4.2). */
|
|
36
36
|
keys(): Keys;
|
|
37
|
-
/** Whether
|
|
38
|
-
environmentSecretBound(
|
|
37
|
+
/** Whether the Environment's vault has an `environment_secret` bound to `host` (R2c). */
|
|
38
|
+
environmentSecretBound(environmentId: string, host: string): Promise<boolean>;
|
|
39
39
|
/** The header an `environment_secret` sets on one request to `host` (R2c). */
|
|
40
|
-
releaseEnvironmentSecret(
|
|
40
|
+
releaseEnvironmentSecret(environmentId: string, host: string): ReturnType<VaultService["releaseEnvironmentSecret"]>;
|
|
41
41
|
}
|
|
42
42
|
export interface TenantVaults {
|
|
43
43
|
/**
|
|
@@ -60,8 +60,8 @@ export interface TenantVaultsOptions {
|
|
|
60
60
|
}
|
|
61
61
|
export declare function createTenantVaults(options: TenantVaultsOptions): TenantVaults;
|
|
62
62
|
/**
|
|
63
|
-
* A request to a session's remote MCP server, authorized from
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* A request to a session's remote MCP server, authorized from its Environment's vault: what the
|
|
64
|
+
* loop does in its own process (`tenant/effects.ts` `authorize`). The Environment comes from the
|
|
65
|
+
* session row.
|
|
66
66
|
*/
|
|
67
67
|
export declare function authorizeSessionMcp(vault: Pick<VaultService, "authorize">, session: (sessionId: string) => Promise<Session | undefined>, sessionId: string, request: McpCredentialRequest): Promise<AuthorizeResult>;
|
|
@@ -52,8 +52,8 @@ export function createTenantVaults(options) {
|
|
|
52
52
|
session,
|
|
53
53
|
authorizeMcp: (sessionId, request) => authorizeSessionMcp(credentials, session, sessionId, request),
|
|
54
54
|
keys: () => keys,
|
|
55
|
-
environmentSecretBound: (
|
|
56
|
-
releaseEnvironmentSecret: (
|
|
55
|
+
environmentSecretBound: (environmentId, host) => credentials.environmentSecretBound(environmentId, host),
|
|
56
|
+
releaseEnvironmentSecret: (environmentId, host) => credentials.releaseEnvironmentSecret({ environmentId, host }),
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
/** The database's Tenant, once its schema is at this build's version. */
|
|
@@ -81,9 +81,9 @@ export function createTenantVaults(options) {
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
|
-
* A request to a session's remote MCP server, authorized from
|
|
85
|
-
*
|
|
86
|
-
*
|
|
84
|
+
* A request to a session's remote MCP server, authorized from its Environment's vault: what the
|
|
85
|
+
* loop does in its own process (`tenant/effects.ts` `authorize`). The Environment comes from the
|
|
86
|
+
* session row.
|
|
87
87
|
*/
|
|
88
88
|
export async function authorizeSessionMcp(vault, session, sessionId, request) {
|
|
89
89
|
const found = await session(sessionId);
|
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
* - The gate also writes a `tool_crossings` row before it calls the server, and the answer
|
|
9
9
|
* after. A re-send that finds the answer gets it. One that finds a row without an answer,
|
|
10
10
|
* for a call this gateway is not running, was lost with an earlier gateway (a restart mid
|
|
11
|
-
* call): it
|
|
11
|
+
* call): it is started again under the same key (R5.3), so the tool sees the key twice and
|
|
12
|
+
* a tool that honors it acts once. Core counts the sends and stops at its cap (R5.4).
|
|
13
|
+
* - A re-send that another gateway process started or settled between this one's read and its
|
|
14
|
+
* write answers `{uncertain: true}`: not answered here, so the caller sends it again later.
|
|
12
15
|
*
|
|
13
|
-
* A cancel aborts the call and
|
|
14
|
-
*
|
|
16
|
+
* A cancel aborts the call and settles its row as cancelled: it never runs again under its key.
|
|
17
|
+
* A shutdown leaves the row open, so the next gateway starts it again. Settled rows are deleted
|
|
18
|
+
* a day later (`prune`); a re-send after that runs the call again, which at least once allows.
|
|
15
19
|
*/
|
|
16
20
|
import type { Logger } from "../tenant/types.js";
|
|
17
21
|
import { type InflightCallsOptions, type InflightOwner } from "./inflight.js";
|
|
@@ -29,8 +33,9 @@ export interface ToolCalls {
|
|
|
29
33
|
*/
|
|
30
34
|
run(tenantId: string | undefined, key: string, hash: string, call: (signal: AbortSignal) => Promise<ToolCallAnswer>, owner?: InflightOwner): Promise<ToolCallAnswer>;
|
|
31
35
|
/**
|
|
32
|
-
* Aborts the call under `key
|
|
33
|
-
* when the running call is another session's (nothing
|
|
36
|
+
* Aborts the call under `key` and settles its crossing as cancelled: it is never run again
|
|
37
|
+
* under that key. With `sessionId`, false when the running call is another session's (nothing
|
|
38
|
+
* is aborted).
|
|
34
39
|
*/
|
|
35
40
|
cancel(key: string, sessionId?: string): boolean;
|
|
36
41
|
/** Aborts every running call (gateway shutdown). */
|
package/dist/gates/tool-calls.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { createInflightCalls, InflightConflict, } from "./inflight.js";
|
|
2
2
|
/** How long a settled crossing is kept. */
|
|
3
3
|
export const TOOL_CROSSING_TTL_MS = 24 * 60 * 60_000;
|
|
4
|
-
const
|
|
4
|
+
const CANCELLED = {
|
|
5
|
+
ok: false,
|
|
6
|
+
error: { message: "The call was cancelled while it ran; it may have reached its server" },
|
|
7
|
+
};
|
|
8
|
+
const ELSEWHERE = {
|
|
5
9
|
ok: false,
|
|
6
10
|
error: {
|
|
7
11
|
uncertain: true,
|
|
8
|
-
message: "
|
|
12
|
+
message: "Another gateway process is running this tool call; send it again",
|
|
9
13
|
},
|
|
10
14
|
};
|
|
11
15
|
export function createToolCalls(options) {
|
|
@@ -16,7 +20,7 @@ export function createToolCalls(options) {
|
|
|
16
20
|
return {
|
|
17
21
|
async run(tenantId, key, hash, call, owner) {
|
|
18
22
|
if (inflight.has(key))
|
|
19
|
-
return inflight.run(key, hash, () => Promise.resolve(
|
|
23
|
+
return inflight.run(key, hash, () => Promise.resolve(ELSEWHERE), owner);
|
|
20
24
|
const vault = await vaults.open(tenantId);
|
|
21
25
|
const store = vault.store;
|
|
22
26
|
const row = await store.tx((t) => t.toolCrossing(key));
|
|
@@ -25,16 +29,19 @@ export function createToolCalls(options) {
|
|
|
25
29
|
throw new InflightConflict(`A different request already ran under ${key}`);
|
|
26
30
|
if (row.answer !== null && row.answer !== undefined)
|
|
27
31
|
return row.answer;
|
|
28
|
-
// Started here a moment ago
|
|
32
|
+
// Started here a moment ago: join it.
|
|
29
33
|
if (inflight.has(key))
|
|
30
|
-
return inflight.run(key, hash, () => Promise.resolve(
|
|
31
|
-
|
|
34
|
+
return inflight.run(key, hash, () => Promise.resolve(ELSEWHERE), owner);
|
|
35
|
+
// Otherwise it was lost with an earlier gateway: it starts again below (R5.3).
|
|
32
36
|
}
|
|
33
37
|
return inflight.run(key, hash, async (signal) => {
|
|
34
|
-
const
|
|
35
|
-
|
|
38
|
+
const startedAt = iso();
|
|
39
|
+
const started = await store.tx((t) => row
|
|
40
|
+
? t.restartToolCrossing({ key, hash, startedAt })
|
|
41
|
+
: t.startToolCrossing({ key, hash, startedAt }));
|
|
42
|
+
// Another gateway process started or settled it between the read and now.
|
|
36
43
|
if (!started)
|
|
37
|
-
return
|
|
44
|
+
return ELSEWHERE;
|
|
38
45
|
const answer = await call(signal);
|
|
39
46
|
// A cancel or a shutdown: the call may have reached the server; the row stays open.
|
|
40
47
|
if (signal.aborted)
|
|
@@ -48,7 +55,18 @@ export function createToolCalls(options) {
|
|
|
48
55
|
return answer;
|
|
49
56
|
}, owner);
|
|
50
57
|
},
|
|
51
|
-
cancel
|
|
58
|
+
cancel(key, sessionId) {
|
|
59
|
+
if (!inflight.cancel(key, sessionId))
|
|
60
|
+
return false;
|
|
61
|
+
void vaults
|
|
62
|
+
.open(undefined)
|
|
63
|
+
.then((vault) => vault.store.tx((t) => t.settleToolCrossing(key, CANCELLED, iso())))
|
|
64
|
+
.catch((error) => logger.warn("tool_crossing_unsettled", {
|
|
65
|
+
effect: key,
|
|
66
|
+
message: error instanceof Error ? error.message : String(error),
|
|
67
|
+
}));
|
|
68
|
+
return true;
|
|
69
|
+
},
|
|
52
70
|
close: () => inflight.close(),
|
|
53
71
|
async prune() {
|
|
54
72
|
try {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { ToolGate } from "./tool-gate.js";
|
|
2
2
|
import type { RunTokens } from "../tenant/run-grants.js";
|
|
3
|
+
/**
|
|
4
|
+
* A keyed call whose answer the hop lost (R5.3): the gate never got it, the connection broke,
|
|
5
|
+
* the gate answered `5xx`, or another gateway process holds the call. Sending it again with the
|
|
6
|
+
* same key is safe: the gate joins the call, answers it from its crossing, or runs it again.
|
|
7
|
+
*/
|
|
8
|
+
export declare class GateCallLost extends Error {
|
|
9
|
+
readonly name = "GateCallLost";
|
|
10
|
+
}
|
|
3
11
|
export interface HttpToolGateOptions {
|
|
4
12
|
/** The gates service, e.g. `http://gateway:4100` (`NYLORUN_GATES_URL`). */
|
|
5
13
|
readonly url: string;
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
* Failures keep the meaning they had in the loop's own process:
|
|
9
9
|
* - An MCP request that fails throws, so the pool reports a diagnostic. A tool call's coded
|
|
10
10
|
* failure (R2b C7) throws the `McpCallFailed` the loop's own call would have, which becomes a
|
|
11
|
-
* failed outcome the model sees.
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
*
|
|
11
|
+
* failed outcome the model sees.
|
|
12
|
+
* - An HTTP tool call throws only for the hop: its own failures are outcomes.
|
|
13
|
+
* - A hop that lost the answer (the gate unreachable, the connection lost, a `5xx`, or a call
|
|
14
|
+
* another gateway process holds) throws `GateCallLost`: sending the call again with the same
|
|
15
|
+
* key joins it, collects its answer, or runs it again (R5.3). A gate's refusal (`4xx`) throws
|
|
16
|
+
* a plain error.
|
|
15
17
|
*
|
|
16
18
|
* Credentials (F5): a session's MCP requests and keyed cancels carry its run token, read per
|
|
17
19
|
* request from the advance's grant, and leave the session out of the body: the gate takes it
|
|
@@ -26,6 +28,14 @@ import { McpCallFailed, } from "../mcp/connect.js";
|
|
|
26
28
|
import { TENANT_HEADER } from "./contract.js";
|
|
27
29
|
import { GATE_CLIENT_TIMEOUT_MS } from "./http-client.js";
|
|
28
30
|
import { HTTP_CALLS_PATH, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, TOOL_CALLS_PATH, } from "./tool-contract.js";
|
|
31
|
+
/**
|
|
32
|
+
* A keyed call whose answer the hop lost (R5.3): the gate never got it, the connection broke,
|
|
33
|
+
* the gate answered `5xx`, or another gateway process holds the call. Sending it again with the
|
|
34
|
+
* same key is safe: the gate joins the call, answers it from its crossing, or runs it again.
|
|
35
|
+
*/
|
|
36
|
+
export class GateCallLost extends Error {
|
|
37
|
+
name = "GateCallLost";
|
|
38
|
+
}
|
|
29
39
|
/** How long a close or a cancel may take; neither changes an outcome. */
|
|
30
40
|
const SHORT_TIMEOUT_MS = 2_000;
|
|
31
41
|
export function httpToolGate(options) {
|
|
@@ -111,8 +121,7 @@ export function httpToolGate(options) {
|
|
|
111
121
|
}
|
|
112
122
|
/**
|
|
113
123
|
* The answer of an MCP or HTTP tool request, or a throw: a coded MCP failure (`McpCallFailed`),
|
|
114
|
-
*
|
|
115
|
-
* `uncertain`.
|
|
124
|
+
* a lost hop (`GateCallLost`, sent again), or a gate's refusal.
|
|
116
125
|
*/
|
|
117
126
|
async function answer(path, scope, request) {
|
|
118
127
|
const result = await exchange(path, scope.body, {
|
|
@@ -123,8 +132,10 @@ export function httpToolGate(options) {
|
|
|
123
132
|
if (result.kind === "aborted")
|
|
124
133
|
throw result.reason ?? new Error("aborted");
|
|
125
134
|
if (result.kind !== "answer")
|
|
126
|
-
throw new
|
|
135
|
+
throw new GateCallLost(result.message);
|
|
127
136
|
const parsed = parse(result.text);
|
|
137
|
+
if (result.status >= 500)
|
|
138
|
+
throw new GateCallLost(gateRefusal(result.status, parsed));
|
|
128
139
|
if (result.status !== 200 || !parsed || !("ok" in parsed))
|
|
129
140
|
throw new Error(gateRefusal(result.status, parsed));
|
|
130
141
|
if (parsed.ok)
|
|
@@ -176,7 +187,9 @@ export function httpToolGate(options) {
|
|
|
176
187
|
async callHttp(call, signal) {
|
|
177
188
|
const token = options.runTokens?.token(call.tool.sessionId);
|
|
178
189
|
const { sessionId: _, ...unnamed } = call.tool;
|
|
179
|
-
|
|
190
|
+
// The gate reads the call's span context from its journal row (R7.4), not from the caller.
|
|
191
|
+
const { traceparent: _traceparent, ...sent } = call;
|
|
192
|
+
const body = { ...sent, tool: token ? unnamed : { ...call.tool } };
|
|
180
193
|
return answered(HTTP_CALLS_PATH, { body, ...(token ? { bearer: token } : {}) }, { signal, idempotencyKey: call.effectId });
|
|
181
194
|
},
|
|
182
195
|
async cancel(request) {
|
|
@@ -190,10 +203,13 @@ export function httpToolGate(options) {
|
|
|
190
203
|
}
|
|
191
204
|
/** An MCP failure the gate reported, as the loop's own MCP code would have thrown it. */
|
|
192
205
|
function mcpError(error) {
|
|
193
|
-
//
|
|
194
|
-
if (error.
|
|
206
|
+
// Held by another gateway process: send it again (R5.3).
|
|
207
|
+
if (error.uncertain)
|
|
208
|
+
return new GateCallLost(error.message);
|
|
209
|
+
// Coded (R2b C7): the model sees it.
|
|
210
|
+
if (error.failure)
|
|
195
211
|
return McpCallFailed.from(error.failure);
|
|
196
|
-
const thrown = new Error(error.
|
|
212
|
+
const thrown = new Error(error.message);
|
|
197
213
|
if (error.code !== undefined)
|
|
198
214
|
thrown.code = error.code;
|
|
199
215
|
if (error.credentialIds !== undefined)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Tool Gate's wire format (blueprint §12, F4.1): the routes the loop calls on the gates
|
|
3
|
-
* service for remote MCP servers and HTTP tools.
|
|
4
|
-
*
|
|
3
|
+
* service for remote MCP servers and HTTP tools. Like the model call route (`contract.ts`): same
|
|
4
|
+
* optional Tenant header, bodies published in `@nylorun/core/harness-api` (R4.5).
|
|
5
5
|
*
|
|
6
6
|
* Credentials (F5). A session's MCP requests carry its run token, and the gate takes the
|
|
7
7
|
* session from it: `server.sessionId` is then absent (or the token's). Core's credential
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
* answer, not a gate error. A tool call's failure is coded (`error.failure`, R2b C7), and neither
|
|
26
26
|
* a result nor a failure carries a credential value the gate sent (C8). An answer past 8 MiB is
|
|
27
27
|
* `mcp.too-large` (C11), as an HTTP tool's is `http.too-large`; core shapes what fits into a
|
|
28
|
-
* result the model can take (`tenant/tool-results.ts`). A keyed call whose
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* result the model can take (`tenant/tool-results.ts`). A keyed call whose earlier attempt was
|
|
29
|
+
* lost with the gateway runs again under the same key (R5.3); one another gateway process holds
|
|
30
|
+
* answers `{ok: false, error: {uncertain: true}}`, and the caller sends it again.
|
|
31
31
|
*
|
|
32
32
|
* HTTP tools (R2 M3). `POST /nylorun/v1/http-calls` `{tool, effectId, turnId, input}`: one call
|
|
33
33
|
* of an HTTP tool. The loop names the tool (`{sessionId?, agentId?, capabilityId, toolName}`), or a
|
|
@@ -36,67 +36,10 @@
|
|
|
36
36
|
* (`gates/http-tool.ts`). A run token's turn is the call's; `turnId` counts only under core's
|
|
37
37
|
* credential. Keyed like a tool call, and cancelled by the same route. It answers
|
|
38
38
|
* `200 {ok: true, result}` with the tool outcome (a failed request is a failed outcome the
|
|
39
|
-
* model sees), or `{ok: false, error: {uncertain: true}}` for a call
|
|
39
|
+
* model sees), or `{ok: false, error: {uncertain: true}}` for a call another gateway process holds.
|
|
40
40
|
*/
|
|
41
|
-
import { z } from "zod";
|
|
42
41
|
import type { McpCallFailure } from "../mcp/connect.js";
|
|
43
|
-
export
|
|
44
|
-
export declare const MCP_LIST_PATH = "/nylorun/v1/mcp/list";
|
|
45
|
-
export declare const MCP_CLOSE_PATH = "/nylorun/v1/mcp/close";
|
|
46
|
-
export declare const TOOL_CALLS_PATH = "/nylorun/v1/tool-calls";
|
|
47
|
-
export declare const HTTP_CALLS_PATH = "/nylorun/v1/http-calls";
|
|
48
|
-
/** Largest tool call body the gate reads. */
|
|
49
|
-
export declare const MAX_TOOL_BODY_BYTES: number;
|
|
50
|
-
export declare const McpServerRefSchema: z.ZodObject<{
|
|
51
|
-
sessionId: z.ZodOptional<z.ZodString>;
|
|
52
|
-
agentId: z.ZodOptional<z.ZodString>;
|
|
53
|
-
capabilityId: z.ZodString;
|
|
54
|
-
serverName: z.ZodString;
|
|
55
|
-
}, z.core.$strip>;
|
|
56
|
-
export declare const McpServerBodySchema: z.ZodObject<{
|
|
57
|
-
server: z.ZodObject<{
|
|
58
|
-
sessionId: z.ZodOptional<z.ZodString>;
|
|
59
|
-
agentId: z.ZodOptional<z.ZodString>;
|
|
60
|
-
capabilityId: z.ZodString;
|
|
61
|
-
serverName: z.ZodString;
|
|
62
|
-
}, z.core.$strip>;
|
|
63
|
-
}, z.core.$strip>;
|
|
64
|
-
export declare const McpListBodySchema: z.ZodObject<{
|
|
65
|
-
server: z.ZodObject<{
|
|
66
|
-
sessionId: z.ZodOptional<z.ZodString>;
|
|
67
|
-
agentId: z.ZodOptional<z.ZodString>;
|
|
68
|
-
capabilityId: z.ZodString;
|
|
69
|
-
serverName: z.ZodString;
|
|
70
|
-
}, z.core.$strip>;
|
|
71
|
-
cursor: z.ZodOptional<z.ZodString>;
|
|
72
|
-
}, z.core.$strip>;
|
|
73
|
-
export declare const ToolCallBodySchema: z.ZodObject<{
|
|
74
|
-
server: z.ZodObject<{
|
|
75
|
-
sessionId: z.ZodOptional<z.ZodString>;
|
|
76
|
-
agentId: z.ZodOptional<z.ZodString>;
|
|
77
|
-
capabilityId: z.ZodString;
|
|
78
|
-
serverName: z.ZodString;
|
|
79
|
-
}, z.core.$strip>;
|
|
80
|
-
effectId: z.ZodString;
|
|
81
|
-
name: z.ZodString;
|
|
82
|
-
arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
83
|
-
}, z.core.$strip>;
|
|
84
|
-
export type ToolCallBody = z.infer<typeof ToolCallBodySchema>;
|
|
85
|
-
export declare const HttpCallBodySchema: z.ZodObject<{
|
|
86
|
-
tool: z.ZodUnion<readonly [z.ZodObject<{
|
|
87
|
-
sessionId: z.ZodOptional<z.ZodString>;
|
|
88
|
-
agentId: z.ZodOptional<z.ZodString>;
|
|
89
|
-
capabilityId: z.ZodString;
|
|
90
|
-
toolName: z.ZodString;
|
|
91
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
92
|
-
sessionId: z.ZodOptional<z.ZodString>;
|
|
93
|
-
stage: z.ZodString;
|
|
94
|
-
}, z.core.$strip>]>;
|
|
95
|
-
effectId: z.ZodString;
|
|
96
|
-
turnId: z.ZodString;
|
|
97
|
-
input: z.ZodUnknown;
|
|
98
|
-
}, z.core.$strip>;
|
|
99
|
-
export type HttpCallBody = z.infer<typeof HttpCallBodySchema>;
|
|
42
|
+
export { HTTP_CALLS_PATH, HttpCallBodySchema, MAX_TOOL_BODY_BYTES, 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";
|
|
100
43
|
/** Why an MCP request failed, as the loop's diagnostics read it (`mcp/connect.ts`). */
|
|
101
44
|
export interface McpGateError {
|
|
102
45
|
readonly message: string;
|
|
@@ -104,13 +47,12 @@ export interface McpGateError {
|
|
|
104
47
|
readonly code?: number;
|
|
105
48
|
/** The vault credentials a refused authorization names. */
|
|
106
49
|
readonly credentialIds?: readonly string[];
|
|
107
|
-
/**
|
|
50
|
+
/** Not answered here: another gateway process holds the keyed call. Send it again (R5.3). */
|
|
108
51
|
readonly uncertain?: boolean;
|
|
109
52
|
/**
|
|
110
53
|
* How the request failed, coded (R2b C1, C7): the loop's `McpCallFailed`, which a tool call
|
|
111
|
-
* turns into a failed outcome the model sees (`mcp.lost`
|
|
112
|
-
*
|
|
113
|
-
* credential was sent. Its message has no credential value in it (C8).
|
|
54
|
+
* turns into a failed outcome the model sees (`mcp.lost` included). `credential_rejected` for a
|
|
55
|
+
* `401`, with the server. Its message has no credential value in it (C8).
|
|
114
56
|
*/
|
|
115
57
|
readonly failure?: McpCallFailure;
|
|
116
58
|
}
|
|
@@ -1,85 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* The Tool Gate's wire format (blueprint §12, F4.1): the routes the loop calls on the gates
|
|
3
|
-
* service for remote MCP servers and HTTP tools. Internal, like the model call route
|
|
4
|
-
* (`contract.ts`): same optional Tenant header, not in the published OpenAPI documents.
|
|
5
|
-
*
|
|
6
|
-
* Credentials (F5). A session's MCP requests carry its run token, and the gate takes the
|
|
7
|
-
* session from it: `server.sessionId` is then absent (or the token's). Core's credential
|
|
8
|
-
* (`NYLORUN_GATES_TOKEN`) is accepted too, for requests made outside a run, and then
|
|
9
|
-
* `server.sessionId` names the session.
|
|
10
|
-
*
|
|
11
|
-
* Remote MCP. The gate holds the connection and the vault credential; the loop names the
|
|
12
|
-
* server, never a URL or a credential, and the gate finds it in the session's pinned manifest.
|
|
13
|
-
* - `POST /nylorun/v1/mcp/connect` `{server}`: opens the server's connection (vault
|
|
14
|
-
* authorization included), or keeps the open one. Its result is `{instructions}` when the
|
|
15
|
-
* server gave instructions at `initialize` (R2b C10, for the note on its deferred tools), else
|
|
16
|
-
* `null`.
|
|
17
|
-
* - `POST /nylorun/v1/mcp/list` `{server, cursor?}`: one page of `tools/list`.
|
|
18
|
-
* - `POST /nylorun/v1/tool-calls` `{server, effectId, name, arguments}`: one
|
|
19
|
-
* `tools/call`. With an `Idempotency-Key` (the effect id) the call outlives its client and runs
|
|
20
|
-
* once (G3); a re-send joins it or gets its outcome.
|
|
21
|
-
* - `POST /nylorun/v1/tool-calls/{key}/cancel`: aborts a keyed call; `204` either way, `403
|
|
22
|
-
* gate_forbidden` for another session's call under a run token.
|
|
23
|
-
* - `POST /nylorun/v1/mcp/close` `{server}`: closes the server's connection; `204`.
|
|
24
|
-
* Each answers `200 {ok: true, result}` or `200 {ok: false, error}`: an MCP failure is an
|
|
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 8 MiB is
|
|
27
|
-
* `mcp.too-large` (C11), as an HTTP tool's is `http.too-large`; core shapes what fits into a
|
|
28
|
-
* result the model can take (`tenant/tool-results.ts`). A keyed call whose
|
|
29
|
-
* earlier attempt was lost with the gateway answers `{ok: false, error: {uncertain: true}}` and
|
|
30
|
-
* is never run again.
|
|
31
|
-
*
|
|
32
|
-
* 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?, capabilityId, toolName}`), or a
|
|
34
|
-
* flow's HTTP stage or HTTP verifier by its stage key (`{sessionId?, stage}`); the gate finds its
|
|
35
|
-
* URL, method and credential in the session's pinned manifest
|
|
36
|
-
* (`gates/http-tool.ts`). A run token's turn is the call's; `turnId` counts only under core's
|
|
37
|
-
* credential. Keyed like a tool call, and cancelled by the same route. It answers
|
|
38
|
-
* `200 {ok: true, result}` with the tool outcome (a failed request is a failed outcome the
|
|
39
|
-
* model sees), or `{ok: false, error: {uncertain: true}}` for a call lost with the gateway.
|
|
40
|
-
*/
|
|
41
|
-
import { z } from "zod";
|
|
42
|
-
export const MCP_CONNECT_PATH = "/nylorun/v1/mcp/connect";
|
|
43
|
-
export const MCP_LIST_PATH = "/nylorun/v1/mcp/list";
|
|
44
|
-
export const MCP_CLOSE_PATH = "/nylorun/v1/mcp/close";
|
|
45
|
-
export const TOOL_CALLS_PATH = "/nylorun/v1/tool-calls";
|
|
46
|
-
export const HTTP_CALLS_PATH = "/nylorun/v1/http-calls";
|
|
47
|
-
/** Largest tool call body the gate reads. */
|
|
48
|
-
export const MAX_TOOL_BODY_BYTES = 8 * 1024 * 1024;
|
|
49
|
-
export const McpServerRefSchema = z.object({
|
|
50
|
-
/** Required with core's credential; a run token names the session itself. */
|
|
51
|
-
sessionId: z.string().min(1).optional(),
|
|
52
|
-
agentId: z.string().min(1).optional(),
|
|
53
|
-
capabilityId: z.string().min(1),
|
|
54
|
-
serverName: z.string().min(1),
|
|
55
|
-
});
|
|
56
|
-
export const McpServerBodySchema = z.object({ server: McpServerRefSchema });
|
|
57
|
-
export const McpListBodySchema = z.object({
|
|
58
|
-
server: McpServerRefSchema,
|
|
59
|
-
cursor: z.string().optional(),
|
|
60
|
-
});
|
|
61
|
-
export const ToolCallBodySchema = z.object({
|
|
62
|
-
server: McpServerRefSchema,
|
|
63
|
-
effectId: z.string().min(1),
|
|
64
|
-
name: z.string().min(1),
|
|
65
|
-
arguments: z.record(z.string(), z.unknown()),
|
|
66
|
-
});
|
|
67
|
-
export const HttpCallBodySchema = z.object({
|
|
68
|
-
tool: z.union([
|
|
69
|
-
z.object({
|
|
70
|
-
/** Required with core's credential; a run token names the session itself. */
|
|
71
|
-
sessionId: z.string().min(1).optional(),
|
|
72
|
-
agentId: z.string().min(1).optional(),
|
|
73
|
-
capabilityId: z.string().min(1),
|
|
74
|
-
toolName: z.string().min(1),
|
|
75
|
-
}),
|
|
76
|
-
z.object({
|
|
77
|
-
sessionId: z.string().min(1).optional(),
|
|
78
|
-
/** A flow's HTTP stage or HTTP verifier. */
|
|
79
|
-
stage: z.string().min(1),
|
|
80
|
-
}),
|
|
81
|
-
]),
|
|
82
|
-
effectId: z.string().min(1),
|
|
83
|
-
turnId: z.string().min(1),
|
|
84
|
-
input: z.unknown(),
|
|
85
|
-
});
|
|
1
|
+
export { HTTP_CALLS_PATH, HttpCallBodySchema, MAX_TOOL_BODY_BYTES, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, McpServerRefSchema, TOOL_CALLS_PATH, ToolCallBodySchema, } from "@nylorun/core/harness-api";
|