@nylorun/agents 0.5.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 +31 -0
- package/README.md +153 -12
- package/dist/client.d.ts +41 -9
- package/dist/client.js +145 -11
- 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 +85 -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,36 @@
|
|
|
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
|
+
|
|
3
34
|
## 0.5.0-beta
|
|
4
35
|
|
|
5
36
|
### 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,7 +154,7 @@ 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).
|
|
112
158
|
|
|
113
159
|
Put an agent in another agent's `tools` to let the model delegate to it:
|
|
114
160
|
|
|
@@ -118,15 +164,21 @@ import { z } from "zod";
|
|
|
118
164
|
|
|
119
165
|
const researcher = Agent({
|
|
120
166
|
id: "researcher",
|
|
121
|
-
description:
|
|
122
|
-
|
|
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.",
|
|
123
171
|
tools: [searchOrders, readTicket],
|
|
124
|
-
outputSchema: z.object({
|
|
172
|
+
outputSchema: z.object({
|
|
173
|
+
summary: z.string(),
|
|
174
|
+
evidence: z.array(z.string()),
|
|
175
|
+
}),
|
|
125
176
|
});
|
|
126
177
|
|
|
127
178
|
const support = Agent({
|
|
128
179
|
id: "support",
|
|
129
|
-
instructions:
|
|
180
|
+
instructions:
|
|
181
|
+
"For anything needing more than two lookups, delegate to researcher with a complete, self-contained task.",
|
|
130
182
|
tools: [lookupOrder, refundOrder, researcher],
|
|
131
183
|
});
|
|
132
184
|
```
|
|
@@ -135,11 +187,100 @@ The tool is named after the agent's `id` and takes `{ task: string }`; the agent
|
|
|
135
187
|
|
|
136
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.
|
|
137
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).
|
|
272
|
+
|
|
138
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.
|
|
139
274
|
|
|
140
|
-
|
|
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.
|
|
141
282
|
|
|
142
|
-
|
|
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.
|
|
143
284
|
|
|
144
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`.
|
|
145
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;
|
|
@@ -162,6 +178,7 @@ export declare class SessionClient {
|
|
|
162
178
|
items: {
|
|
163
179
|
eventId: string;
|
|
164
180
|
sessionId: string;
|
|
181
|
+
tenantId: string;
|
|
165
182
|
turnId: string | null;
|
|
166
183
|
cursor: string;
|
|
167
184
|
createdAt: string;
|
|
@@ -173,17 +190,32 @@ export declare class SessionClient {
|
|
|
173
190
|
observe(options?: {
|
|
174
191
|
cursor?: string;
|
|
175
192
|
signal?: AbortSignal;
|
|
193
|
+
follow?: boolean;
|
|
176
194
|
}): AsyncGenerator<{
|
|
177
195
|
eventId: string;
|
|
178
196
|
sessionId: string;
|
|
197
|
+
tenantId: string;
|
|
179
198
|
turnId: string | null;
|
|
180
199
|
cursor: string;
|
|
181
200
|
createdAt: string;
|
|
182
201
|
type: string;
|
|
183
202
|
payload: unknown;
|
|
184
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;
|
|
185
210
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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";
|
|
189
221
|
export type { LiveEvent } from "@nylorun/core/contracts";
|
package/dist/client.js
CHANGED
|
@@ -1,14 +1,42 @@
|
|
|
1
|
-
import { delegateOf } from "@nylorun/core/define";
|
|
1
|
+
import { SANDBOX_TOOL_NAMES, delegateOf, isBuiltWorkflow, isSandboxToolName, } from "@nylorun/core/define";
|
|
2
2
|
import { LiveEventSchema, SessionItemsResponseSchema, } from "@nylorun/core/contracts";
|
|
3
|
+
import { resolveConnection } from "./connection.js";
|
|
3
4
|
import { Transport, id, segment } from "./http.js";
|
|
4
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
|
+
}
|
|
5
27
|
const middlewareClosure = "Hosted definitions support declarative capabilities and before/after hooks, not middleware closures";
|
|
6
28
|
/** The agent's declarations, then those of each agent it uses as a tool, keyed `<child>/<capability>`. */
|
|
7
29
|
function declarationsOf(agent) {
|
|
30
|
+
if (isBuiltWorkflow(agent) || agent.manifest.kind === "workflow")
|
|
31
|
+
return [];
|
|
8
32
|
const built = agent.build?.() ?? agent;
|
|
33
|
+
if (isBuiltWorkflow(built))
|
|
34
|
+
return [];
|
|
9
35
|
const binding = built.getBinding?.();
|
|
10
|
-
|
|
11
|
-
|
|
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 ?? []) {
|
|
12
40
|
const child = delegateOf(tool)?.agent;
|
|
13
41
|
for (const item of child?.getBinding().declarations ?? [])
|
|
14
42
|
found.push({ key: `${child.id}/${item.id}`, item });
|
|
@@ -41,7 +69,21 @@ export class AgentsClient {
|
|
|
41
69
|
: "";
|
|
42
70
|
return this.transport.json(`/v1/sessions${query}`, "GET", undefined, options.signal);
|
|
43
71
|
}
|
|
44
|
-
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
|
+
}
|
|
45
87
|
assertNoMiddlewareClosures(agent);
|
|
46
88
|
const pluginRoots = pluginRootsOf(agent);
|
|
47
89
|
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
@@ -62,6 +104,7 @@ export class AgentsClient {
|
|
|
62
104
|
...(options.credentialSelections
|
|
63
105
|
? { credentialSelections: options.credentialSelections }
|
|
64
106
|
: {}),
|
|
107
|
+
...(options.sandbox ? { sandbox: options.sandbox } : {}),
|
|
65
108
|
});
|
|
66
109
|
return this.session(sessionId);
|
|
67
110
|
}
|
|
@@ -114,9 +157,11 @@ export class AgentsClient {
|
|
|
114
157
|
export class SessionClient {
|
|
115
158
|
transport;
|
|
116
159
|
id;
|
|
160
|
+
sandbox;
|
|
117
161
|
constructor(transport, id) {
|
|
118
162
|
this.transport = transport;
|
|
119
163
|
this.id = id;
|
|
164
|
+
this.sandbox = sessionSandboxOf(transport, id);
|
|
120
165
|
}
|
|
121
166
|
get path() {
|
|
122
167
|
return `/v1/sessions/${segment(this.id)}`;
|
|
@@ -124,16 +169,27 @@ export class SessionClient {
|
|
|
124
169
|
inspect(signal) {
|
|
125
170
|
return this.transport.json(this.path, "GET", undefined, signal);
|
|
126
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
|
+
}
|
|
127
177
|
command(command, signal) {
|
|
128
178
|
return this.transport.json(`${this.path}/commands`, "POST", command, signal);
|
|
129
179
|
}
|
|
130
|
-
|
|
131
|
-
|
|
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 = {
|
|
132
186
|
type: "message",
|
|
133
|
-
content,
|
|
134
187
|
requestId: options.requestId ?? id(),
|
|
135
188
|
idempotencyKey: options.idempotencyKey,
|
|
136
|
-
}
|
|
189
|
+
};
|
|
190
|
+
return this.command(typeof value === "string"
|
|
191
|
+
? { ...base, content: value }
|
|
192
|
+
: { ...base, data: value }, options.signal);
|
|
137
193
|
}
|
|
138
194
|
approve(interactionId, approved, options) {
|
|
139
195
|
return this.command({
|
|
@@ -172,14 +228,92 @@ export class SessionClient {
|
|
|
172
228
|
return SessionItemsResponseSchema.parse(await this.transport.json(`${this.path}/items${search ? `?${search}` : ""}`, "GET", undefined, options.signal));
|
|
173
229
|
}
|
|
174
230
|
async *observe(options = {}) {
|
|
175
|
-
|
|
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)) {
|
|
176
239
|
if (frame.event === "heartbeat" || frame.event === "ready")
|
|
177
240
|
continue;
|
|
178
241
|
yield LiveEventSchema.parse(JSON.parse(frame.data));
|
|
179
242
|
}
|
|
180
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
|
+
}
|
|
181
305
|
}
|
|
182
306
|
export function createClient(destination = {}) {
|
|
183
|
-
|
|
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
|
+
}));
|
|
184
318
|
}
|
|
185
|
-
export { RuntimeError } from "./http.js";
|
|
319
|
+
export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ErrorCode } from "@nylorun/core/compatibility";
|
|
2
|
+
export interface ResolvedConnection {
|
|
3
|
+
url: string;
|
|
4
|
+
tenant: string;
|
|
5
|
+
key: string;
|
|
6
|
+
role: "application" | "executor";
|
|
7
|
+
source: "options" | "environment" | "project-link";
|
|
8
|
+
}
|
|
9
|
+
export declare class ConnectionError extends Error {
|
|
10
|
+
readonly code: ErrorCode;
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/** Resolve Tenant API connection: options → environment → project link (D§7.1). */
|
|
14
|
+
export declare function resolveConnection(options?: {
|
|
15
|
+
url?: string;
|
|
16
|
+
tenant?: string;
|
|
17
|
+
key?: string;
|
|
18
|
+
cwd?: string;
|
|
19
|
+
}): Promise<ResolvedConnection>;
|