@nylorun/agents 0.7.0-beta → 0.8.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 +104 -1
- package/dist/ag-ui/handler.d.ts +42 -0
- package/dist/ag-ui/handler.js +397 -0
- package/dist/ag-ui/history.d.ts +7 -0
- package/dist/ag-ui/history.js +60 -0
- package/dist/ag-ui/index.d.ts +7 -0
- package/dist/ag-ui/index.js +6 -0
- package/dist/ag-ui/node.d.ts +5 -0
- package/dist/ag-ui/node.js +51 -0
- package/dist/ag-ui/sse.d.ts +9 -0
- package/dist/ag-ui/sse.js +7 -0
- package/dist/ag-ui/translate.d.ts +38 -0
- package/dist/ag-ui/translate.js +222 -0
- package/dist/client.d.ts +18 -1
- package/dist/client.js +31 -1
- package/dist/http.d.ts +10 -1
- package/dist/http.js +24 -4
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
|
+
## 0.8.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a322696: **`@nylorun/agents/ag-ui`: serve agents to AG-UI clients from your own server.** `createAgUiHandler({ basePath, agents, subject })` returns a web-standard `fetch` handler (plus `run`, `history`, `reattach` and `cancel`), and `toNodeListener` adapts it to `node:http` and Express.
|
|
8
|
+
|
|
9
|
+
- One session per person, agent and thread; the AG-UI message id is the idempotency key, so a retried run replays the same turn.
|
|
10
|
+
- Approvals become AG-UI interrupts and resume through `runAgent({ resume })`.
|
|
11
|
+
- History returns a plain AG-UI `Message[]` with the ids the live run used; reattach continues a run from `Last-Event-ID`.
|
|
12
|
+
- Needs a Runtime with `transcript-events`. Adds the `@ag-ui/core` dependency (`~1.0.0`), loaded only by this subpath.
|
|
13
|
+
|
|
14
|
+
`AgentsClient.hostFeatures()` returns the Runtime's protocol features, including optional ones.
|
|
15
|
+
|
|
16
|
+
- 844bff3: **Act for a person: `Nylorun-Subject` and `Nylorun-Scopes`.** An app server that holds the Tenant key can name the person each request is for, and the Runtime enforces it (optional Host feature `subject-headers`).
|
|
17
|
+
|
|
18
|
+
- `client.as(subject, { scopes })` in `@nylorun/agents` sends both headers on every call, event streams included. Scopes: `agents:read`, `agents:write`, `sessions:own`, `vaults:own`, `tenant:settings`; default `["sessions:own"]`.
|
|
19
|
+
- The Runtime limits a subject to the routes its scopes allow (`403 scope_required`) and to its own sessions and vaults: another owner's session, vault or sandbox is the same `404` as a missing one, including `PUT` on its session id (was `409`). Reset, config seed, executors, actions and the sandbox tool routes are open to no subject. Only application keys may send the headers.
|
|
20
|
+
- The AG-UI handler calls the Runtime as each person and requires `subject-headers`; new optional `scopes` option. The host's `session()` parameters can no longer replace a session's id, agent or owner.
|
|
21
|
+
- Core exports `SUBJECT_HEADER`, `SCOPES_HEADER`, `SUBJECT_SCOPES` and `parseSubjectHeaders`. Postgres Tenant schemas migrate to version 2 (an indexed session owner column).
|
|
22
|
+
|
|
23
|
+
Requests without `Nylorun-Subject` are unchanged.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- 42272f8: The AG-UI handler answers `400` (`invalid_request`) when the Runtime refuses what the client sent, such as a `Last-Event-ID` it cannot read, instead of `502`.
|
|
28
|
+
- Pin core to the tested release.
|
|
29
|
+
- Updated dependencies [a322696]
|
|
30
|
+
- Updated dependencies [844bff3]
|
|
31
|
+
- Updated dependencies [a322696]
|
|
32
|
+
- @nylorun/core@0.7.0-beta
|
|
33
|
+
|
|
3
34
|
## 0.7.0-beta
|
|
4
35
|
|
|
5
36
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -286,4 +286,107 @@ After registration, `connectAgents` opens authenticated fetch SSE before discove
|
|
|
286
286
|
|
|
287
287
|
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`.
|
|
288
288
|
|
|
289
|
-
|
|
289
|
+
## AG-UI
|
|
290
|
+
|
|
291
|
+
`@nylorun/agents/ag-ui` serves the Tenant's agents to any
|
|
292
|
+
[AG-UI](https://docs.ag-ui.com) client (`@ag-ui/client`'s `HttpAgent`,
|
|
293
|
+
CopilotKit, a desktop renderer) from your own server. Your server signs people
|
|
294
|
+
in and names the person each request is for; the handler maps each AG-UI thread
|
|
295
|
+
to one session per person, agent and thread, and streams it as AG-UI events.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import { createServer } from "node:http";
|
|
299
|
+
import { createAgUiHandler, toNodeListener } from "@nylorun/agents/ag-ui";
|
|
300
|
+
import support from "./agents/support.js";
|
|
301
|
+
|
|
302
|
+
const agui = createAgUiHandler({
|
|
303
|
+
basePath: "/api/agui",
|
|
304
|
+
agents: [support], // nothing else is reachable
|
|
305
|
+
subject: async (request) => (await getSignedInUser(request))?.id, // undefined → 401
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
createServer(toNodeListener(agui)).listen(3000);
|
|
309
|
+
// Next.js, Hono, Bun, Deno, Workers: export or mount `agui.fetch` directly.
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
| Method and path under `basePath` | Operation |
|
|
313
|
+
| --- | --- |
|
|
314
|
+
| `POST /{agentId}` | Run: an AG-UI `RunAgentInput` in, server-sent events out |
|
|
315
|
+
| `GET /{agentId}/threads/{threadId}/messages` | History as a plain AG-UI `Message[]`, usable as `HttpAgent`'s `initialMessages` |
|
|
316
|
+
| `GET /{agentId}/threads/{threadId}/events` | Reattach: the rest of a run after a dropped connection, from `Last-Event-ID` (or `?cursor=`); `204` when nothing is left |
|
|
317
|
+
| `POST /{agentId}/threads/{threadId}/cancel` | Cancel the running turn |
|
|
318
|
+
|
|
319
|
+
`run`, `history`, `reattach` and `cancel` are also on the handler for your own
|
|
320
|
+
routing. The default `client` is `createClient()`; pass `client` to use another
|
|
321
|
+
one, and `session(subject, agentId)` to add the person's `vaultIds` or
|
|
322
|
+
`credentialSelections` (keep `info` stable: it is part of the session's
|
|
323
|
+
identity).
|
|
324
|
+
|
|
325
|
+
- Each AG-UI message id is the command's idempotency key: a retried run replays
|
|
326
|
+
the same turn instead of starting a second one.
|
|
327
|
+
- An approval ends the run with an AG-UI interrupt (`reason: "tool_approval"`,
|
|
328
|
+
`toolCallId` the model's call id). Resume with
|
|
329
|
+
`runAgent({ resume: [{ interruptId, status: "resolved", payload: { approved: true } }] })`.
|
|
330
|
+
Other interactions answer with `payload` as the response.
|
|
331
|
+
- Every event that ends a group carries the Runtime cursor as its SSE `id`.
|
|
332
|
+
`HttpAgent` does not reconnect by itself; call the reattach route with the
|
|
333
|
+
last id you received.
|
|
334
|
+
- Closing the connection stops reading; it never cancels the turn.
|
|
335
|
+
- A busy or paused thread ends the run with `RUN_ERROR` code `session_busy`.
|
|
336
|
+
- The handler calls the Runtime as each person (`client.as(subject, { scopes })`,
|
|
337
|
+
below), so the Runtime itself keeps one person out of another's threads.
|
|
338
|
+
`scopes` defaults to `["sessions:own"]`; add `"vaults:own"` when `session()`
|
|
339
|
+
attaches the person's vaults. A `subject` the Runtime cannot name (see below)
|
|
340
|
+
answers `500` (`subject_invalid`).
|
|
341
|
+
- The handler needs a Runtime with the optional features `transcript-events`
|
|
342
|
+
and `subject-headers` and answers `502` (`runtime_feature_missing`) without
|
|
343
|
+
them.
|
|
344
|
+
|
|
345
|
+
A complete web backend with a test is in
|
|
346
|
+
[examples/src/ag-ui](../examples/README.md#an-agent-in-your-web-app-ag-ui).
|
|
347
|
+
|
|
348
|
+
Limitations: assistant text arrives once per model step (no token streaming);
|
|
349
|
+
no reasoning, state, activity or subagent events, and an agent used as a tool
|
|
350
|
+
shows only its result; frontend tools in `RunAgentInput.tools` are rejected
|
|
351
|
+
(`400`); one text part per user message; earlier messages cannot be edited or
|
|
352
|
+
regenerated. Browsers always go through your server: never let one call the
|
|
353
|
+
Runtime directly.
|
|
354
|
+
|
|
355
|
+
## Acting for a person (app servers)
|
|
356
|
+
|
|
357
|
+
A server that signs people in and calls the Runtime for them (an "app server")
|
|
358
|
+
keeps the Tenant key to itself and names the person on each call:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { createClient } from "@nylorun/agents";
|
|
362
|
+
|
|
363
|
+
const app = createClient(); // the Tenant key, on the server only
|
|
364
|
+
|
|
365
|
+
// Per request, after your own sign-in:
|
|
366
|
+
const person = app.as(`app:${user.id}`, { scopes: ["sessions:own", "vaults:own"] });
|
|
367
|
+
await person.createSession({ agentId: "support", ownerUserId: `app:${user.id}` });
|
|
368
|
+
await person.listSessions(); // only this person's sessions
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`as()` sends `Nylorun-Subject` and `Nylorun-Scopes` on every call, event
|
|
372
|
+
streams included, and the Runtime (optional feature `subject-headers`)
|
|
373
|
+
enforces both: another person's sessions and vaults answer the same `404` as
|
|
374
|
+
missing ones, and a route outside the scopes answers `403` (`scope_required`).
|
|
375
|
+
Nothing is minted, cached or refreshed, and the copy shares the client's
|
|
376
|
+
compatibility check, so calling `as()` per request is cheap.
|
|
377
|
+
|
|
378
|
+
| Scope | Allows |
|
|
379
|
+
| --- | --- |
|
|
380
|
+
| `sessions:own` | The person's own sessions: create, list, read, stream, message, approve, respond, cancel |
|
|
381
|
+
| `vaults:own` | The person's own vaults and credentials |
|
|
382
|
+
| `agents:read` | Listing the Tenant's agents |
|
|
383
|
+
| `agents:write` | Saving agents; listing agents, models and providers |
|
|
384
|
+
| `tenant:settings` | The Tenant's status, model provider and sandbox settings |
|
|
385
|
+
|
|
386
|
+
No scope reaches Tenant reset, config seed, executors, actions or the sandbox
|
|
387
|
+
tool routes; call those without `as()`. A subject is 1–200 visible ASCII
|
|
388
|
+
characters (spaces only inside) and `host` is reserved. Your server must drop
|
|
389
|
+
any `Nylorun-*` header its own clients send, and only an application key can act
|
|
390
|
+
for a subject: an executor key that tries is `403`.
|
|
391
|
+
|
|
392
|
+
The SDK depends only on core within the Nylorun packages; installing it does not install harness. `/ag-ui` adds `@ag-ui/core`; nothing else imports it. Use `/define`, `/client`, `/executor` or `/ag-ui` for focused imports, or the root for convenience. Studio uses `/client`. See [the adopted host contract](../harness/HOST_CONTRACT.md).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BuiltWorkflow } from "@nylorun/core/define";
|
|
2
|
+
import { type SubjectScope } from "@nylorun/core/contracts";
|
|
3
|
+
import { type AgentSource, type AgentsClient } from "../client.js";
|
|
4
|
+
/** Per-session parameters the host may add, such as the person's vault for MCP servers. */
|
|
5
|
+
export interface AgUiSessionOptions {
|
|
6
|
+
/** Part of the session's identity: keep it stable for a subject and agent, or runs get 409. */
|
|
7
|
+
info?: Record<string, unknown>;
|
|
8
|
+
vaultIds?: readonly string[];
|
|
9
|
+
credentialSelections?: Parameters<AgentsClient["createSession"]>[0]["credentialSelections"];
|
|
10
|
+
}
|
|
11
|
+
export interface AgUiHandlerOptions {
|
|
12
|
+
/** Agents this endpoint may run: built agents, workflows or their ids. Nothing else is reachable. */
|
|
13
|
+
agents: readonly (AgentSource | BuiltWorkflow | string)[];
|
|
14
|
+
/** The signed-in person the request is for. `undefined` answers 401; there is no anonymous mode. */
|
|
15
|
+
subject(request: Request): string | undefined | Promise<string | undefined>;
|
|
16
|
+
/** Where `fetch` is mounted, e.g. `/api/agui`. Default `/`. */
|
|
17
|
+
basePath?: string;
|
|
18
|
+
/** Application client. Default: `createClient()`, from the environment or the Project link. */
|
|
19
|
+
client?: AgentsClient | Promise<AgentsClient>;
|
|
20
|
+
/**
|
|
21
|
+
* What each subject may do through this endpoint. Default `["sessions:own"]`; add
|
|
22
|
+
* `vaults:own` if `session()` attaches the person's vaults.
|
|
23
|
+
*/
|
|
24
|
+
scopes?: readonly SubjectScope[];
|
|
25
|
+
/** Optional per-session parameters, e.g. the person's vault for connected accounts. */
|
|
26
|
+
session?(subject: string, agentId: string): AgUiSessionOptions | Promise<AgUiSessionOptions>;
|
|
27
|
+
}
|
|
28
|
+
export interface AgUiHandler {
|
|
29
|
+
/** One entry point that routes by method and path under `basePath`. Bound. */
|
|
30
|
+
readonly fetch: (request: Request) => Promise<Response>;
|
|
31
|
+
/** `POST`: an AG-UI `RunAgentInput` in, the run's events out as server-sent events. */
|
|
32
|
+
run(request: Request, agentId: string): Promise<Response>;
|
|
33
|
+
/** `GET`: the thread's messages as a plain AG-UI `Message[]`. */
|
|
34
|
+
history(request: Request, agentId: string, threadId: string): Promise<Response>;
|
|
35
|
+
/** `GET`: the rest of a run after a dropped connection, from `Last-Event-ID` or `?cursor=`. */
|
|
36
|
+
reattach(request: Request, agentId: string, threadId: string): Promise<Response>;
|
|
37
|
+
/** `POST`: cancels the thread's running turn. */
|
|
38
|
+
cancel(request: Request, agentId: string, threadId: string): Promise<Response>;
|
|
39
|
+
}
|
|
40
|
+
/** A session per subject, agent and thread; one subject can never name another's session. */
|
|
41
|
+
export declare function sessionIdFor(subject: string, agentId: string, threadId: string): string;
|
|
42
|
+
export declare function createAgUiHandler(options: AgUiHandlerOptions): AgUiHandler;
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An AG-UI endpoint over the Tenant's agents, as web-standard `Request` → `Response`
|
|
3
|
+
* handlers. The host authenticates people (`subject`); the handler maps each AG-UI thread to
|
|
4
|
+
* one session per subject, agent and thread, and streams the session's transcript events as
|
|
5
|
+
* AG-UI events. It never trusts a session id from the client, and it calls the Runtime as the
|
|
6
|
+
* subject (`client.as`), so the Runtime itself keeps one subject out of another's sessions.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
9
|
+
import { EventType } from "@ag-ui/core";
|
|
10
|
+
import { RunAgentInputSchema } from "@ag-ui/core/schemas";
|
|
11
|
+
import { parseSubjectHeaders, } from "@nylorun/core/contracts";
|
|
12
|
+
import { createClient, } from "../client.js";
|
|
13
|
+
import { IncompatibleRuntimeError, RuntimeError } from "../http.js";
|
|
14
|
+
import { messagesFromEvents } from "./history.js";
|
|
15
|
+
import { SSE_CONTENT_TYPE, SSE_HEARTBEAT, sseFrame } from "./sse.js";
|
|
16
|
+
import { RunTranslator } from "./translate.js";
|
|
17
|
+
const HEARTBEAT_MS = 15_000;
|
|
18
|
+
/** Host features the handler needs: the chat transcript, and acting for each subject. */
|
|
19
|
+
const REQUIRED_FEATURES = ["transcript-events", "subject-headers"];
|
|
20
|
+
const DEFAULT_SCOPES = ["sessions:own"];
|
|
21
|
+
const TERMINAL = new Set([
|
|
22
|
+
"turn.completed",
|
|
23
|
+
"turn.paused",
|
|
24
|
+
"turn.failed",
|
|
25
|
+
"turn.cancelled",
|
|
26
|
+
]);
|
|
27
|
+
class Problem extends Error {
|
|
28
|
+
status;
|
|
29
|
+
code;
|
|
30
|
+
constructor(status, message, code) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.status = status;
|
|
33
|
+
this.code = code;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function problem(status, message, code) {
|
|
37
|
+
return Response.json({ error: message, ...(code ? { code } : {}) }, { status });
|
|
38
|
+
}
|
|
39
|
+
function problemOf(error) {
|
|
40
|
+
if (error instanceof Problem)
|
|
41
|
+
return error;
|
|
42
|
+
if (error instanceof RuntimeError) {
|
|
43
|
+
if (error.status === 404)
|
|
44
|
+
return new Problem(404, "Not found");
|
|
45
|
+
// The Runtime refused what the client sent (e.g. a cursor it cannot read).
|
|
46
|
+
if (error.status === 400)
|
|
47
|
+
return new Problem(400, "The request was rejected", "invalid_request");
|
|
48
|
+
if (error.status === 409)
|
|
49
|
+
return new Problem(409, "The agent is busy or waiting for an answer to an open interrupt", "session_busy");
|
|
50
|
+
return new Problem(502, "The agent service failed", "runtime_error");
|
|
51
|
+
}
|
|
52
|
+
if (error instanceof IncompatibleRuntimeError)
|
|
53
|
+
return new Problem(502, error.message, "runtime_incompatible");
|
|
54
|
+
return new Problem(500, "Internal error");
|
|
55
|
+
}
|
|
56
|
+
/** A session per subject, agent and thread; one subject can never name another's session. */
|
|
57
|
+
export function sessionIdFor(subject, agentId, threadId) {
|
|
58
|
+
return createHash("sha256")
|
|
59
|
+
.update(`${subject}\u0000${agentId}\u0000${threadId}`)
|
|
60
|
+
.digest("hex")
|
|
61
|
+
.slice(0, 32);
|
|
62
|
+
}
|
|
63
|
+
/** The trailing user message: the Runtime keeps the transcript, AG-UI resends all of it. */
|
|
64
|
+
function lastUserMessage(messages) {
|
|
65
|
+
const last = messages.at(-1);
|
|
66
|
+
if (!last || last.role !== "user")
|
|
67
|
+
throw new Problem(400, "The run's last message must be a user message");
|
|
68
|
+
const text = typeof last.content === "string"
|
|
69
|
+
? last.content
|
|
70
|
+
: last.content
|
|
71
|
+
.filter((part) => part.type === "text")
|
|
72
|
+
.map((part) => part.text)
|
|
73
|
+
.join("\n");
|
|
74
|
+
if (!text.trim())
|
|
75
|
+
throw new Problem(400, "The user message has no text");
|
|
76
|
+
return { id: last.id, text };
|
|
77
|
+
}
|
|
78
|
+
function normalizeBase(basePath = "/") {
|
|
79
|
+
const trimmed = basePath.replace(/\/+$/, "");
|
|
80
|
+
return trimmed === "" ? "" : trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
81
|
+
}
|
|
82
|
+
export function createAgUiHandler(options) {
|
|
83
|
+
const allowed = new Set(options.agents.map((agent) => (typeof agent === "string" ? agent : agent.id)));
|
|
84
|
+
const base = normalizeBase(options.basePath);
|
|
85
|
+
const scopes = options.scopes ?? DEFAULT_SCOPES;
|
|
86
|
+
const checked = parseSubjectHeaders("probe", scopes.join(" "));
|
|
87
|
+
if (!checked.ok)
|
|
88
|
+
throw new TypeError(`createAgUiHandler: ${checked.message}`);
|
|
89
|
+
let ready;
|
|
90
|
+
/** The client, once the Runtime is known to have every required feature. Retries after a failure. */
|
|
91
|
+
function connected() {
|
|
92
|
+
ready ??= (async () => {
|
|
93
|
+
const client = await (options.client ?? createClient());
|
|
94
|
+
const features = await client.hostFeatures();
|
|
95
|
+
const missing = REQUIRED_FEATURES.filter((f) => !features.includes(f));
|
|
96
|
+
if (missing.length > 0)
|
|
97
|
+
throw new Problem(502, `The Runtime does not support ${missing.join(", ")}; upgrade the stack (nylorun up).`, "runtime_feature_missing");
|
|
98
|
+
return client;
|
|
99
|
+
})();
|
|
100
|
+
ready.catch(() => {
|
|
101
|
+
ready = undefined;
|
|
102
|
+
});
|
|
103
|
+
return ready;
|
|
104
|
+
}
|
|
105
|
+
/** The client acting for `subject`: the Runtime limits it to the subject's own sessions. */
|
|
106
|
+
async function clientFor(subject) {
|
|
107
|
+
const client = await connected();
|
|
108
|
+
try {
|
|
109
|
+
return client.as(subject, { scopes });
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// The host's `subject` returned something the Runtime cannot name (see `as`).
|
|
113
|
+
throw new Problem(500, error.message, "subject_invalid");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function subjectOf(request) {
|
|
117
|
+
const subject = await options.subject(request);
|
|
118
|
+
if (!subject)
|
|
119
|
+
throw new Problem(401, "Sign in required");
|
|
120
|
+
return subject;
|
|
121
|
+
}
|
|
122
|
+
function assertAgent(agentId) {
|
|
123
|
+
if (!allowed.has(agentId))
|
|
124
|
+
throw new Problem(404, "Not found");
|
|
125
|
+
}
|
|
126
|
+
async function sessionFor(request, agentId, threadId) {
|
|
127
|
+
assertAgent(agentId);
|
|
128
|
+
const subject = await subjectOf(request);
|
|
129
|
+
const client = await clientFor(subject);
|
|
130
|
+
return {
|
|
131
|
+
client,
|
|
132
|
+
subject,
|
|
133
|
+
session: client.session(sessionIdFor(subject, agentId, threadId)),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async function submit(session, input) {
|
|
137
|
+
if (input.resume?.length) {
|
|
138
|
+
let cursor = null;
|
|
139
|
+
// The interaction's kind is on the pending view, not in its id.
|
|
140
|
+
const pending = await session.pending();
|
|
141
|
+
const waits = Array.isArray(pending) ? pending : [];
|
|
142
|
+
for (const entry of input.resume) {
|
|
143
|
+
const wait = waits.find((w) => w?.interaction?.id === entry.interruptId);
|
|
144
|
+
const key = `${input.runId}:${entry.interruptId}`;
|
|
145
|
+
const payload = entry.payload;
|
|
146
|
+
const accepted = (wait?.interaction?.kind ?? "approval") === "approval"
|
|
147
|
+
? await session.approve(entry.interruptId, entry.status === "resolved" &&
|
|
148
|
+
(payload === true ||
|
|
149
|
+
(typeof payload === "object" && payload?.approved === true)), { idempotencyKey: key })
|
|
150
|
+
: entry.status === "cancelled"
|
|
151
|
+
? await session.cancel({ idempotencyKey: key, reason: "interrupt cancelled" })
|
|
152
|
+
: await session.respond(entry.interruptId, (entry.payload ?? null), {
|
|
153
|
+
idempotencyKey: key,
|
|
154
|
+
});
|
|
155
|
+
cursor ??= accepted.cursor;
|
|
156
|
+
}
|
|
157
|
+
return cursor;
|
|
158
|
+
}
|
|
159
|
+
const message = lastUserMessage(input.messages);
|
|
160
|
+
// The AG-UI message id is the idempotency key: a retried run replays the same turn,
|
|
161
|
+
// and history gives the message back its client id.
|
|
162
|
+
const accepted = await session.input(message.text, {
|
|
163
|
+
idempotencyKey: message.id,
|
|
164
|
+
});
|
|
165
|
+
return accepted.cursor;
|
|
166
|
+
}
|
|
167
|
+
/** Streams translated events until the run ends. Closing the response stops the read only. */
|
|
168
|
+
function stream(request, translator, source, preface = []) {
|
|
169
|
+
const reading = new AbortController();
|
|
170
|
+
request.signal.addEventListener("abort", () => reading.abort(), { once: true });
|
|
171
|
+
const bytes = new TextEncoder();
|
|
172
|
+
const body = new ReadableStream({
|
|
173
|
+
async start(controller) {
|
|
174
|
+
const write = (text) => {
|
|
175
|
+
try {
|
|
176
|
+
controller.enqueue(bytes.encode(text));
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
reading.abort();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const heartbeat = setInterval(() => write(SSE_HEARTBEAT), HEARTBEAT_MS);
|
|
183
|
+
try {
|
|
184
|
+
write(sseFrame(translator.started()));
|
|
185
|
+
for (const event of preface)
|
|
186
|
+
write(sseFrame(event));
|
|
187
|
+
if (source)
|
|
188
|
+
for await (const event of source(reading.signal)) {
|
|
189
|
+
const step = translator.translate(event);
|
|
190
|
+
step.events.forEach((out, index) => write(sseFrame(out, index === step.events.length - 1 ? event.cursor : undefined)));
|
|
191
|
+
if (step.finished || reading.signal.aborted)
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
if (!reading.signal.aborted) {
|
|
197
|
+
const failure = problemOf(error);
|
|
198
|
+
write(sseFrame({
|
|
199
|
+
type: EventType.RUN_ERROR,
|
|
200
|
+
message: failure.message,
|
|
201
|
+
...(failure.code ? { code: failure.code } : {}),
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
clearInterval(heartbeat);
|
|
207
|
+
reading.abort();
|
|
208
|
+
try {
|
|
209
|
+
controller.close();
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
/* the client is gone */
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
cancel() {
|
|
217
|
+
reading.abort();
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
return new Response(body, {
|
|
221
|
+
headers: {
|
|
222
|
+
"content-type": SSE_CONTENT_TYPE,
|
|
223
|
+
"cache-control": "no-cache, no-transform",
|
|
224
|
+
"x-accel-buffering": "no",
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
async function run(request, agentId) {
|
|
229
|
+
try {
|
|
230
|
+
assertAgent(agentId);
|
|
231
|
+
const subject = await subjectOf(request);
|
|
232
|
+
const parsed = RunAgentInputSchema.safeParse(await request.json().catch(() => undefined));
|
|
233
|
+
if (!parsed.success)
|
|
234
|
+
throw new Problem(400, "Invalid RunAgentInput");
|
|
235
|
+
const input = parsed.data;
|
|
236
|
+
// Nylorun tools run in the Runtime or the app's executor; the browser runs none.
|
|
237
|
+
if (input.tools?.length)
|
|
238
|
+
throw new Problem(400, "Frontend tools are not supported");
|
|
239
|
+
const client = await clientFor(subject);
|
|
240
|
+
const extra = (await options.session?.(subject, agentId)) ?? {};
|
|
241
|
+
const translator = new RunTranslator(input.threadId, input.runId);
|
|
242
|
+
let session;
|
|
243
|
+
try {
|
|
244
|
+
// The host's parameters first: they never replace the session's identity.
|
|
245
|
+
session = await client.createSession({
|
|
246
|
+
...extra,
|
|
247
|
+
id: sessionIdFor(subject, agentId, input.threadId),
|
|
248
|
+
agentId,
|
|
249
|
+
ownerUserId: subject,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (error instanceof RuntimeError && error.status === 409)
|
|
254
|
+
throw new Problem(409, "The thread's session was opened with other parameters", "session_conflict");
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
let cursor;
|
|
258
|
+
try {
|
|
259
|
+
cursor = await submit(session, input);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
const failure = problemOf(error);
|
|
263
|
+
// A busy or paused session is a run that could not start, not a transport failure.
|
|
264
|
+
if (failure.status !== 409)
|
|
265
|
+
throw failure;
|
|
266
|
+
return stream(request, translator, undefined, [
|
|
267
|
+
{
|
|
268
|
+
type: EventType.RUN_ERROR,
|
|
269
|
+
message: failure.message,
|
|
270
|
+
code: failure.code,
|
|
271
|
+
},
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
274
|
+
return stream(request, translator, (signal) => session.observe({ ...(cursor ? { cursor } : {}), signal }));
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
const failure = problemOf(error);
|
|
278
|
+
return problem(failure.status, failure.message, failure.code);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function history(request, agentId, threadId) {
|
|
282
|
+
try {
|
|
283
|
+
const { session } = await sessionFor(request, agentId, threadId);
|
|
284
|
+
const items = [];
|
|
285
|
+
let cursor;
|
|
286
|
+
try {
|
|
287
|
+
for (;;) {
|
|
288
|
+
const page = await session.history({ ...(cursor ? { cursor } : {}) });
|
|
289
|
+
items.push(...page.items);
|
|
290
|
+
if (!page.cursor || page.cursor === cursor || page.items.length === 0)
|
|
291
|
+
break;
|
|
292
|
+
cursor = page.cursor;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
// A thread that never ran has no session yet.
|
|
297
|
+
if (error instanceof RuntimeError && error.status === 404)
|
|
298
|
+
return Response.json([]);
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
return Response.json(messagesFromEvents(items));
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
const failure = problemOf(error);
|
|
305
|
+
return problem(failure.status, failure.message, failure.code);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function reattach(request, agentId, threadId) {
|
|
309
|
+
try {
|
|
310
|
+
const { session } = await sessionFor(request, agentId, threadId);
|
|
311
|
+
const url = new URL(request.url);
|
|
312
|
+
const cursor = request.headers.get("last-event-id") ?? url.searchParams.get("cursor");
|
|
313
|
+
if (!cursor)
|
|
314
|
+
throw new Problem(400, "Last-Event-ID or ?cursor= is required");
|
|
315
|
+
const view = await session.inspect();
|
|
316
|
+
const runId = url.searchParams.get("runId") ??
|
|
317
|
+
view.activeTurnId ??
|
|
318
|
+
(typeof view.lastTurnId === "string" ? view.lastTurnId : randomUUID());
|
|
319
|
+
const translator = new RunTranslator(threadId, runId, { reattached: true });
|
|
320
|
+
if (view.activeTurnId)
|
|
321
|
+
return stream(request, translator, (signal) => session.observe({ cursor, signal }));
|
|
322
|
+
// No turn is running: send what the client missed, if the run ended after the cursor.
|
|
323
|
+
const missed = [];
|
|
324
|
+
let from = cursor;
|
|
325
|
+
for (;;) {
|
|
326
|
+
const page = await session.history({ cursor: from });
|
|
327
|
+
missed.push(...page.items);
|
|
328
|
+
if (!page.cursor || page.cursor === from || page.items.length === 0)
|
|
329
|
+
break;
|
|
330
|
+
from = page.cursor;
|
|
331
|
+
}
|
|
332
|
+
if (!missed.some((event) => TERMINAL.has(event.type)))
|
|
333
|
+
return new Response(null, { status: 204 });
|
|
334
|
+
return stream(request, translator, () => iterate(missed));
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
const failure = problemOf(error);
|
|
338
|
+
return problem(failure.status, failure.message, failure.code);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function cancel(request, agentId, threadId) {
|
|
342
|
+
try {
|
|
343
|
+
const { session } = await sessionFor(request, agentId, threadId);
|
|
344
|
+
await session.cancel({ idempotencyKey: `cancel:${randomUUID()}` });
|
|
345
|
+
return new Response(null, { status: 204 });
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
const failure = problemOf(error);
|
|
349
|
+
return problem(failure.status, failure.message, failure.code);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async function route(request) {
|
|
353
|
+
const path = new URL(request.url).pathname;
|
|
354
|
+
if (path !== base && !path.startsWith(`${base}/`))
|
|
355
|
+
return problem(404, "Not found");
|
|
356
|
+
let segments;
|
|
357
|
+
try {
|
|
358
|
+
segments = path
|
|
359
|
+
.slice(base.length)
|
|
360
|
+
.split("/")
|
|
361
|
+
.slice(1)
|
|
362
|
+
.map((segment) => decodeURIComponent(segment));
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return problem(404, "Not found");
|
|
366
|
+
}
|
|
367
|
+
if (segments.some((segment) => segment === ""))
|
|
368
|
+
return problem(404, "Not found");
|
|
369
|
+
const [agentId, threads, threadId, action] = segments;
|
|
370
|
+
const method = request.method.toUpperCase();
|
|
371
|
+
const operation = (allow, handle) => {
|
|
372
|
+
if (!agentId || !allowed.has(agentId))
|
|
373
|
+
return problem(404, "Not found");
|
|
374
|
+
if (method !== allow)
|
|
375
|
+
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
376
|
+
status: 405,
|
|
377
|
+
headers: { allow, "content-type": "application/json" },
|
|
378
|
+
});
|
|
379
|
+
return handle();
|
|
380
|
+
};
|
|
381
|
+
if (segments.length === 1)
|
|
382
|
+
return operation("POST", () => run(request, agentId));
|
|
383
|
+
if (segments.length === 4 && threads === "threads") {
|
|
384
|
+
if (action === "messages")
|
|
385
|
+
return operation("GET", () => history(request, agentId, threadId));
|
|
386
|
+
if (action === "events")
|
|
387
|
+
return operation("GET", () => reattach(request, agentId, threadId));
|
|
388
|
+
if (action === "cancel")
|
|
389
|
+
return operation("POST", () => cancel(request, agentId, threadId));
|
|
390
|
+
}
|
|
391
|
+
return problem(404, "Not found");
|
|
392
|
+
}
|
|
393
|
+
return { fetch: route, run, history, reattach, cancel };
|
|
394
|
+
}
|
|
395
|
+
async function* iterate(items) {
|
|
396
|
+
yield* items;
|
|
397
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An AG-UI transcript rebuilt from a session's log, with the ids a live run gave each
|
|
3
|
+
* message (see `translate.ts`), usable as `HttpAgent`'s `initialMessages`.
|
|
4
|
+
*/
|
|
5
|
+
import type { Message } from "@ag-ui/core";
|
|
6
|
+
import { type LiveEvent } from "@nylorun/core/contracts";
|
|
7
|
+
export declare function messagesFromEvents(items: readonly LiveEvent[]): Message[];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { parseTranscriptEvent } from "@nylorun/core/contracts";
|
|
2
|
+
import { outputText, toolResultOf } from "./translate.js";
|
|
3
|
+
export function messagesFromEvents(items) {
|
|
4
|
+
const messages = [];
|
|
5
|
+
const results = new Set();
|
|
6
|
+
let sawAssistant = false;
|
|
7
|
+
for (const raw of items) {
|
|
8
|
+
if (raw.type === "command.message") {
|
|
9
|
+
const p = (raw.payload ?? {});
|
|
10
|
+
messages.push({
|
|
11
|
+
// The handler sends the client's message id as the idempotency key.
|
|
12
|
+
id: p.idempotencyKey ?? raw.eventId,
|
|
13
|
+
role: "user",
|
|
14
|
+
content: typeof p.content === "string" ? p.content : JSON.stringify(p.data),
|
|
15
|
+
});
|
|
16
|
+
sawAssistant = false;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const event = parseTranscriptEvent(raw);
|
|
20
|
+
if (!event)
|
|
21
|
+
continue;
|
|
22
|
+
const result = toolResultOf(event);
|
|
23
|
+
if (result) {
|
|
24
|
+
if (results.has(result.callId))
|
|
25
|
+
continue;
|
|
26
|
+
results.add(result.callId);
|
|
27
|
+
messages.push({
|
|
28
|
+
id: `${result.callId}:result`,
|
|
29
|
+
role: "tool",
|
|
30
|
+
toolCallId: result.callId,
|
|
31
|
+
content: result.content,
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (event.type === "message.assistant" && !event.payload.agent) {
|
|
36
|
+
const p = event.payload;
|
|
37
|
+
sawAssistant = true;
|
|
38
|
+
const message = { id: p.invocationId, role: "assistant" };
|
|
39
|
+
if (p.text)
|
|
40
|
+
message.content = p.text;
|
|
41
|
+
if (p.toolCalls.length)
|
|
42
|
+
message.toolCalls = p.toolCalls.map((call) => ({
|
|
43
|
+
id: call.callId,
|
|
44
|
+
type: "function",
|
|
45
|
+
function: { name: call.name, arguments: JSON.stringify(call.input ?? {}) },
|
|
46
|
+
}));
|
|
47
|
+
messages.push(message);
|
|
48
|
+
}
|
|
49
|
+
else if (event.type === "turn.completed" && !sawAssistant) {
|
|
50
|
+
const output = event.payload.output;
|
|
51
|
+
if (output !== undefined && output !== null && outputText(output))
|
|
52
|
+
messages.push({
|
|
53
|
+
id: raw.eventId,
|
|
54
|
+
role: "assistant",
|
|
55
|
+
content: outputText(output),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return messages;
|
|
60
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nylorun/agents/ag-ui`: serve the Tenant's agents to AG-UI clients from the app's own
|
|
3
|
+
* server. The only entry point that loads `@ag-ui/core`.
|
|
4
|
+
*/
|
|
5
|
+
export { createAgUiHandler } from "./handler.js";
|
|
6
|
+
export type { AgUiHandler, AgUiHandlerOptions, AgUiSessionOptions, } from "./handler.js";
|
|
7
|
+
export { toNodeListener } from "./node.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** `node:http` and Express adapter for a fetch-style handler. */
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
export declare function toNodeListener(handler: {
|
|
4
|
+
readonly fetch: (request: Request) => Promise<Response>;
|
|
5
|
+
}): (req: IncomingMessage, res: ServerResponse) => void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
export function toNodeListener(handler) {
|
|
3
|
+
return (req, res) => {
|
|
4
|
+
void (async () => {
|
|
5
|
+
// A closed connection aborts the request, which stops the read, never the turn.
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
res.on("close", () => controller.abort());
|
|
8
|
+
const host = req.headers.host ?? "localhost";
|
|
9
|
+
const url = new URL(req.url ?? "/", `http://${host}`);
|
|
10
|
+
const headers = new Headers();
|
|
11
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
12
|
+
if (value === undefined)
|
|
13
|
+
continue;
|
|
14
|
+
for (const item of Array.isArray(value) ? value : [value])
|
|
15
|
+
headers.append(name, item);
|
|
16
|
+
}
|
|
17
|
+
const hasBody = req.method !== "GET" && req.method !== "HEAD";
|
|
18
|
+
const request = new Request(url, {
|
|
19
|
+
method: req.method,
|
|
20
|
+
headers,
|
|
21
|
+
signal: controller.signal,
|
|
22
|
+
...(hasBody
|
|
23
|
+
? {
|
|
24
|
+
body: Readable.toWeb(req),
|
|
25
|
+
duplex: "half",
|
|
26
|
+
}
|
|
27
|
+
: {}),
|
|
28
|
+
});
|
|
29
|
+
let response;
|
|
30
|
+
try {
|
|
31
|
+
response = await handler.fetch(request);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
response = new Response(JSON.stringify({ error: "Internal error" }), {
|
|
35
|
+
status: 500,
|
|
36
|
+
headers: { "content-type": "application/json" },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
res.writeHead(response.status, Object.fromEntries(response.headers));
|
|
40
|
+
if (!response.body) {
|
|
41
|
+
res.end();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
res.flushHeaders();
|
|
45
|
+
const body = Readable.fromWeb(response.body);
|
|
46
|
+
body.on("error", () => res.destroy());
|
|
47
|
+
res.on("close", () => body.destroy());
|
|
48
|
+
body.pipe(res);
|
|
49
|
+
})();
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AG-UI over server-sent events: one `data:` line of JSON per event. The last AG-UI event
|
|
3
|
+
* made from a Runtime event carries that event's cursor as `id:`, so a client that resumes
|
|
4
|
+
* from `Last-Event-ID` never lands inside a group.
|
|
5
|
+
*/
|
|
6
|
+
import type { BaseEvent } from "@ag-ui/core";
|
|
7
|
+
export declare const SSE_CONTENT_TYPE = "text/event-stream";
|
|
8
|
+
export declare function sseFrame(event: BaseEvent, id?: string): string;
|
|
9
|
+
export declare const SSE_HEARTBEAT = ": keep-alive\n\n";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const SSE_CONTENT_TYPE = "text/event-stream";
|
|
2
|
+
export function sseFrame(event, id) {
|
|
3
|
+
// Cursors are opaque base64; a line break would end the field early.
|
|
4
|
+
const idLine = id && !/[\r\n]/.test(id) ? `id: ${id}\n` : "";
|
|
5
|
+
return `${idLine}data: ${JSON.stringify(event)}\n\n`;
|
|
6
|
+
}
|
|
7
|
+
export const SSE_HEARTBEAT = ": keep-alive\n\n";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime transcript events → AG-UI 1.0 events for one run. Ids: an assistant message is its
|
|
3
|
+
* model call's `invocationId`, a tool call is the model's `callId`, a tool result is
|
|
4
|
+
* `<callId>:result`, so live runs and `messagesFromEvents` agree.
|
|
5
|
+
*/
|
|
6
|
+
import { type BaseEvent } from "@ag-ui/core";
|
|
7
|
+
import { type LiveEvent, type TranscriptEvent } from "@nylorun/core/contracts";
|
|
8
|
+
/** The text a tool result shows; `undefined` while there is no result yet. */
|
|
9
|
+
export declare function toolResultText(result: unknown): string | undefined;
|
|
10
|
+
export declare function outputText(output: unknown): string;
|
|
11
|
+
/** One Runtime event's result for a run: AG-UI events, and whether the run ended. */
|
|
12
|
+
export interface Step {
|
|
13
|
+
readonly events: BaseEvent[];
|
|
14
|
+
readonly finished: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** A tool result from any of the three places a call can end. */
|
|
17
|
+
export declare function toolResultOf(event: TranscriptEvent): {
|
|
18
|
+
callId: string;
|
|
19
|
+
content: string;
|
|
20
|
+
} | undefined;
|
|
21
|
+
export declare class RunTranslator {
|
|
22
|
+
private readonly threadId;
|
|
23
|
+
private readonly runId;
|
|
24
|
+
/** Tool invocation → the model's call id, for interrupts that name the invocation. */
|
|
25
|
+
private readonly callIds;
|
|
26
|
+
private readonly closed;
|
|
27
|
+
private sawAssistant;
|
|
28
|
+
/**
|
|
29
|
+
* `reattached`: the run continues from a cursor, so earlier assistant messages already
|
|
30
|
+
* reached the client and a workflow's output is not repeated as text.
|
|
31
|
+
*/
|
|
32
|
+
constructor(threadId: string, runId: string, options?: {
|
|
33
|
+
reattached?: boolean;
|
|
34
|
+
});
|
|
35
|
+
translate(raw: LiveEvent): Step;
|
|
36
|
+
started(): BaseEvent;
|
|
37
|
+
private finished;
|
|
38
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime transcript events → AG-UI 1.0 events for one run. Ids: an assistant message is its
|
|
3
|
+
* model call's `invocationId`, a tool call is the model's `callId`, a tool result is
|
|
4
|
+
* `<callId>:result`, so live runs and `messagesFromEvents` agree.
|
|
5
|
+
*/
|
|
6
|
+
import { EventType } from "@ag-ui/core";
|
|
7
|
+
import { parseTranscriptEvent, } from "@nylorun/core/contracts";
|
|
8
|
+
/** The text a tool result shows; `undefined` while there is no result yet. */
|
|
9
|
+
export function toolResultText(result) {
|
|
10
|
+
const value = result;
|
|
11
|
+
switch (value?.kind) {
|
|
12
|
+
case "completed":
|
|
13
|
+
return outputText(value.output ?? null);
|
|
14
|
+
case "failed":
|
|
15
|
+
return JSON.stringify({ error: value.code, message: value.message });
|
|
16
|
+
case "denied":
|
|
17
|
+
return JSON.stringify({ denied: true, reason: value.reason });
|
|
18
|
+
default:
|
|
19
|
+
// interaction-required and deferred: the call is still open.
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function outputText(output) {
|
|
24
|
+
return typeof output === "string" ? output : JSON.stringify(output);
|
|
25
|
+
}
|
|
26
|
+
const none = { events: [], finished: false };
|
|
27
|
+
/** A tool result from any of the three places a call can end. */
|
|
28
|
+
export function toolResultOf(event) {
|
|
29
|
+
switch (event.type) {
|
|
30
|
+
case "action.completed": {
|
|
31
|
+
const p = event.payload;
|
|
32
|
+
if (p.kind !== "tool" || !p.callId || p.agent)
|
|
33
|
+
return undefined;
|
|
34
|
+
const content = toolResultText(p.result);
|
|
35
|
+
return content === undefined ? undefined : { callId: p.callId, content };
|
|
36
|
+
}
|
|
37
|
+
case "tool.completed": {
|
|
38
|
+
const p = event.payload;
|
|
39
|
+
if (p.agent)
|
|
40
|
+
return undefined;
|
|
41
|
+
return {
|
|
42
|
+
callId: p.callId,
|
|
43
|
+
content: p.error !== undefined
|
|
44
|
+
? JSON.stringify({ error: p.error.code, message: p.error.message })
|
|
45
|
+
: outputText(p.output ?? null),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
case "delegation.completed": {
|
|
49
|
+
// An agent used as a tool: its own work stays inside it; the parent sees the result.
|
|
50
|
+
const p = event.payload;
|
|
51
|
+
if (!p.callId)
|
|
52
|
+
return undefined;
|
|
53
|
+
const content = toolResultText(p.outcome);
|
|
54
|
+
return content === undefined ? undefined : { callId: p.callId, content };
|
|
55
|
+
}
|
|
56
|
+
default:
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class RunTranslator {
|
|
61
|
+
threadId;
|
|
62
|
+
runId;
|
|
63
|
+
/** Tool invocation → the model's call id, for interrupts that name the invocation. */
|
|
64
|
+
callIds = new Map();
|
|
65
|
+
closed = new Set();
|
|
66
|
+
sawAssistant;
|
|
67
|
+
/**
|
|
68
|
+
* `reattached`: the run continues from a cursor, so earlier assistant messages already
|
|
69
|
+
* reached the client and a workflow's output is not repeated as text.
|
|
70
|
+
*/
|
|
71
|
+
constructor(threadId, runId, options = {}) {
|
|
72
|
+
this.threadId = threadId;
|
|
73
|
+
this.runId = runId;
|
|
74
|
+
this.sawAssistant = options.reattached ?? false;
|
|
75
|
+
}
|
|
76
|
+
translate(raw) {
|
|
77
|
+
const event = parseTranscriptEvent(raw);
|
|
78
|
+
if (!event)
|
|
79
|
+
return none;
|
|
80
|
+
if (event.type === "action.pending" || event.type === "action.completed") {
|
|
81
|
+
const { callId, invocationId } = event.payload;
|
|
82
|
+
if (callId && invocationId)
|
|
83
|
+
this.callIds.set(invocationId, callId);
|
|
84
|
+
}
|
|
85
|
+
const result = toolResultOf(event);
|
|
86
|
+
if (result) {
|
|
87
|
+
if (this.closed.has(result.callId))
|
|
88
|
+
return none;
|
|
89
|
+
this.closed.add(result.callId);
|
|
90
|
+
return {
|
|
91
|
+
events: [
|
|
92
|
+
{
|
|
93
|
+
type: EventType.TOOL_CALL_RESULT,
|
|
94
|
+
messageId: `${result.callId}:result`,
|
|
95
|
+
toolCallId: result.callId,
|
|
96
|
+
content: result.content,
|
|
97
|
+
role: "tool",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
finished: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
switch (event.type) {
|
|
104
|
+
case "message.assistant": {
|
|
105
|
+
const p = event.payload;
|
|
106
|
+
if (p.agent)
|
|
107
|
+
return none;
|
|
108
|
+
this.sawAssistant = true;
|
|
109
|
+
return { events: assistantEvents(p), finished: false };
|
|
110
|
+
}
|
|
111
|
+
case "turn.completed": {
|
|
112
|
+
const p = event.payload;
|
|
113
|
+
// Workflows finish without a model step of their own: show their output.
|
|
114
|
+
const text = this.sawAssistant || p.output === undefined || p.output === null
|
|
115
|
+
? []
|
|
116
|
+
: textEvents(raw.eventId, outputText(p.output));
|
|
117
|
+
return {
|
|
118
|
+
events: [...text, this.finished({ type: "success" }, p.output)],
|
|
119
|
+
finished: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
case "turn.paused": {
|
|
123
|
+
const interrupts = event.payload.interactions.map((w) => {
|
|
124
|
+
const interaction = w.interaction;
|
|
125
|
+
const toolCallId = this.callIds.get(w.invocationId);
|
|
126
|
+
return {
|
|
127
|
+
id: interaction.id,
|
|
128
|
+
reason: interaction.kind === "approval" ? "tool_approval" : "input_required",
|
|
129
|
+
...(typeof interaction.prompt === "string"
|
|
130
|
+
? { message: interaction.prompt }
|
|
131
|
+
: {}),
|
|
132
|
+
...(toolCallId ? { toolCallId } : {}),
|
|
133
|
+
...(interaction.kind === "approval"
|
|
134
|
+
? {
|
|
135
|
+
responseSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: { approved: { type: "boolean" } },
|
|
138
|
+
required: ["approved"],
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
142
|
+
metadata: { nylorun: { kind: interaction.kind } },
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
events: [this.finished({ type: "interrupt", interrupts })],
|
|
147
|
+
finished: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
case "turn.cancelled":
|
|
151
|
+
return { events: [this.finished({ type: "cancelled" })], finished: true };
|
|
152
|
+
case "turn.failed": {
|
|
153
|
+
const p = event.payload;
|
|
154
|
+
return {
|
|
155
|
+
events: [
|
|
156
|
+
{
|
|
157
|
+
type: EventType.RUN_ERROR,
|
|
158
|
+
message: p.error?.message ?? p.message ?? "The agent failed",
|
|
159
|
+
code: p.error?.code ?? "turn.failed",
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
finished: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
case "effect.uncertain":
|
|
166
|
+
case "action.uncertain":
|
|
167
|
+
// The Runtime cannot tell whether a side effect happened; the run goes on.
|
|
168
|
+
return {
|
|
169
|
+
events: [
|
|
170
|
+
{
|
|
171
|
+
type: EventType.CUSTOM,
|
|
172
|
+
name: "nylorun.uncertain",
|
|
173
|
+
value: event.payload,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
finished: false,
|
|
177
|
+
};
|
|
178
|
+
default:
|
|
179
|
+
return none;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
started() {
|
|
183
|
+
return {
|
|
184
|
+
type: EventType.RUN_STARTED,
|
|
185
|
+
threadId: this.threadId,
|
|
186
|
+
runId: this.runId,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
finished(outcome, result) {
|
|
190
|
+
return {
|
|
191
|
+
type: EventType.RUN_FINISHED,
|
|
192
|
+
threadId: this.threadId,
|
|
193
|
+
runId: this.runId,
|
|
194
|
+
outcome,
|
|
195
|
+
...(result === undefined ? {} : { result }),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function textEvents(messageId, text) {
|
|
200
|
+
if (!text)
|
|
201
|
+
return [];
|
|
202
|
+
return [
|
|
203
|
+
{ type: EventType.TEXT_MESSAGE_START, messageId, role: "assistant" },
|
|
204
|
+
{ type: EventType.TEXT_MESSAGE_CONTENT, messageId, delta: text },
|
|
205
|
+
{ type: EventType.TEXT_MESSAGE_END, messageId },
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
function assistantEvents(p) {
|
|
209
|
+
const events = textEvents(p.invocationId, p.text);
|
|
210
|
+
for (const call of p.toolCalls)
|
|
211
|
+
events.push({
|
|
212
|
+
type: EventType.TOOL_CALL_START,
|
|
213
|
+
toolCallId: call.callId,
|
|
214
|
+
toolCallName: call.name,
|
|
215
|
+
parentMessageId: p.invocationId,
|
|
216
|
+
}, {
|
|
217
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
218
|
+
toolCallId: call.callId,
|
|
219
|
+
delta: JSON.stringify(call.input ?? {}),
|
|
220
|
+
}, { type: EventType.TOOL_CALL_END, toolCallId: call.callId });
|
|
221
|
+
return events;
|
|
222
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentManifest, BuiltAgent, BuiltWorkflow, JsonValue, SandboxToolName, WorkflowManifest } from "@nylorun/core/define";
|
|
2
|
-
import { type CredentialInfo, type CredentialSelection, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
|
|
2
|
+
import { type SubjectScope, type CredentialInfo, type CredentialSelection, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
|
|
3
3
|
import { Transport, type Destination } from "./http.js";
|
|
4
4
|
/** Application-principal access to a session's sandbox built-ins (workflows.md §8). */
|
|
5
5
|
export type SessionSandbox = {
|
|
@@ -22,9 +22,26 @@ export interface SessionView {
|
|
|
22
22
|
waits?: unknown;
|
|
23
23
|
[key: string]: unknown;
|
|
24
24
|
}
|
|
25
|
+
export interface ActAsOptions {
|
|
26
|
+
/** What the subject may do; the Runtime grants nothing else. Default `["sessions:own"]`. */
|
|
27
|
+
scopes?: readonly SubjectScope[];
|
|
28
|
+
}
|
|
25
29
|
export declare class AgentsClient {
|
|
26
30
|
readonly transport: Transport;
|
|
31
|
+
/** The subject this client acts for (`as`), or `undefined` for the principal itself. */
|
|
32
|
+
readonly subject: string | undefined;
|
|
27
33
|
constructor(destination?: Destination);
|
|
34
|
+
/**
|
|
35
|
+
* A client that acts for `subject`: every call sends `Nylorun-Subject` and
|
|
36
|
+
* `Nylorun-Scopes`, and the Runtime limits it to the scopes and to the subject's own sessions
|
|
37
|
+
* and vaults (Host feature `subject-headers`). For app servers, which authenticate the person
|
|
38
|
+
* themselves and hold the key; nothing is minted, cached or refreshed.
|
|
39
|
+
*/
|
|
40
|
+
as(subject: string, options?: ActAsOptions): AgentsClient;
|
|
41
|
+
/** The Runtime's protocol features, including optional ones such as `transcript-events`. */
|
|
42
|
+
hostFeatures(options?: {
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
}): Promise<readonly string[]>;
|
|
28
45
|
listAgents(options?: {
|
|
29
46
|
signal?: AbortSignal;
|
|
30
47
|
}): Promise<{
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SANDBOX_TOOL_NAMES, delegateOf, isBuiltWorkflow, isSandboxToolName, } from "@nylorun/core/define";
|
|
2
|
-
import {
|
|
2
|
+
import { SCOPES_HEADER, SUBJECT_HEADER } from "@nylorun/core/compatibility";
|
|
3
|
+
import { LiveEventSchema, SessionItemsResponseSchema, parseSubjectHeaders, } from "@nylorun/core/contracts";
|
|
3
4
|
import { resolveConnection } from "./connection.js";
|
|
4
5
|
import { Transport, id, segment } from "./http.js";
|
|
5
6
|
import { observeSSE } from "./sse.js";
|
|
@@ -57,8 +58,37 @@ export function assertNoMiddlewareClosures(agent) {
|
|
|
57
58
|
}
|
|
58
59
|
export class AgentsClient {
|
|
59
60
|
transport;
|
|
61
|
+
/** The subject this client acts for (`as`), or `undefined` for the principal itself. */
|
|
62
|
+
subject;
|
|
60
63
|
constructor(destination = {}) {
|
|
61
64
|
this.transport = new Transport(destination);
|
|
65
|
+
this.subject = undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A client that acts for `subject`: every call sends `Nylorun-Subject` and
|
|
69
|
+
* `Nylorun-Scopes`, and the Runtime limits it to the scopes and to the subject's own sessions
|
|
70
|
+
* and vaults (Host feature `subject-headers`). For app servers, which authenticate the person
|
|
71
|
+
* themselves and hold the key; nothing is minted, cached or refreshed.
|
|
72
|
+
*/
|
|
73
|
+
as(subject, options = {}) {
|
|
74
|
+
if (this.subject !== undefined)
|
|
75
|
+
throw new Error(`This client already acts for ${this.subject}`);
|
|
76
|
+
const scopes = [...(options.scopes ?? ["sessions:own"])].join(" ");
|
|
77
|
+
const parsed = parseSubjectHeaders(subject, scopes);
|
|
78
|
+
if (!parsed.ok)
|
|
79
|
+
throw new TypeError(parsed.message);
|
|
80
|
+
const client = Object.create(AgentsClient.prototype);
|
|
81
|
+
return Object.assign(client, {
|
|
82
|
+
transport: this.transport.withHeaders({
|
|
83
|
+
[SUBJECT_HEADER]: subject,
|
|
84
|
+
[SCOPES_HEADER]: [...parsed.scopes].join(" "),
|
|
85
|
+
}),
|
|
86
|
+
subject,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** The Runtime's protocol features, including optional ones such as `transcript-events`. */
|
|
90
|
+
hostFeatures(options = {}) {
|
|
91
|
+
return this.transport.hostFeatures(options.signal);
|
|
62
92
|
}
|
|
63
93
|
listAgents(options = {}) {
|
|
64
94
|
return this.transport.json("/v1/agents", "GET", undefined, options.signal);
|
package/dist/http.d.ts
CHANGED
|
@@ -26,11 +26,20 @@ export declare class Transport {
|
|
|
26
26
|
readonly key: string;
|
|
27
27
|
readonly tenant: string;
|
|
28
28
|
readonly fetcher: typeof fetch;
|
|
29
|
-
|
|
29
|
+
/** Sent on every request, e.g. `Nylorun-Subject` and `Nylorun-Scopes` (`withHeaders`). */
|
|
30
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
31
|
+
private readonly check;
|
|
30
32
|
constructor(options?: Destination, role?: "server" | "executor");
|
|
33
|
+
/**
|
|
34
|
+
* The same destination with `headers` added to every request. The copy shares this
|
|
35
|
+
* transport's compatibility check, so a copy made per request does not repeat `/health`.
|
|
36
|
+
*/
|
|
37
|
+
withHeaders(headers: Readonly<Record<string, string>>): Transport;
|
|
31
38
|
/** Clears the cached Host compatibility result (used after a 426). */
|
|
32
39
|
clearCompatibilityCache(): void;
|
|
33
40
|
private ensureCompatible;
|
|
41
|
+
/** The Host's protocol features, including optional ones, from its `/health`. */
|
|
42
|
+
hostFeatures(signal?: AbortSignal): Promise<readonly string[]>;
|
|
34
43
|
private authHeaders;
|
|
35
44
|
request(path: string, init?: RequestInit, options?: {
|
|
36
45
|
retried426?: boolean;
|
package/dist/http.js
CHANGED
|
@@ -57,7 +57,9 @@ export class Transport {
|
|
|
57
57
|
key;
|
|
58
58
|
tenant;
|
|
59
59
|
fetcher;
|
|
60
|
-
|
|
60
|
+
/** Sent on every request, e.g. `Nylorun-Subject` and `Nylorun-Scopes` (`withHeaders`). */
|
|
61
|
+
headers = {};
|
|
62
|
+
check = { compatible: false, features: [] };
|
|
61
63
|
constructor(options = {}, role = "server") {
|
|
62
64
|
const url = options.url ?? env("NYLORUN_RUNTIME_URL");
|
|
63
65
|
const key = options.key ??
|
|
@@ -75,12 +77,22 @@ export class Transport {
|
|
|
75
77
|
this.tenant = tenant;
|
|
76
78
|
this.fetcher = options.fetch ?? globalThis.fetch;
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The same destination with `headers` added to every request. The copy shares this
|
|
82
|
+
* transport's compatibility check, so a copy made per request does not repeat `/health`.
|
|
83
|
+
*/
|
|
84
|
+
withHeaders(headers) {
|
|
85
|
+
const copy = Object.create(Transport.prototype);
|
|
86
|
+
return Object.assign(copy, this, {
|
|
87
|
+
headers: Object.freeze({ ...this.headers, ...headers }),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
78
90
|
/** Clears the cached Host compatibility result (used after a 426). */
|
|
79
91
|
clearCompatibilityCache() {
|
|
80
|
-
this.compatible = false;
|
|
92
|
+
this.check.compatible = false;
|
|
81
93
|
}
|
|
82
94
|
async ensureCompatible(signal) {
|
|
83
|
-
if (this.compatible)
|
|
95
|
+
if (this.check.compatible)
|
|
84
96
|
return;
|
|
85
97
|
const response = await this.fetcher(`${this.url}/health`, {
|
|
86
98
|
method: "GET",
|
|
@@ -110,10 +122,18 @@ export class Transport {
|
|
|
110
122
|
const result = checkCompatibility({ version: PROTOCOL_VERSION, required: [...PROTOCOL_FEATURES] }, protocol);
|
|
111
123
|
if (!result.ok)
|
|
112
124
|
throw new IncompatibleRuntimeError(result);
|
|
113
|
-
this.
|
|
125
|
+
this.check.features = [...protocol.features];
|
|
126
|
+
this.check.compatible = true;
|
|
127
|
+
}
|
|
128
|
+
/** The Host's protocol features, including optional ones, from its `/health`. */
|
|
129
|
+
async hostFeatures(signal) {
|
|
130
|
+
await this.ensureCompatible(signal);
|
|
131
|
+
return this.check.features;
|
|
114
132
|
}
|
|
115
133
|
authHeaders(init = {}) {
|
|
116
134
|
const headers = new Headers(init.headers);
|
|
135
|
+
for (const [name, value] of Object.entries(this.headers))
|
|
136
|
+
headers.set(name, value);
|
|
117
137
|
headers.set("Authorization", `Bearer ${this.key}`);
|
|
118
138
|
headers.set(TENANT_HEADER, this.tenant);
|
|
119
139
|
headers.set(PROTOCOL_HEADER, String(PROTOCOL_VERSION));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
1
|
+
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
2
2
|
export type { ProtocolFeature, ProtocolRange, Compatibility, ErrorCode, } from "@nylorun/core/compatibility";
|
|
3
3
|
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError, Loop, Chain, Switch, Parallel, Map, VerdictSchema, isVerdict, withInstructions, withoutTools, WorkflowBuildError, isBuiltWorkflow, capability, tool, defineSchema, ToolError, } from "@nylorun/core/define";
|
|
4
4
|
export type { AgentOptions, AgentManifest, BuiltAgent, BuiltWorkflow, LoopOptions, LoopDecideArgs, LoopVerifyArgs, LoopDecision, ChainOptions, SwitchOptions, ParallelOptions, MapOptions, Verdict, WorkflowBinding, WorkflowManifest, ToolDefinition, ToolExecutionContext, ToolOutcome, Patch, Decision, TurnDecision, HookScope, BeforeHook, AfterHook, JsonValue, JsonObject, } from "@nylorun/core/define";
|
|
5
|
-
export type { LiveEvent, SessionCommand, Action, ActionOutcome, CredentialInfo, CredentialSelection, VaultInfo, } from "@nylorun/core/contracts";
|
|
5
|
+
export type { LiveEvent, SessionCommand, Action, ActionOutcome, CredentialInfo, CredentialSelection, VaultInfo, SubjectScope, } from "@nylorun/core/contracts";
|
|
6
|
+
export { SUBJECT_SCOPES } from "@nylorun/core/contracts";
|
|
6
7
|
export { AgentsClient, SessionClient, createClient } from "./client.js";
|
|
7
|
-
export type { AgentSource, SessionView, CommandOptions, SessionSandbox, } from "./client.js";
|
|
8
|
+
export type { ActAsOptions, AgentSource, SessionView, CommandOptions, SessionSandbox, } from "./client.js";
|
|
8
9
|
export { connectAgents } from "./executor.js";
|
|
9
10
|
export type { ConnectOptions, AgentConnection } from "./executor.js";
|
|
10
11
|
export type { ExecuteActionOptions, ExecutableDefinition, } from "./execute-action.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Definition authoring and wire contracts are the only harness runtime imports.
|
|
2
|
-
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
2
|
+
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
3
3
|
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError, Loop, Chain, Switch, Parallel, Map, VerdictSchema, isVerdict, withInstructions, withoutTools, WorkflowBuildError, isBuiltWorkflow, capability, tool, defineSchema, ToolError, } from "@nylorun/core/define";
|
|
4
|
+
export { SUBJECT_SCOPES } from "@nylorun/core/contracts";
|
|
4
5
|
export { AgentsClient, SessionClient, createClient } from "./client.js";
|
|
5
6
|
export { connectAgents } from "./executor.js";
|
|
6
7
|
export { resolveConnection, ConnectionError } from "./connection.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-beta",
|
|
4
4
|
"description": "Nylorun agent definitions, session client and connected executor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"./executor": {
|
|
28
28
|
"types": "./dist/executor.d.ts",
|
|
29
29
|
"import": "./dist/executor.js"
|
|
30
|
+
},
|
|
31
|
+
"./ag-ui": {
|
|
32
|
+
"types": "./dist/ag-ui/index.d.ts",
|
|
33
|
+
"import": "./dist/ag-ui/index.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
"scripts": {
|
|
@@ -35,7 +39,8 @@
|
|
|
35
39
|
"test": "vitest run --passWithNoTests"
|
|
36
40
|
},
|
|
37
41
|
"dependencies": {
|
|
38
|
-
"@
|
|
42
|
+
"@ag-ui/core": "~1.0.0",
|
|
43
|
+
"@nylorun/core": "0.7.0-beta",
|
|
39
44
|
"gray-matter": "^4.0.3"
|
|
40
45
|
},
|
|
41
46
|
"peerDependencies": {
|
|
@@ -51,7 +56,7 @@
|
|
|
51
56
|
},
|
|
52
57
|
"repository": {
|
|
53
58
|
"type": "git",
|
|
54
|
-
"url": "git+https://github.com/nylorun/
|
|
59
|
+
"url": "git+https://github.com/nylorun/agents.git",
|
|
55
60
|
"directory": "agents"
|
|
56
61
|
},
|
|
57
62
|
"publishConfig": {
|