@nylorun/runtime 0.26.0-beta → 0.28.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 +171 -0
- package/README.md +18 -14
- package/dist/adapters/execution/restate.d.ts +15 -18
- package/dist/adapters/execution/restate.js +31 -27
- package/dist/adapters/replication/pgoutput.d.ts +1 -1
- package/dist/adapters/replication/pgoutput.js +3 -2
- package/dist/adapters/sandbox/local.js +8 -5
- package/dist/adapters/sandbox/virtual.js +2 -2
- package/dist/adapters/streams/s2.d.ts +1 -1
- package/dist/adapters/streams/s2.js +18 -14
- package/dist/api/a2a/card.d.ts +4 -3
- package/dist/api/a2a/card.js +4 -4
- package/dist/api/a2a/endpoint.js +3 -4
- package/dist/api/a2a/protocol.js +4 -0
- package/dist/api/a2a/routes.d.ts +2 -6
- package/dist/api/a2a/routes.js +30 -25
- package/dist/api/ag-ui/endpoint.js +2 -3
- package/dist/api/ag-ui/routes.js +8 -11
- package/dist/api/components.d.ts +6789 -3405
- package/dist/api/components.js +43 -30
- package/dist/api/gate/keys.js +7 -4
- package/dist/api/gate/routes.d.ts +6 -2
- package/dist/api/gate/routes.js +15 -11
- package/dist/api/http/app.js +9 -4
- package/dist/api/http/body.d.ts +17 -3
- package/dist/api/http/body.js +35 -7
- package/dist/api/http/respond.js +6 -2
- package/dist/api/http/routes/access.js +5 -4
- package/dist/api/http/routes/agent-manifests.d.ts +10 -0
- package/dist/api/http/routes/agent-manifests.js +135 -0
- package/dist/api/http/routes/agents.d.ts +11 -0
- package/dist/api/http/routes/agents.js +418 -0
- package/dist/api/http/routes/artifacts.js +21 -9
- package/dist/api/http/routes/coverage.d.ts +4 -4
- package/dist/api/http/routes/coverage.js +13 -7
- package/dist/api/http/routes/files.d.ts +2 -1
- package/dist/api/http/routes/files.js +31 -5
- package/dist/api/http/routes/mcp.js +3 -3
- package/dist/api/http/routes/me.d.ts +1 -1
- package/dist/api/http/routes/me.js +1 -2
- package/dist/api/http/routes/memories.d.ts +9 -0
- package/dist/api/http/routes/memories.js +152 -0
- package/dist/api/http/routes/reads.d.ts +10 -111
- package/dist/api/http/routes/reads.js +51 -29
- package/dist/api/http/routes/sessions.js +26 -54
- package/dist/api/http/routes/tenant.d.ts +2 -2
- package/dist/api/http/routes/tenant.js +77 -24
- package/dist/api/http/routes/triggers.d.ts +10 -0
- package/dist/api/http/routes/triggers.js +180 -0
- package/dist/api/inbound-files.d.ts +3 -9
- package/dist/api/inbound-files.js +18 -24
- package/dist/api/openapi.js +73 -47
- package/dist/artifacts/disk.d.ts +10 -14
- package/dist/artifacts/disk.js +99 -95
- package/dist/artifacts/files.d.ts +13 -4
- package/dist/artifacts/files.js +27 -9
- package/dist/artifacts/folder-upload.d.ts +75 -0
- package/dist/artifacts/folder-upload.js +263 -0
- package/dist/artifacts/folders.d.ts +14 -8
- package/dist/artifacts/folders.js +19 -11
- package/dist/artifacts/links.js +7 -6
- package/dist/artifacts/parts.d.ts +9 -14
- package/dist/artifacts/parts.js +11 -9
- package/dist/artifacts/pipe.d.ts +15 -0
- package/dist/artifacts/pipe.js +71 -0
- package/dist/artifacts/quota.d.ts +36 -0
- package/dist/artifacts/quota.js +44 -0
- package/dist/artifacts/service.d.ts +25 -1
- package/dist/artifacts/service.js +72 -27
- package/dist/blob/fs.js +2 -1
- package/dist/blob/s3.d.ts +6 -2
- package/dist/blob/s3.js +9 -7
- package/dist/blob/types.d.ts +1 -1
- package/dist/blob/types.js +1 -1
- package/dist/core/flow-host.d.ts +0 -1
- package/dist/core/limits.d.ts +11 -35
- package/dist/core/limits.js +5 -73
- package/dist/core/sandbox-routes.js +0 -1
- package/dist/core/turn-manifest.d.ts +1 -2
- package/dist/core/turn-manifest.js +4 -28
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +8 -6
- package/dist/execution/types.d.ts +1 -6
- package/dist/execution/types.js +0 -5
- package/dist/gates/contract.d.ts +3 -2
- package/dist/gates/contract.js +1 -1
- package/dist/gates/egress-credentials.js +5 -4
- package/dist/gates/egress.d.ts +13 -13
- package/dist/gates/egress.js +19 -21
- package/dist/gates/fixture-model.js +13 -2
- package/dist/gates/handler.d.ts +1 -3
- package/dist/gates/handler.js +4 -3
- package/dist/gates/http-client.d.ts +4 -3
- package/dist/gates/http-client.js +5 -7
- package/dist/gates/http-tool.d.ts +24 -4
- package/dist/gates/http-tool.js +41 -13
- package/dist/gates/in-process.d.ts +2 -1
- package/dist/gates/in-process.js +17 -9
- package/dist/gates/inflight.d.ts +2 -2
- package/dist/gates/inflight.js +3 -2
- package/dist/gates/journal.js +5 -7
- package/dist/gates/mcp-handler.d.ts +4 -3
- package/dist/gates/mcp-handler.js +15 -5
- package/dist/gates/meter.js +6 -5
- package/dist/gates/model-gate.d.ts +11 -2
- package/dist/gates/policy.d.ts +34 -6
- package/dist/gates/policy.js +66 -18
- package/dist/gates/provider-gate.d.ts +22 -0
- package/dist/gates/provider-gate.js +30 -0
- package/dist/gates/tool-calls.d.ts +1 -3
- package/dist/gates/tool-calls.js +2 -3
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +5 -6
- package/dist/gates/tool-contract.d.ts +8 -5
- package/dist/gates/tool-contract.js +1 -1
- package/dist/gates/tool-gate.d.ts +18 -7
- package/dist/gates/tool-gate.js +49 -7
- package/dist/harness/calls.d.ts +27 -25
- package/dist/harness/calls.js +40 -46
- package/dist/harness/executors.d.ts +10 -3
- package/dist/harness/executors.js +53 -23
- package/dist/harness/main.js +2 -2
- package/dist/harness/pod.d.ts +2 -2
- package/dist/harness/pod.js +7 -6
- package/dist/harness/run-tokens.d.ts +4 -1
- package/dist/harness/run-tokens.js +6 -2
- package/dist/harness/service.d.ts +17 -14
- package/dist/harness/service.js +21 -35
- package/dist/harness/setup.d.ts +0 -2
- package/dist/harness/setup.js +21 -15
- package/dist/harness/ws-client.d.ts +2 -2
- package/dist/harness/ws-client.js +7 -5
- package/dist/harness/ws-port.d.ts +3 -4
- package/dist/harness/ws-port.js +3 -4
- package/dist/harness-api/artifacts.d.ts +20 -0
- package/dist/harness-api/artifacts.js +478 -0
- package/dist/harness-api/in-process.d.ts +2 -2
- package/dist/harness-api/in-process.js +18 -16
- package/dist/harness-api/record.d.ts +10 -9
- package/dist/harness-api/record.js +113 -157
- package/dist/harness-api/renew.d.ts +1 -1
- package/dist/harness-api/renew.js +2 -2
- package/dist/harness-api/server.d.ts +1 -16
- package/dist/harness-api/server.js +86 -73
- package/dist/harness-api/start.d.ts +27 -5
- package/dist/harness-api/start.js +102 -19
- package/dist/harness-api/tool-journal.d.ts +15 -10
- package/dist/harness-api/tool-journal.js +26 -24
- package/dist/harness-api/workspace.d.ts +0 -4
- package/dist/harness-api/workspace.js +4 -47
- package/dist/harness-api/ws-server.js +10 -10
- package/dist/host/create-host.js +2 -3
- package/dist/host/execution.js +2 -3
- package/dist/host/gates.d.ts +2 -9
- package/dist/host/gates.js +15 -12
- package/dist/host/http.d.ts +2 -4
- package/dist/host/http.js +5 -29
- package/dist/host/main.js +10 -3
- package/dist/host/operate.js +12 -3
- package/dist/host/stack-config.d.ts +11 -0
- package/dist/host/stack-config.js +21 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/index.js +2 -1
- package/dist/infra/readiness.d.ts +0 -2
- package/dist/infra/readiness.js +4 -3
- package/dist/keys/client.d.ts +1 -0
- package/dist/keys/client.js +2 -3
- package/dist/keys/contract.d.ts +0 -2
- package/dist/keys/contract.js +0 -2
- package/dist/keys/egress-ca.d.ts +0 -2
- package/dist/keys/egress-ca.js +2 -3
- package/dist/keys/keys.d.ts +15 -4
- package/dist/keys/keys.js +22 -1
- package/dist/keys/webhooks.d.ts +25 -0
- package/dist/keys/webhooks.js +62 -0
- package/dist/keys/x509.d.ts +8 -4
- package/dist/keys/x509.js +14 -5
- package/dist/management-openapi.json +3043 -1954
- package/dist/mcp/connect.d.ts +35 -26
- package/dist/mcp/connect.js +112 -141
- package/dist/mcp/pool.d.ts +27 -22
- package/dist/mcp/pool.js +34 -48
- package/dist/mcp/preview.d.ts +12 -5
- package/dist/mcp/preview.js +43 -26
- package/dist/mcp/snapshot.d.ts +35 -86
- package/dist/mcp/snapshot.js +93 -135
- package/dist/memory/service.d.ts +97 -0
- package/dist/memory/service.js +335 -0
- package/dist/memory/tool.d.ts +36 -0
- package/dist/memory/tool.js +84 -0
- package/dist/model/auth-store.js +5 -3
- package/dist/model/capabilities.d.ts +1 -1
- package/dist/model/catalog.d.ts +9 -36
- package/dist/model/catalog.js +6 -33
- package/dist/model/descriptor.d.ts +51 -0
- package/dist/model/descriptor.js +83 -0
- package/dist/model/models.d.ts +0 -3
- package/dist/model/models.js +3 -5
- package/dist/model/native-blocks.js +1 -1
- package/dist/model/pi-model.d.ts +16 -5
- package/dist/model/pi-model.js +86 -105
- package/dist/openapi.json +13653 -9986
- package/dist/reads/cursor.js +3 -1
- package/dist/reads/types.d.ts +3 -3
- package/dist/record/agent.d.ts +27 -0
- package/dist/record/agent.js +46 -0
- package/dist/record/append.d.ts +34 -2
- package/dist/record/append.js +50 -1
- package/dist/record/index.d.ts +2 -2
- package/dist/record/index.js +2 -2
- package/dist/redact.d.ts +1 -1
- package/dist/redact.js +3 -2
- package/dist/sandbox/egress-token.d.ts +3 -3
- package/dist/sandbox/egress-token.js +3 -3
- package/dist/sandbox/join.js +8 -7
- package/dist/sandbox/manager.d.ts +41 -41
- package/dist/sandbox/manager.js +112 -44
- package/dist/sandbox/placement.d.ts +2 -2
- package/dist/sandbox/placement.js +7 -10
- package/dist/sandbox/pods/client.d.ts +1 -10
- package/dist/sandbox/pods/client.js +8 -4
- package/dist/sandbox/pods/lifecycle.d.ts +25 -17
- package/dist/sandbox/pods/lifecycle.js +51 -29
- package/dist/sandbox/pods/network-gate.d.ts +4 -5
- package/dist/sandbox/pods/network-gate.js +7 -7
- package/dist/sandbox/pods/reconcile.js +14 -3
- package/dist/sandbox/pods/spec.js +8 -5
- package/dist/sandbox/policy.d.ts +7 -1
- package/dist/sandbox/policy.js +10 -3
- package/dist/sandbox/resolve.d.ts +11 -6
- package/dist/sandbox/resolve.js +19 -9
- package/dist/sandbox/setup.d.ts +6 -2
- package/dist/sandbox/setup.js +47 -14
- package/dist/sandbox/share.d.ts +5 -14
- package/dist/sandbox/share.js +5 -34
- package/dist/sandbox/skills.d.ts +7 -13
- package/dist/sandbox/skills.js +16 -31
- package/dist/sandbox/tenant-config.d.ts +2 -2
- package/dist/sandbox/tenant-config.js +4 -3
- package/dist/sandbox/tools.d.ts +7 -1
- package/dist/sandbox/tools.js +26 -26
- package/dist/sandbox/types.d.ts +10 -3
- package/dist/sandbox/types.js +9 -0
- package/dist/store/postgres/connect.d.ts +6 -5
- package/dist/store/postgres/connect.js +14 -11
- package/dist/store/postgres/control.js +13 -13
- package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
- package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
- package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
- package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
- package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
- package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
- package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
- package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +63 -0
- package/dist/store/postgres/reads.js +17 -82
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/schema.d.ts +1382 -151
- package/dist/store/postgres/schema.js +191 -37
- package/dist/store/postgres/store.js +384 -105
- package/dist/store/postgres/tenant.js +1 -2
- package/dist/store/types.d.ts +172 -57
- package/dist/store/types.js +0 -1
- package/dist/streams/relay/core.d.ts +3 -3
- package/dist/streams/relay/core.js +9 -10
- package/dist/tenant/advance.js +41 -39
- package/dist/tenant/agent-manifests.d.ts +72 -0
- package/dist/tenant/agent-manifests.js +359 -0
- package/dist/tenant/agent-versions.d.ts +126 -0
- package/dist/tenant/agent-versions.js +389 -0
- package/dist/tenant/agents.d.ts +230 -0
- package/dist/tenant/{environments.js → agents.js} +493 -328
- package/dist/tenant/auth.d.ts +1 -1
- package/dist/tenant/auth.js +3 -3
- package/dist/tenant/commands.d.ts +63 -2
- package/dist/tenant/commands.js +240 -90
- package/dist/tenant/context.d.ts +57 -33
- package/dist/tenant/context.js +6 -2
- package/dist/tenant/definition-files.d.ts +25 -4
- package/dist/tenant/definition-files.js +85 -47
- package/dist/tenant/effects.js +15 -51
- package/dist/tenant/ephemeral.js +2 -1
- package/dist/tenant/history.d.ts +1 -1
- package/dist/tenant/history.js +1 -1
- package/dist/tenant/host-token.d.ts +3 -3
- package/dist/tenant/host-token.js +11 -10
- package/dist/tenant/http.d.ts +5 -8
- package/dist/tenant/http.js +9 -0
- package/dist/tenant/identity-file.d.ts +29 -6
- package/dist/tenant/identity-file.js +45 -15
- package/dist/tenant/issuers.d.ts +5 -5
- package/dist/tenant/issuers.js +34 -26
- package/dist/tenant/jwt.d.ts +8 -2
- package/dist/tenant/jwt.js +10 -2
- package/dist/tenant/limits.d.ts +87 -0
- package/dist/tenant/limits.js +172 -0
- package/dist/tenant/memories.d.ts +38 -0
- package/dist/tenant/memories.js +119 -0
- package/dist/tenant/module.d.ts +4 -1
- package/dist/tenant/module.js +9 -8
- package/dist/tenant/outbound.d.ts +11 -14
- package/dist/tenant/outbound.js +10 -15
- package/dist/tenant/reset.js +2 -2
- package/dist/tenant/run-grants.d.ts +6 -0
- package/dist/tenant/run-grants.js +7 -2
- package/dist/tenant/run-token.d.ts +0 -4
- package/dist/tenant/run-token.js +6 -11
- package/dist/tenant/runtime-limits.d.ts +24 -0
- package/dist/tenant/runtime-limits.js +27 -0
- package/dist/tenant/runtime.d.ts +1 -1
- package/dist/tenant/runtime.js +38 -43
- package/dist/tenant/session-streams.js +7 -6
- package/dist/tenant/session.d.ts +1 -1
- package/dist/tenant/session.js +1 -1
- package/dist/tenant/sessions.d.ts +28 -45
- package/dist/tenant/sessions.js +89 -209
- package/dist/tenant/signing-keys.d.ts +1 -1
- package/dist/tenant/signing-keys.js +5 -6
- package/dist/tenant/status.js +10 -10
- package/dist/tenant/streams.js +18 -20
- package/dist/tenant/sweep.d.ts +1 -21
- package/dist/tenant/sweep.js +51 -51
- package/dist/tenant/transcript.d.ts +70 -23
- package/dist/tenant/transcript.js +28 -22
- package/dist/tenant/triggers.d.ts +143 -0
- package/dist/tenant/triggers.js +715 -0
- package/dist/tenant/turn-timing.d.ts +11 -1
- package/dist/tenant/turn-timing.js +3 -1
- package/dist/tenant/types.d.ts +9 -14
- package/dist/tenant/worker.d.ts +4 -10
- package/dist/tenant/worker.js +10 -14
- package/dist/traces/ids.js +5 -1
- package/dist/traces/map.d.ts +10 -7
- package/dist/traces/map.js +16 -12
- package/dist/traces/otlp.d.ts +3 -3
- package/dist/traces/otlp.js +5 -3
- package/dist/traces/projector.d.ts +9 -4
- package/dist/traces/projector.js +13 -7
- package/dist/traces/read.js +6 -1
- package/dist/tuning.d.ts +390 -0
- package/dist/tuning.js +677 -0
- package/dist/vault/headers.js +4 -2
- package/dist/vault/service.d.ts +13 -8
- package/dist/vault/service.js +61 -17
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -12
- package/dist/api/http/routes/environment-manifests.d.ts +0 -9
- package/dist/api/http/routes/environment-manifests.js +0 -118
- package/dist/api/http/routes/environments.d.ts +0 -11
- package/dist/api/http/routes/environments.js +0 -332
- package/dist/artifacts/export.d.ts +0 -8
- package/dist/artifacts/export.js +0 -204
- package/dist/artifacts/media-types.d.ts +0 -30
- package/dist/artifacts/media-types.js +0 -137
- package/dist/artifacts/workspace.d.ts +0 -60
- package/dist/artifacts/workspace.js +0 -45
- package/dist/media.d.ts +0 -10
- package/dist/media.js +0 -58
- package/dist/record/environment.d.ts +0 -25
- package/dist/record/environment.js +0 -44
- package/dist/sandbox/runtime-tools.d.ts +0 -9
- package/dist/sandbox/runtime-tools.js +0 -16
- package/dist/sandbox/session-sandbox.d.ts +0 -16
- package/dist/sandbox/session-sandbox.js +0 -77
- package/dist/tenant/artifact-tool.d.ts +0 -13
- package/dist/tenant/artifact-tool.js +0 -243
- package/dist/tenant/environment-manifests.d.ts +0 -57
- package/dist/tenant/environment-manifests.js +0 -200
- package/dist/tenant/environments.d.ts +0 -174
- package/dist/tenant/skill-tool.d.ts +0 -20
- package/dist/tenant/skill-tool.js +0 -67
- package/dist/tenant/tool-results.d.ts +0 -57
- package/dist/tenant/tool-results.js +0 -298
- package/dist/tenant/tool-search.d.ts +0 -39
- package/dist/tenant/tool-search.js +0 -103
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What artifacts, skills and disk versions may hold: the Tenant's limits (`artifacts.config`,
|
|
3
|
+
* `skills.config` here, and the Runtime limits `diskVersion*` of `limits.config`), and an
|
|
4
|
+
* Environment's, which its manifest sets (`limits`). A limit the manifest leaves out is the
|
|
5
|
+
* Tenant's as it is when the limit is checked, and none is ever above the Tenant's: a manifest
|
|
6
|
+
* asking for more is refused when the Environment is made, and a Tenant that lowers a limit
|
|
7
|
+
* later lowers it for every Environment.
|
|
8
|
+
*
|
|
9
|
+
* Also the shape a manifest and an Environment's labels must fit, which the Tenant's Runtime
|
|
10
|
+
* limits set (`manifestShapeIssues`, `labelIssues`), checked when they are put rather than by
|
|
11
|
+
* their schemas (B15).
|
|
12
|
+
*/
|
|
13
|
+
import { SKILL_TOTAL_BYTES_DEFAULT, TenantSkillsConfigSchema, } from "@nylorun/core/contracts";
|
|
14
|
+
import { parseSandboxDuration } from "@nylorun/core/define";
|
|
15
|
+
import { readArtifactLimits } from "../artifacts/config.js";
|
|
16
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
17
|
+
export const SKILLS_CONFIG_SETTING = "skills.config";
|
|
18
|
+
/** The skill limits in force: the Tenant's, with the default for what it leaves unset. */
|
|
19
|
+
export function effectiveSkillLimits(config) {
|
|
20
|
+
return { totalBytes: config.limits?.totalBytes ?? SKILL_TOTAL_BYTES_DEFAULT };
|
|
21
|
+
}
|
|
22
|
+
export async function readSkillsConfig(t) {
|
|
23
|
+
const raw = await t.getSetting(SKILLS_CONFIG_SETTING);
|
|
24
|
+
if (raw === undefined)
|
|
25
|
+
return {};
|
|
26
|
+
const parsed = TenantSkillsConfigSchema.safeParse(JSON.parse(raw));
|
|
27
|
+
return parsed.success ? parsed.data : {};
|
|
28
|
+
}
|
|
29
|
+
export async function readSkillLimits(t) {
|
|
30
|
+
return effectiveSkillLimits(await readSkillsConfig(t));
|
|
31
|
+
}
|
|
32
|
+
export async function writeSkillsConfig(t, config) {
|
|
33
|
+
await t.putSetting(SKILLS_CONFIG_SETTING, JSON.stringify(TenantSkillsConfigSchema.parse(config)));
|
|
34
|
+
}
|
|
35
|
+
/** The Tenant's artifact, skill and disk version limits. */
|
|
36
|
+
export async function readTenantLimits(t) {
|
|
37
|
+
const runtime = await readRuntimeLimits(t);
|
|
38
|
+
return {
|
|
39
|
+
artifacts: await readArtifactLimits(t),
|
|
40
|
+
skills: await readSkillLimits(t),
|
|
41
|
+
disk: { versionBytes: runtime.diskVersionBytes, versionEntries: runtime.diskVersionEntries },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** The limits in force for an Environment whose manifest sets `set`: each within the Tenant's. */
|
|
45
|
+
export function limitsInForce(tenant, set) {
|
|
46
|
+
const within = (asked, ceiling) => Math.min(asked ?? ceiling, ceiling);
|
|
47
|
+
return {
|
|
48
|
+
artifacts: {
|
|
49
|
+
fileBytes: within(set?.artifacts?.fileBytes, tenant.artifacts.fileBytes),
|
|
50
|
+
totalBytes: within(set?.artifacts?.totalBytes, tenant.artifacts.totalBytes),
|
|
51
|
+
},
|
|
52
|
+
skills: { totalBytes: within(set?.skills?.totalBytes, tenant.skills.totalBytes) },
|
|
53
|
+
disk: {
|
|
54
|
+
versionBytes: within(set?.disk?.versionBytes, tenant.disk.versionBytes),
|
|
55
|
+
versionEntries: within(set?.disk?.versionEntries, tenant.disk.versionEntries),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Why a manifest's `limits` cannot be made: each limit above the Tenant's. */
|
|
60
|
+
export function limitIssues(set, tenant) {
|
|
61
|
+
const issues = [];
|
|
62
|
+
const check = (path, asked, ceiling, unit = "bytes") => {
|
|
63
|
+
if (asked !== undefined && asked > ceiling)
|
|
64
|
+
issues.push(`limits.${path} asks for ${asked} ${unit}; this Tenant allows at most ${ceiling}.`);
|
|
65
|
+
};
|
|
66
|
+
check("artifacts.fileBytes", set?.artifacts?.fileBytes, tenant.artifacts.fileBytes);
|
|
67
|
+
check("artifacts.totalBytes", set?.artifacts?.totalBytes, tenant.artifacts.totalBytes);
|
|
68
|
+
check("skills.totalBytes", set?.skills?.totalBytes, tenant.skills.totalBytes);
|
|
69
|
+
check("disk.versionBytes", set?.disk?.versionBytes, tenant.disk.versionBytes);
|
|
70
|
+
check("disk.versionEntries", set?.disk?.versionEntries, tenant.disk.versionEntries, "entries");
|
|
71
|
+
return issues;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Why `labels` cannot be set: more than the Tenant's Runtime limit `count` names
|
|
75
|
+
* (`environmentLabels` or `artifactLabels`), or a key or a value longer than `labelKeyChars` or
|
|
76
|
+
* `labelValueChars`.
|
|
77
|
+
*/
|
|
78
|
+
export function labelIssues(labels, limits, count = "environmentLabels") {
|
|
79
|
+
if (labels === undefined)
|
|
80
|
+
return [];
|
|
81
|
+
const issues = [];
|
|
82
|
+
const keys = Object.keys(labels);
|
|
83
|
+
if (keys.length > limits[count])
|
|
84
|
+
issues.push(`labels has ${keys.length}; the Runtime limit ${count} allows at most ${limits[count]} (PUT /v1/tenant/limits).`);
|
|
85
|
+
for (const key of keys) {
|
|
86
|
+
if (key.length > limits.labelKeyChars)
|
|
87
|
+
issues.push(`Label key ${JSON.stringify(key.slice(0, limits.labelKeyChars))}… has ${key.length} characters; the Runtime limit labelKeyChars allows at most ${limits.labelKeyChars} (PUT /v1/tenant/limits).`);
|
|
88
|
+
const value = labels[key];
|
|
89
|
+
if (value.length > limits.labelValueChars)
|
|
90
|
+
issues.push(`Label ${JSON.stringify(key)}'s value has ${value.length} characters; the Runtime limit labelValueChars allows at most ${limits.labelValueChars} (PUT /v1/tenant/limits).`);
|
|
91
|
+
}
|
|
92
|
+
return issues;
|
|
93
|
+
}
|
|
94
|
+
/** `"{what} has {n} characters; the Runtime limit {name} allows at most {limit} …"`, when over. */
|
|
95
|
+
function longer(what, text, name, limits) {
|
|
96
|
+
return text !== undefined && text.length > limits[name]
|
|
97
|
+
? [`${what} has ${text.length} characters; the Runtime limit ${name} allows at most ${limits[name]} (PUT /v1/tenant/limits).`]
|
|
98
|
+
: [];
|
|
99
|
+
}
|
|
100
|
+
/** `"{what} has {n}; the Runtime limit {name} allows at most {limit} …"`, when over. */
|
|
101
|
+
function more(what, count, name, limits) {
|
|
102
|
+
return count > limits[name]
|
|
103
|
+
? [`${what} has ${count}; the Runtime limit ${name} allows at most ${limits[name]} (PUT /v1/tenant/limits).`]
|
|
104
|
+
: [];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Why a secret write does not fit the Tenant's Runtime limits: an `environment_secret`'s or an
|
|
108
|
+
* `environment_variable`'s value past `secretValueChars`, or more hosts than `secretHosts`.
|
|
109
|
+
*/
|
|
110
|
+
export function secretRequestIssues(request, limits) {
|
|
111
|
+
const valued = request.type === "environment_secret" || request.type === "environment_variable";
|
|
112
|
+
return [
|
|
113
|
+
...(valued && typeof request.value === "string" ? longer("value", request.value, "secretValueChars", limits) : []),
|
|
114
|
+
...more("allowedHosts", request.allowedHosts?.length ?? 0, "secretHosts", limits),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Why a tool policy does not fit the Tenant's Runtime limits: more rules than `toolRules`, a
|
|
119
|
+
* selector or a `when` path past `toolRuleFieldChars`, a `reason` or a `matches` past
|
|
120
|
+
* `toolRuleTextChars`.
|
|
121
|
+
*/
|
|
122
|
+
export function toolPolicyIssues(rules, limits) {
|
|
123
|
+
const issues = more("rules", rules.length, "toolRules", limits);
|
|
124
|
+
rules.forEach((rule, index) => {
|
|
125
|
+
const at = `rules[${index}]`;
|
|
126
|
+
issues.push(...longer(`${at}.tool`, rule.tool, "toolRuleFieldChars", limits), ...longer(`${at}.server`, rule.server, "toolRuleFieldChars", limits), ...longer(`${at}.agent`, rule.agent, "toolRuleFieldChars", limits), ...longer(`${at}.reason`, rule.reason, "toolRuleTextChars", limits));
|
|
127
|
+
for (const [path, condition] of Object.entries(rule.when ?? {}))
|
|
128
|
+
issues.push(...longer(`${at}.when path ${JSON.stringify(path.slice(0, limits.toolRuleFieldChars))}`, path, "toolRuleFieldChars", limits), ...longer(`${at}.when[${JSON.stringify(path)}].matches`, condition.matches, "toolRuleTextChars", limits));
|
|
129
|
+
});
|
|
130
|
+
return issues;
|
|
131
|
+
}
|
|
132
|
+
/** Why a set of model budgets does not fit: more than the Tenant's Runtime limit `modelBudgets`. */
|
|
133
|
+
export function budgetIssues(budgets, limits) {
|
|
134
|
+
return more("budgets", budgets.length, "modelBudgets", limits);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Why an agent's `requires` does not fit: more secrets than an Environment's manifest may declare
|
|
138
|
+
* slots for (the Runtime limit `environmentSecretSlots`).
|
|
139
|
+
*/
|
|
140
|
+
/**
|
|
141
|
+
* Why a manifest (inline, or saved) does not fit the Tenant's Runtime limits: its description,
|
|
142
|
+
* its setup commands, their lengths and timeouts, its input file entries and the files of each,
|
|
143
|
+
* and its secret slots, their descriptions and hosts.
|
|
144
|
+
*/
|
|
145
|
+
export function manifestShapeIssues(manifest, limits) {
|
|
146
|
+
const issues = [];
|
|
147
|
+
const over = (what, count, name) => {
|
|
148
|
+
if (count > limits[name])
|
|
149
|
+
issues.push(`${what} has ${count}; the Runtime limit ${name} allows at most ${limits[name]} (PUT /v1/tenant/limits).`);
|
|
150
|
+
};
|
|
151
|
+
over("setup", manifest.setup?.length ?? 0, "environmentSetupCommands");
|
|
152
|
+
over("files", manifest.files?.length ?? 0, "environmentFiles");
|
|
153
|
+
over("secrets", Object.keys(manifest.secrets ?? {}).length, "environmentSecretSlots");
|
|
154
|
+
issues.push(...longer("description", manifest.description, "manifestDescriptionChars", limits));
|
|
155
|
+
for (const [name, slot] of Object.entries(manifest.secrets ?? {})) {
|
|
156
|
+
issues.push(...longer(`secrets.${name}.description`, slot.description, "secretDescriptionChars", limits));
|
|
157
|
+
if ("allowedHosts" in slot && Array.isArray(slot.allowedHosts))
|
|
158
|
+
issues.push(...more(`secrets.${name}.allowedHosts`, slot.allowedHosts.length, "secretHosts", limits));
|
|
159
|
+
}
|
|
160
|
+
manifest.setup?.forEach((command, index) => {
|
|
161
|
+
if (command.command.length > limits.setupCommandChars)
|
|
162
|
+
issues.push(`setup[${index}].command has ${command.command.length} characters; the Runtime limit setupCommandChars allows at most ${limits.setupCommandChars} (PUT /v1/tenant/limits).`);
|
|
163
|
+
const ms = command.timeout === undefined ? undefined : parseSandboxDuration(command.timeout);
|
|
164
|
+
if (ms !== undefined && ms > limits.setupCommandMs)
|
|
165
|
+
issues.push(`setup[${index}].timeout asks for ${ms} ms; the Runtime limit setupCommandMs allows at most ${limits.setupCommandMs} (PUT /v1/tenant/limits).`);
|
|
166
|
+
});
|
|
167
|
+
manifest.files?.forEach((file, index) => {
|
|
168
|
+
if ("files" in file)
|
|
169
|
+
over(`files[${index}].files`, Object.keys(file.files).length, "environmentFilesPerEntry");
|
|
170
|
+
});
|
|
171
|
+
return issues;
|
|
172
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Agent's memories over the Runtime API (track T5, D72), shaped like Anthropic's memory stores:
|
|
3
|
+
* list and read them, write one whole (create or replace, with an optional `content_sha256`
|
|
4
|
+
* precondition), delete one or a folder, and read the versions every change leaves. Writes go
|
|
5
|
+
* through the memory service (`memory/service.ts`), as a model's do, so the same paths, limits
|
|
6
|
+
* and versions hold; the actor is the caller (`human:<subject>`, else `application`).
|
|
7
|
+
*/
|
|
8
|
+
import { type Memory, type MemoryList, type MemoryVersionDetail, type MemoryVersionPage, type PutMemoryRequest } from "@nylorun/core/contracts";
|
|
9
|
+
import type { AuthScope, TenantContext } from "./context.js";
|
|
10
|
+
/** Who an API write names: the person a request acts for, else the application. */
|
|
11
|
+
export declare function memoryActorOf(scope: AuthScope): string;
|
|
12
|
+
/** `GET /v1/agents/{id}/memories`: the Agent's memories by path, under `pathPrefix` when given. */
|
|
13
|
+
export declare function listMemories(ctx: TenantContext, agentId: string, options: {
|
|
14
|
+
pathPrefix?: string;
|
|
15
|
+
cursor?: string;
|
|
16
|
+
limit: number;
|
|
17
|
+
}): Promise<MemoryList>;
|
|
18
|
+
/** `GET /v1/agents/{id}/memories/{path}`: one memory with its content. */
|
|
19
|
+
export declare function readMemory(ctx: TenantContext, agentId: string, rawPath: string): Promise<Memory>;
|
|
20
|
+
/**
|
|
21
|
+
* `PUT /v1/agents/{id}/memories/{path}`: writes the memory whole, as the tool's `create` does. With
|
|
22
|
+
* `contentSha256` the write applies only while the memory still has that content (`""`: only while
|
|
23
|
+
* no memory has the path); otherwise `409 memory_conflict` with the current memory.
|
|
24
|
+
*/
|
|
25
|
+
export declare function putMemoryContent(ctx: TenantContext, agentId: string, rawPath: string, body: PutMemoryRequest, scope: AuthScope): Promise<Memory>;
|
|
26
|
+
/** `DELETE /v1/agents/{id}/memories/{path}`: deletes a memory, or every memory in a folder. */
|
|
27
|
+
export declare function deleteMemoryPath(ctx: TenantContext, agentId: string, rawPath: string, scope: AuthScope): Promise<{
|
|
28
|
+
path: string;
|
|
29
|
+
deleted: number;
|
|
30
|
+
}>;
|
|
31
|
+
/** `GET /v1/agents/{id}/memory_versions`: versions, newest first; `path` keeps one path's. */
|
|
32
|
+
export declare function listMemoryVersions(ctx: TenantContext, agentId: string, options: {
|
|
33
|
+
path?: string;
|
|
34
|
+
cursor?: string;
|
|
35
|
+
limit: number;
|
|
36
|
+
}): Promise<MemoryVersionPage>;
|
|
37
|
+
/** `GET /v1/agents/{id}/memory_versions/{versionId}`: one version with the content it left. */
|
|
38
|
+
export declare function readMemoryVersion(ctx: TenantContext, agentId: string, versionId: string): Promise<MemoryVersionDetail>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Agent's memories over the Runtime API (track T5, D72), shaped like Anthropic's memory stores:
|
|
3
|
+
* list and read them, write one whole (create or replace, with an optional `content_sha256`
|
|
4
|
+
* precondition), delete one or a folder, and read the versions every change leaves. Writes go
|
|
5
|
+
* through the memory service (`memory/service.ts`), as a model's do, so the same paths, limits
|
|
6
|
+
* and versions hold; the actor is the caller (`human:<subject>`, else `application`).
|
|
7
|
+
*/
|
|
8
|
+
import { memoryPathOf, } from "@nylorun/core/contracts";
|
|
9
|
+
import { applyMemoryCommand, memorySummaryOf, memoryVersionDetailOf, memoryVersionOf, } from "../memory/service.js";
|
|
10
|
+
import { liveEnvironment } from "./agents.js";
|
|
11
|
+
import { fail } from "./http.js";
|
|
12
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
13
|
+
/** Who an API write names: the person a request acts for, else the application. */
|
|
14
|
+
export function memoryActorOf(scope) {
|
|
15
|
+
return scope.kind === "token" || scope.kind === "subject" ? `human:${scope.subject}` : "application";
|
|
16
|
+
}
|
|
17
|
+
async function agentOf(t, agentId) {
|
|
18
|
+
if (!(await liveEnvironment(t, agentId)))
|
|
19
|
+
fail(404, `Agent ${agentId} not found`, { code: "not_found" });
|
|
20
|
+
}
|
|
21
|
+
/** A path from a route: relative to `/memories` (as the route's segment), or absolute. */
|
|
22
|
+
function routePath(raw, kind) {
|
|
23
|
+
const trimmed = raw.replace(/^\/+/, "");
|
|
24
|
+
const parsed = memoryPathOf(trimmed.startsWith("memories/") || trimmed === "memories" ? trimmed : `memories/${trimmed}`, kind);
|
|
25
|
+
return "error" in parsed ? fail(400, parsed.error, { code: "invalid_request" }) : parsed.path;
|
|
26
|
+
}
|
|
27
|
+
/** The HTTP answer to a refused write. */
|
|
28
|
+
function refusal(result) {
|
|
29
|
+
switch (result.code) {
|
|
30
|
+
case "memory.not_found":
|
|
31
|
+
return fail(404, result.text, { code: "not_found" });
|
|
32
|
+
case "memory.limit":
|
|
33
|
+
return fail(409, result.text, { code: "limit_exceeded", details: { limit: "memoriesPerAgent" } });
|
|
34
|
+
case "memory.too_large":
|
|
35
|
+
return fail(413, result.text, { code: "limit_exceeded", details: { limit: "memoryBytes" } });
|
|
36
|
+
case "memory.exists":
|
|
37
|
+
return fail(409, result.text, { code: "memory_conflict" });
|
|
38
|
+
default:
|
|
39
|
+
return fail(400, result.text, { code: "invalid_request" });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** `GET /v1/agents/{id}/memories`: the Agent's memories by path, under `pathPrefix` when given. */
|
|
43
|
+
export async function listMemories(ctx, agentId, options) {
|
|
44
|
+
const folder = options.pathPrefix === undefined ? undefined : routePath(options.pathPrefix, "any");
|
|
45
|
+
return ctx.store.tx(async (t) => {
|
|
46
|
+
await agentOf(t, agentId);
|
|
47
|
+
const rows = await t.memories(agentId, {
|
|
48
|
+
...(folder === undefined ? {} : { folder }),
|
|
49
|
+
...(options.cursor === undefined ? {} : { after: options.cursor }),
|
|
50
|
+
limit: options.limit + 1,
|
|
51
|
+
});
|
|
52
|
+
const page = rows.slice(0, options.limit);
|
|
53
|
+
return { memories: page.map(memorySummaryOf), nextCursor: rows.length > options.limit ? page.at(-1).path : null };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** `GET /v1/agents/{id}/memories/{path}`: one memory with its content. */
|
|
57
|
+
export async function readMemory(ctx, agentId, rawPath) {
|
|
58
|
+
const path = routePath(rawPath, "file");
|
|
59
|
+
return ctx.store.tx(async (t) => {
|
|
60
|
+
await agentOf(t, agentId);
|
|
61
|
+
const row = (await t.memory(agentId, path)) ?? fail(404, `The path ${path} does not exist`, { code: "not_found" });
|
|
62
|
+
return { ...memorySummaryOf(row), content: row.content };
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `PUT /v1/agents/{id}/memories/{path}`: writes the memory whole, as the tool's `create` does. With
|
|
67
|
+
* `contentSha256` the write applies only while the memory still has that content (`""`: only while
|
|
68
|
+
* no memory has the path); otherwise `409 memory_conflict` with the current memory.
|
|
69
|
+
*/
|
|
70
|
+
export async function putMemoryContent(ctx, agentId, rawPath, body, scope) {
|
|
71
|
+
const path = routePath(rawPath, "file");
|
|
72
|
+
return ctx.store.tx(async (t) => {
|
|
73
|
+
await agentOf(t, agentId);
|
|
74
|
+
const current = await t.memory(agentId, path, { lock: true });
|
|
75
|
+
if (body.contentSha256 !== undefined && body.contentSha256 !== (current?.contentSha256 ?? ""))
|
|
76
|
+
fail(409, `${path} has changed since that content_sha256: read it again and write on top of what it holds now.`, {
|
|
77
|
+
code: "memory_conflict",
|
|
78
|
+
details: { current: current ? { ...memorySummaryOf(current), content: current.content } : null },
|
|
79
|
+
});
|
|
80
|
+
const result = await applyMemoryCommand(t, agentId, { command: "create", path, fileText: body.content }, { actor: memoryActorOf(scope) }, await readRuntimeLimits(t));
|
|
81
|
+
if (!result.ok)
|
|
82
|
+
refusal(result);
|
|
83
|
+
const row = (await t.memory(agentId, path));
|
|
84
|
+
return { ...memorySummaryOf(row), content: row.content };
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** `DELETE /v1/agents/{id}/memories/{path}`: deletes a memory, or every memory in a folder. */
|
|
88
|
+
export async function deleteMemoryPath(ctx, agentId, rawPath, scope) {
|
|
89
|
+
const path = routePath(rawPath, "any");
|
|
90
|
+
return ctx.store.tx(async (t) => {
|
|
91
|
+
await agentOf(t, agentId);
|
|
92
|
+
const result = await applyMemoryCommand(t, agentId, { command: "delete", path }, { actor: memoryActorOf(scope) }, await readRuntimeLimits(t));
|
|
93
|
+
if (!result.ok)
|
|
94
|
+
refusal(result);
|
|
95
|
+
return { path, deleted: result.changes.length };
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** `GET /v1/agents/{id}/memory_versions`: versions, newest first; `path` keeps one path's. */
|
|
99
|
+
export async function listMemoryVersions(ctx, agentId, options) {
|
|
100
|
+
const path = options.path === undefined ? undefined : routePath(options.path, "file");
|
|
101
|
+
return ctx.store.tx(async (t) => {
|
|
102
|
+
await agentOf(t, agentId);
|
|
103
|
+
const rows = await t.memoryVersions(agentId, {
|
|
104
|
+
...(path === undefined ? {} : { path }),
|
|
105
|
+
...(options.cursor === undefined ? {} : { before: options.cursor }),
|
|
106
|
+
limit: options.limit + 1,
|
|
107
|
+
});
|
|
108
|
+
const page = rows.slice(0, options.limit);
|
|
109
|
+
return { versions: page.map(memoryVersionOf), nextCursor: rows.length > options.limit ? page.at(-1).id : null };
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** `GET /v1/agents/{id}/memory_versions/{versionId}`: one version with the content it left. */
|
|
113
|
+
export async function readMemoryVersion(ctx, agentId, versionId) {
|
|
114
|
+
return ctx.store.tx(async (t) => {
|
|
115
|
+
await agentOf(t, agentId);
|
|
116
|
+
const row = (await t.memoryVersion(agentId, versionId)) ?? fail(404, `Memory version ${versionId} not found`, { code: "not_found" });
|
|
117
|
+
return memoryVersionDetailOf(row);
|
|
118
|
+
});
|
|
119
|
+
}
|
package/dist/tenant/module.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ export interface CreateTenantModuleOptions {
|
|
|
3
3
|
/** Opens the Host's Tenant (`store-pg.ts`). */
|
|
4
4
|
open: TenantOpener;
|
|
5
5
|
logger: Logger;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Bound on opening the Tenant; a slower open fails with `open-timeout`. Default the Host
|
|
8
|
+
* tuning's `tenant.openTimeoutMs` (30 s).
|
|
9
|
+
*/
|
|
7
10
|
openTimeoutMs?: number;
|
|
8
11
|
/**
|
|
9
12
|
* Runs once the Tenant is open, at start or on a later retry: the Host starts what needs the
|
package/dist/tenant/module.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { openError, TenantOpenError } from "./cause.js";
|
|
2
2
|
import { TimeoutError, withTimeout } from "./pool.js";
|
|
3
|
+
import { hostTuning } from "../tuning.js";
|
|
3
4
|
import { TenantUnavailableError, } from "./types.js";
|
|
4
|
-
const OPEN_TIMEOUT_MS = 30_000;
|
|
5
|
-
/** Retries of an open that failed outside the Tenant: from 1 s, doubling, at most 30 s apart. */
|
|
6
|
-
const RETRY_FIRST_MS = 1_000;
|
|
7
|
-
const RETRY_MAX_MS = 30_000;
|
|
8
5
|
/**
|
|
9
6
|
* Deep Tenant module (§8): the Host's one Tenant over an injected opener (tenancy.md §5).
|
|
10
7
|
*
|
|
@@ -15,11 +12,15 @@ const RETRY_MAX_MS = 30_000;
|
|
|
15
12
|
* with backoff, and by the next request, which gets 503 meanwhile.
|
|
16
13
|
*/
|
|
17
14
|
export function createTenantModule(options) {
|
|
18
|
-
const
|
|
15
|
+
const tuning = hostTuning().tenant;
|
|
16
|
+
const { logger, openTimeoutMs = tuning.openTimeoutMs } = options;
|
|
17
|
+
// Retries of an open that failed outside the Tenant: from `openRetry.minMs`, doubling, at most
|
|
18
|
+
// `openRetry.maxMs` apart.
|
|
19
|
+
const { minMs: firstRetryMs, maxMs: maxRetryMs } = tuning.openRetry;
|
|
19
20
|
let state = { kind: "idle" };
|
|
20
21
|
let opening;
|
|
21
22
|
let retry;
|
|
22
|
-
let nextDelayMs =
|
|
23
|
+
let nextDelayMs = firstRetryMs;
|
|
23
24
|
const current = () => state;
|
|
24
25
|
async function openOnce() {
|
|
25
26
|
let handle;
|
|
@@ -64,7 +65,7 @@ export function createTenantModule(options) {
|
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
67
|
state = { kind: "open", handle };
|
|
67
|
-
nextDelayMs =
|
|
68
|
+
nextDelayMs = firstRetryMs;
|
|
68
69
|
logger.info("tenant opened", { tenantId: handle.envelope.id });
|
|
69
70
|
try {
|
|
70
71
|
await options.onOpen?.(handle);
|
|
@@ -92,7 +93,7 @@ export function createTenantModule(options) {
|
|
|
92
93
|
if (retry)
|
|
93
94
|
return;
|
|
94
95
|
const delayMs = nextDelayMs;
|
|
95
|
-
nextDelayMs = Math.min(delayMs * 2,
|
|
96
|
+
nextDelayMs = Math.min(delayMs * 2, maxRetryMs);
|
|
96
97
|
retry = setTimeout(() => {
|
|
97
98
|
retry = undefined;
|
|
98
99
|
void load();
|
|
@@ -30,8 +30,6 @@ export type OutboundResult = {
|
|
|
30
30
|
code: string;
|
|
31
31
|
message: string;
|
|
32
32
|
};
|
|
33
|
-
/** The largest answer read from an endpoint. */
|
|
34
|
-
export declare const MAX_RESPONSE_BYTES: number;
|
|
35
33
|
/** True when `address` is private, loopback, link-local or unspecified (IPv4-mapped included). */
|
|
36
34
|
export declare function isPrivateAddress(address: string): boolean;
|
|
37
35
|
/** Why the Host refuses `url`, or `undefined` when it may be called. */
|
|
@@ -44,7 +42,8 @@ export declare function refusal(url: URL, policy: OutboundPolicy): string | unde
|
|
|
44
42
|
export declare function send(target: string, body: string | undefined, headers: Record<string, string>, options: {
|
|
45
43
|
policy: OutboundPolicy;
|
|
46
44
|
signal: AbortSignal;
|
|
47
|
-
|
|
45
|
+
/** The largest answer read: the Tenant's Runtime limit `toolAnswerBytes` for an HTTP tool. */
|
|
46
|
+
maxResponseBytes: number;
|
|
48
47
|
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
49
48
|
}): Promise<OutboundResult>;
|
|
50
49
|
/**
|
|
@@ -71,23 +70,21 @@ export declare class OutboundFailed extends Error {
|
|
|
71
70
|
*/
|
|
72
71
|
sent?: boolean);
|
|
73
72
|
}
|
|
74
|
-
/** The largest answer `guardedFetch` reads. */
|
|
75
|
-
export declare const MAX_FETCH_RESPONSE_BYTES: number;
|
|
76
|
-
/** How long a `guardedFetch` request may take without a signal of its own. */
|
|
77
|
-
export declare const FETCH_TIMEOUT_MS = 30000;
|
|
78
73
|
/**
|
|
79
74
|
* A `fetch` under the Host's address policy (F9 C2): the literal-IP and DNS checks of `post`
|
|
80
75
|
* on the address actually connected to, `localhost` rewritten for the local stack, no
|
|
81
|
-
* redirects (`redirect: "error"`; a 3xx answer rejects),
|
|
82
|
-
* unless the caller passes a signal
|
|
83
|
-
*
|
|
76
|
+
* redirects (`redirect: "error"`; a 3xx answer rejects), an answer of at most `maxResponseBytes`
|
|
77
|
+
* and a `timeoutMs` timeout unless the caller passes a signal, both the caller's (a Tenant's
|
|
78
|
+
* Runtime limits). A refused URL or address rejects with `OutboundRefused`, any other failure
|
|
79
|
+
* with `OutboundFailed`.
|
|
84
80
|
*
|
|
85
81
|
* `stream`: the Response resolves once the headers arrive and its body streams, unbounded and
|
|
86
82
|
* with no timeout but the caller's signal: remote MCP servers, whose answers and event streams
|
|
87
83
|
* stay open (`mcp/connect.ts`).
|
|
88
84
|
*/
|
|
89
|
-
export declare function guardedFetch(policy: OutboundPolicy, options
|
|
90
|
-
maxResponseBytes
|
|
91
|
-
timeoutMs
|
|
92
|
-
|
|
85
|
+
export declare function guardedFetch(policy: OutboundPolicy, options: {
|
|
86
|
+
maxResponseBytes: number;
|
|
87
|
+
timeoutMs: number;
|
|
88
|
+
} | {
|
|
89
|
+
stream: true;
|
|
93
90
|
}): typeof fetch;
|
package/dist/tenant/outbound.js
CHANGED
|
@@ -13,8 +13,6 @@ import { request as httpRequest } from "node:http";
|
|
|
13
13
|
import { request as httpsRequest } from "node:https";
|
|
14
14
|
import { BlockList, isIP } from "node:net";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
|
-
/** The largest answer read from an endpoint. */
|
|
17
|
-
export const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
18
16
|
const PRIVATE = new BlockList();
|
|
19
17
|
for (const [network, prefix] of [
|
|
20
18
|
["0.0.0.0", 8],
|
|
@@ -82,7 +80,7 @@ function guardedLookup(policy) {
|
|
|
82
80
|
*/
|
|
83
81
|
export async function send(target, body, headers, options) {
|
|
84
82
|
const { policy, signal } = options;
|
|
85
|
-
const limit = options.maxResponseBytes
|
|
83
|
+
const limit = options.maxResponseBytes;
|
|
86
84
|
const url = new URL(target);
|
|
87
85
|
const refused = refusal(url, policy);
|
|
88
86
|
if (refused)
|
|
@@ -168,25 +166,22 @@ export class OutboundFailed extends Error {
|
|
|
168
166
|
this.name = "OutboundFailed";
|
|
169
167
|
}
|
|
170
168
|
}
|
|
171
|
-
/** The largest answer `guardedFetch` reads. */
|
|
172
|
-
export const MAX_FETCH_RESPONSE_BYTES = 1024 * 1024;
|
|
173
|
-
/** How long a `guardedFetch` request may take without a signal of its own. */
|
|
174
|
-
export const FETCH_TIMEOUT_MS = 30_000;
|
|
175
169
|
/**
|
|
176
170
|
* A `fetch` under the Host's address policy (F9 C2): the literal-IP and DNS checks of `post`
|
|
177
171
|
* on the address actually connected to, `localhost` rewritten for the local stack, no
|
|
178
|
-
* redirects (`redirect: "error"`; a 3xx answer rejects),
|
|
179
|
-
* unless the caller passes a signal
|
|
180
|
-
*
|
|
172
|
+
* redirects (`redirect: "error"`; a 3xx answer rejects), an answer of at most `maxResponseBytes`
|
|
173
|
+
* and a `timeoutMs` timeout unless the caller passes a signal, both the caller's (a Tenant's
|
|
174
|
+
* Runtime limits). A refused URL or address rejects with `OutboundRefused`, any other failure
|
|
175
|
+
* with `OutboundFailed`.
|
|
181
176
|
*
|
|
182
177
|
* `stream`: the Response resolves once the headers arrive and its body streams, unbounded and
|
|
183
178
|
* with no timeout but the caller's signal: remote MCP servers, whose answers and event streams
|
|
184
179
|
* stay open (`mcp/connect.ts`).
|
|
185
180
|
*/
|
|
186
|
-
export function guardedFetch(policy, options
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
const
|
|
181
|
+
export function guardedFetch(policy, options) {
|
|
182
|
+
const stream = "stream" in options;
|
|
183
|
+
const limit = "stream" in options ? Infinity : options.maxResponseBytes;
|
|
184
|
+
const timeoutMs = "stream" in options ? undefined : options.timeoutMs;
|
|
190
185
|
const lookup = guardedLookup(policy);
|
|
191
186
|
const guarded = async (input, init = {}) => {
|
|
192
187
|
const request = input instanceof Request ? input : undefined;
|
|
@@ -217,7 +212,7 @@ export function guardedFetch(policy, options = {}) {
|
|
|
217
212
|
}
|
|
218
213
|
const originalHost = url.host;
|
|
219
214
|
dockerHost(url, policy);
|
|
220
|
-
const signal = init.signal ?? request?.signal ?? (
|
|
215
|
+
const signal = init.signal ?? request?.signal ?? (timeoutMs === undefined ? undefined : AbortSignal.timeout(timeoutMs));
|
|
221
216
|
const outgoing = { host: originalHost };
|
|
222
217
|
for (const [name, value] of headers)
|
|
223
218
|
outgoing[name] = value;
|
package/dist/tenant/reset.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { detachAllSessions } from "./
|
|
3
|
+
import { detachAllSessions } from "./agents.js";
|
|
4
4
|
import { contentKey } from "../artifacts/folders.js";
|
|
5
5
|
/**
|
|
6
6
|
* Rename `dir` to a sibling, delete the sibling, recreate an empty `dir`.
|
|
@@ -38,7 +38,7 @@ function replaceDirectory(dir) {
|
|
|
38
38
|
*/
|
|
39
39
|
export async function resetTenant(ctx, scope) {
|
|
40
40
|
const clearSessions = scope === "sessions" || scope === "all";
|
|
41
|
-
const clearSandboxes = scope === "
|
|
41
|
+
const clearSandboxes = scope === "agents" || scope === "all";
|
|
42
42
|
const clearAll = scope === "all";
|
|
43
43
|
if (clearSandboxes) {
|
|
44
44
|
await ctx.sandbox.removeAll();
|
|
@@ -23,6 +23,12 @@ export interface RunGrants extends RunTokens {
|
|
|
23
23
|
}
|
|
24
24
|
export declare function createRunGrants(): RunGrants;
|
|
25
25
|
type GrantContext = Pick<TenantContext, "keys" | "config" | "runGrants">;
|
|
26
|
+
/** The root agent a session's run token names: its loop manifest's id (the agent manifest's name). */
|
|
27
|
+
export declare function rootAgentOf(session: {
|
|
28
|
+
readonly manifest: {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
};
|
|
31
|
+
}): string;
|
|
26
32
|
/** What a run token names of the session: its root agent and the turn the advance runs. */
|
|
27
33
|
export interface RunOf {
|
|
28
34
|
readonly agentId: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { mintRunToken
|
|
1
|
+
import { mintRunToken } from "./run-token.js";
|
|
2
|
+
import { hostTuning } from "../tuning.js";
|
|
2
3
|
export function createRunGrants() {
|
|
3
4
|
const grants = new Map();
|
|
4
5
|
return {
|
|
@@ -17,6 +18,10 @@ export function createRunGrants() {
|
|
|
17
18
|
},
|
|
18
19
|
};
|
|
19
20
|
}
|
|
21
|
+
/** The root agent a session's run token names: its loop manifest's id (the agent manifest's name). */
|
|
22
|
+
export function rootAgentOf(session) {
|
|
23
|
+
return session.manifest.id;
|
|
24
|
+
}
|
|
20
25
|
/**
|
|
21
26
|
* Mints and registers the run token of the advance that just took `lease`. Nothing when the
|
|
22
27
|
* gates run in this process, or the session has no active turn (it makes no gate call).
|
|
@@ -50,7 +55,7 @@ export async function renewRunGrant(ctx, lease, run, active = () => true) {
|
|
|
50
55
|
const held = ctx.runGrants.get(lease.sessionId);
|
|
51
56
|
if (held && held.claims.epoch !== lease.epoch)
|
|
52
57
|
return;
|
|
53
|
-
if (held && held.claims.expiresAt - Date.now() >
|
|
58
|
+
if (held && held.claims.expiresAt - Date.now() > hostTuning().keys.runTokenRenewSeconds * 1000)
|
|
54
59
|
return;
|
|
55
60
|
const grant = await mintRunToken(ctx, lease, held ? { agentId: held.claims.agentId, activeTurnId: held.claims.turnId } : run);
|
|
56
61
|
if (active())
|
|
@@ -4,10 +4,6 @@ import type { SessionStore } from "../store/types.js";
|
|
|
4
4
|
import type { Lease } from "./context.js";
|
|
5
5
|
export declare const RUN_TOKEN_TYP = "nylorun-run+jwt";
|
|
6
6
|
export declare const RUN_TOKEN_AUD = "nylorun-gates";
|
|
7
|
-
/** How long a run token lives. */
|
|
8
|
-
export declare const RUN_TOKEN_TTL_SECONDS: number;
|
|
9
|
-
/** The lease heartbeat re-mints a token with less than this left. */
|
|
10
|
-
export declare const RUN_TOKEN_RENEW_SECONDS: number;
|
|
11
7
|
/** What a verified run token says: one lease of one session's turn. */
|
|
12
8
|
export interface RunClaims {
|
|
13
9
|
readonly tenantId: string;
|
package/dist/tenant/run-token.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Signed by the keys service with the Tenant's current signing key, like capability links,
|
|
8
8
|
* and told apart from them by `typ` and `aud`. Internal: not in `openapi.json`. A token lives
|
|
9
|
-
* `
|
|
9
|
+
* `keys.runTokenTtlSeconds` (Host tuning), no longer than `keys.tokenTtlMaxSeconds`, so a key rotation never
|
|
10
10
|
* revokes a live one (`signing-keys.ts`).
|
|
11
11
|
*
|
|
12
12
|
* ```text
|
|
@@ -18,15 +18,10 @@
|
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
19
|
import { errors, importJWK, jwtVerify } from "jose";
|
|
20
20
|
import { tenantTokenIssuer } from "@nylorun/core/contracts";
|
|
21
|
-
import {
|
|
21
|
+
import { clockToleranceSeconds, tokenKeyId } from "./jwt.js";
|
|
22
|
+
import { hostTuning } from "../tuning.js";
|
|
22
23
|
export const RUN_TOKEN_TYP = "nylorun-run+jwt";
|
|
23
24
|
export const RUN_TOKEN_AUD = "nylorun-gates";
|
|
24
|
-
/** How long a run token lives. */
|
|
25
|
-
export const RUN_TOKEN_TTL_SECONDS = 15 * 60;
|
|
26
|
-
/** The lease heartbeat re-mints a token with less than this left. */
|
|
27
|
-
export const RUN_TOKEN_RENEW_SECONDS = 5 * 60;
|
|
28
|
-
/** Allowed spread between `iat` and `exp` beyond the lifetime. */
|
|
29
|
-
const LIFETIME_SLACK_SECONDS = 60;
|
|
30
25
|
/**
|
|
31
26
|
* Mints the run token of `lease` over `session`'s active turn. Throws when the session has
|
|
32
27
|
* no active turn: there is no run to name.
|
|
@@ -37,7 +32,7 @@ export async function mintRunToken(signer, lease, session) {
|
|
|
37
32
|
throw new Error(`Session ${lease.sessionId} has no active turn to mint a run token for`);
|
|
38
33
|
const tenantId = signer.config.tenantId;
|
|
39
34
|
const iat = Math.floor(Date.now() / 1000);
|
|
40
|
-
const exp = iat +
|
|
35
|
+
const exp = iat + hostTuning().keys.runTokenTtlSeconds;
|
|
41
36
|
// Signed by the keys service (F4.2): this process never holds the private key.
|
|
42
37
|
const { token } = await signer.keys.sign({
|
|
43
38
|
typ: RUN_TOKEN_TYP,
|
|
@@ -92,7 +87,7 @@ export async function verifyRunToken(store, tenantId, raw, cache = new Map()) {
|
|
|
92
87
|
issuer: tenantTokenIssuer(tenantId),
|
|
93
88
|
audience: RUN_TOKEN_AUD,
|
|
94
89
|
typ: RUN_TOKEN_TYP,
|
|
95
|
-
clockTolerance:
|
|
90
|
+
clockTolerance: clockToleranceSeconds(),
|
|
96
91
|
requiredClaims: ["sub", "iat", "exp", "jti"],
|
|
97
92
|
});
|
|
98
93
|
payload = verified.payload;
|
|
@@ -112,7 +107,7 @@ export async function verifyRunToken(store, tenantId, raw, cache = new Map()) {
|
|
|
112
107
|
typeof epc !== "number" ||
|
|
113
108
|
!Number.isSafeInteger(epc) ||
|
|
114
109
|
epc < 1 ||
|
|
115
|
-
exp - iat >
|
|
110
|
+
exp - iat > hostTuning().keys.runTokenTtlSeconds + hostTuning().keys.tokenLifetimeSlackSeconds)
|
|
116
111
|
return refused("claims");
|
|
117
112
|
return {
|
|
118
113
|
ok: true,
|