@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
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { SESSION_STATUSES } from "@nylorun/core/contracts";
|
|
3
|
-
import { SessionManifestView, SessionUsageTotals, ModelCallsPage, ModelCallExportPage, } from "../../components.js";
|
|
3
|
+
import { SessionManifestView, SessionUsageTotals, ModelCallsPage, ModelCallExportPage, ToolCallsPage, TurnTrace, } from "../../components.js";
|
|
4
|
+
import { turnTrace } from "../../../traces/read.js";
|
|
4
5
|
import { readAccess, readStoreOf } from "../../../reads/access.js";
|
|
5
6
|
import { requireApplication, requirePrincipal } from "../../../tenant/auth.js";
|
|
6
7
|
import { fail } from "../../../tenant/http.js";
|
|
7
|
-
import {
|
|
8
|
+
import { environmentGrantsOf } from "../../../tenant/environments.js";
|
|
8
9
|
import { tenantRoute } from "../define.js";
|
|
9
10
|
import { jsonResponse } from "../respond.js";
|
|
10
11
|
export const pageQuery = z.object({
|
|
@@ -15,7 +16,7 @@ export const sessionPageQuery = pageQuery
|
|
|
15
16
|
.extend({
|
|
16
17
|
agentId: z.string().optional(),
|
|
17
18
|
status: z.enum(SESSION_STATUSES).optional(),
|
|
18
|
-
|
|
19
|
+
environmentId: z.string().optional(),
|
|
19
20
|
ownerUserId: z.string().optional(),
|
|
20
21
|
})
|
|
21
22
|
.strict();
|
|
@@ -31,11 +32,11 @@ export async function sessionPage(ctx, scope, query) {
|
|
|
31
32
|
requireApplication(scope);
|
|
32
33
|
return readStoreOf(ctx).sessions(filters, { limit, cursor }, readAccess(scope));
|
|
33
34
|
}
|
|
34
|
-
export async function
|
|
35
|
+
export async function environmentPage(ctx, scope, query, labels) {
|
|
35
36
|
const { limit, cursor } = parseQuery(pageQuery.extend({ label: z.string().optional() }).strict(), query);
|
|
36
37
|
if (limit === undefined)
|
|
37
38
|
return fail(400, "Pagination requires limit");
|
|
38
|
-
return readStoreOf(ctx).
|
|
39
|
+
return readStoreOf(ctx).environments(labels, { limit, cursor }, readAccess(scope), environmentGrantsOf(scope));
|
|
39
40
|
}
|
|
40
41
|
export function readRoutes(api) {
|
|
41
42
|
const sessionId = z.object({ sessionId: z.string() });
|
|
@@ -101,6 +102,54 @@ export function readRoutes(api) {
|
|
|
101
102
|
return jsonResponse(200, await store.modelCalls(id, q.turnId, q, access));
|
|
102
103
|
});
|
|
103
104
|
}
|
|
105
|
+
const toolCallsQuery = turnQuery
|
|
106
|
+
.extend({
|
|
107
|
+
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
108
|
+
cursor: z.string().optional(),
|
|
109
|
+
})
|
|
110
|
+
.strict();
|
|
111
|
+
// What ran, with what input, how many sends and how it ended (R5.2): the effect journal's tool
|
|
112
|
+
// rows, readable as the model calls are.
|
|
113
|
+
tenantRoute(api, { credentials: ["application"], scopes: "never" }, {
|
|
114
|
+
method: "get",
|
|
115
|
+
path: "/v1/sessions/{sessionId}/calls/tool",
|
|
116
|
+
tags: ["Sessions"],
|
|
117
|
+
summary: "Page a session's tool calls",
|
|
118
|
+
request: { params: sessionId, query: toolCallsQuery },
|
|
119
|
+
responses: {
|
|
120
|
+
200: {
|
|
121
|
+
description: "Each tool call in the order it was first sent, with its status and sends",
|
|
122
|
+
content: { "application/json": { schema: ToolCallsPage } },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
}, async (c) => {
|
|
126
|
+
requirePrincipal(c.get("scope"));
|
|
127
|
+
const q = parseQuery(toolCallsQuery, c.req.query());
|
|
128
|
+
return jsonResponse(200, await readStoreOf(c.env.tenant).toolCalls(c.req.param("sessionId"), q.turnId, q, readAccess(c.get("scope"))));
|
|
129
|
+
});
|
|
130
|
+
// A turn's spans (R7.5): the same mapping the trace projector posts, from the turn's log rows,
|
|
131
|
+
// with the installation's content opt-ins. Spans carry cost: the application's, as /calls are.
|
|
132
|
+
tenantRoute(api, { credentials: ["application"], scopes: "never" }, {
|
|
133
|
+
method: "get",
|
|
134
|
+
path: "/v1/sessions/{sessionId}/turns/{turnId}/trace",
|
|
135
|
+
tags: ["Sessions"],
|
|
136
|
+
summary: "Read a turn's trace",
|
|
137
|
+
description: "The turn's spans as OTLP/HTTP JSON (`resourceSpans`), as the Runtime posts them to an OTLP collector: a root span for the turn, a span for each model, tool and egress crossing, ids derived so every read agrees. Post it to a collector's `/v1/traces` to backfill. A turn still running has no root span yet; a turn the session never had has no spans.",
|
|
138
|
+
request: { params: sessionId.extend({ turnId: z.string() }) },
|
|
139
|
+
responses: {
|
|
140
|
+
200: {
|
|
141
|
+
description: "The turn's spans",
|
|
142
|
+
content: { "application/json": { schema: TurnTrace } },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
}, async (c) => {
|
|
146
|
+
requirePrincipal(c.get("scope"));
|
|
147
|
+
const ctx = c.env.tenant;
|
|
148
|
+
const id = c.req.param("sessionId");
|
|
149
|
+
const turnId = c.req.param("turnId");
|
|
150
|
+
const events = await readStoreOf(ctx).turnEvents(id, turnId, readAccess(c.get("scope")));
|
|
151
|
+
return jsonResponse(200, await turnTrace(ctx, id, turnId, events));
|
|
152
|
+
});
|
|
104
153
|
const exportQuery = z
|
|
105
154
|
.object({
|
|
106
155
|
after: z.string().optional(),
|
|
@@ -2,16 +2,17 @@ import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { PutAgentRequestSchema, PutSessionRequestSchema, SessionCommandSchema, } from "@nylorun/core/contracts";
|
|
4
4
|
import { handleSessionSandboxTool, SandboxRouteError } from "../../../core/sandbox-routes.js";
|
|
5
|
+
import { traceContextOf } from "../../../traces/ids.js";
|
|
5
6
|
import { AcceptedResponse, ListAgentsResponse, ListPublicAgentsResponse, ListSessionsResponse, SessionPage, HistoryPage, SessionEvent, PutAgentRequest, PutAgentResponse, PutSessionRequest, SandboxToolOutcome, SessionCommand, SessionItemsResponse, SessionView, StreamClosedFrame, } from "../../components.js";
|
|
6
7
|
import { accessOf, requireApplication } from "../../../tenant/auth.js";
|
|
7
8
|
import { command } from "../../../tenant/commands.js";
|
|
8
9
|
import { loadSession, sessionOf } from "../../../tenant/context.js";
|
|
9
10
|
import { fail } from "../../../tenant/http.js";
|
|
10
|
-
import {
|
|
11
|
+
import { environmentGrantsOf, sandboxRouteDeps } from "../../../tenant/environments.js";
|
|
11
12
|
import { readHistory, requestCursor, streamHolderOf, streamSessionEvents } from "../../../tenant/session-streams.js";
|
|
12
13
|
import { listAgentsPublic, listDefinitions, listSessions, putDefinition, putSession, sessionView, } from "../../../tenant/sessions.js";
|
|
13
14
|
import { readJson } from "../body.js";
|
|
14
|
-
import { tenantRoute } from "../define.js";
|
|
15
|
+
import { TraceHeaders, tenantRoute } from "../define.js";
|
|
15
16
|
import { pageQuery, sessionPage, sessionPageQuery, parseQuery } from "./reads.js";
|
|
16
17
|
import { jsonResponse } from "../respond.js";
|
|
17
18
|
const OWN_SESSIONS = {
|
|
@@ -52,12 +53,19 @@ export function sessionRoutes(api) {
|
|
|
52
53
|
tags: ["Sessions"],
|
|
53
54
|
summary: "Send a command to a session",
|
|
54
55
|
description: "A message starts a turn; the others answer what the session waits on, or cancel. Idempotent on the command's `idempotencyKey`.",
|
|
55
|
-
request: { params: sessionId, body: body(SessionCommand) },
|
|
56
|
+
request: { params: sessionId, body: body(SessionCommand), headers: TraceHeaders },
|
|
56
57
|
responses: {
|
|
57
58
|
200: json(AcceptedResponse, "The command, accepted"),
|
|
58
59
|
409: { description: "A turn is running, or the session does not wait on this" },
|
|
60
|
+
422: {
|
|
61
|
+
description: "A message names a file the Tenant's model does not read (`attachment_unsupported`; `details.parts` lists each with its reason). No turn starts.",
|
|
62
|
+
},
|
|
59
63
|
},
|
|
60
|
-
}, async (c) =>
|
|
64
|
+
}, async (c) => {
|
|
65
|
+
// A `traceparent` joins the turn to the caller's trace (R7.4); a malformed one is ignored.
|
|
66
|
+
const trace = traceContextOf(c.req.raw.headers);
|
|
67
|
+
return jsonResponse(200, await command(c.env.tenant, c.req.param("sessionId"), SessionCommandSchema.parse(await readJson(c.req.raw)), c.get("scope"), trace ? { trace } : {}));
|
|
68
|
+
});
|
|
61
69
|
tenantRoute(api, {
|
|
62
70
|
credentials: ["application", "subject", "token"],
|
|
63
71
|
scopes: ["agents:read", "agents:write"],
|
|
@@ -82,10 +90,17 @@ export function sessionRoutes(api) {
|
|
|
82
90
|
path: "/v1/agents/{agentId}",
|
|
83
91
|
tags: ["Agents"],
|
|
84
92
|
summary: "Put an agent's definition",
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
description: "Saves the agent's definition. With `?dryRun=true` (protocol 14) every check runs and nothing is saved: the answer says what a save would record.",
|
|
94
|
+
request: {
|
|
95
|
+
params: z.object({ agentId: z.string() }),
|
|
96
|
+
query: z.object({
|
|
97
|
+
dryRun: z.enum(["true", "false"]).optional().meta({ description: "`true`: check and save nothing" }),
|
|
98
|
+
}),
|
|
99
|
+
body: body(PutAgentRequest),
|
|
100
|
+
},
|
|
101
|
+
responses: { 200: json(PutAgentResponse, "The definition, stored (or checked, with dryRun)") },
|
|
87
102
|
}, async (c) => {
|
|
88
|
-
return jsonResponse(200, await putDefinition(c.env.tenant, c.req.param("agentId"), PutAgentRequestSchema.parse(await readJson(c.req.raw))));
|
|
103
|
+
return jsonResponse(200, await putDefinition(c.env.tenant, c.req.param("agentId"), PutAgentRequestSchema.parse(await readJson(c.req.raw)), { dryRun: c.req.query("dryRun") === "true" }));
|
|
89
104
|
});
|
|
90
105
|
tenantRoute(api, OWN_SESSIONS, {
|
|
91
106
|
method: "get",
|
|
@@ -119,8 +134,8 @@ export function sessionRoutes(api) {
|
|
|
119
134
|
// Agent code may trust `info`: only an app server sets it.
|
|
120
135
|
if (scope.kind === "token" && request.info !== undefined)
|
|
121
136
|
fail(403, "A token caller cannot set session info", { code: "scope_required" });
|
|
122
|
-
const grants =
|
|
123
|
-
const session = await putSession(ctx, c.req.param("sessionId"), request, accessOf(scope), grants === undefined ? {} : {
|
|
137
|
+
const grants = environmentGrantsOf(scope);
|
|
138
|
+
const session = await putSession(ctx, c.req.param("sessionId"), request, accessOf(scope), grants === undefined ? {} : { environmentGrants: grants });
|
|
124
139
|
return jsonResponse(200, await ctx.store.tx((t) => sessionView(t, session)));
|
|
125
140
|
});
|
|
126
141
|
tenantRoute(api, OWN_SESSIONS, {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { ModelUsageQuerySchema, PutModelBudgetsRequestSchema, PutHostModelRequestSchema,
|
|
3
|
-
import { HostModelCatalog, HostModelView, ListProvidersResponse, ModelBudgets, ModelUsageTotals, PutModelBudgetsRequest, PutHostModelRequest,
|
|
2
|
+
import { ModelUsageQuerySchema, PutModelBudgetsRequestSchema, PutToolPolicyRequestSchema, PutModelInputsRequestSchema, PutHostModelRequestSchema, PutTenantEnvironmentsRequestSchema, ResetTenantRequestSchema, SeedTenantConfigRequestSchema, SelectHostModelRequestSchema, } from "@nylorun/core/contracts";
|
|
3
|
+
import { HostModelCatalog, HostModelView, ListProvidersResponse, ModelBudgets, ModelInputsView, ModelUsageTotals, PutModelBudgetsRequest, PutToolPolicyRequest, PutModelInputsRequest, ToolPolicy, PutHostModelRequest, PutTenantEnvironmentsRequest, ResetTenantRequest, ResetTenantResponse, SeedTenantConfigRequest, SeedTenantConfigResponse, SelectHostModelRequest, TenantSandboxView, TenantStatus, } from "../../components.js";
|
|
4
4
|
import { periodStart } from "../../../gates/meter.js";
|
|
5
|
-
import {
|
|
5
|
+
import { readToolPolicy, TOOL_POLICY_SETTING } from "../../../gates/policy.js";
|
|
6
|
+
import { addableInputs, capabilitiesOf, inputsError } from "../../../model/capabilities.js";
|
|
7
|
+
import { catalogModel, hostModelCatalog } from "../../../model/catalog.js";
|
|
6
8
|
import { sandboxConfigErrors } from "../../../sandbox/resolve.js";
|
|
7
9
|
import { effectiveSandboxConfig, readSandboxConfig, writeSandboxConfig, } from "../../../sandbox/tenant-config.js";
|
|
8
10
|
import { fail } from "../../../tenant/http.js";
|
|
9
11
|
import { clearObservers } from "../../../tenant/session-streams.js";
|
|
10
|
-
import { usesFixtureModel } from "../../../tenant/model-setting.js";
|
|
12
|
+
import { readAddedInputs, usesFixtureModel, writeAddedInputs } from "../../../tenant/model-setting.js";
|
|
11
13
|
import { resetTenant } from "../../../tenant/reset.js";
|
|
12
14
|
import { clearWork, drain } from "../../../tenant/scheduler.js";
|
|
13
15
|
import { buildTenantStatus, harnessStatusOf, seedTenantConfig } from "../../../tenant/status.js";
|
|
@@ -15,7 +17,7 @@ import { tenantReset, streamsStatus, } from "../../../tenant/streams.js";
|
|
|
15
17
|
import { readJson } from "../body.js";
|
|
16
18
|
import { pathSegments, tenantRoute } from "../define.js";
|
|
17
19
|
import { jsonResponse } from "../respond.js";
|
|
18
|
-
import { rearmPodTimers } from "../../../tenant/
|
|
20
|
+
import { rearmPodTimers } from "../../../tenant/environments.js";
|
|
19
21
|
/** Every Management API route (`/v1/tenant/*`): a management key, acting as itself (protocol 8). */
|
|
20
22
|
const MANAGEMENT = { credentials: ["management"], scopes: "never" };
|
|
21
23
|
const json = (schema, description) => ({
|
|
@@ -105,7 +107,7 @@ export function tenantRoutes(api) {
|
|
|
105
107
|
path: "/v1/tenant/reset",
|
|
106
108
|
tags: ["Tenant"],
|
|
107
109
|
summary: "Reset the Tenant",
|
|
108
|
-
description: "Deletes its sessions, its
|
|
110
|
+
description: "Deletes its sessions, its Environments (with their vaults), or both, after draining or cancelling the turns in progress. The Tenant stays open.",
|
|
109
111
|
request: { body: body(ResetTenantRequest) },
|
|
110
112
|
responses: { 200: json(ResetTenantResponse, "Reset") },
|
|
111
113
|
}, async (c) => {
|
|
@@ -114,7 +116,7 @@ export function tenantRoutes(api) {
|
|
|
114
116
|
await drain(ctx, request.activeWork, 30_000);
|
|
115
117
|
// A sessions reset moves the Tenant to a new basin generation, so a session created
|
|
116
118
|
// again with the same id, during or after the reset, starts in an empty basin at 0.
|
|
117
|
-
const sessionsReset = request.scope !== "
|
|
119
|
+
const sessionsReset = request.scope !== "environments";
|
|
118
120
|
await resetTenant({
|
|
119
121
|
store: ctx.store,
|
|
120
122
|
sandbox: ctx.sandbox,
|
|
@@ -149,29 +151,56 @@ export function tenantRoutes(api) {
|
|
|
149
151
|
path: "/v1/tenant/models",
|
|
150
152
|
tags: ["Tenant"],
|
|
151
153
|
summary: "List model providers and models",
|
|
152
|
-
|
|
154
|
+
description: "Each model says what it reads (`inputs`: its catalog's inputs and those the operator added) and what may be added (`addableInputs`).",
|
|
155
|
+
responses: { 200: json(HostModelCatalog, "Provider and model names, and their inputs") },
|
|
153
156
|
}, async (c) => {
|
|
154
|
-
c.env.tenant;
|
|
155
|
-
return jsonResponse(200, hostModelCatalog());
|
|
157
|
+
const added = await c.env.tenant.store.tx((t) => readAddedInputs(t));
|
|
158
|
+
return jsonResponse(200, hostModelCatalog(added));
|
|
159
|
+
});
|
|
160
|
+
tenantRoute(api, MANAGEMENT, {
|
|
161
|
+
method: "put",
|
|
162
|
+
path: "/v1/tenant/models/inputs",
|
|
163
|
+
tags: ["Tenant"],
|
|
164
|
+
summary: "Set what a catalog model reads",
|
|
165
|
+
description: "Replaces the inputs the operator added to one catalog model (Tenant setting `model.inputs`): only those in its `addableInputs`, which its provider API carries. Its catalog's own inputs always stay; an empty list removes the additions. A message naming a file the Tenant's model does not read is refused with `422 attachment_unsupported`. A custom endpoint sets `settings.inputs` on `PUT /v1/tenant/model` instead.",
|
|
166
|
+
request: { body: body(PutModelInputsRequest) },
|
|
167
|
+
responses: { 200: json(ModelInputsView, "What the model reads now") },
|
|
168
|
+
}, async (c) => {
|
|
169
|
+
const { provider, model, inputs } = PutModelInputsRequestSchema.parse(await readJson(c.req.raw));
|
|
170
|
+
if (provider === "custom")
|
|
171
|
+
fail(400, "A custom endpoint's inputs are its settings: set settings.inputs on PUT /v1/tenant/model.");
|
|
172
|
+
const found = catalogModel(provider, model);
|
|
173
|
+
if (!found)
|
|
174
|
+
fail(400, `Unknown model ${provider}/${model}.`);
|
|
175
|
+
const refused = inputsError(found, inputs);
|
|
176
|
+
if (refused)
|
|
177
|
+
fail(400, refused);
|
|
178
|
+
const added = inputs.filter((input) => addableInputs(found).includes(input));
|
|
179
|
+
await c.env.tenant.store.tx((t) => writeAddedInputs(t, `${provider}/${model}`, added));
|
|
180
|
+
return jsonResponse(200, {
|
|
181
|
+
provider,
|
|
182
|
+
model,
|
|
183
|
+
inputs: capabilitiesOf(found, added).inputs,
|
|
184
|
+
});
|
|
156
185
|
});
|
|
157
186
|
tenantRoute(api, MANAGEMENT, {
|
|
158
187
|
method: "get",
|
|
159
188
|
path: "/v1/tenant/sandbox",
|
|
160
189
|
tags: ["Tenant"],
|
|
161
|
-
summary: "Get the Tenant's sandbox
|
|
190
|
+
summary: "Get the Tenant's sandbox backend and Environment settings",
|
|
162
191
|
responses: { 200: json(TenantSandboxView, "The backend in use and the configuration in force") },
|
|
163
192
|
}, async (c) => jsonResponse(200, await sandboxView(c.env.tenant)));
|
|
164
193
|
tenantRoute(api, MANAGEMENT, {
|
|
165
194
|
method: "put",
|
|
166
195
|
path: "/v1/tenant/sandbox",
|
|
167
196
|
tags: ["Tenant"],
|
|
168
|
-
summary: "Set the Tenant's
|
|
169
|
-
description: "
|
|
170
|
-
request: { body: body(
|
|
197
|
+
summary: "Set the Tenant's Environment settings",
|
|
198
|
+
description: "The limits every Environment's recipe must fit, the pods' stop grace and where harnesses may run (Tenant setting `environments.config`).",
|
|
199
|
+
request: { body: body(PutTenantEnvironmentsRequest) },
|
|
171
200
|
responses: { 200: json(TenantSandboxView, "The configuration now in force") },
|
|
172
201
|
}, async (c) => {
|
|
173
202
|
const ctx = c.env.tenant;
|
|
174
|
-
const { requestId: _requestId, ...config } =
|
|
203
|
+
const { requestId: _requestId, ...config } = PutTenantEnvironmentsRequestSchema.parse(await readJson(c.req.raw));
|
|
175
204
|
const errors = sandboxConfigErrors(effectiveSandboxConfig(config));
|
|
176
205
|
if (errors.length > 0)
|
|
177
206
|
fail(400, errors.join(" "));
|
|
@@ -236,6 +265,27 @@ export function tenantRoutes(api) {
|
|
|
236
265
|
});
|
|
237
266
|
return jsonResponse(200, { budgets: rows.map(budgetView) });
|
|
238
267
|
});
|
|
268
|
+
tenantRoute(api, MANAGEMENT, {
|
|
269
|
+
method: "get",
|
|
270
|
+
path: "/v1/tenant/tool-policy",
|
|
271
|
+
tags: ["Tenant"],
|
|
272
|
+
summary: "Get the Tenant's tool policy",
|
|
273
|
+
responses: { 200: json(ToolPolicy, "The rules the Tool Gate applies to every MCP and HTTP tool call") },
|
|
274
|
+
}, async (c) => jsonResponse(200, await c.env.tenant.store.tx((t) => readToolPolicy(t))));
|
|
275
|
+
tenantRoute(api, MANAGEMENT, {
|
|
276
|
+
method: "put",
|
|
277
|
+
path: "/v1/tenant/tool-policy",
|
|
278
|
+
tags: ["Tenant"],
|
|
279
|
+
summary: "Set the Tenant's tool policy",
|
|
280
|
+
description: "Replaces every rule. The Tool Gate applies them to each MCP and HTTP tool call of every harness, before it sends the call: the strictest rule that matches decides. `deny` refuses the call (the model gets `tool.denied`), `ask` holds it and pauses the turn until a person answers it with an `approve` command on the session, `allow` runs it. A call no rule matches runs. A rule matches when every selector it sets matches: `tool` (the name the model calls it by; `*` matches any run of characters), `server` (an MCP server's name), `kind` (`mcp` or `http`), `agent` (the agent that calls it) and `when` (fields of the call's input by dotted path, each tested with `equals`, `in`, `gt`, `gte`, `lt`, `lte` or `matches`). Sandbox tools never cross the gate. An empty list allows every call.",
|
|
281
|
+
request: { body: body(PutToolPolicyRequest) },
|
|
282
|
+
responses: { 200: json(ToolPolicy, "The policy now in force") },
|
|
283
|
+
}, async (c) => {
|
|
284
|
+
const { rules } = PutToolPolicyRequestSchema.parse(await readJson(c.req.raw));
|
|
285
|
+
const policy = { rules, updatedAt: new Date().toISOString() };
|
|
286
|
+
await c.env.tenant.store.tx((t) => t.putSetting(TOOL_POLICY_SETTING, JSON.stringify(policy)));
|
|
287
|
+
return jsonResponse(200, policy);
|
|
288
|
+
});
|
|
239
289
|
tenantRoute(api, MANAGEMENT, {
|
|
240
290
|
method: "get",
|
|
241
291
|
path: "/v1/tenant/providers",
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Files that arrive inside a message (multimodal messages, M7): the AG-UI and A2A endpoints take
|
|
3
|
+
* a file's bytes inline (base64), store them as an artifact of the message's session, and send
|
|
4
|
+
* the message as `parts` through the session's normal command path, so every check a message
|
|
5
|
+
* with file parts meets applies. A file by URL is refused: the Runtime fetches nothing.
|
|
6
|
+
*
|
|
7
|
+
* A retried message (the same idempotency key) reuses the files the first one stored rather than
|
|
8
|
+
* storing them again, so it replays the first turn instead of conflicting with it. A message the
|
|
9
|
+
* Runtime refuses takes the files it stored with it (`discardFiles`).
|
|
10
|
+
*/
|
|
11
|
+
import type { MessagePart } from "@nylorun/core/contracts";
|
|
12
|
+
import { type ArtifactAccess } from "../artifacts/service.js";
|
|
13
|
+
import type { TenantContext } from "../tenant/context.js";
|
|
14
|
+
/** The largest file a message may carry inline, decoded. */
|
|
15
|
+
export declare const INLINE_FILE_MAX_BYTES: number;
|
|
16
|
+
/**
|
|
17
|
+
* The request body of an endpoint that takes inline files: one file at the cap, base64-encoded
|
|
18
|
+
* (a third larger), and the rest of the request.
|
|
19
|
+
*/
|
|
20
|
+
export declare const INLINE_BODY_MAX_BYTES: number;
|
|
21
|
+
/** A file sent inline: its name, declared media type and base64 bytes. */
|
|
22
|
+
export interface InlineFile {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly mediaType: string;
|
|
25
|
+
/** The bytes, base64 (standard or URL-safe alphabet). */
|
|
26
|
+
readonly data: string;
|
|
27
|
+
}
|
|
28
|
+
/** One part of an inbound message: text, a file sent inline, or an artifact the Runtime holds. */
|
|
29
|
+
export type InboundPart = {
|
|
30
|
+
readonly type: "text";
|
|
31
|
+
readonly text: string;
|
|
32
|
+
} | ({
|
|
33
|
+
readonly type: "inline";
|
|
34
|
+
} & InlineFile) | {
|
|
35
|
+
readonly type: "artifact";
|
|
36
|
+
readonly artifactId: string;
|
|
37
|
+
};
|
|
38
|
+
/** The 400 for a file sent by URL. */
|
|
39
|
+
export declare function refuseUrl(): never;
|
|
40
|
+
/** A name for a file its client did not name: its place in the message and its type's extension. */
|
|
41
|
+
export declare function inlineFileName(mediaType: string, index: number): string;
|
|
42
|
+
/**
|
|
43
|
+
* Stores `file` as a new artifact of `sessionId`, within the inline cap and the Tenant's
|
|
44
|
+
* artifact limits: `400` for bad base64 or a bad media type, `413 limit_exceeded` past a limit.
|
|
45
|
+
*/
|
|
46
|
+
export declare function storeInlineFile(ctx: TenantContext, sessionId: string, file: InlineFile, access: ArtifactAccess): Promise<{
|
|
47
|
+
artifactId: string;
|
|
48
|
+
version: number;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* The parts of the message the session already took under `idempotencyKey`, when it took one
|
|
52
|
+
* with parts: a retry sends the same files again.
|
|
53
|
+
*/
|
|
54
|
+
export declare function priorParts(ctx: TenantContext, sessionId: string, idempotencyKey: string): Promise<readonly MessagePart[] | undefined>;
|
|
55
|
+
/**
|
|
56
|
+
* A message's parts, its inline files stored as artifacts of `sessionId` (or, on a retry, the
|
|
57
|
+
* first message's files in their place, by position). `stored` lists the artifacts this call
|
|
58
|
+
* created, for `discardFiles` when the message is refused.
|
|
59
|
+
*/
|
|
60
|
+
export declare function messageParts(ctx: TenantContext, sessionId: string, idempotencyKey: string, inbound: readonly InboundPart[], access: ArtifactAccess): Promise<{
|
|
61
|
+
parts: MessagePart[];
|
|
62
|
+
stored: string[];
|
|
63
|
+
}>;
|
|
64
|
+
/** Deletes the artifacts a refused message stored; one already gone is no error. */
|
|
65
|
+
export declare function discardFiles(ctx: TenantContext, artifactIds: readonly string[], access: ArtifactAccess): Promise<void>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { MAX_MESSAGE_PARTS } from "@nylorun/core/contracts";
|
|
2
|
+
import { commandKey } from "../core/flow-host.js";
|
|
3
|
+
import { extensionFor, normalizeContentType } from "../artifacts/media-types.js";
|
|
4
|
+
import { deleteArtifact, uploadArtifact } from "../artifacts/service.js";
|
|
5
|
+
import { fail } from "../tenant/http.js";
|
|
6
|
+
/** The largest file a message may carry inline, decoded. */
|
|
7
|
+
export const INLINE_FILE_MAX_BYTES = 20 * 1024 * 1024;
|
|
8
|
+
/**
|
|
9
|
+
* The request body of an endpoint that takes inline files: one file at the cap, base64-encoded
|
|
10
|
+
* (a third larger), and the rest of the request.
|
|
11
|
+
*/
|
|
12
|
+
export const INLINE_BODY_MAX_BYTES = 28 * 1024 * 1024;
|
|
13
|
+
const BASE64 = /^[A-Za-z0-9+/_-]*={0,2}$/;
|
|
14
|
+
/** The 400 for a file sent by URL. */
|
|
15
|
+
export function refuseUrl() {
|
|
16
|
+
return fail(400, "File URLs are not fetched yet; send the bytes inline", {
|
|
17
|
+
code: "invalid_request",
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** A name for a file its client did not name: its place in the message and its type's extension. */
|
|
21
|
+
export function inlineFileName(mediaType, index) {
|
|
22
|
+
const extension = extensionFor(mediaType);
|
|
23
|
+
return `file-${index + 1}${extension ? `.${extension}` : ""}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Stores `file` as a new artifact of `sessionId`, within the inline cap and the Tenant's
|
|
27
|
+
* artifact limits: `400` for bad base64 or a bad media type, `413 limit_exceeded` past a limit.
|
|
28
|
+
*/
|
|
29
|
+
export async function storeInlineFile(ctx, sessionId, file, access) {
|
|
30
|
+
const data = file.data.replace(/\s+/gu, "");
|
|
31
|
+
if (!BASE64.test(data) || data.length % 4 === 1)
|
|
32
|
+
fail(400, `File "${file.name}" is not valid base64`, { code: "invalid_request" });
|
|
33
|
+
// Three bytes per four characters, before decoding a byte.
|
|
34
|
+
if (Math.floor((data.length * 3) / 4) > INLINE_FILE_MAX_BYTES + 2)
|
|
35
|
+
fail(413, `A file sent inline may hold at most ${INLINE_FILE_MAX_BYTES} bytes`, {
|
|
36
|
+
code: "limit_exceeded",
|
|
37
|
+
details: { limitBytes: INLINE_FILE_MAX_BYTES },
|
|
38
|
+
});
|
|
39
|
+
// Node's base64 decoder reads both alphabets.
|
|
40
|
+
const bytes = Buffer.from(data, "base64");
|
|
41
|
+
if (bytes.byteLength > INLINE_FILE_MAX_BYTES)
|
|
42
|
+
fail(413, `A file sent inline may hold at most ${INLINE_FILE_MAX_BYTES} bytes`, {
|
|
43
|
+
code: "limit_exceeded",
|
|
44
|
+
details: { limitBytes: INLINE_FILE_MAX_BYTES },
|
|
45
|
+
});
|
|
46
|
+
if (bytes.byteLength === 0)
|
|
47
|
+
fail(400, `File "${file.name}" is empty`, { code: "invalid_request" });
|
|
48
|
+
const contentType = normalizeContentType(file.mediaType) ??
|
|
49
|
+
fail(400, `File "${file.name}" has no valid media type`, { code: "invalid_request" });
|
|
50
|
+
const { artifact, version } = await uploadArtifact(ctx, {
|
|
51
|
+
name: file.name,
|
|
52
|
+
contentType,
|
|
53
|
+
sessionId,
|
|
54
|
+
declaredBytes: bytes.byteLength,
|
|
55
|
+
source: "upload",
|
|
56
|
+
}, new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength), access);
|
|
57
|
+
return { artifactId: artifact.artifactId, version: version.version };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The parts of the message the session already took under `idempotencyKey`, when it took one
|
|
61
|
+
* with parts: a retry sends the same files again.
|
|
62
|
+
*/
|
|
63
|
+
export async function priorParts(ctx, sessionId, idempotencyKey) {
|
|
64
|
+
const prior = await ctx.store.tx((t) => t.get("commands", commandKey(sessionId, idempotencyKey)));
|
|
65
|
+
return prior?.command.type === "message" && Array.isArray(prior.command.parts)
|
|
66
|
+
? prior.command.parts
|
|
67
|
+
: undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A message's parts, its inline files stored as artifacts of `sessionId` (or, on a retry, the
|
|
71
|
+
* first message's files in their place, by position). `stored` lists the artifacts this call
|
|
72
|
+
* created, for `discardFiles` when the message is refused.
|
|
73
|
+
*/
|
|
74
|
+
export async function messageParts(ctx, sessionId, idempotencyKey, inbound, access) {
|
|
75
|
+
const parts = inbound.filter((part) => part.type !== "text" || part.text !== "");
|
|
76
|
+
if (parts.length > MAX_MESSAGE_PARTS)
|
|
77
|
+
fail(400, `A message may carry at most ${MAX_MESSAGE_PARTS} parts`, { code: "invalid_request" });
|
|
78
|
+
const prior = parts.some((part) => part.type === "inline")
|
|
79
|
+
? (await priorParts(ctx, sessionId, idempotencyKey))?.filter((part) => part.type === "file")
|
|
80
|
+
: undefined;
|
|
81
|
+
const out = [];
|
|
82
|
+
const stored = [];
|
|
83
|
+
let files = 0;
|
|
84
|
+
try {
|
|
85
|
+
for (const part of parts) {
|
|
86
|
+
if (part.type === "text") {
|
|
87
|
+
out.push({ type: "text", text: part.text });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const index = files++;
|
|
91
|
+
if (part.type === "artifact") {
|
|
92
|
+
out.push({ type: "file", artifactId: part.artifactId });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const again = prior?.[index];
|
|
96
|
+
if (again) {
|
|
97
|
+
out.push(again);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const file = await storeInlineFile(ctx, sessionId, part, access);
|
|
101
|
+
stored.push(file.artifactId);
|
|
102
|
+
out.push({ type: "file", artifactId: file.artifactId, version: file.version });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
await discardFiles(ctx, stored, access);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
return { parts: out, stored };
|
|
110
|
+
}
|
|
111
|
+
/** Deletes the artifacts a refused message stored; one already gone is no error. */
|
|
112
|
+
export async function discardFiles(ctx, artifactIds, access) {
|
|
113
|
+
for (const id of artifactIds)
|
|
114
|
+
await deleteArtifact(ctx, id, access).catch(() => undefined);
|
|
115
|
+
}
|
package/dist/api/openapi.js
CHANGED
|
@@ -135,6 +135,8 @@ const RUNTIME_TAGS = [
|
|
|
135
135
|
"GET /v1/sessions/{sessionId}/manifest",
|
|
136
136
|
"GET /v1/sessions/{sessionId}/usage",
|
|
137
137
|
"GET /v1/sessions/{sessionId}/calls/model",
|
|
138
|
+
"GET /v1/sessions/{sessionId}/calls/tool",
|
|
139
|
+
"GET /v1/sessions/{sessionId}/turns/{turnId}/trace",
|
|
138
140
|
"POST /v1/sessions/{sessionId}/sandbox/{tool}",
|
|
139
141
|
],
|
|
140
142
|
},
|
|
@@ -156,17 +158,39 @@ const RUNTIME_TAGS = [
|
|
|
156
158
|
operations: ["GET /v1/a2a/agents/{agentId}/card", "POST /v1/a2a/agents/{agentId}"],
|
|
157
159
|
},
|
|
158
160
|
{
|
|
159
|
-
name: "
|
|
160
|
-
group: "
|
|
161
|
-
description: "
|
|
161
|
+
name: "Environments",
|
|
162
|
+
group: "Environments",
|
|
163
|
+
description: "Where sessions run: one sandbox (a virtual workspace, a pod with its own volume, or none) made from an environment manifest or inline fields and never changed after that, and one vault of secrets for its shell, MCP servers and HTTP tools. Create or find one, watch its lifecycle, stop, reset or delete it, and write its secrets. In `@nylorun/agents`.",
|
|
162
164
|
operations: [
|
|
163
|
-
"PUT /v1/
|
|
164
|
-
"GET /v1/
|
|
165
|
-
"GET /v1/
|
|
166
|
-
"GET /v1/
|
|
167
|
-
"POST /v1/
|
|
168
|
-
"POST /v1/
|
|
169
|
-
"DELETE /v1/
|
|
165
|
+
"PUT /v1/environments/{environmentId}",
|
|
166
|
+
"GET /v1/environments",
|
|
167
|
+
"GET /v1/environments/{environmentId}",
|
|
168
|
+
"GET /v1/environments/{environmentId}/events",
|
|
169
|
+
"POST /v1/environments/{environmentId}/stop",
|
|
170
|
+
"POST /v1/environments/{environmentId}/reset",
|
|
171
|
+
"DELETE /v1/environments/{environmentId}",
|
|
172
|
+
"GET /v1/environments/{environmentId}/secrets",
|
|
173
|
+
"GET /v1/environments/{environmentId}/secrets/{name}",
|
|
174
|
+
"PUT /v1/environments/{environmentId}/secrets/{name}",
|
|
175
|
+
"DELETE /v1/environments/{environmentId}/secrets/{name}",
|
|
176
|
+
"POST /v1/environments/{environmentId}/snapshot",
|
|
177
|
+
"POST /v1/environments/{environmentId}/restore",
|
|
178
|
+
"POST /v1/environments/{environmentId}/fork",
|
|
179
|
+
"GET /v1/environments/{environmentId}/versions",
|
|
180
|
+
"DELETE /v1/environments/{environmentId}/versions/{version}",
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "Environment manifests",
|
|
185
|
+
group: "Environments",
|
|
186
|
+
description: "Named, versioned definitions of a kind of Environment, saved like agents (protocol 14). Save one with a plan first (`?dryRun=true`) and `If-Match`; an Environment is made from one version and keeps it, so a save changes no Environment. In `@nylorun/agents`.",
|
|
187
|
+
operations: [
|
|
188
|
+
"PUT /v1/environment-manifests/{name}",
|
|
189
|
+
"GET /v1/environment-manifests",
|
|
190
|
+
"GET /v1/environment-manifests/{name}",
|
|
191
|
+
"GET /v1/environment-manifests/{name}/versions",
|
|
192
|
+
"GET /v1/environment-manifests/{name}/versions/{version}",
|
|
193
|
+
"DELETE /v1/environment-manifests/{name}",
|
|
170
194
|
],
|
|
171
195
|
},
|
|
172
196
|
{
|
|
@@ -221,9 +245,10 @@ const MANAGEMENT_TAGS = [
|
|
|
221
245
|
},
|
|
222
246
|
{
|
|
223
247
|
name: "Models",
|
|
224
|
-
description: "Model providers, their credentials, the model the Tenant uses, what it spent, every model call for export, and the budgets that cap it. `admin.models`.",
|
|
248
|
+
description: "Model providers, their credentials, the model the Tenant uses, what each model reads, what it spent, every model call for export, and the budgets that cap it. `admin.models`.",
|
|
225
249
|
operations: [
|
|
226
250
|
"GET /v1/tenant/models",
|
|
251
|
+
"PUT /v1/tenant/models/inputs",
|
|
227
252
|
"GET /v1/tenant/providers",
|
|
228
253
|
"GET /v1/tenant/model",
|
|
229
254
|
"PUT /v1/tenant/model",
|
|
@@ -235,23 +260,18 @@ const MANAGEMENT_TAGS = [
|
|
|
235
260
|
],
|
|
236
261
|
},
|
|
237
262
|
{
|
|
238
|
-
name: "
|
|
239
|
-
description: "The
|
|
240
|
-
operations: [
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
"GET /v1/tenant/vaults/{vaultId}/credentials",
|
|
247
|
-
"GET /v1/tenant/vaults/{vaultId}/credentials/{credentialId}",
|
|
248
|
-
"POST /v1/tenant/vaults/{vaultId}/credentials/{credentialId}",
|
|
249
|
-
"DELETE /v1/tenant/vaults/{vaultId}/credentials/{credentialId}",
|
|
250
|
-
],
|
|
263
|
+
name: "Tool policy",
|
|
264
|
+
description: "The rules the Tool Gate applies to every MCP and HTTP tool call of every harness: refuse it, hold it for a person's approval, or run it. `admin.toolPolicy`.",
|
|
265
|
+
operations: ["GET /v1/tenant/tool-policy", "PUT /v1/tenant/tool-policy"],
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: "Credential coverage",
|
|
269
|
+
description: "Which secret of an Environment each of an agent's MCP servers and HTTP tools would get, before a session opens. The secrets themselves are written on the Runtime API. `admin.coverage`.",
|
|
270
|
+
operations: ["POST /v1/tenant/credential-coverage"],
|
|
251
271
|
},
|
|
252
272
|
{
|
|
253
273
|
name: "MCP servers",
|
|
254
|
-
description: "See a remote MCP server's tools before an agent names it, with
|
|
274
|
+
description: "See a remote MCP server's tools before an agent names it, with an Environment's credential for its URL: their model names, sizes and annotations, or that it needs a person's sign-in. `admin.mcp`.",
|
|
255
275
|
operations: ["POST /v1/tenant/mcp/preview"],
|
|
256
276
|
},
|
|
257
277
|
{
|
|
@@ -265,7 +285,7 @@ const MANAGEMENT_TAGS = [
|
|
|
265
285
|
},
|
|
266
286
|
{
|
|
267
287
|
name: "Settings",
|
|
268
|
-
description: "The Tenant's sandbox and artifact settings. `admin.settings`.",
|
|
288
|
+
description: "The Tenant's sandbox backend, Environment and artifact settings. `admin.settings`.",
|
|
269
289
|
operations: [
|
|
270
290
|
"GET /v1/tenant/sandbox",
|
|
271
291
|
"PUT /v1/tenant/sandbox",
|
|
@@ -368,11 +388,11 @@ function documents() {
|
|
|
368
388
|
built = {
|
|
369
389
|
runtime: document(all, RUNTIME_TAGS, {
|
|
370
390
|
title: "Nylorun Runtime API",
|
|
371
|
-
description: "For developers: agents, sessions (with AG-UI and A2A),
|
|
391
|
+
description: "For developers: agents, sessions (with AG-UI and A2A), Environments and artifacts, through `@nylorun/agents`. Every Tenant serves it on its one URL, beside the Management API (`/openapi/management.json`). Every request sends `Nylorun-Protocol`. Who may call each operation is its `security` and its `x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields: an application key (servers) or a trusted issuer's token (browsers and apps).",
|
|
372
392
|
}),
|
|
373
393
|
management: document(all, MANAGEMENT_TAGS, {
|
|
374
394
|
title: "Nylorun Management API",
|
|
375
|
-
description: "For operators: the Tenant's models, vaults, signing keys, settings and application keys, at `/v1/tenant/*` on the Tenant's URL, through `@nylorun/admin`. Every operation takes a management key, acting as itself: never for a subject and never from a browser. Management keys are issued only on the Tenant's machine (`nylorun
|
|
395
|
+
description: "For operators: the Tenant's models, vaults, signing keys, settings and application keys, at `/v1/tenant/*` on the Tenant's URL, through `@nylorun/admin`. Every operation takes a management key, acting as itself: never for a subject and never from a browser. Management keys are issued only on the Tenant's machine (`nylorun tenant keys put <id> --management`) or from `NYLORUN_MANAGEMENT_KEY_FILE`.",
|
|
376
396
|
}),
|
|
377
397
|
};
|
|
378
398
|
return built;
|