@nylorun/agents 0.4.0-beta → 0.6.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 +45 -0
- package/README.md +174 -8
- package/dist/client.d.ts +43 -9
- package/dist/client.js +166 -16
- package/dist/connection.d.ts +19 -0
- package/dist/connection.js +98 -0
- package/dist/derived-credentials.d.ts +5 -0
- package/dist/derived-credentials.js +17 -0
- package/dist/execute-action.d.ts +8 -2
- package/dist/execute-action.js +97 -8
- package/dist/executor.d.ts +7 -2
- package/dist/executor.js +145 -8
- package/dist/http.d.ts +21 -1
- package/dist/http.js +120 -3
- package/dist/index.d.ts +13 -6
- package/dist/index.js +6 -2
- package/dist/sandbox/client.d.ts +64 -0
- package/dist/sandbox/client.js +33 -0
- package/dist/sandbox/index.d.ts +2 -0
- package/dist/sandbox/index.js +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
|
+
## 0.6.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c49efed: **Breaking (pre-1.0 minor):** Tenant API connection and executor credentials for Runtime Clients.
|
|
8
|
+
|
|
9
|
+
- `resolveConnection()` — options → environment → Project link; sources never mix.
|
|
10
|
+
- `createClient()` with no arguments uses `resolveConnection`.
|
|
11
|
+
- `connectAgents` application mode: save agents, `PUT /v1/executors` with **derived** tokens (HMAC of application key + Tenant + agent id), connect; tokens are not stored in Project credentials.
|
|
12
|
+
- Re-exports `PROTOCOL_FEATURES`, `ERROR_CODES`, `compareVersions`.
|
|
13
|
+
|
|
14
|
+
- c49efed: **Breaking (pre-1.0 minor):** Replace a single SQLite Runtime per Project or home directory with a **Runtime Host** that serves isolated **Tenants**, selected by `Nylorun-Tenant` and negotiated with `Nylorun-Protocol` (protocol `2`, feature `runtime-tenants`). Vocabulary: Host root + Tenant + Project link.
|
|
15
|
+
|
|
16
|
+
- **core:** `PROTOCOL_VERSION = 2`, `HOST_PROTOCOL`, `TENANT_HEADER`, `PROTOCOL_HEADER`, `newTenantId` / `isTenantId`, `checkCompatibility`; health schema gains `hostId` + `protocol` (`service: "nylorun-runtime"`); Tenant/admin wire schemas; Tenant model routes under `/v1/tenant/*`.
|
|
17
|
+
- **runtime:** Host process + Tenant module; Tenant model routes under `/v1/tenant/*`; `startEphemeralRuntime` for tests/embeds; executors via `PUT /v1/executors`; sandbox prefix `nylorun-<tenant-id>-`.
|
|
18
|
+
- **agents:** `createClient({ url, key, tenant })`; Transport sends Tenant + protocol headers; `/health` compatibility cache; `IncompatibleRuntimeError` with upgrade remedies.
|
|
19
|
+
- **cli:** Host root lifecycle (`runtime up|down|status|logs|restart|run`); Project link (`.nylorun/link.json` + `credentials.json`); `tenant` commands; `runtime status --env` exports `NYLORUN_RUNTIME_URL`, `NYLORUN_SERVER_KEY`, `NYLORUN_TENANT`; removed Project/home SQLite selectors.
|
|
20
|
+
- **studio:** `startStudio({ …, tenant: { id, name } })`; proxy forwards Tenant + protocol headers; UI shows Tenant name/short id.
|
|
21
|
+
|
|
22
|
+
- fd9fd87: Add workflows: compose agents and `tool()` with `Chain`, `Switch`, `Parallel`, `Map`, and `Loop`. A workflow is a registered runnable (`kind: "workflow"`) with the same session API as an agent — `export const agents`, `saveAgent` (saves referenced agents first), `createSession`, `input` (`content` or `data`), `observe({ follow })`, `pending`, `approve`, `cancel`. Slots (`{ run, id?, input? }`) reshape data between nodes. The flow engine (`runFlowDurable`) returns effects only; `harness/src/loop/` and `runDurable` are unchanged.
|
|
23
|
+
|
|
24
|
+
HostEffect gains flow kinds `agent`, `tool` (node), `fn`, and `verify`, each with `path`, `key`, and `iterations`. The Runtime drives agent nodes through the public session contract (linked sessions, shared sandbox via `PutSession.sandbox`), offers `fn` / `verify` again on lease expiry, and routes executor Actions by `(workflowId, key)` with claim-scoped `ctx.sandbox`. Optional `message.manifest` is a turn-only variant of the session pin (turn manifests). Studio shows the manifest tree, live node status, and session links. Examples under `examples/agents/{chain,switch,parallel,map,loop,ship-feature}/`.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Pin core to the tested release.
|
|
29
|
+
- Updated dependencies [c49efed]
|
|
30
|
+
- Updated dependencies [c49efed]
|
|
31
|
+
- Updated dependencies [fd9fd87]
|
|
32
|
+
- @nylorun/core@0.5.0-beta
|
|
33
|
+
|
|
34
|
+
## 0.5.0-beta
|
|
35
|
+
|
|
36
|
+
### Minor Changes
|
|
37
|
+
|
|
38
|
+
- 1cd7dc7: Add subagents: put an agent in another agent's `tools` (`Agent({ tools: [lookupOrder, researcher] })` or `.use({ tools: [researcher] })`) and the model can delegate to it. The tool is named after the agent's id, takes `{ task: string }`, and its description is the agent's `description`, which is now required for an agent used as a tool. The engine runs the child inside the parent's turn as a durable branch: fresh context, its own tools and hooks served by the root agent's executor, its own MCP servers, the session's sandbox, and only its final output (or `outputSchema` result) returned. Empty output, failures (with partial output marked as evidence), and requests for input or approval inside a child reach the parent as failed tool results. Parallel delegation calls run concurrently, completed child work is never re-run on replay, and cancelling the session cancels every child.
|
|
39
|
+
|
|
40
|
+
v1 is one level deep and non-interactive. Nested delegation, child tools that declare `approval`, and differing sandboxes across the tree fail the build with a named diagnostic. The manifest adds an optional `agent` body on a tool (schema version unchanged), actions and effects carry `agent: { id, path, delegationId }`, tool context gains `ctx.agent`, the durable host resolves a new `delegation` effect kind, and the Runtime emits `delegation.started` / `delegation.completed` events and filters history with `?agent=` (`session.history({ agent })`). Studio shows delegations, labels child actions with their agent, and filters events by agent.
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- Pin core to the tested release.
|
|
45
|
+
- Updated dependencies [1cd7dc7]
|
|
46
|
+
- @nylorun/core@0.4.0-beta
|
|
47
|
+
|
|
3
48
|
## 0.4.0-beta
|
|
4
49
|
|
|
5
50
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tenant API client package: definition authoring, a session client, and a
|
|
4
|
+
connected customer executor. Depends only on `@nylorun/core` among Nylorun
|
|
5
|
+
packages. A developer application's production tree should contain only this
|
|
6
|
+
package and `@nylorun/core` from Nylorun. Vocabulary:
|
|
7
|
+
[runtime/src/CONTEXT.md](../runtime/src/CONTEXT.md).
|
|
8
|
+
|
|
9
|
+
## Application entry (preferred)
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// src/main.ts
|
|
13
|
+
import { connectAgents } from "@nylorun/agents";
|
|
14
|
+
import { agents } from "../agents/index.js";
|
|
15
|
+
|
|
16
|
+
await connectAgents({ agents }).ready;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Application mode saves definitions, registers **derived** executor credentials
|
|
20
|
+
(HMAC of the application key + Tenant + agent id), and connects. Restarts and
|
|
21
|
+
replicas re-register the same hashes; tokens are never stored in the Project.
|
|
22
|
+
The same entry runs under `nylorun dev` and as `node dist/src/main.js`
|
|
23
|
+
(`npm start`). See [MIGRATION.md](../MIGRATION.md#runtime-clients-and-admin-api-breaking-beta)
|
|
24
|
+
for upgrading from `nylorun serve`.
|
|
25
|
+
|
|
26
|
+
## Connection resolution
|
|
27
|
+
|
|
28
|
+
`resolveConnection` / `createClient()` / `connectAgents({ agents })`:
|
|
29
|
+
|
|
30
|
+
1. Explicit `{ url, tenant, key }`
|
|
31
|
+
2. Environment — if any of `NYLORUN_RUNTIME_URL`, `NYLORUN_TENANT`,
|
|
32
|
+
`NYLORUN_SERVER_KEY` or `NYLORUN_EXECUTOR_KEY` is set, all required pieces
|
|
33
|
+
must be present (role `executor` when `NYLORUN_EXECUTOR_KEY` is set)
|
|
34
|
+
3. Project link — `.nylorun/link.json` + `credentials.json` (application role)
|
|
35
|
+
|
|
36
|
+
Sources never mix. Partial environment fails with `connection_missing`.
|
|
4
37
|
|
|
5
38
|
```ts
|
|
6
39
|
import { Agent, createClient, connectAgents, tool } from "@nylorun/agents";
|
|
@@ -21,10 +54,11 @@ const assistant = Agent({
|
|
|
21
54
|
],
|
|
22
55
|
});
|
|
23
56
|
|
|
24
|
-
//
|
|
57
|
+
// Explicit Tenant API client (options or env / link via createClient()).
|
|
25
58
|
const client = createClient({
|
|
26
59
|
url: process.env.NYLORUN_RUNTIME_URL,
|
|
27
60
|
key: process.env.NYLORUN_SERVER_KEY,
|
|
61
|
+
tenant: process.env.NYLORUN_TENANT,
|
|
28
62
|
});
|
|
29
63
|
await client.saveAgent(assistant, { implementationVersion: "app-1" });
|
|
30
64
|
const session = await client.createSession({
|
|
@@ -34,21 +68,33 @@ const session = await client.createSession({
|
|
|
34
68
|
await session.input("Look up item 123", { idempotencyKey: requestId });
|
|
35
69
|
const history = await session.history();
|
|
36
70
|
|
|
37
|
-
//
|
|
71
|
+
// Executor-only mode when you already hold an executor key:
|
|
38
72
|
const connection = connectAgents({
|
|
39
73
|
agents: [assistant],
|
|
40
74
|
implementationVersion: "app-1",
|
|
41
75
|
runtime: {
|
|
42
76
|
url: process.env.NYLORUN_RUNTIME_URL,
|
|
43
77
|
key: process.env.NYLORUN_EXECUTOR_KEY,
|
|
78
|
+
tenant: process.env.NYLORUN_TENANT,
|
|
44
79
|
},
|
|
45
80
|
onError: console.error,
|
|
46
81
|
});
|
|
47
82
|
await connection.ready;
|
|
48
|
-
// On shutdown:
|
|
49
83
|
await connection.close();
|
|
50
84
|
```
|
|
51
85
|
|
|
86
|
+
```sh
|
|
87
|
+
eval "$(npx nylorun runtime status --env)"
|
|
88
|
+
# → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY, NYLORUN_TENANT
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Every request sets `Nylorun-Tenant` and `Nylorun-Protocol`. A `tenant` field in a
|
|
92
|
+
body or query is never read from caller input. Before the first authenticated
|
|
93
|
+
request, `Transport` fetches `/health` once, checks protocol compatibility, and
|
|
94
|
+
throws `IncompatibleRuntimeError` / `incompatible_host` when the Host range or
|
|
95
|
+
required features do not match. Re-exports include `PROTOCOL_FEATURES`,
|
|
96
|
+
`ERROR_CODES` and `compareVersions`.
|
|
97
|
+
|
|
52
98
|
Load Agent Skills from a local catalog folder with `skills(path)`:
|
|
53
99
|
|
|
54
100
|
```ts
|
|
@@ -80,7 +126,7 @@ const assistant = Agent({
|
|
|
80
126
|
type: "streamable-http",
|
|
81
127
|
url: "https://mcp.example.com/github",
|
|
82
128
|
},
|
|
83
|
-
})
|
|
129
|
+
}),
|
|
84
130
|
);
|
|
85
131
|
```
|
|
86
132
|
|
|
@@ -108,13 +154,133 @@ The model gets `bash`, `read`, `write`, `edit`, `grep` and `glob` on a Linux mac
|
|
|
108
154
|
}))
|
|
109
155
|
```
|
|
110
156
|
|
|
111
|
-
The `dev` preset allows package registries and code hosts. Private networks, loopback, the host and cloud metadata endpoints are always blocked. Options from the full design that are not in this version (`setup`, `files`, `secrets`, `mount`, `onStart`,
|
|
157
|
+
The `dev` preset allows package registries and code hosts. Private networks, loopback, the host and cloud metadata endpoints are always blocked. Options from the full design that are not in this version (`setup`, `files`, `secrets`, `mount`, `onStart`, …) throw a `SandboxError` that says so. See [the sandbox design](../docs/design/sandboxes.md).
|
|
158
|
+
|
|
159
|
+
Put an agent in another agent's `tools` to let the model delegate to it:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { Agent } from "@nylorun/agents";
|
|
163
|
+
import { z } from "zod";
|
|
164
|
+
|
|
165
|
+
const researcher = Agent({
|
|
166
|
+
id: "researcher",
|
|
167
|
+
description:
|
|
168
|
+
"Investigates an order's history. Returns a short summary with the ids it relied on.",
|
|
169
|
+
instructions:
|
|
170
|
+
"Investigate one question about one order. Be exhaustive, then be brief.",
|
|
171
|
+
tools: [searchOrders, readTicket],
|
|
172
|
+
outputSchema: z.object({
|
|
173
|
+
summary: z.string(),
|
|
174
|
+
evidence: z.array(z.string()),
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const support = Agent({
|
|
179
|
+
id: "support",
|
|
180
|
+
instructions:
|
|
181
|
+
"For anything needing more than two lookups, delegate to researcher with a complete, self-contained task.",
|
|
182
|
+
tools: [lookupOrder, refundOrder, researcher],
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The tool is named after the agent's `id` and takes `{ task: string }`; the agent's `description` (required) is what the parent's model reads to decide when to delegate. The child starts with a fresh context: it sees its own instructions and the task, nothing of the parent's conversation, and only its final text (or `outputSchema` result) comes back. It keeps its own tools, hooks, skills and MCP servers, served by the executor you already run for the parent (`connectAgents({ agents: [support] })` serves both), shares the session's sandbox, and starts with empty `ctx.state`. Tools can read `ctx.agent` (`{ id, path, delegationId }`). Several delegation calls in one model response run in parallel. An empty answer, a failure (with the child's last text marked as evidence) or a cancelled child reaches the parent's model as a failed tool result, never as success. The Runtime emits `delegation.started` and `delegation.completed`; `session.history({ agent })` filters by `delegationId` (one child invocation) or by path such as `support/researcher` (every concurrent child that shares that path).
|
|
187
|
+
|
|
188
|
+
Delegate when the parent should keep the answer. When a specialist should own the rest of the conversation, switch capabilities with a `before("step")` patch instead. This version is one level deep and non-interactive: a delegated agent cannot use agents as tools, its tools cannot declare `approval` (keep those on the parent), and `ctx.ask`, `ctx.approve`, `ctx.sleep` or `ctx.waitFor` inside it fail with `delegation.interaction-unsupported`. Delegation is not an approval boundary; approvals live on tools.
|
|
189
|
+
|
|
190
|
+
## Workflows
|
|
191
|
+
|
|
192
|
+
Nest `Chain`, `Switch`, `Parallel`, `Map`, and `Loop` around agents and `tool()` to
|
|
193
|
+
build durable flows. A workflow is a definition with `kind: "workflow"`: register
|
|
194
|
+
it in `export const agents` (or `saveAgent(workflow)` — referenced agents are
|
|
195
|
+
saved first), then `createSession({ agentId })` and `session.input(value)` like
|
|
196
|
+
any agent. `input` sends string values as `content` and other JSON as `data`.
|
|
197
|
+
`connectAgents({ agents: [workflow] })` saves the tree and connects executors for
|
|
198
|
+
the workflow id and every referenced agent. Tool nodes, pure functions (slot
|
|
199
|
+
`input`, Switch `on`, Map `over`, Loop `decide`), and Loop `verify` run as Actions
|
|
200
|
+
routed by `(workflowId, key)`.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { Agent, Chain, Loop, Map, tool } from "@nylorun/agents";
|
|
204
|
+
import { z } from "zod";
|
|
205
|
+
|
|
206
|
+
const planner = Agent({
|
|
207
|
+
id: "planner",
|
|
208
|
+
instructions: "Return { tasks: string[] }.",
|
|
209
|
+
outputSchema: z.object({ tasks: z.array(z.string()) }),
|
|
210
|
+
}).build();
|
|
211
|
+
|
|
212
|
+
const coder = Agent({
|
|
213
|
+
id: "coder",
|
|
214
|
+
instructions: "Implement one task. Return { summary }.",
|
|
215
|
+
outputSchema: z.object({ summary: z.string() }),
|
|
216
|
+
}).build();
|
|
217
|
+
|
|
218
|
+
const openPr = tool({
|
|
219
|
+
name: "open-pr",
|
|
220
|
+
input: z.object({ summaries: z.array(z.string()) }),
|
|
221
|
+
async run({ summaries }, ctx) {
|
|
222
|
+
if (!(await ctx.approve("Open the PR?"))) throw new Error("Rejected");
|
|
223
|
+
return { opened: true, count: summaries.length };
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export const shipFeature = Chain({
|
|
228
|
+
id: "ship-feature",
|
|
229
|
+
steps: [
|
|
230
|
+
planner,
|
|
231
|
+
Map({
|
|
232
|
+
id: "implement",
|
|
233
|
+
over: (plan) => plan.tasks,
|
|
234
|
+
each: Loop({
|
|
235
|
+
id: "code",
|
|
236
|
+
run: coder,
|
|
237
|
+
verify: () => ({ pass: true }),
|
|
238
|
+
decide: ({ output }) => ({ output }),
|
|
239
|
+
}),
|
|
240
|
+
}),
|
|
241
|
+
{
|
|
242
|
+
run: openPr,
|
|
243
|
+
input: ({ value }) => ({
|
|
244
|
+
summaries: value.map((item: { summary: string }) => item.summary),
|
|
245
|
+
}),
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
export const agents = [shipFeature];
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
| Primitive | Role |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| **Chain** | Steps in order; slots reshape with `{ run, id?, input? }` |
|
|
256
|
+
| **Switch** | Pure `on(input) → key`, then the matching case (or `default`) |
|
|
257
|
+
| **Parallel** | Fixed named branches at once; output is an object |
|
|
258
|
+
| **Map** | Pure `over(input) → items`, one child per item; output is an array |
|
|
259
|
+
| **Loop** | `run` → `verify` → `decide` until `{ output }` |
|
|
260
|
+
|
|
261
|
+
Agents inside a workflow keep their own sessions (linked from the workflow
|
|
262
|
+
session) and share one sandbox: declare identical specs on every agent that
|
|
263
|
+
uses one, attach with `createSession({ …, sandbox: { session } })`, call
|
|
264
|
+
built-ins via `session.sandbox` (application) or `ctx.sandbox` (executor).
|
|
265
|
+
Observe with `session.observe({ follow: true })` to merge linked agent streams;
|
|
266
|
+
`pending()` lists waits across the tree. Studio renders the manifest tree and
|
|
267
|
+
live node status. Source examples:
|
|
268
|
+
`examples/agents/{chain,switch,parallel,map,loop,ship-feature}/` (outside the
|
|
269
|
+
default release registry). Host contract:
|
|
270
|
+
[HOST_CONTRACT.md](../harness/HOST_CONTRACT.md). Vocabulary (turn loop vs
|
|
271
|
+
workflow Loop): [harness/src/CONTEXT.md](../harness/src/CONTEXT.md).
|
|
112
272
|
|
|
113
273
|
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.
|
|
114
274
|
|
|
115
|
-
|
|
275
|
+
Connection defaults follow `resolveConnection` (options → environment → Project
|
|
276
|
+
link). Implementation version defaults from `NYLORUN_IMPLEMENTATION_VERSION`,
|
|
277
|
+
then `dev`. Application mode of `connectAgents({ agents })` registers derived
|
|
278
|
+
executor principals; executor-only mode still takes an explicit executor key.
|
|
279
|
+
Agents do not hash the manifest, and an in-flight action stays claimable after
|
|
280
|
+
the registered digest changes. Model selection and model credentials belong to
|
|
281
|
+
the Tenant.
|
|
116
282
|
|
|
117
|
-
|
|
283
|
+
After registration, `connectAgents` opens authenticated fetch SSE before discovering actions, rediscovers after reconnection, and claims pending actions for a connected agent id. It renews leases while executing, then retries HTTP result delivery with the same recorded outcome and idempotency key. Reconnect delay is bounded at 30 seconds. Notifications confer no execution authority. There is no periodic action-discovery polling. Close aborts the stream, HTTP requests and lease timers and signals running functions; JavaScript cannot forcibly terminate a function that ignores its signal. The host makes expired in-flight actions uncertain instead of automatically repeating external effects.
|
|
118
284
|
|
|
119
285
|
Tools receive state, info, identity, resume, signal, approval/response helpers and memoized `step`. Step outcomes survive a persisted wait result; they do not establish exactly-once external effects after an unacknowledged crash. `sleep` and `waitFor` currently return inspectable deferred outcomes; automatic timer/event wakeups remain runtime implementation work. Remote `onModelCall` convenience and progress-event transport are not supplied in this pass. Arbitrary middleware closures are rejected for durable definitions; use `before`/`after` hooks. The executor runs every capability registered at a hook point in one action. Agent definitions have no `.run()`; explicit local execution is available through `@nylorun/harness/run`.
|
|
120
286
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import type { AgentManifest, BuiltAgent, JsonValue } from "@nylorun/core/define";
|
|
1
|
+
import type { AgentManifest, BuiltAgent, BuiltWorkflow, JsonValue, SandboxToolName, WorkflowManifest } from "@nylorun/core/define";
|
|
2
2
|
import { type CredentialInfo, type CredentialSelection, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
|
|
3
3
|
import { Transport, type Destination } from "./http.js";
|
|
4
|
+
/** Application-principal access to a session's sandbox built-ins (workflows.md §8). */
|
|
5
|
+
export type SessionSandbox = {
|
|
6
|
+
readonly [K in SandboxToolName]: (args: Record<string, unknown>) => Promise<unknown>;
|
|
7
|
+
};
|
|
4
8
|
export interface AgentSource {
|
|
5
9
|
readonly id: string;
|
|
6
|
-
readonly manifest: AgentManifest;
|
|
7
|
-
build?(): BuiltAgent;
|
|
8
|
-
getBinding?: BuiltAgent["getBinding"];
|
|
10
|
+
readonly manifest: AgentManifest | WorkflowManifest;
|
|
11
|
+
build?(): BuiltAgent | BuiltWorkflow;
|
|
12
|
+
getBinding?: BuiltAgent["getBinding"] | BuiltWorkflow["getBinding"];
|
|
9
13
|
}
|
|
10
14
|
/** Closures stay on the live binding. The published manifest cannot name them. */
|
|
11
15
|
export declare function assertNoMiddlewareClosures(agent: AgentSource): void;
|
|
@@ -15,6 +19,7 @@ export interface SessionView {
|
|
|
15
19
|
ownerUserId: string;
|
|
16
20
|
status: string;
|
|
17
21
|
activeTurnId: string | null;
|
|
22
|
+
waits?: unknown;
|
|
18
23
|
[key: string]: unknown;
|
|
19
24
|
}
|
|
20
25
|
export declare class AgentsClient {
|
|
@@ -33,7 +38,7 @@ export declare class AgentsClient {
|
|
|
33
38
|
}): Promise<{
|
|
34
39
|
sessions: SessionView[];
|
|
35
40
|
}>;
|
|
36
|
-
saveAgent(agent: AgentSource, options: {
|
|
41
|
+
saveAgent(agent: AgentSource | BuiltWorkflow, options: {
|
|
37
42
|
implementationVersion: string;
|
|
38
43
|
requestId?: string;
|
|
39
44
|
}): Promise<unknown>;
|
|
@@ -45,6 +50,10 @@ export declare class AgentsClient {
|
|
|
45
50
|
requestId?: string;
|
|
46
51
|
vaultIds?: readonly string[];
|
|
47
52
|
credentialSelections?: readonly CredentialSelection[];
|
|
53
|
+
/** Share another session's sandbox (public PutSession.sandbox). */
|
|
54
|
+
sandbox?: {
|
|
55
|
+
session: string;
|
|
56
|
+
};
|
|
48
57
|
}): Promise<SessionClient>;
|
|
49
58
|
createVault(options: {
|
|
50
59
|
name: string;
|
|
@@ -118,16 +127,23 @@ export type CommandOptions = {
|
|
|
118
127
|
export declare class SessionClient {
|
|
119
128
|
private readonly transport;
|
|
120
129
|
readonly id: string;
|
|
130
|
+
readonly sandbox: SessionSandbox;
|
|
121
131
|
constructor(transport: Transport, id: string);
|
|
122
132
|
private get path();
|
|
123
133
|
inspect(signal?: AbortSignal): Promise<SessionView>;
|
|
134
|
+
/** Pending waits / interactions from the session inspect view. */
|
|
135
|
+
pending(signal?: AbortSignal): Promise<{}>;
|
|
124
136
|
command(command: SessionCommand, signal?: AbortSignal): Promise<{
|
|
125
137
|
status: "accepted";
|
|
126
138
|
turnId: string | null;
|
|
127
139
|
cursor: string | null;
|
|
128
140
|
requestId: string;
|
|
129
141
|
}>;
|
|
130
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Start a turn. Strings become `message.content`; any other JSON value becomes
|
|
144
|
+
* `message.data` (workflows.md §5).
|
|
145
|
+
*/
|
|
146
|
+
input(value: string | JsonValue, options: CommandOptions): Promise<{
|
|
131
147
|
status: "accepted";
|
|
132
148
|
turnId: string | null;
|
|
133
149
|
cursor: string | null;
|
|
@@ -153,13 +169,16 @@ export declare class SessionClient {
|
|
|
153
169
|
cursor: string | null;
|
|
154
170
|
requestId: string;
|
|
155
171
|
}>;
|
|
172
|
+
/** `agent` narrows to one agent used as a tool: its delegationId or its path. */
|
|
156
173
|
history(options?: {
|
|
157
174
|
cursor?: string;
|
|
175
|
+
agent?: string;
|
|
158
176
|
signal?: AbortSignal;
|
|
159
177
|
}): Promise<{
|
|
160
178
|
items: {
|
|
161
179
|
eventId: string;
|
|
162
180
|
sessionId: string;
|
|
181
|
+
tenantId: string;
|
|
163
182
|
turnId: string | null;
|
|
164
183
|
cursor: string;
|
|
165
184
|
createdAt: string;
|
|
@@ -171,17 +190,32 @@ export declare class SessionClient {
|
|
|
171
190
|
observe(options?: {
|
|
172
191
|
cursor?: string;
|
|
173
192
|
signal?: AbortSignal;
|
|
193
|
+
follow?: boolean;
|
|
174
194
|
}): AsyncGenerator<{
|
|
175
195
|
eventId: string;
|
|
176
196
|
sessionId: string;
|
|
197
|
+
tenantId: string;
|
|
177
198
|
turnId: string | null;
|
|
178
199
|
cursor: string;
|
|
179
200
|
createdAt: string;
|
|
180
201
|
type: string;
|
|
181
202
|
payload: unknown;
|
|
182
203
|
}, void, unknown>;
|
|
204
|
+
private observeOne;
|
|
205
|
+
/**
|
|
206
|
+
* Own session stream plus linked agent sessions announced in `node.agent`
|
|
207
|
+
* events, merged by `createdAt` then arrival order (workflows.md §11).
|
|
208
|
+
*/
|
|
209
|
+
private observeFollowing;
|
|
183
210
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Build a Tenant API client.
|
|
213
|
+
* Explicit connection fields keep today's sync Transport resolution (including
|
|
214
|
+
* environment fill-ins). With no connection fields, resolves via
|
|
215
|
+
* `resolveConnection` (environment → project link).
|
|
216
|
+
*/
|
|
217
|
+
export declare function createClient(destination: Destination): AgentsClient;
|
|
218
|
+
export declare function createClient(destination?: undefined): Promise<AgentsClient>;
|
|
219
|
+
export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
|
|
220
|
+
export type { Destination, IncompatibleReason } from "./http.js";
|
|
187
221
|
export type { LiveEvent } from "@nylorun/core/contracts";
|
package/dist/client.js
CHANGED
|
@@ -1,21 +1,58 @@
|
|
|
1
|
+
import { SANDBOX_TOOL_NAMES, delegateOf, isBuiltWorkflow, isSandboxToolName, } from "@nylorun/core/define";
|
|
1
2
|
import { LiveEventSchema, SessionItemsResponseSchema, } from "@nylorun/core/contracts";
|
|
3
|
+
import { resolveConnection } from "./connection.js";
|
|
2
4
|
import { Transport, id, segment } from "./http.js";
|
|
3
5
|
import { observeSSE } from "./sse.js";
|
|
6
|
+
function sessionSandboxOf(transport, sessionId) {
|
|
7
|
+
const call = (tool, args) => transport.json(`/v1/sessions/${segment(sessionId)}/sandbox/${segment(tool)}`, "POST", args);
|
|
8
|
+
const sandbox = {};
|
|
9
|
+
for (const tool of SANDBOX_TOOL_NAMES) {
|
|
10
|
+
if (!isSandboxToolName(tool))
|
|
11
|
+
continue;
|
|
12
|
+
sandbox[tool] = (args) => call(tool, args);
|
|
13
|
+
}
|
|
14
|
+
return sandbox;
|
|
15
|
+
}
|
|
16
|
+
function linkedSessionId(event) {
|
|
17
|
+
if (event.type !== "node.agent")
|
|
18
|
+
return undefined;
|
|
19
|
+
const payload = event.payload;
|
|
20
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
21
|
+
return undefined;
|
|
22
|
+
const sessionId = payload.sessionId;
|
|
23
|
+
return typeof sessionId === "string" && sessionId.length > 0
|
|
24
|
+
? sessionId
|
|
25
|
+
: undefined;
|
|
26
|
+
}
|
|
4
27
|
const middlewareClosure = "Hosted definitions support declarative capabilities and before/after hooks, not middleware closures";
|
|
5
|
-
|
|
28
|
+
/** The agent's declarations, then those of each agent it uses as a tool, keyed `<child>/<capability>`. */
|
|
29
|
+
function declarationsOf(agent) {
|
|
30
|
+
if (isBuiltWorkflow(agent) || agent.manifest.kind === "workflow")
|
|
31
|
+
return [];
|
|
6
32
|
const built = agent.build?.() ?? agent;
|
|
7
|
-
|
|
33
|
+
if (isBuiltWorkflow(built))
|
|
34
|
+
return [];
|
|
35
|
+
const binding = built.getBinding?.();
|
|
36
|
+
if (!binding || !("declarations" in binding))
|
|
37
|
+
return [];
|
|
38
|
+
const found = (binding.declarations ?? []).map((item) => ({ key: item.id, item }));
|
|
39
|
+
for (const tool of binding.tools ?? []) {
|
|
40
|
+
const child = delegateOf(tool)?.agent;
|
|
41
|
+
for (const item of child?.getBinding().declarations ?? [])
|
|
42
|
+
found.push({ key: `${child.id}/${item.id}`, item });
|
|
43
|
+
}
|
|
44
|
+
return found;
|
|
45
|
+
}
|
|
46
|
+
function pluginRootsOf(agent) {
|
|
8
47
|
const roots = {};
|
|
9
|
-
for (const item of
|
|
48
|
+
for (const { key, item } of declarationsOf(agent))
|
|
10
49
|
if (item.pluginRoot)
|
|
11
|
-
roots[
|
|
12
|
-
}
|
|
50
|
+
roots[key] = item.pluginRoot;
|
|
13
51
|
return Object.keys(roots).length === 0 ? undefined : roots;
|
|
14
52
|
}
|
|
15
53
|
/** Closures stay on the live binding. The published manifest cannot name them. */
|
|
16
54
|
export function assertNoMiddlewareClosures(agent) {
|
|
17
|
-
|
|
18
|
-
if (built.getBinding?.().declarations.some((item) => item.hasMiddleware))
|
|
55
|
+
if (declarationsOf(agent).some(({ item }) => item.hasMiddleware))
|
|
19
56
|
throw new Error(middlewareClosure);
|
|
20
57
|
}
|
|
21
58
|
export class AgentsClient {
|
|
@@ -32,7 +69,21 @@ export class AgentsClient {
|
|
|
32
69
|
: "";
|
|
33
70
|
return this.transport.json(`/v1/sessions${query}`, "GET", undefined, options.signal);
|
|
34
71
|
}
|
|
35
|
-
saveAgent(agent, options) {
|
|
72
|
+
async saveAgent(agent, options) {
|
|
73
|
+
if (isBuiltWorkflow(agent)) {
|
|
74
|
+
for (const binding of Object.values(agent.getBinding().agents)) {
|
|
75
|
+
await this.saveAgent({
|
|
76
|
+
id: binding.manifest.id,
|
|
77
|
+
manifest: binding.manifest,
|
|
78
|
+
getBinding: () => binding,
|
|
79
|
+
}, options);
|
|
80
|
+
}
|
|
81
|
+
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
82
|
+
requestId: options.requestId ?? id(),
|
|
83
|
+
manifest: agent.manifest,
|
|
84
|
+
implementationVersion: options.implementationVersion,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
36
87
|
assertNoMiddlewareClosures(agent);
|
|
37
88
|
const pluginRoots = pluginRootsOf(agent);
|
|
38
89
|
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
@@ -53,6 +104,7 @@ export class AgentsClient {
|
|
|
53
104
|
...(options.credentialSelections
|
|
54
105
|
? { credentialSelections: options.credentialSelections }
|
|
55
106
|
: {}),
|
|
107
|
+
...(options.sandbox ? { sandbox: options.sandbox } : {}),
|
|
56
108
|
});
|
|
57
109
|
return this.session(sessionId);
|
|
58
110
|
}
|
|
@@ -105,9 +157,11 @@ export class AgentsClient {
|
|
|
105
157
|
export class SessionClient {
|
|
106
158
|
transport;
|
|
107
159
|
id;
|
|
160
|
+
sandbox;
|
|
108
161
|
constructor(transport, id) {
|
|
109
162
|
this.transport = transport;
|
|
110
163
|
this.id = id;
|
|
164
|
+
this.sandbox = sessionSandboxOf(transport, id);
|
|
111
165
|
}
|
|
112
166
|
get path() {
|
|
113
167
|
return `/v1/sessions/${segment(this.id)}`;
|
|
@@ -115,16 +169,27 @@ export class SessionClient {
|
|
|
115
169
|
inspect(signal) {
|
|
116
170
|
return this.transport.json(this.path, "GET", undefined, signal);
|
|
117
171
|
}
|
|
172
|
+
/** Pending waits / interactions from the session inspect view. */
|
|
173
|
+
async pending(signal) {
|
|
174
|
+
const view = await this.inspect(signal);
|
|
175
|
+
return view.waits ?? [];
|
|
176
|
+
}
|
|
118
177
|
command(command, signal) {
|
|
119
178
|
return this.transport.json(`${this.path}/commands`, "POST", command, signal);
|
|
120
179
|
}
|
|
121
|
-
|
|
122
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Start a turn. Strings become `message.content`; any other JSON value becomes
|
|
182
|
+
* `message.data` (workflows.md §5).
|
|
183
|
+
*/
|
|
184
|
+
input(value, options) {
|
|
185
|
+
const base = {
|
|
123
186
|
type: "message",
|
|
124
|
-
content,
|
|
125
187
|
requestId: options.requestId ?? id(),
|
|
126
188
|
idempotencyKey: options.idempotencyKey,
|
|
127
|
-
}
|
|
189
|
+
};
|
|
190
|
+
return this.command(typeof value === "string"
|
|
191
|
+
? { ...base, content: value }
|
|
192
|
+
: { ...base, data: value }, options.signal);
|
|
128
193
|
}
|
|
129
194
|
approve(interactionId, approved, options) {
|
|
130
195
|
return this.command({
|
|
@@ -152,18 +217,103 @@ export class SessionClient {
|
|
|
152
217
|
...(options.reason ? { reason: options.reason } : {}),
|
|
153
218
|
}, options.signal);
|
|
154
219
|
}
|
|
220
|
+
/** `agent` narrows to one agent used as a tool: its delegationId or its path. */
|
|
155
221
|
async history(options = {}) {
|
|
156
|
-
|
|
222
|
+
const query = new URLSearchParams();
|
|
223
|
+
if (options.cursor)
|
|
224
|
+
query.set("cursor", options.cursor);
|
|
225
|
+
if (options.agent)
|
|
226
|
+
query.set("agent", options.agent);
|
|
227
|
+
const search = query.toString();
|
|
228
|
+
return SessionItemsResponseSchema.parse(await this.transport.json(`${this.path}/items${search ? `?${search}` : ""}`, "GET", undefined, options.signal));
|
|
157
229
|
}
|
|
158
230
|
async *observe(options = {}) {
|
|
159
|
-
|
|
231
|
+
if (!options.follow) {
|
|
232
|
+
yield* this.observeOne(this.id, options);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
yield* this.observeFollowing(options);
|
|
236
|
+
}
|
|
237
|
+
async *observeOne(sessionId, options) {
|
|
238
|
+
for await (const frame of observeSSE(this.transport, `/v1/sessions/${segment(sessionId)}/events`, options)) {
|
|
160
239
|
if (frame.event === "heartbeat" || frame.event === "ready")
|
|
161
240
|
continue;
|
|
162
241
|
yield LiveEventSchema.parse(JSON.parse(frame.data));
|
|
163
242
|
}
|
|
164
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Own session stream plus linked agent sessions announced in `node.agent`
|
|
246
|
+
* events, merged by `createdAt` then arrival order (workflows.md §11).
|
|
247
|
+
*/
|
|
248
|
+
async *observeFollowing(options) {
|
|
249
|
+
const signal = options.signal;
|
|
250
|
+
const linked = new Set();
|
|
251
|
+
const queue = [];
|
|
252
|
+
let wait;
|
|
253
|
+
let open = 1;
|
|
254
|
+
let failed;
|
|
255
|
+
const wake = () => {
|
|
256
|
+
wait?.();
|
|
257
|
+
wait = undefined;
|
|
258
|
+
};
|
|
259
|
+
const push = (event) => {
|
|
260
|
+
queue.push(event);
|
|
261
|
+
queue.sort((a, b) => {
|
|
262
|
+
const byTime = a.createdAt.localeCompare(b.createdAt);
|
|
263
|
+
return byTime !== 0 ? byTime : a.cursor.localeCompare(b.cursor);
|
|
264
|
+
});
|
|
265
|
+
wake();
|
|
266
|
+
};
|
|
267
|
+
const pump = async (sessionId, cursor) => {
|
|
268
|
+
try {
|
|
269
|
+
for await (const event of this.observeOne(sessionId, { cursor, signal })) {
|
|
270
|
+
push(event);
|
|
271
|
+
const link = linkedSessionId(event);
|
|
272
|
+
if (link && link !== this.id && !linked.has(link)) {
|
|
273
|
+
linked.add(link);
|
|
274
|
+
open += 1;
|
|
275
|
+
void pump(link).finally(() => {
|
|
276
|
+
open -= 1;
|
|
277
|
+
wake();
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
if (!signal?.aborted)
|
|
284
|
+
failed = error;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
void pump(this.id, options.cursor).finally(() => {
|
|
288
|
+
open -= 1;
|
|
289
|
+
wake();
|
|
290
|
+
});
|
|
291
|
+
while (!signal?.aborted) {
|
|
292
|
+
if (failed)
|
|
293
|
+
throw failed;
|
|
294
|
+
if (queue.length) {
|
|
295
|
+
yield queue.shift();
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (open <= 0)
|
|
299
|
+
return;
|
|
300
|
+
await new Promise((resolve) => {
|
|
301
|
+
wait = resolve;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
165
305
|
}
|
|
166
306
|
export function createClient(destination = {}) {
|
|
167
|
-
|
|
307
|
+
if (destination.url !== undefined ||
|
|
308
|
+
destination.key !== undefined ||
|
|
309
|
+
destination.tenant !== undefined) {
|
|
310
|
+
return new AgentsClient(destination);
|
|
311
|
+
}
|
|
312
|
+
return resolveConnection().then((connection) => new AgentsClient({
|
|
313
|
+
url: connection.url,
|
|
314
|
+
key: connection.key,
|
|
315
|
+
tenant: connection.tenant,
|
|
316
|
+
...(destination.fetch ? { fetch: destination.fetch } : {}),
|
|
317
|
+
}));
|
|
168
318
|
}
|
|
169
|
-
export { RuntimeError } from "./http.js";
|
|
319
|
+
export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
|