@nylorun/runtime 0.23.0-beta → 0.25.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 +252 -0
- package/README.md +87 -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/card.d.ts +11 -1
- package/dist/api/a2a/card.js +26 -2
- package/dist/api/a2a/endpoint.js +9 -3
- package/dist/api/a2a/protocol.d.ts +25 -2
- package/dist/api/a2a/protocol.js +20 -3
- package/dist/api/a2a/routes.d.ts +6 -1
- package/dist/api/a2a/routes.js +67 -9
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +8 -3
- package/dist/api/ag-ui/history.d.ts +6 -1
- package/dist/api/ag-ui/history.js +39 -3
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +63 -20
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +5327 -1091
- package/dist/api/components.js +40 -16
- package/dist/api/gate/keys.d.ts +2 -1
- package/dist/api/gate/keys.js +14 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +52 -3
- package/dist/api/http/app.js +17 -6
- package/dist/api/http/body.d.ts +3 -3
- package/dist/api/http/body.js +7 -7
- 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/environment-manifests.d.ts +9 -0
- package/dist/api/http/routes/environment-manifests.js +118 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +332 -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 +94 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +24 -9
- package/dist/api/http/routes/tenant.js +65 -15
- package/dist/api/inbound-files.d.ts +65 -0
- package/dist/api/inbound-files.js +115 -0
- package/dist/api/openapi.js +48 -28
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +569 -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/media-types.d.ts +15 -0
- package/dist/artifacts/media-types.js +56 -4
- package/dist/artifacts/parts.d.ts +12 -3
- package/dist/artifacts/parts.js +15 -4
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +5 -5
- package/dist/artifacts/workspace.js +7 -19
- package/dist/core/flow-host.d.ts +3 -4
- package/dist/core/flow-host.js +5 -2
- 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 +11 -44
- package/dist/core/turn-manifest.d.ts +3 -2
- package/dist/core/turn-manifest.js +29 -5
- 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 +22 -1
- package/dist/gates/egress.js +116 -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 +9 -6
- package/dist/gates/http-tool.js +11 -17
- 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 +51 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +7 -4
- 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 +132 -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 +11 -69
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +14 -7
- package/dist/harness/calls.js +30 -24
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +24 -5
- package/dist/harness/pod.d.ts +39 -0
- package/dist/harness/pod.js +46 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +17 -9
- package/dist/harness/setup.d.ts +29 -0
- package/dist/harness/setup.js +174 -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 +180 -50
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +35 -4
- package/dist/harness-api/server.js +130 -17
- 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 +105 -0
- package/dist/harness-api/tool-journal.js +132 -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 +12 -7
- package/dist/harness-api/workspace.js +47 -25
- 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 +62 -2
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +13 -6
- package/dist/keys/keys.js +9 -8
- package/dist/management-openapi.json +1264 -2171
- package/dist/mcp/connect.d.ts +26 -22
- package/dist/mcp/connect.js +30 -34
- package/dist/mcp/pool.d.ts +3 -6
- package/dist/mcp/pool.js +14 -28
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -21
- package/dist/mcp/snapshot.d.ts +16 -24
- package/dist/mcp/snapshot.js +33 -45
- package/dist/model/capabilities.d.ts +26 -0
- package/dist/model/capabilities.js +44 -0
- package/dist/model/catalog.d.ts +34 -3
- package/dist/model/catalog.js +38 -2
- package/dist/model/models.d.ts +2 -0
- package/dist/model/models.js +1 -1
- package/dist/model/native-blocks.d.ts +35 -0
- package/dist/model/native-blocks.js +170 -0
- package/dist/model/pi-model.js +114 -94
- package/dist/openapi.json +12822 -3943
- 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 +46 -15
- package/dist/sandbox/manager.d.ts +47 -21
- package/dist/sandbox/manager.js +162 -107
- package/dist/sandbox/placement.d.ts +14 -13
- package/dist/sandbox/placement.js +31 -14
- package/dist/sandbox/pods/client.d.ts +9 -2
- package/dist/sandbox/pods/lifecycle.d.ts +36 -27
- package/dist/sandbox/pods/lifecycle.js +29 -26
- package/dist/sandbox/pods/reconcile.js +18 -12
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +17 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +37 -27
- package/dist/sandbox/resolve.js +73 -41
- package/dist/sandbox/runtime-tools.d.ts +9 -0
- package/dist/sandbox/runtime-tools.js +16 -0
- package/dist/sandbox/session-sandbox.d.ts +4 -6
- package/dist/sandbox/session-sandbox.js +47 -94
- package/dist/sandbox/setup.d.ts +64 -0
- package/dist/sandbox/setup.js +160 -0
- package/dist/sandbox/share.d.ts +21 -26
- package/dist/sandbox/share.js +36 -63
- package/dist/sandbox/skills.d.ts +8 -6
- package/dist/sandbox/skills.js +24 -23
- 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/0024_flat_manifest.sql +7 -0
- package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
- package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
- package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
- package/dist/store/postgres/reads.js +123 -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 +1126 -736
- package/dist/store/postgres/schema.js +136 -73
- package/dist/store/postgres/store.js +195 -67
- package/dist/store/types.d.ts +165 -50
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +118 -42
- package/dist/tenant/artifact-tool.d.ts +1 -1
- package/dist/tenant/artifact-tool.js +46 -26
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +115 -14
- package/dist/tenant/context.d.ts +56 -24
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +44 -29
- package/dist/tenant/environment-manifests.d.ts +57 -0
- package/dist/tenant/environment-manifests.js +200 -0
- package/dist/tenant/environments.d.ts +174 -0
- package/dist/tenant/environments.js +1044 -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/model-setting.d.ts +14 -0
- package/dist/tenant/model-setting.js +51 -0
- 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 +18 -14
- package/dist/tenant/sessions.js +99 -143
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/skill-tool.d.ts +3 -3
- package/dist/tenant/skill-tool.js +6 -11
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/sweep.js +13 -7
- package/dist/tenant/tool-results.js +1 -1
- package/dist/tenant/tool-search.d.ts +7 -11
- package/dist/tenant/tool-search.js +8 -5
- package/dist/tenant/transcript.d.ts +245 -36
- package/dist/tenant/transcript.js +64 -12
- 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 +41 -0
- package/dist/traces/ids.js +81 -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 +289 -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 +62 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/host-model.d.ts +4 -1
- package/dist/vault/host-model.js +10 -2
- package/dist/vault/service.d.ts +92 -48
- package/dist/vault/service.js +462 -376
- 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/sandbox/capability.d.ts +0 -3
- package/dist/sandbox/capability.js +0 -10
- package/dist/tenant/sandboxes.d.ts +0 -123
- package/dist/tenant/sandboxes.js +0 -566
|
@@ -17,22 +17,20 @@ export type HttpOutcome = {
|
|
|
17
17
|
readonly kind: "failed";
|
|
18
18
|
readonly code: string;
|
|
19
19
|
readonly message: string;
|
|
20
|
-
/** With `credential_rejected`: the tool's `credential
|
|
20
|
+
/** With `credential_rejected`: the tool's `credential`. */
|
|
21
21
|
readonly server?: string;
|
|
22
|
-
readonly vault?: "installation" | "user";
|
|
23
22
|
/** How many credential values the message had in it, replaced (R2b C8). */
|
|
24
23
|
readonly redacted?: number;
|
|
25
24
|
};
|
|
26
25
|
/**
|
|
27
26
|
* One declared HTTP tool of a session: what the gate finds it by in the pinned manifest. An
|
|
28
|
-
* agent's HTTP tool is named by its
|
|
27
|
+
* agent's HTTP tool is named by its agent and tool name; a flow's HTTP stage or HTTP verifier
|
|
29
28
|
* by its stage key.
|
|
30
29
|
*/
|
|
31
30
|
export type HttpToolRef = {
|
|
32
31
|
readonly sessionId: string;
|
|
33
32
|
/** The agent used as a tool that declares it; absent for the session's root agent. */
|
|
34
33
|
readonly agentId?: string;
|
|
35
|
-
readonly capabilityId: string;
|
|
36
34
|
readonly toolName: string;
|
|
37
35
|
} | {
|
|
38
36
|
readonly sessionId: string;
|
|
@@ -45,6 +43,11 @@ export interface HttpToolCall {
|
|
|
45
43
|
readonly effectId: string;
|
|
46
44
|
readonly turnId: string;
|
|
47
45
|
readonly input: unknown;
|
|
46
|
+
/**
|
|
47
|
+
* The call's span context (R7.4), sent as `traceparent`: in the gates service, from the call's
|
|
48
|
+
* journal row (`gates/journal.ts`); in process, from core's `execute` answer. Never a body field.
|
|
49
|
+
*/
|
|
50
|
+
readonly traceparent?: string;
|
|
48
51
|
}
|
|
49
52
|
/** What `runHttpTool` reads of the Tenant: the session's pinned manifest and its credentials. */
|
|
50
53
|
export interface HttpToolTenant {
|
|
@@ -53,8 +56,8 @@ export interface HttpToolTenant {
|
|
|
53
56
|
} | undefined>;
|
|
54
57
|
authorize(sessionId: string, request: McpCredentialRequest): Promise<AuthorizeResult>;
|
|
55
58
|
}
|
|
56
|
-
/** The HTTP tool `
|
|
57
|
-
export declare function declaredHttpTool(manifest: unknown, agentId: string | undefined,
|
|
59
|
+
/** The HTTP tool `toolName` of the agent `agentId` (the root when absent). */
|
|
60
|
+
export declare function declaredHttpTool(manifest: unknown, agentId: string | undefined, toolName: string | undefined): (ToolManifest & {
|
|
58
61
|
readonly http: HttpToolTarget;
|
|
59
62
|
}) | undefined;
|
|
60
63
|
/** The HTTP stage or HTTP verifier at stage key `stage` of a flow session's `manifest`. */
|
package/dist/gates/http-tool.js
CHANGED
|
@@ -20,15 +20,13 @@ import { MAX_RESPONSE_BYTES, post } from "../tenant/outbound.js";
|
|
|
20
20
|
import { credentialSecrets } from "../vault/headers.js";
|
|
21
21
|
/** How much of a failed answer's body the model sees. */
|
|
22
22
|
export const HTTP_ERROR_BODY_CHARS = 2_000;
|
|
23
|
-
/** The HTTP tool `
|
|
24
|
-
export function declaredHttpTool(manifest, agentId,
|
|
23
|
+
/** The HTTP tool `toolName` of the agent `agentId` (the root when absent). */
|
|
24
|
+
export function declaredHttpTool(manifest, agentId, toolName) {
|
|
25
25
|
const root = manifest;
|
|
26
|
-
if (!root ||
|
|
26
|
+
if (!root || typeof root !== "object" || isWorkflowManifest(root) || toolName === undefined)
|
|
27
27
|
return undefined;
|
|
28
28
|
const agent = agentId === undefined ? root : delegateManifest(root, agentId);
|
|
29
|
-
const tool = agent?.
|
|
30
|
-
.find((capability) => capability.id === capabilityId)
|
|
31
|
-
?.tools?.find((item) => item.name === toolName);
|
|
29
|
+
const tool = agent?.tools?.find((item) => item.name === toolName);
|
|
32
30
|
return tool?.http ? tool : undefined;
|
|
33
31
|
}
|
|
34
32
|
/** The HTTP stage or HTTP verifier at stage key `stage` of a flow session's `manifest`. */
|
|
@@ -44,18 +42,17 @@ export function declaredFlowHttp(manifest, stage) {
|
|
|
44
42
|
export function isHttpToolCall(manifest, request) {
|
|
45
43
|
if (request.kind !== "tool")
|
|
46
44
|
return false;
|
|
47
|
-
if (request.
|
|
45
|
+
if (request.toolName === undefined)
|
|
48
46
|
return declaredFlowHttp(manifest, request.key) !== undefined;
|
|
49
|
-
return declaredHttpTool(manifest, request.agent?.id, request.
|
|
47
|
+
return declaredHttpTool(manifest, request.agent?.id, request.toolName) !== undefined;
|
|
50
48
|
}
|
|
51
49
|
/** The ref a `tool` effect calls the Tool Gate with: its agent's HTTP tool, or its flow stage. */
|
|
52
50
|
export function httpToolRefOf(request) {
|
|
53
|
-
if (request.
|
|
51
|
+
if (request.toolName === undefined)
|
|
54
52
|
return { sessionId: request.sessionId, stage: request.key };
|
|
55
53
|
return {
|
|
56
54
|
sessionId: request.sessionId,
|
|
57
55
|
...(request.agent ? { agentId: request.agent.id } : {}),
|
|
58
|
-
capabilityId: request.capabilityId,
|
|
59
56
|
toolName: request.toolName,
|
|
60
57
|
};
|
|
61
58
|
}
|
|
@@ -138,7 +135,6 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
138
135
|
...("stage" in tool
|
|
139
136
|
? { stage: tool.stage }
|
|
140
137
|
: {
|
|
141
|
-
capability: tool.capabilityId,
|
|
142
138
|
tool: tool.toolName,
|
|
143
139
|
...(tool.agentId === undefined ? {} : { agent: tool.agentId }),
|
|
144
140
|
}),
|
|
@@ -154,7 +150,7 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
154
150
|
const agentId = "stage" in tool ? undefined : tool.agentId;
|
|
155
151
|
const declared = "stage" in tool
|
|
156
152
|
? declaredFlowHttp(session.manifest, tool.stage)
|
|
157
|
-
: declaredHttpTool(session.manifest, agentId, tool.
|
|
153
|
+
: declaredHttpTool(session.manifest, agentId, tool.toolName);
|
|
158
154
|
if (!declared)
|
|
159
155
|
return failed("http.undeclared", "stage" in tool
|
|
160
156
|
? `'${tool.stage}' is not an HTTP stage of the flow`
|
|
@@ -163,7 +159,6 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
163
159
|
let credential = {};
|
|
164
160
|
let identity;
|
|
165
161
|
let target = http;
|
|
166
|
-
let vault;
|
|
167
162
|
if (http.credential !== undefined) {
|
|
168
163
|
const authorized = await tenant.authorize(tool.sessionId, {
|
|
169
164
|
url: http.url,
|
|
@@ -172,10 +167,9 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
172
167
|
if (authorized.status === "refused")
|
|
173
168
|
return failed("http.credential", `The credential '${http.credential}' was refused: ${authorized.reason}`);
|
|
174
169
|
if (authorized.status === "unauthenticated")
|
|
175
|
-
return failed("http.credential", `The session's
|
|
170
|
+
return failed("http.credential", `The session's Environment holds no credential '${http.credential}' for ${authorized.url}`);
|
|
176
171
|
credential = authorized.headers;
|
|
177
172
|
identity = authorized.identity;
|
|
178
|
-
vault = authorized.vault;
|
|
179
173
|
// A credential with `via` sends the call there, a gateway (R2b C2, Q9).
|
|
180
174
|
if (authorized.via !== undefined)
|
|
181
175
|
target = { ...http, url: authorized.via };
|
|
@@ -187,6 +181,7 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
187
181
|
[TURN_ID_HEADER]: call.turnId,
|
|
188
182
|
[AGENT_ID_HEADER]: agentId ?? session.manifest.id,
|
|
189
183
|
"idempotency-key": call.effectId,
|
|
184
|
+
...(call.traceparent ? { traceparent: call.traceparent } : {}),
|
|
190
185
|
},
|
|
191
186
|
policy: options.policy,
|
|
192
187
|
signal: options.signal,
|
|
@@ -199,9 +194,8 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
199
194
|
return outcome;
|
|
200
195
|
return {
|
|
201
196
|
...outcome,
|
|
202
|
-
message: `The service answered HTTP 401: it rejected the credential '${http.credential}' from the
|
|
197
|
+
message: `The service answered HTTP 401: it rejected the credential '${http.credential}' from the session's Environment`,
|
|
203
198
|
server: http.credential,
|
|
204
|
-
...(vault === undefined ? {} : { vault }),
|
|
205
199
|
};
|
|
206
200
|
}
|
|
207
201
|
}
|
|
@@ -41,4 +41,9 @@ export declare function tenantModelGate(options: {
|
|
|
41
41
|
/** The Tenant's Object store, for the files a prompt names. */
|
|
42
42
|
readonly blobs?: BlobStore;
|
|
43
43
|
}): ModelGate;
|
|
44
|
+
/**
|
|
45
|
+
* Serves one call: from the fixture model while the Tenant's `model.fixture` setting is on (R4.3),
|
|
46
|
+
* else from `gate`. Read per call, so a seed applies to the next call.
|
|
47
|
+
*/
|
|
48
|
+
export declare function serve(store: SessionStore, gate: ModelGate, request: ModelGateRequest, signal: AbortSignal): ReturnType<ModelGate["call"]>;
|
|
44
49
|
export declare function inProcessModelGate(options: InProcessModelGateOptions): ModelGate;
|
package/dist/gates/in-process.js
CHANGED
|
@@ -2,6 +2,8 @@ import { piModel } from "../model/pi-model.js";
|
|
|
2
2
|
import { HostModelVault } from "../vault/host-model.js";
|
|
3
3
|
import { createMeter } from "./meter.js";
|
|
4
4
|
import { artifactFiles } from "../artifacts/files.js";
|
|
5
|
+
import { usesFixtureModel } from "../tenant/model-setting.js";
|
|
6
|
+
import { fixtureAnswer } from "./fixture-model.js";
|
|
5
7
|
/**
|
|
6
8
|
* The files a call names, from the Tenant's artifacts, limited to the call's session: in
|
|
7
9
|
* process, the loop's own request; over HTTP, the session of the run token's claims, which the
|
|
@@ -26,9 +28,18 @@ export function tenantModelGate(options) {
|
|
|
26
28
|
});
|
|
27
29
|
const meter = createMeter({ logger: options.logger });
|
|
28
30
|
return {
|
|
29
|
-
call: (request, signal) => meter.call(options.store, request, () => gate
|
|
31
|
+
call: (request, signal) => meter.call(options.store, request, () => serve(options.store, gate, request, signal)),
|
|
30
32
|
};
|
|
31
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Serves one call: from the fixture model while the Tenant's `model.fixture` setting is on (R4.3),
|
|
36
|
+
* else from `gate`. Read per call, so a seed applies to the next call.
|
|
37
|
+
*/
|
|
38
|
+
export async function serve(store, gate, request, signal) {
|
|
39
|
+
if (await store.tx((t) => usesFixtureModel(t)))
|
|
40
|
+
return fixtureAnswer(request.call, signal);
|
|
41
|
+
return gate.call(request, signal);
|
|
42
|
+
}
|
|
32
43
|
export function inProcessModelGate(options) {
|
|
33
44
|
return {
|
|
34
45
|
async call(request, signal) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { McpServerRef } from "../mcp/pool.js";
|
|
2
|
+
import type { RunClaims } from "../tenant/run-token.js";
|
|
3
|
+
import { type HttpToolRef } from "./http-tool.js";
|
|
4
|
+
import type { TenantVaults } from "./tenant-vaults.js";
|
|
5
|
+
/** The call a run asks the gate to send, as the gate's body names it. */
|
|
6
|
+
export type GateToolCall = {
|
|
7
|
+
readonly kind: "mcp";
|
|
8
|
+
readonly server: McpServerRef;
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly arguments: Record<string, unknown>;
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: "http";
|
|
13
|
+
readonly tool: HttpToolRef;
|
|
14
|
+
readonly input: unknown;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* What the journal says of a call: why it does not hold it, or that it does, with the call's span
|
|
18
|
+
* context from the row (R7.4), which the gate sends on as `traceparent`.
|
|
19
|
+
*/
|
|
20
|
+
export type JournalAnswer = {
|
|
21
|
+
readonly refused: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly refused?: undefined;
|
|
24
|
+
readonly traceparent?: string;
|
|
25
|
+
};
|
|
26
|
+
export interface JournalCheck {
|
|
27
|
+
/** Whether the journal holds `call` under `key` for the run `claims` names. */
|
|
28
|
+
check(tenantId: string | undefined, claims: RunClaims, key: string, call: GateToolCall): Promise<JournalAnswer>;
|
|
29
|
+
}
|
|
30
|
+
export declare function journalCheck(vaults: TenantVaults): JournalCheck;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mcpToolOf } from "../mcp/snapshot.js";
|
|
2
|
+
import { canonical } from "../store/canonical.js";
|
|
3
|
+
import { httpToolRefOf } from "./http-tool.js";
|
|
4
|
+
export function journalCheck(vaults) {
|
|
5
|
+
return {
|
|
6
|
+
async check(tenantId, claims, key, call) {
|
|
7
|
+
const store = (await vaults.open(tenantId)).store;
|
|
8
|
+
const refusal = await store.tx(async (t) => {
|
|
9
|
+
const row = await t.get("effects", key);
|
|
10
|
+
if (!row)
|
|
11
|
+
return `No journaled call has the effect id '${key}'`;
|
|
12
|
+
const request = row.request;
|
|
13
|
+
if (request.kind !== "tool" ||
|
|
14
|
+
request.sessionId !== claims.sessionId ||
|
|
15
|
+
request.turnId !== claims.turnId)
|
|
16
|
+
return `Effect '${key}' is not a tool call of this run's turn`;
|
|
17
|
+
if (row.status !== "invoking")
|
|
18
|
+
return `Effect '${key}' is not running: it is ${row.status}`;
|
|
19
|
+
if (call.kind === "http") {
|
|
20
|
+
const { sessionId: _journaled, ...expected } = httpToolRefOf(request);
|
|
21
|
+
const { sessionId: _named, ...named } = call.tool;
|
|
22
|
+
if (canonical(expected) !== canonical(named))
|
|
23
|
+
return `Effect '${key}' calls another tool`;
|
|
24
|
+
if (canonical(request.input ?? null) !== canonical(call.input ?? null))
|
|
25
|
+
return `Effect '${key}' was journaled with other input`;
|
|
26
|
+
return held(row);
|
|
27
|
+
}
|
|
28
|
+
const session = await t.get("sessions", request.sessionId);
|
|
29
|
+
const tool = mcpToolOf(session?.mcpSnapshot, request);
|
|
30
|
+
if (!tool ||
|
|
31
|
+
tool.serverName !== call.server.serverName ||
|
|
32
|
+
(tool.agentId ?? null) !== (call.server.agentId ?? null) ||
|
|
33
|
+
tool.serverToolName !== call.name)
|
|
34
|
+
return `Effect '${key}' calls another tool`;
|
|
35
|
+
if (canonical(argumentsOf(request.input)) !== canonical(call.arguments))
|
|
36
|
+
return `Effect '${key}' was journaled with other input`;
|
|
37
|
+
return held(row);
|
|
38
|
+
});
|
|
39
|
+
return typeof refusal === "string" ? { refused: refusal } : refusal;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** A journaled call: its span context, when core recorded one (R7.4). */
|
|
44
|
+
function held(row) {
|
|
45
|
+
const traceparent = row.traceparent;
|
|
46
|
+
return typeof traceparent === "string" ? { traceparent } : {};
|
|
47
|
+
}
|
|
48
|
+
/** A tool call's input as `tools/call` arguments (`callMcpTool`): an object, else none. */
|
|
49
|
+
function argumentsOf(input) {
|
|
50
|
+
return input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
51
|
+
}
|
|
@@ -29,6 +29,10 @@ export interface McpHandler {
|
|
|
29
29
|
effectId: string;
|
|
30
30
|
name: string;
|
|
31
31
|
arguments: Record<string, unknown>;
|
|
32
|
+
/** The call's `Idempotency-Key`, sent to the server in `_meta` (R5.5). */
|
|
33
|
+
key?: string;
|
|
34
|
+
/** The call's span context from its journal row (R7.4), sent as `traceparent`. */
|
|
35
|
+
traceparent?: string;
|
|
32
36
|
}, signal: AbortSignal): Promise<McpAnswer<Record<string, unknown>>>;
|
|
33
37
|
close(server: McpServerRef): Promise<void>;
|
|
34
38
|
/** Closes connections idle past the timeout. */
|
|
@@ -13,13 +13,13 @@ export function createMcpHandler(options) {
|
|
|
13
13
|
const open = options.open ?? openMcpServer;
|
|
14
14
|
const live = new Map();
|
|
15
15
|
const opening = new Map();
|
|
16
|
-
const keyOf = (server) => JSON.stringify([server.sessionId, server.agentId ?? null, server.
|
|
16
|
+
const keyOf = (server) => JSON.stringify([server.sessionId, server.agentId ?? null, server.serverName]);
|
|
17
17
|
/** The declared remote server, from the session's pinned manifest. */
|
|
18
18
|
async function declared(vault, server) {
|
|
19
19
|
const session = await vault.session(server.sessionId);
|
|
20
20
|
if (!session)
|
|
21
21
|
throw new NotDeclared(`Session ${server.sessionId} not found`);
|
|
22
|
-
const found = findServer(session.manifest, server.agentId, server.
|
|
22
|
+
const found = findServer(session.manifest, server.agentId, server.serverName);
|
|
23
23
|
if (!found)
|
|
24
24
|
throw new NotDeclared(`MCP server '${server.serverName}' is not declared`);
|
|
25
25
|
return found.server;
|
|
@@ -114,7 +114,11 @@ export function createMcpHandler(options) {
|
|
|
114
114
|
async function callOnce(tenantId, request, signal) {
|
|
115
115
|
for (let attempt = 0;; attempt += 1) {
|
|
116
116
|
try {
|
|
117
|
-
return await use(tenantId, request.server, (entry) => entry.connection.client.callTool({ name: request.name, arguments: request.arguments }, {
|
|
117
|
+
return await use(tenantId, request.server, (entry) => entry.connection.client.callTool({ name: request.name, arguments: request.arguments }, {
|
|
118
|
+
signal,
|
|
119
|
+
...(request.key === undefined ? {} : { key: request.key }),
|
|
120
|
+
...(request.traceparent === undefined ? {} : { traceparent: request.traceparent }),
|
|
121
|
+
}), true);
|
|
118
122
|
}
|
|
119
123
|
catch (error) {
|
|
120
124
|
if (!(error instanceof McpConnectionStale) || attempt > 0)
|
|
@@ -170,7 +174,6 @@ export function createMcpHandler(options) {
|
|
|
170
174
|
function fields(server) {
|
|
171
175
|
return {
|
|
172
176
|
session: server.sessionId,
|
|
173
|
-
capability: server.capabilityId,
|
|
174
177
|
server: server.serverName,
|
|
175
178
|
...(server.agentId === undefined ? {} : { agent: server.agentId }),
|
|
176
179
|
};
|
package/dist/gates/meter.d.ts
CHANGED
|
@@ -13,8 +13,16 @@ export interface MeterOptions {
|
|
|
13
13
|
readonly now?: () => Date;
|
|
14
14
|
}
|
|
15
15
|
export declare function createMeter(options: MeterOptions): Meter;
|
|
16
|
-
/**
|
|
17
|
-
export
|
|
16
|
+
/** When the gate sent a call and when its answer was complete (R7.1). */
|
|
17
|
+
export interface CallTiming {
|
|
18
|
+
readonly startedAt: Date;
|
|
19
|
+
readonly endedAt: Date;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The ledger row of an outcome that carries usage; undefined for a failure. With `timing`, the
|
|
23
|
+
* row keeps the gate's start and end of the call, and the adapter's `firstTokenMs`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function usageRow(request: ModelGateRequest, outcome: ModelGateOutcome, at: Date, timing?: CallTiming): ModelUsageWrite | undefined;
|
|
18
26
|
/**
|
|
19
27
|
* Why the budget refuses another call, or undefined. Calls in flight count at the scope's
|
|
20
28
|
* average cost per recorded call.
|
package/dist/gates/meter.js
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* the scope at the scope's average cost per call, reaches its limit. Calls in a turn run one
|
|
9
9
|
* at a time, so a scope overspends by at most one call per concurrent call. The refusal is a
|
|
10
10
|
* `budget_exhausted` failure, which the turn does not retry.
|
|
11
|
+
*
|
|
12
|
+
* The row also keeps the gate's own timing of the call (R7.1): `now()` before the call and
|
|
13
|
+
* after it, and the adapter's `firstTokenMs` (`evidence.extras`), so a model call's span is
|
|
14
|
+
* what the gate measured.
|
|
11
15
|
*/
|
|
12
16
|
import { randomUUID } from "node:crypto";
|
|
13
17
|
import { failure } from "../model/classify.js";
|
|
@@ -60,8 +64,9 @@ export function createMeter(options) {
|
|
|
60
64
|
for (const key of keys)
|
|
61
65
|
inflight.set(key, (inflight.get(key) ?? 0) + 1);
|
|
62
66
|
try {
|
|
67
|
+
const startedAt = now();
|
|
63
68
|
const outcome = await call();
|
|
64
|
-
await record(store, request, outcome);
|
|
69
|
+
await record(store, request, outcome, { startedAt, endedAt: now() });
|
|
65
70
|
return outcome;
|
|
66
71
|
}
|
|
67
72
|
finally {
|
|
@@ -75,8 +80,8 @@ export function createMeter(options) {
|
|
|
75
80
|
}
|
|
76
81
|
},
|
|
77
82
|
};
|
|
78
|
-
async function record(store, request, outcome) {
|
|
79
|
-
const row = usageRow(request, outcome,
|
|
83
|
+
async function record(store, request, outcome, timing) {
|
|
84
|
+
const row = usageRow(request, outcome, timing.endedAt, timing);
|
|
80
85
|
if (!row)
|
|
81
86
|
return;
|
|
82
87
|
try {
|
|
@@ -96,11 +101,15 @@ export function createMeter(options) {
|
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
}
|
|
99
|
-
/**
|
|
100
|
-
|
|
104
|
+
/**
|
|
105
|
+
* The ledger row of an outcome that carries usage; undefined for a failure. With `timing`, the
|
|
106
|
+
* row keeps the gate's start and end of the call, and the adapter's `firstTokenMs`.
|
|
107
|
+
*/
|
|
108
|
+
export function usageRow(request, outcome, at, timing) {
|
|
101
109
|
if (typeof outcome !== "object" || "kind" in outcome || !outcome.usage)
|
|
102
110
|
return undefined;
|
|
103
111
|
const usage = outcome.usage;
|
|
112
|
+
const firstTokenMs = outcome.evidence?.extras?.firstTokenMs;
|
|
104
113
|
const producer = producerOf(outcome);
|
|
105
114
|
const inputTokens = count(usage.inputTokens);
|
|
106
115
|
const outputTokens = count(usage.outputTokens);
|
|
@@ -126,6 +135,12 @@ export function usageRow(request, outcome, at) {
|
|
|
126
135
|
reasoningTokens: count(usage.reasoningTokens),
|
|
127
136
|
costUsd: Number.isFinite(usage.costUsd) && usage.costUsd > 0 ? usage.costUsd : 0,
|
|
128
137
|
createdAt: at.toISOString(),
|
|
138
|
+
...(timing
|
|
139
|
+
? { startedAt: timing.startedAt.toISOString(), endedAt: timing.endedAt.toISOString() }
|
|
140
|
+
: {}),
|
|
141
|
+
...(typeof firstTokenMs === "number" && Number.isFinite(firstTokenMs) && firstTokenMs >= 0
|
|
142
|
+
? { firstTokenMs: Math.round(firstTokenMs) }
|
|
143
|
+
: {}),
|
|
129
144
|
};
|
|
130
145
|
}
|
|
131
146
|
/** The ledger rows a budget counts for `request`; undefined when it doesn't apply. */
|
|
@@ -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,132 @@
|
|
|
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
|
+
return declaredHttpTool(manifest, request.agent?.id, request.toolName)?.approval === "always";
|
|
72
|
+
}
|
|
73
|
+
function matches(rule, call) {
|
|
74
|
+
if (rule.kind !== undefined && rule.kind !== call.kind)
|
|
75
|
+
return false;
|
|
76
|
+
if (rule.tool !== undefined && !glob(rule.tool).test(call.tool))
|
|
77
|
+
return false;
|
|
78
|
+
if (rule.server !== undefined && (call.server === undefined || !glob(rule.server).test(call.server)))
|
|
79
|
+
return false;
|
|
80
|
+
if (rule.agent !== undefined && !glob(rule.agent).test(call.agent))
|
|
81
|
+
return false;
|
|
82
|
+
for (const [path, condition] of Object.entries(rule.when ?? {}))
|
|
83
|
+
if (!holds(condition, fieldOf(call.input, path)))
|
|
84
|
+
return false;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
/** A pattern where `*` is any run of characters, matched whole. */
|
|
88
|
+
function glob(pattern) {
|
|
89
|
+
return new RegExp(`^${pattern.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`);
|
|
90
|
+
}
|
|
91
|
+
/** The value at a dotted `path` of `input`, or undefined. */
|
|
92
|
+
function fieldOf(input, path) {
|
|
93
|
+
let value = input;
|
|
94
|
+
for (const key of path.split(".")) {
|
|
95
|
+
if (!value || typeof value !== "object")
|
|
96
|
+
return undefined;
|
|
97
|
+
value = value[key];
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
function holds(condition, value) {
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
return false;
|
|
104
|
+
if (condition.equals !== undefined && canonical(value) !== canonical(condition.equals))
|
|
105
|
+
return false;
|
|
106
|
+
if (condition.in !== undefined && !condition.in.some((item) => canonical(item) === canonical(value)))
|
|
107
|
+
return false;
|
|
108
|
+
const number = typeof value === "number" ? value : undefined;
|
|
109
|
+
if (condition.gt !== undefined && !(number !== undefined && number > condition.gt))
|
|
110
|
+
return false;
|
|
111
|
+
if (condition.gte !== undefined && !(number !== undefined && number >= condition.gte))
|
|
112
|
+
return false;
|
|
113
|
+
if (condition.lt !== undefined && !(number !== undefined && number < condition.lt))
|
|
114
|
+
return false;
|
|
115
|
+
if (condition.lte !== undefined && !(number !== undefined && number <= condition.lte))
|
|
116
|
+
return false;
|
|
117
|
+
if (condition.matches !== undefined && !(typeof value === "string" && new RegExp(condition.matches).test(value)))
|
|
118
|
+
return false;
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/** What a rule with no `reason` says. */
|
|
122
|
+
function describe(rule) {
|
|
123
|
+
const what = [
|
|
124
|
+
rule.tool === undefined ? undefined : `tool '${rule.tool}'`,
|
|
125
|
+
rule.server === undefined ? undefined : `server '${rule.server}'`,
|
|
126
|
+
rule.kind === undefined ? undefined : `${rule.kind} tools`,
|
|
127
|
+
rule.agent === undefined ? undefined : `agent '${rule.agent}'`,
|
|
128
|
+
rule.when === undefined ? undefined : `input ${Object.keys(rule.when).join(", ")}`,
|
|
129
|
+
].filter((part) => part !== undefined);
|
|
130
|
+
const verb = rule.effect === "deny" ? "refuses" : "asks a person about";
|
|
131
|
+
return `The Tenant's tool policy ${verb} ${what.length === 0 ? "every tool call" : `calls of ${what.join(", ")}`}`;
|
|
132
|
+
}
|
|
@@ -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);
|