@nylorun/agents 0.19.1-beta → 0.21.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 +179 -0
- package/README.md +191 -106
- package/dist/ag-ui/handler.d.ts +4 -4
- package/dist/artifacts.d.ts +14 -1
- package/dist/artifacts.js +28 -0
- package/dist/builder.d.ts +2 -3
- package/dist/builder.js +5 -5
- package/dist/environments.d.ts +387 -0
- package/dist/environments.js +299 -0
- package/dist/http.js +2 -2
- package/dist/index.d.ts +11 -13
- package/dist/index.js +5 -6
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js +1 -1
- package/dist/plugins/load.d.ts +1 -0
- package/dist/plugins/load.js +1 -0
- package/dist/plugins/plugin.d.ts +9 -5
- package/dist/plugins/plugin.js +19 -21
- package/dist/reads.d.ts +1 -1
- package/dist/session-client.d.ts +108 -21
- package/dist/session-client.js +43 -30
- package/dist/skills/index.d.ts +1 -3
- package/dist/skills/index.js +1 -2
- package/dist/skills/skills.d.ts +11 -11
- package/dist/skills/skills.js +17 -23
- package/package.json +4 -4
- package/dist/sandboxes.d.ts +0 -106
- package/dist/sandboxes.js +0 -109
- package/dist/skills/catalog.d.ts +0 -5
- package/dist/skills/catalog.js +0 -14
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
3
|
Runtime API client package: definition authoring and a session client. The Tenant's
|
|
4
|
-
settings, models,
|
|
4
|
+
settings, models, credential coverage, signing keys and application keys are the Management
|
|
5
5
|
API's, through [`@nylorun/admin`](../admin/README.md). Depends only on `@nylorun/core` among Nylorun
|
|
6
6
|
packages. A developer application's production tree should contain only this
|
|
7
7
|
package and `@nylorun/core` from Nylorun. Vocabulary:
|
|
8
|
-
[runtime/src/CONTEXT.md](
|
|
8
|
+
[runtime/server/src/CONTEXT.md](../../runtime/server/src/CONTEXT.md).
|
|
9
9
|
|
|
10
10
|
## Application entry: save your agents
|
|
11
11
|
|
|
@@ -14,12 +14,12 @@ package and `@nylorun/core` from Nylorun. Vocabulary:
|
|
|
14
14
|
import { createClient } from "@nylorun/agents";
|
|
15
15
|
import { agents } from "../agents/index.js";
|
|
16
16
|
|
|
17
|
-
const client = createClient();
|
|
17
|
+
const client = await createClient();
|
|
18
18
|
for (const agent of agents) await client.saveAgent(agent);
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
The Runtime runs each agent from its manifest and the files saved with it
|
|
22
|
-
|
|
21
|
+
The Runtime runs each agent entirely from its manifest and the files saved with it
|
|
22
|
+
(skills), so a session needs nothing from your process. Your services are reached as tools: [HTTP
|
|
23
23
|
tools](#http-tools) and remote MCP servers. `saveAgent` refuses, before sending anything, an
|
|
24
24
|
agent with a tool that would run your code (`tool({ run })`) or a flow agent with a tool
|
|
25
25
|
stage: make it an `http()` tool or serve it from a remote MCP server. `tool({ run })` still
|
|
@@ -29,7 +29,7 @@ runs in the local engine (`@nylorun/harness/run`). `implementationVersion` defau
|
|
|
29
29
|
`createClient()` finds the Runtime through the two `NYLORUN_*` variables or the Project link
|
|
30
30
|
that `npx nylorun start` writes; with neither, it fails with `connection_missing` and names
|
|
31
31
|
those steps. Action endpoints (`createActionHandler`), and before them `connectAgents` and
|
|
32
|
-
executors, were removed; see [MIGRATION.md](
|
|
32
|
+
executors, were removed; see [MIGRATION.md](../../guides/MIGRATION.md#action-endpoints-are-removed).
|
|
33
33
|
|
|
34
34
|
## HTTP tools
|
|
35
35
|
|
|
@@ -47,15 +47,14 @@ const refundOrder = http({
|
|
|
47
47
|
output: z.object({ refundId: z.string() }), // optional
|
|
48
48
|
url: "https://billing.example.com/refunds",
|
|
49
49
|
method: "POST", // default; or PUT, PATCH
|
|
50
|
-
credential: "billing", // optional: a
|
|
50
|
+
credential: "billing", // optional: the session's Environment holds a credential for this URL
|
|
51
51
|
timeoutMs: 20_000, // default 60000, at most 300000
|
|
52
|
-
approval: "always", // optional: each call waits for session.approve()
|
|
53
52
|
});
|
|
54
53
|
|
|
55
54
|
export const support = Agent({ id: "support" }).tools(refundOrder);
|
|
56
55
|
```
|
|
57
56
|
|
|
58
|
-
The tool goes into the manifest as `http
|
|
57
|
+
The tool goes into the manifest as `http`, and the Runtime's
|
|
59
58
|
Tool Gate sends the model's input as the JSON body, under the Host's address
|
|
60
59
|
policy (`NYLORUN_ENDPOINT_*`), with `content-type: application/json`,
|
|
61
60
|
`Nylorun-Session-Id`, `Nylorun-Turn-Id`, `Nylorun-Agent-Id` and an
|
|
@@ -63,21 +62,76 @@ policy (`NYLORUN_ENDPOINT_*`), with `content-type: application/json`,
|
|
|
63
62
|
restart, so your service can drop duplicates. A `2xx` JSON answer is the output
|
|
64
63
|
(text when it is not JSON and the tool has no `output`), checked against
|
|
65
64
|
`output`. Any other status (with the start of its body), a timeout, a refused
|
|
66
|
-
address or a mismatched output is a tool error the model sees.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
address or a mismatched output is a tool error the model sees.
|
|
66
|
+
|
|
67
|
+
**How tool calls are delivered.** Each MCP or HTTP tool call the model decides
|
|
68
|
+
is sent at least once, and every send carries the same key: `Idempotency-Key`
|
|
69
|
+
for an HTTP tool, `_meta["nylorun/idempotency-key"]` on an MCP `tools/call`. A
|
|
70
|
+
crash, a restart or a lost connection to the gateway sends the call again with
|
|
71
|
+
that key, up to 3 sends; after that the model gets `tool.interrupted` ("may
|
|
72
|
+
have run") and decides. A tool that honors the key runs once; one that ignores
|
|
73
|
+
it may act twice, and `attempts` above 1 in `tool.completed` and in
|
|
74
|
+
`session.toolCalls()` marks where. Both also say where each call ran (`kind`:
|
|
75
|
+
`mcp`, `http`, `sandbox`, `platform` for the Runtime's own tools, or `harness`),
|
|
76
|
+
its `server`, the `skill` it ran and the `plugin` that brought its server or
|
|
77
|
+
skill. A sandbox command (`bash`, `write` and the
|
|
78
|
+
rest) is never run twice by the Runtime: a crash fails it to the model as
|
|
79
|
+
`tool.interrupted`. When the model decides the same call again, in a later
|
|
80
|
+
turn say, that is a new call with a new key: put a business key (an order or
|
|
81
|
+
charge id) in the tool's input schema to refuse it too.
|
|
82
|
+
|
|
83
|
+
`credential` works as for a remote MCP server: the vault of the session's
|
|
84
|
+
Environment must hold a credential bound to the tool's exact URL, else the call
|
|
85
|
+
fails before it is sent (`http.credential`). A credential with `via`
|
|
73
86
|
sends the call there instead, such as a gateway, with the session owner in its
|
|
74
|
-
identity header, and a `401` is `credential_rejected` to the model, not retried.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
`.mcp({ shop: { type: "streamable-http", url, approval: "always" } })`.
|
|
78
|
-
An HTTP tool is also a flow stage (see [Flow agents](#flow-agents)), and runs
|
|
87
|
+
identity header, and a `401` is `credential_rejected` to the model, not retried.
|
|
88
|
+
Whether a call waits for a person is not the tool's to say: it is the Tenant's
|
|
89
|
+
[tool policy](#tool-policy). An HTTP tool is also a flow stage (see [Flow agents](#flow-agents)), and runs
|
|
79
90
|
only on the Runtime (a local `run()` reports `http.runtime-only`).
|
|
80
91
|
|
|
92
|
+
## Tool policy
|
|
93
|
+
|
|
94
|
+
Which MCP and HTTP tool calls run, wait for a person, or are refused is the
|
|
95
|
+
Tenant's tool policy, set by the operator on the Management API with a
|
|
96
|
+
management key (`PUT /v1/tenant/tool-policy`, `admin.toolPolicy.put()` in
|
|
97
|
+
`@nylorun/admin`). The Runtime applies it to every call of every harness as it
|
|
98
|
+
journals the call, before anything is sent, and its Tool Gate runs only a call
|
|
99
|
+
the journal holds running, so a harness cannot skip it.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { createAdmin } from "@nylorun/admin";
|
|
103
|
+
|
|
104
|
+
await createAdmin().toolPolicy.put({
|
|
105
|
+
rules: [
|
|
106
|
+
{ effect: "ask", tool: "refund_order", reason: "Refunds need a person" },
|
|
107
|
+
{ effect: "deny", tool: "refund_order", when: { amount: { gt: 10_000 } } },
|
|
108
|
+
{ effect: "ask", server: "github", tool: "github__delete_*" },
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
A rule matches a call when every selector it sets matches: `tool` (the name the
|
|
114
|
+
model calls it by; `*` matches any run of characters), `server` (an MCP
|
|
115
|
+
server's name), `kind` (`mcp` or `http`), `agent` (the agent that calls it) and
|
|
116
|
+
`when` (fields of the call's input by dotted path, each tested with `equals`,
|
|
117
|
+
`in`, `gt`, `gte`, `lt`, `lte` or `matches`). The strictest matching rule
|
|
118
|
+
decides, and a call no rule matches runs:
|
|
119
|
+
|
|
120
|
+
- `deny`: the call is never sent; the model gets `tool.denied` with the rule's
|
|
121
|
+
`reason`.
|
|
122
|
+
- `ask`: the turn pauses (`turn.paused`, with the tool, its input and the
|
|
123
|
+
reason). `session.approve(interactionId, true)` sends that exact call;
|
|
124
|
+
`false` fails it to the model as `tool.denied`, unsent. Nothing holds a
|
|
125
|
+
process while it waits, so an approval can wait a day, or a Runtime restart.
|
|
126
|
+
Studio shows an approval card; AG-UI ends the run with a `tool_approval`
|
|
127
|
+
interrupt.
|
|
128
|
+
- `allow`: it runs.
|
|
129
|
+
|
|
130
|
+
Sandbox tools never cross the gate: their network side is egress policy. A
|
|
131
|
+
manifest no longer sets `approval` (the Runtime and `saveAgent` refuse it); a
|
|
132
|
+
session pinned to a manifest saved with `approval: "always"` keeps it, as an
|
|
133
|
+
ask, until it ends.
|
|
134
|
+
|
|
81
135
|
## Connection resolution
|
|
82
136
|
|
|
83
137
|
`resolveConnection` / `createClient()`:
|
|
@@ -92,7 +146,7 @@ only on the Runtime (a local `run()` reports `http.runtime-only`).
|
|
|
92
146
|
(format 0 to 2), a link without credentials, or a file that does not
|
|
93
147
|
validate fails with `connection_missing`: run `npx nylorun start` in the
|
|
94
148
|
project again. The Project files are read by `@nylorun/core/project`, as
|
|
95
|
-
`@nylorun/admin
|
|
149
|
+
`@nylorun/admin` and `nylorun` read them.
|
|
96
150
|
|
|
97
151
|
Sources never mix. Partial environment fails with `connection_missing`. A
|
|
98
152
|
Runtime serves one Tenant, so nothing names it: the `tenant` option is gone
|
|
@@ -127,12 +181,7 @@ await session.input("Look up item 123", { idempotencyKey: requestId });
|
|
|
127
181
|
const history = await session.history();
|
|
128
182
|
```
|
|
129
183
|
|
|
130
|
-
|
|
131
|
-
eval "$(npx @nylorun/cli env)"
|
|
132
|
-
# → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Every request sets `Nylorun-Protocol` (8) and no `Nylorun-Tenant`: the Runtime
|
|
184
|
+
Every request sets `Nylorun-Protocol` (14) and no `Nylorun-Tenant`: the Runtime
|
|
136
185
|
serves one Tenant. Before the first authenticated
|
|
137
186
|
request, `Transport` fetches `/health` once, checks protocol compatibility, and
|
|
138
187
|
throws `IncompatibleRuntimeError` / `incompatible_host` when the Host range or
|
|
@@ -152,7 +201,7 @@ const assistant = Agent({ id: "assistant", name: "Order assistant" })
|
|
|
152
201
|
|
|
153
202
|
Each subdirectory under the catalog must contain a `SKILL.md` with YAML frontmatter (`name`, `description`) per [Agent Skills](https://agentskills.io/home). Every file of a skill's folder, binary included (not `.git/`, `node_modules/`, OS files like `.DS_Store`, or `.env` and `.env.*` files, which hold secrets), is part of the definition: the manifest names each one by path and SHA-256 (`skills.<name>.files`), at most 500 files of 10 MiB each. `saveAgent` uploads the files the Runtime does not hold (`client.files`, `PUT /v1/files/sha256:<hex>`) before it puts the definition, which the Runtime refuses while it names a file it lacks (`400 definition_files_missing`).
|
|
154
203
|
|
|
155
|
-
The Runtime serves the skills itself, with no call to your process: `load_skill` returns a skill's `SKILL.md` body and the paths of its other files, and `read_skill_resource` returns a text file. A session with a sandbox also has each skill's files read-only under `/skills/<name>/`, so the model can run a skill's scripts with `bash`.
|
|
204
|
+
The Runtime serves the skills itself, with no call to your process. When a session pins the agent, the Runtime adds the skill catalog to the instructions and two tools to the manifest's `runtime` block: `load_skill` returns a skill's `SKILL.md` body and the paths of its other files, and `read_skill_resource` returns a text file. A session with a sandbox also has each skill's files read-only under `/skills/<name>@<hash>/`, so the model can run a skill's scripts with `bash`.
|
|
156
205
|
|
|
157
206
|
Declare MCP servers with `.mcp(...)` (same map shape as agent-plugins `mcpServers`):
|
|
158
207
|
|
|
@@ -166,44 +215,76 @@ const assistant = Agent({ id: "assistant", name: "Assistant" })
|
|
|
166
215
|
});
|
|
167
216
|
```
|
|
168
217
|
|
|
169
|
-
Each key names a server; its `name` defaults to the key and, when given, must equal it. Repeated `.mcp()` calls add servers to the
|
|
218
|
+
Each key names a server; its `name` defaults to the key and, when given, must equal it. Repeated `.mcp()` calls add servers to the manifest's `mcpServers`. Nylorun accepts remote servers only: `streamable-http` and `sse` ([Agent Plugins MCP servers](https://agent-plugins.org/plugin-authors/mcp-servers)); a `stdio` server is refused, so run it behind an HTTP transport and declare its URL. Attach an Agent Plugin package with `.plugin(path)`: its skills and remote MCP servers join the agent's `skills` and `mcpServers`, each labelled `plugin: { name, version }` from its `plugin.json`, and a stdio server in its `mcp.json` throws. Names are unique across the agent's tools, skills and servers.
|
|
170
219
|
|
|
171
|
-
Set a server's tools one by one with `tools`, keyed by the server's own tool names, `"*"` for the rest: `{ "*": { enabled: false }, search_issues: { enabled: true }, create_issue: { enabled: true
|
|
220
|
+
Set a server's tools one by one with `tools`, keyed by the server's own tool names, `"*"` for the rest: `{ "*": { enabled: false }, search_issues: { enabled: true }, create_issue: { enabled: true } }` is an allowlist (whether a call waits for a person is the [tool policy](#tool-policy)). When an agent's MCP tools would fill more than a tenth of the model's context window, the Runtime defers them: the model finds them with `tool_search` and runs them with `tool_call`. `deferred: true` or `false`, on a tool or on the server, decides instead. Either setting makes the manifest v6 (see MIGRATION.md).
|
|
172
221
|
|
|
173
|
-
A manifest never carries a server's credential, and its `headers` hold no secret.
|
|
222
|
+
A manifest never carries a server's credential, and its `headers` hold no secret. You write a credential for the server's URL into an Environment (a token or a header map, or a gateway's key with `via` and an identity header for servers that need each person's sign-in, with `client.environments.secrets.put`), and sessions that name the Environment use it: [DEPLOYMENT.md](../../guides/DEPLOYMENT.md#mcp-servers-and-http-tools), which also shows `nylorun mcp inspect` for listing a server's tools first. After `saveAgent`, `admin.coverage({ agentId, environmentId })` in `@nylorun/admin` says which credential each server and HTTP tool would get in that Environment ([Checking an agent's credentials](../../guides/DEPLOYMENT.md#checking-an-agents-credentials)). The model knows each tool as `server__tool`, with characters outside `[A-Za-z0-9_-]` replaced by `_`; a failed call reaches it with a code (`credential_rejected` on a `401`, `mcp.unreachable`, …), and a result past 32 KiB, or an image, becomes an artifact of the session that it reads with `read_artifact`.
|
|
174
223
|
|
|
175
|
-
|
|
224
|
+
A session runs in an **Environment**: one sandbox and one vault, with an id you choose, which outlives the sessions that name it (protocol 11). It is made from one version of an **environment manifest** or from inline fields, and never changes what it runs after that (protocol 14). The agent names no Environment, so the same agent runs in any Tenant; it may state what it needs (`.requires()`). Whether an Environment serves one session, one person or a project is your app's choice:
|
|
176
225
|
|
|
177
226
|
```ts
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
227
|
+
import { environmentManifest } from "@nylorun/agents";
|
|
228
|
+
|
|
229
|
+
// A kind of Environment, saved like an agent: a named, versioned definition (or a JSON file with $schema).
|
|
230
|
+
const analystBox = environmentManifest({
|
|
231
|
+
name: "analyst-box",
|
|
232
|
+
kind: "pod",
|
|
233
|
+
image: "python:3.13-slim",
|
|
234
|
+
network: { allowedHosts: ["pypi.org", "files.pythonhosted.org", "api.github.com"] },
|
|
235
|
+
setup: [{ command: "pip install pandas" }],
|
|
236
|
+
secrets: { GH_TOKEN: { type: "environment_secret", allowedHosts: ["api.github.com"], required: true } },
|
|
237
|
+
});
|
|
238
|
+
const plan = await client.environments.manifests.plan(analystBox); // changes, errors, warnings, Environments behind
|
|
239
|
+
await client.environments.manifests.save(analystBox, { ifMatch: plan.from ?? 0 });
|
|
240
|
+
|
|
241
|
+
// One per person or project, made from the manifest's latest version and pinned to it.
|
|
242
|
+
await client.environments.put("users/alice/analyst", { manifest: "analyst-box", ownerUserId: "alice" });
|
|
243
|
+
await client.environments.secrets.put("users/alice/analyst", "GH_TOKEN", { value: process.env.ALICE_GITHUB_TOKEN! });
|
|
244
|
+
|
|
245
|
+
// Or from inline fields: get-or-create by id, write its secrets, open sessions on it.
|
|
246
|
+
await client.environments.put("users/alice/web", {
|
|
247
|
+
kind: "virtual", // the default; "pod" for a container with its own volume, "none" for a vault without compute
|
|
248
|
+
network: { allowedHosts: ["pypi.org", "files.pythonhosted.org"] }, // no egress unless you list hosts
|
|
249
|
+
ownerUserId: "alice", // the owner guard: only alice's sessions may name it
|
|
250
|
+
labels: { team: "data" },
|
|
251
|
+
lifecycle: { deleteAfter: "30d" }, // deleted once unused this long
|
|
252
|
+
});
|
|
253
|
+
await client.environments.secrets.put("users/alice/web", "GH_TOKEN", {
|
|
254
|
+
type: "environment_secret",
|
|
255
|
+
value: process.env.ALICE_GITHUB_TOKEN!,
|
|
256
|
+
allowedHosts: ["api.github.com"],
|
|
257
|
+
});
|
|
258
|
+
const session = await client.createSession({ agentId: "analyst", ownerUserId: "alice", environment: "users/alice/web" });
|
|
259
|
+
|
|
260
|
+
// One per session: created with the session (id `sessions/<session id>`), deleted by release().
|
|
261
|
+
const { session: scratch, release } = await client.environments.forSession({
|
|
262
|
+
session: { agentId: "analyst", ownerUserId: "alice" },
|
|
263
|
+
recipe: { network: { allowedHosts: ["pypi.org"] } },
|
|
185
264
|
});
|
|
265
|
+
await release();
|
|
186
266
|
```
|
|
187
267
|
|
|
188
|
-
|
|
268
|
+
With a sandbox (kind `virtual` or `pod`), the model gets `bash`, `read`, `write`, `edit`, `grep` and `glob` on a persistent `/workspace`. These tools run in the Runtime, not in your process. A `virtual` Environment is an emulated shell in the Runtime's harness, which is not a VM boundary and takes no `image`; a `pod` runs `image` with its own volume (`storage`) once the Tenant has sandbox pods (`nylorun local sandbox enable`). Kind `none` has a vault and no sandbox, for agents that use only remote MCP servers and HTTP tools. A session that names no Environment has no sandbox and no credentials.
|
|
189
269
|
|
|
190
|
-
`
|
|
270
|
+
An Environment never changes what it runs (protocol 14, its view's `manifest`): a `put` on an existing one may change its `labels` and owner guard, fields that match are accepted (so get-or-create stays one call), and anything else is `409 environment_immutable`. A save of a new manifest version changes no Environment: make a new one, which can start from the old one's disk (`from`). The Runtime checks what an Environment runs against the Tenant's limits (`GET`/`PUT /v1/tenant/sandbox`: a network ceiling, a resource maximum, the idle timeout) and answers `400` with every problem it finds. `lifecycle.idle` stops a pod after that long without a turn (at most the Tenant's); `lifecycle.deleteAfter` deletes the Environment, its vault and its files once nothing has used it that long. Private networks, loopback, the host and cloud metadata endpoints are always blocked.
|
|
191
271
|
|
|
192
|
-
|
|
272
|
+
Secrets are write-only: `client.environments.secrets.put(id, name, secret)` creates or replaces one, and `list`, `get` and `delete` never return a value. An `environment_secret` (`{ value, allowedHosts, inject? }`) shows its name to commands as the sentinel `nylorun-managed`, and egress-gate sets its header on HTTPS requests to `allowedHosts` (pods only); an `environment_variable` (`{ value }`) is a plain variable; `bearer` (`{ url, value, via?, identity? }`) and `headers` (`{ url, headers, via?, identity? }`) are credentials for the MCP servers and HTTP tools at `url`. A second secret for one host, or a second credential for one URL, is `409 credential_conflict` at the write ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#credentials)). When what an Environment runs declares a secret's slot (a manifest's `secrets`), send only `{ value }` (or `{ headers }`): the type and binding come from the slot, and a name it does not declare is `409 secret_not_in_manifest`. An agent built with `.requires({ sandbox: "pod", secrets: ["GH_TOKEN"] })` opens a session only on an Environment that has them (`409 environment_unsuitable`). `saveAgent(agent, { dryRun: true })` checks a definition and saves nothing.
|
|
273
|
+
|
|
274
|
+
Sessions on one Environment share its `/workspace` and run their turns at the same time (one turn per session); which sessions work on one disk, and when, is your app's call. Stop, reset, snapshot and restore wait until no turn runs (`409 environment_busy`). Deleting a session only detaches it. With `ownerUserId` set, a session of another owner is `403 environment_owner_mismatch` (a caller acting for a person gets the `404` of a missing Environment), checked when it opens and at every turn start. `client.environments.list({ labels })`, `page`, `get(id)`, `events(id, { types, since, until })` (its lifecycle, and `environment.egress` for every connection its sandbox opens, naming the turns that were running), `stop(id)`, `reset(id)` and `delete(id)` manage them, and `npx nylorun local sandbox ls | rm` does the same on a local Tenant. Acting for a person (`app.as(...)`), changing an Environment or its secrets needs `environments:write`. A trusted issuer's token reaches only the Environments its issuer grants (the identity file's `environments` templates, exact ids or prefixes ending in `/*`), checked at every turn start. The Tenant holds at most `limits.environments` of them (`PUT /v1/tenant/sandbox`, default 100).
|
|
275
|
+
|
|
276
|
+
A pod's disk can start prepared: `files` copies versions of Tenant-wide artifacts in, then `setup` runs its commands, once per new disk and before its first turn (a failed command fails the pod, and the next turn start runs the setup again). Between turns, `client.environments.snapshot(id)` keeps a version of the disk, `restore(id, version)` puts one back (a pod on a new volume seeded before its next turn), `put(newId, { from: { environment: id, version: "latest" } })` starts a new Environment from it, and `versions(id)` and `deleteVersion(id, n)` manage them ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#environments)):
|
|
193
277
|
|
|
194
278
|
```ts
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
279
|
+
await client.environments.put("projects/acme", {
|
|
280
|
+
kind: "pod",
|
|
281
|
+
files: [{ artifactId: repoTarball.artifactId, version: 1, path: "/workspace/app" }],
|
|
282
|
+
setup: [{ command: "npm ci", cwd: "/workspace/app", timeout: "15m" }],
|
|
199
283
|
});
|
|
200
|
-
|
|
201
|
-
await client.
|
|
202
|
-
await client.createSession({ agentId: "analyst", ownerUserId, sandbox: { id: "team-a/proj-42" } });
|
|
284
|
+
const { version } = await client.environments.snapshot("projects/acme");
|
|
285
|
+
await client.environments.put("projects/acme-try", { kind: "pod", from: { environment: "projects/acme", version } });
|
|
203
286
|
```
|
|
204
287
|
|
|
205
|
-
Sessions attached to one sandbox share its `/workspace`, and one turn runs in it at a time: a second session's turn is refused with `409 sandbox_busy` until the first ends. Deleting a session only detaches it. A spec is fixed once the sandbox exists; `labels` can change. `client.sandboxes.list({ labels })`, `get(id)` and `delete(id)` manage them, and `npx nylorun sandbox ls | rm` does the same on a local Tenant. A trusted issuer's token reaches only the sandboxes its issuer grants (the identity file's `sandboxes` templates, exact ids or prefixes ending in `/*`), checked at every turn start; creating and deleting through one needs `sandboxes:write`. The Tenant holds at most `limits.sandboxes` of them (`PUT /v1/tenant/sandbox`, default 100).
|
|
206
|
-
|
|
207
288
|
Add an agent with `.subagents()` to let the model delegate to it:
|
|
208
289
|
|
|
209
290
|
```ts
|
|
@@ -243,9 +324,9 @@ session like any agent: put it in `export const agents`, `saveAgent(flowAgent)`,
|
|
|
243
324
|
(`kind: "workflow"`, `workflowSchemaVersion: 3`) that embeds the agents it runs, so one
|
|
244
325
|
document and one manifest hash cover the whole flow; its agents are not listed on their own.
|
|
245
326
|
|
|
246
|
-
A flow
|
|
247
|
-
gets the previous stage's output. So each agent returns, through its
|
|
248
|
-
what the next stage needs.
|
|
327
|
+
A flow passes data between its stages itself: the first stage gets the flow's input, and
|
|
328
|
+
every later stage gets the previous stage's output. So each agent returns, through its
|
|
329
|
+
`.output()` schema, what the next stage needs.
|
|
249
330
|
|
|
250
331
|
```ts
|
|
251
332
|
import { Agent, VerdictSchema, http } from "@nylorun/agents";
|
|
@@ -284,28 +365,9 @@ export const shipFeature = Agent({ id: "ship-feature" })
|
|
|
284
365
|
export const agents = [shipFeature];
|
|
285
366
|
```
|
|
286
367
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
the wait:
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
const prOpener = Agent({ id: "pr-opener" })
|
|
294
|
-
.instructions("Open one pull request with open_pr from the summaries you are given.")
|
|
295
|
-
.tools(
|
|
296
|
-
http({
|
|
297
|
-
name: "open_pr",
|
|
298
|
-
description: "Open a pull request with the given summaries.",
|
|
299
|
-
input: z.object({ summaries: z.array(z.string()) }),
|
|
300
|
-
url: "https://ci.example.com/pull-requests",
|
|
301
|
-
credential: "github",
|
|
302
|
-
approval: "always",
|
|
303
|
-
}),
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
// …
|
|
307
|
-
.pipe(prWriter, prOpener);
|
|
308
|
-
```
|
|
368
|
+
The Tenant's [tool policy](#tool-policy) decides a flow's HTTP stages as it decides an
|
|
369
|
+
agent's HTTP tool calls: an `ask` rule on `open_pr` pauses the flow's turn until a person
|
|
370
|
+
approves the call, and the flow session's `pending()` lists the wait.
|
|
309
371
|
|
|
310
372
|
| Stage | Role |
|
|
311
373
|
| --- | --- |
|
|
@@ -329,12 +391,11 @@ body. Its input is the previous output, which must match the tool's `input` (an
|
|
|
329
391
|
object); a mismatch the build can see, such as an agent with no `.output()` before it,
|
|
330
392
|
is refused (`flow.input-mismatch`), and any other fails the stage when it runs
|
|
331
393
|
(`tool.invalid-input`). The Runtime makes the request as for an agent's HTTP tool, with
|
|
332
|
-
`Nylorun-Agent-Id` set to the flow agent's id and `credential` taken from the
|
|
333
|
-
session's
|
|
394
|
+
`Nylorun-Agent-Id` set to the flow agent's id and `credential` taken from the
|
|
395
|
+
vault of the flow session's Environment; its answer is the next stage's input. A failure status, a timeout or
|
|
334
396
|
an answer that does not match `output` fails the stage with its code (`http.status`,
|
|
335
397
|
`http.timeout`, `tool.invalid-output`, …). A Loop body that starts with an HTTP stage
|
|
336
|
-
is retried with the Loop's input, not the feedback.
|
|
337
|
-
supported on a flow stage yet (`flow.approval-unsupported`).
|
|
398
|
+
is retried with the Loop's input, not the feedback.
|
|
338
399
|
|
|
339
400
|
A Loop may be judged by your service instead of an agent: `http({ url, method?,
|
|
340
401
|
credential?, timeoutMs? })`, with no name or input, is an HTTP verifier. The Runtime
|
|
@@ -374,21 +435,21 @@ nested flow agent's id in front of its own agents'. Control stages add nothing,
|
|
|
374
435
|
wrapping a step in `.loop()` or moving it between cases keeps its session. An agent
|
|
375
436
|
may appear once per flow; use it again under a new id with `writer.withId("final-writer")`
|
|
376
437
|
(`flow.duplicate-leaf`). A code tool stage (`.pipe(tool({ run }))`) runs only in the local
|
|
377
|
-
engine: the Runtime runs
|
|
378
|
-
an HTTP stage instead.
|
|
438
|
+
engine: the Runtime runs flows from their manifests alone, so `saveAgent` refuses a flow
|
|
439
|
+
agent with one. Use an HTTP stage instead.
|
|
379
440
|
|
|
380
|
-
The agents in a flow share one
|
|
381
|
-
`createSession({ …,
|
|
382
|
-
|
|
383
|
-
|
|
441
|
+
The agents in a flow share one Environment: open the flow's session on it,
|
|
442
|
+
`createSession({ …, environment: id })`, and every linked session in the flow
|
|
443
|
+
inherits it; turns within one flow do not block each other. Other sessions share
|
|
444
|
+
it by naming the same id (see `client.environments`). Call the sandbox
|
|
384
445
|
built-ins via `session.sandbox` (application).
|
|
385
446
|
Observe with `session.observe({ follow: true })` to merge linked agent streams;
|
|
386
447
|
`pending()` lists waits across the tree. Studio renders the manifest tree and
|
|
387
448
|
live node status. Source examples:
|
|
388
449
|
`examples/agents/{chain,switch,parallel,map,loop,ship-feature}/` (outside the
|
|
389
450
|
default release registry). Host contract:
|
|
390
|
-
[HOST_CONTRACT.md](
|
|
391
|
-
workflow Loop): [harness/src/CONTEXT.md](
|
|
451
|
+
[HOST_CONTRACT.md](../../runtime/harness/HOST_CONTRACT.md). Vocabulary (turn loop vs
|
|
452
|
+
workflow Loop): [runtime/harness/src/CONTEXT.md](../../runtime/harness/src/CONTEXT.md).
|
|
392
453
|
|
|
393
454
|
Use `session.observe({ cursor, signal })` for resumable canonical events, `session.inspect()` for waiting/uncertain state, and `approve`, `respond`, or `cancel` with an explicit stable idempotency key. Retry the same semantic command with the same key. `ownerUserId` must come from trusted server authentication. Application credentials are not browser credentials; browser applications need an authorized backend. Definition authoring is browser-bundleable.
|
|
394
455
|
|
|
@@ -400,8 +461,8 @@ the Tenant.
|
|
|
400
461
|
|
|
401
462
|
The Runtime makes a lost HTTP tool call `uncertain` instead of repeating its external effect.
|
|
402
463
|
In the local engine (`@nylorun/harness/run`), `tool({ run })` tools receive state, info,
|
|
403
|
-
identity, resume, signal, approval/response helpers and memoized `step`.
|
|
404
|
-
|
|
464
|
+
identity, resume, signal, approval/response helpers and memoized `step`. Agent definitions
|
|
465
|
+
have no `.run()`; explicit
|
|
405
466
|
local execution is available through `@nylorun/harness/run`.
|
|
406
467
|
|
|
407
468
|
## AG-UI
|
|
@@ -446,8 +507,8 @@ createServer(toNodeListener(agui)).listen(3000);
|
|
|
446
507
|
|
|
447
508
|
`run`, `history`, `reattach` and `cancel` are also on the handler for your own
|
|
448
509
|
routing. The default `client` is `createClient()`; pass `client` to use another
|
|
449
|
-
one, and `session(subject, agentId)` to add the person's `
|
|
450
|
-
|
|
510
|
+
one, and `session(subject, agentId)` to add the person's `environment` (their
|
|
511
|
+
Environment's id: its sandbox and its vault's credentials) or `info`. They apply when the thread's session is
|
|
451
512
|
created, on its first run; later runs keep them. Whatever the browser sends in
|
|
452
513
|
`forwardedProps.nylorun` is replaced.
|
|
453
514
|
|
|
@@ -471,7 +532,7 @@ created, on its first run; later runs keep them. Whatever the browser sends in
|
|
|
471
532
|
package itself: the Runtime does the protocol work.
|
|
472
533
|
|
|
473
534
|
A complete web backend with a test is in
|
|
474
|
-
[examples/src/ag-ui](
|
|
535
|
+
[examples/src/ag-ui](../../examples/README.md#an-agent-in-your-web-app-ag-ui).
|
|
475
536
|
|
|
476
537
|
Limitations: assistant text arrives once per model step (no token streaming);
|
|
477
538
|
no reasoning, state, activity or subagent events, and an agent used as a tool
|
|
@@ -537,7 +598,7 @@ What the Runtime answers (Host feature `a2a-endpoint`):
|
|
|
537
598
|
(`UnsupportedOperationError`; the card says `streaming: false`), push
|
|
538
599
|
notifications, the extended card, and approvals. An agent that pauses for an
|
|
539
600
|
approval shows `INPUT_REQUIRED`, refuses a reply, and can be canceled, so do
|
|
540
|
-
not publish agents
|
|
601
|
+
not publish agents whose tools the Tenant's tool policy asks about yet.
|
|
541
602
|
- The handler calls the Runtime as the partner's subject with the scope
|
|
542
603
|
`sessions:own` only, so one partner never reaches another's tasks. It sends
|
|
543
604
|
no header the partner chose except `A2A-Version` and `A2A-Extensions`. Without
|
|
@@ -571,6 +632,29 @@ refuse a larger upload with `413 limit_exceeded`, and nothing is stored. Acting
|
|
|
571
632
|
a person (`as()`, or a trusted issuer's token), a client reaches only the artifacts of that
|
|
572
633
|
person's sessions, and an upload names one of them.
|
|
573
634
|
|
|
635
|
+
Hosts advertising `artifact-reads` also support bounded metadata pages, newest
|
|
636
|
+
first. `artifacts.page()` defaults to 50 rows (1–200), checks feature discovery,
|
|
637
|
+
and returns `{ artifacts, nextCursor }`. Pass the same filters on every page:
|
|
638
|
+
|
|
639
|
+
```ts
|
|
640
|
+
const filters = { kind: "file" as const, labels: { team: "design" } };
|
|
641
|
+
let cursor: string | undefined;
|
|
642
|
+
do {
|
|
643
|
+
const page = await client.artifacts.page({ ...filters, cursor });
|
|
644
|
+
for (const artifact of page.artifacts) console.log(artifact.name, artifact.latestVersion);
|
|
645
|
+
cursor = page.nextCursor ?? undefined;
|
|
646
|
+
} while (cursor);
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Omit `sessionId` for all reachable artifacts, including Tenant-owned artifacts
|
|
650
|
+
when using an application key as itself. With `sessionId`, only that session's
|
|
651
|
+
artifacts are included; acting for a person also applies ownership and agent
|
|
652
|
+
grants on every page. Every label must match exactly. Pages omit version histories
|
|
653
|
+
and bytes: use `get()` and the existing version/download methods for those.
|
|
654
|
+
Cursors bind to the Tenant and filters. Pages are independent reads; refresh from
|
|
655
|
+
the beginning for newer artifacts. The existing unbounded `list()` keeps its
|
|
656
|
+
oldest-first behavior and does not require `artifact-reads`.
|
|
657
|
+
|
|
574
658
|
### Outputs: folder artifacts
|
|
575
659
|
|
|
576
660
|
At the end of each turn, the Runtime exports the files the agent wrote into
|
|
@@ -601,7 +685,7 @@ keeps its application key to itself and names the person on each call:
|
|
|
601
685
|
```ts
|
|
602
686
|
import { createClient } from "@nylorun/agents";
|
|
603
687
|
|
|
604
|
-
const app = createClient(); // the application key, on the server only
|
|
688
|
+
const app = await createClient(); // the application key, on the server only
|
|
605
689
|
|
|
606
690
|
// Per request, after your own sign-in:
|
|
607
691
|
const person = app.as(`app:${user.id}`, { scopes: ["sessions:own"] });
|
|
@@ -621,14 +705,15 @@ compatibility check, so calling `as()` per request is cheap.
|
|
|
621
705
|
| `sessions:own` | The person's own sessions: create, list, read, stream, message, approve, respond, cancel; and their artifacts |
|
|
622
706
|
| `agents:read` | Listing the Tenant's agents |
|
|
623
707
|
| `agents:write` | Saving agents; listing agents |
|
|
708
|
+
| `environments:write` | Creating, changing, stopping, resetting and deleting Environments, and writing their secrets (reading them takes `sessions:own` or this) |
|
|
624
709
|
|
|
625
710
|
No scope reaches the sandbox tool routes; call them without `as()`. `tenant:settings` is retired (protocol 8): it grants
|
|
626
|
-
nothing, and no subject reaches the Management API (`/v1/tenant/*`).
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
([DEPLOYMENT.md](
|
|
711
|
+
nothing, and no subject reaches the Management API (`/v1/tenant/*`).
|
|
712
|
+
`sandboxes:write` (protocol 10) is refused with a message naming
|
|
713
|
+
`environments:write`. Credentials live in Environments: the server writes a
|
|
714
|
+
person's own keys into their Environment (with `ownerUserId` set to them) and
|
|
715
|
+
names it when it opens their sessions
|
|
716
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#credentials)). A subject is 1–200 visible ASCII
|
|
632
717
|
characters (spaces only inside) and `host` is reserved. Your server must drop
|
|
633
718
|
any `Nylorun-*` header its own clients send, and only an application key can act
|
|
634
719
|
for a subject.
|
|
@@ -638,14 +723,14 @@ The Tenant's signing keys sign capability links and run tokens.
|
|
|
638
723
|
needed). Listing, rotating
|
|
639
724
|
and revoking them is management: `admin.signingKeys.list()`, `.rotate()`
|
|
640
725
|
(`{ force: true }` for incidents) and `.revoke(kid)` in `@nylorun/admin`, or
|
|
641
|
-
`
|
|
726
|
+
`nylorun tenant signing-keys …` from the terminal.
|
|
642
727
|
|
|
643
728
|
## In the browser
|
|
644
729
|
|
|
645
730
|
A web page or an app calls the Runtime directly with the JWT your identity
|
|
646
731
|
provider gave the person: list the provider in the Runtime's identity file
|
|
647
|
-
([Trusted issuers](
|
|
648
|
-
token's subject, scopes, agents and
|
|
732
|
+
([Trusted issuers](../../guides/DEPLOYMENT.md#trusted-issuers)), and the Runtime takes the
|
|
733
|
+
token's subject, scopes, agents and Environment grants from it. Nylorun mints no
|
|
649
734
|
token and ships no browser client: use your provider's SDK for sign-in and send
|
|
650
735
|
the token as `Authorization: Bearer <token>` with `Nylorun-Protocol`, through a
|
|
651
736
|
reverse proxy that answers CORS (the Runtime sends no CORS headers). An expired
|
|
@@ -653,7 +738,7 @@ token answers `401` with `code: "token_expired"`; `GET /v1/me` shows what a toke
|
|
|
653
738
|
renders to. For a chat UI, give `@ag-ui/client`'s `HttpAgent` a `fetch` that adds
|
|
654
739
|
those headers.
|
|
655
740
|
|
|
656
|
-
The SDK depends only on core within the Nylorun packages; installing it does not install harness or any AG-UI or A2A package. Use `/define`, `/client`, `/ag-ui` or `/a2a` for focused imports, or the root for convenience. Studio uses `/client`. See [the adopted host contract](
|
|
741
|
+
The SDK depends only on core within the Nylorun packages; installing it does not install harness or any AG-UI or A2A package. Use `/define`, `/client`, `/ag-ui` or `/a2a` for focused imports, or the root for convenience. Studio uses `/client`. See [the adopted host contract](../../runtime/harness/HOST_CONTRACT.md).
|
|
657
742
|
|
|
658
743
|
## Embedding Studio (desktop apps)
|
|
659
744
|
|
package/dist/ag-ui/handler.d.ts
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
import { type SubjectScope } from "@nylorun/core/contracts";
|
|
12
12
|
import type { BuiltWorkflow } from "@nylorun/core/define";
|
|
13
13
|
import { type AgentSource, type AgentsClient } from "../client.js";
|
|
14
|
-
/** Per-session parameters the host may add, such as the person's
|
|
14
|
+
/** Per-session parameters the host may add, such as the person's Environment. */
|
|
15
15
|
export interface AgUiSessionOptions {
|
|
16
16
|
/** Set when the thread's session is created, on its first run; later runs keep them. */
|
|
17
17
|
info?: Record<string, unknown>;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/** The Environment the thread's session runs in: its sandbox and its vault's credentials. */
|
|
19
|
+
environment?: string;
|
|
20
20
|
}
|
|
21
21
|
export interface AgUiHandlerOptions {
|
|
22
22
|
/** Agents this endpoint may run: built agents, workflows or their ids. Nothing else is reachable. */
|
|
@@ -29,7 +29,7 @@ export interface AgUiHandlerOptions {
|
|
|
29
29
|
client?: AgentsClient | Promise<AgentsClient>;
|
|
30
30
|
/** What each subject may do through this endpoint. Default `["sessions:own"]`. */
|
|
31
31
|
scopes?: readonly SubjectScope[];
|
|
32
|
-
/** Optional per-session parameters, e.g. the person's
|
|
32
|
+
/** Optional per-session parameters, e.g. the person's Environment with their keys. */
|
|
33
33
|
session?(subject: string, agentId: string): AgUiSessionOptions | Promise<AgUiSessionOptions>;
|
|
34
34
|
}
|
|
35
35
|
export interface AgUiHandler {
|
package/dist/artifacts.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Acting for a person (`client.as`, or a trusted issuer's token), only the artifacts of their own sessions.
|
|
13
13
|
*/
|
|
14
|
-
import type
|
|
14
|
+
import { type ArtifactKind, type ArtifactPage, type ArtifactDiff, type ArtifactLink, type ArtifactTree, type ArtifactView, type DeleteArtifactResponse, type UploadArtifactResponse } from "@nylorun/core/contracts";
|
|
15
15
|
import { type Transport } from "./http.js";
|
|
16
16
|
/** What an upload sends: bytes, a `Blob` or `File`, text, or a stream of bytes. */
|
|
17
17
|
export type ArtifactBody = Uint8Array | ArrayBuffer | Blob | string | ReadableStream<Uint8Array>;
|
|
@@ -25,6 +25,17 @@ export interface UploadArtifactOptions {
|
|
|
25
25
|
readonly labels?: Readonly<Record<string, string>>;
|
|
26
26
|
readonly signal?: AbortSignal;
|
|
27
27
|
}
|
|
28
|
+
export interface ArtifactPageOptions {
|
|
29
|
+
/** Only this session's artifacts; excludes Tenant-owned artifacts. */
|
|
30
|
+
readonly sessionId?: string;
|
|
31
|
+
readonly kind?: ArtifactKind;
|
|
32
|
+
/** Every label must match exactly. */
|
|
33
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
34
|
+
/** Default 50, from 1 to 200. */
|
|
35
|
+
readonly limit?: number;
|
|
36
|
+
readonly cursor?: string;
|
|
37
|
+
readonly signal?: AbortSignal;
|
|
38
|
+
}
|
|
28
39
|
/** A capability link, with the absolute URL to open on this Runtime. */
|
|
29
40
|
export type ArtifactLinkWithUrl = ArtifactLink & {
|
|
30
41
|
readonly url: string;
|
|
@@ -48,6 +59,8 @@ export declare class ArtifactsClient {
|
|
|
48
59
|
sessionId?: string;
|
|
49
60
|
signal?: AbortSignal;
|
|
50
61
|
}): Promise<ArtifactView[]>;
|
|
62
|
+
/** Newest first, with metadata only (Host feature `artifact-reads`). */
|
|
63
|
+
page(options?: ArtifactPageOptions): Promise<ArtifactPage>;
|
|
51
64
|
/** The artifact, with every version. */
|
|
52
65
|
get(artifactId: string, options?: {
|
|
53
66
|
signal?: AbortSignal;
|
package/dist/artifacts.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifacts (protocol 6): `client.artifacts`. A file is uploaded in one streamed request and
|
|
3
|
+
* kept as an artifact with numbered versions; a session's artifacts appear in its history as
|
|
4
|
+
* `artifact.*` events. Download through the Runtime (with Range), or mint a capability link: a
|
|
5
|
+
* short-lived URL that needs no credential, for `<img>` tags and sharing. A message names an
|
|
6
|
+
* artifact in a file part (`session.inputParts`), and the model reads it.
|
|
7
|
+
*
|
|
8
|
+
* Folders (F8.2): at the end of each turn the Runtime exports `/workspace/outputs` of the
|
|
9
|
+
* session's sandbox as a version of its `outputs` folder. Read one as a tree, one file by path,
|
|
10
|
+
* a diff between versions or a zip.
|
|
11
|
+
*
|
|
12
|
+
* Acting for a person (`client.as`, or a trusted issuer's token), only the artifacts of their own sessions.
|
|
13
|
+
*/
|
|
14
|
+
import { ArtifactPageSchema, } from "@nylorun/core/contracts";
|
|
1
15
|
import { segment } from "./http.js";
|
|
2
16
|
function contentTypeOf(body, declared) {
|
|
3
17
|
if (declared)
|
|
@@ -46,6 +60,20 @@ export class ArtifactsClient {
|
|
|
46
60
|
const reply = await this.transport.json(`/v1/artifacts${query}`, "GET", undefined, options.signal);
|
|
47
61
|
return reply.artifacts;
|
|
48
62
|
}
|
|
63
|
+
/** Newest first, with metadata only (Host feature `artifact-reads`). */
|
|
64
|
+
async page(options = {}) {
|
|
65
|
+
await this.transport.requireFeature("artifact-reads", options.signal);
|
|
66
|
+
const query = new URLSearchParams({ limit: String(options.limit ?? 50) });
|
|
67
|
+
if (options.cursor !== undefined)
|
|
68
|
+
query.set("cursor", options.cursor);
|
|
69
|
+
if (options.sessionId !== undefined)
|
|
70
|
+
query.set("sessionId", options.sessionId);
|
|
71
|
+
if (options.kind !== undefined)
|
|
72
|
+
query.set("kind", options.kind);
|
|
73
|
+
for (const [key, value] of Object.entries(options.labels ?? {}))
|
|
74
|
+
query.append("label", `${key}=${value}`);
|
|
75
|
+
return ArtifactPageSchema.parse(await this.transport.json(`/v1/artifacts?${query}`, "GET", undefined, options.signal));
|
|
76
|
+
}
|
|
49
77
|
/** The artifact, with every version. */
|
|
50
78
|
get(artifactId, options = {}) {
|
|
51
79
|
return this.transport.json(`/v1/artifacts/${segment(artifactId)}`, "GET", undefined, options.signal);
|