@nylorun/agents 0.9.0-beta → 0.10.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +287 -69
  3. package/dist/a2a/handler.d.ts +59 -0
  4. package/dist/a2a/handler.js +178 -0
  5. package/dist/a2a/index.d.ts +8 -0
  6. package/dist/a2a/index.js +7 -0
  7. package/dist/access.d.ts +101 -0
  8. package/dist/access.js +111 -0
  9. package/dist/action-handler.d.ts +64 -0
  10. package/dist/action-handler.js +283 -0
  11. package/dist/ag-ui/handler.d.ts +12 -4
  12. package/dist/ag-ui/handler.js +87 -281
  13. package/dist/ag-ui/index.d.ts +2 -1
  14. package/dist/ag-ui/index.js +2 -1
  15. package/dist/browser.d.ts +97 -0
  16. package/dist/browser.js +277 -0
  17. package/dist/client.d.ts +3 -230
  18. package/dist/client.js +2 -357
  19. package/dist/connection.d.ts +0 -1
  20. package/dist/connection.js +3 -9
  21. package/dist/delivery-token.d.ts +57 -0
  22. package/dist/delivery-token.js +150 -0
  23. package/dist/execute-action.d.ts +6 -0
  24. package/dist/execute-action.js +24 -0
  25. package/dist/http.d.ts +31 -1
  26. package/dist/http.js +74 -14
  27. package/dist/index.d.ts +9 -7
  28. package/dist/index.js +3 -3
  29. package/dist/sandbox/client.d.ts +4 -5
  30. package/dist/sandbox/client.js +4 -8
  31. package/dist/sandbox/index.d.ts +0 -2
  32. package/dist/sandbox/index.js +0 -1
  33. package/dist/served-definitions.d.ts +20 -0
  34. package/dist/served-definitions.js +100 -0
  35. package/dist/session-client.d.ts +282 -0
  36. package/dist/session-client.js +375 -0
  37. package/dist/sse.js +12 -0
  38. package/dist/studio-embed.d.ts +9 -0
  39. package/dist/studio-embed.js +8 -0
  40. package/dist/token-endpoint.d.ts +23 -0
  41. package/dist/token-endpoint.js +30 -0
  42. package/package.json +15 -8
  43. package/dist/ag-ui/history.d.ts +0 -7
  44. package/dist/ag-ui/history.js +0 -60
  45. package/dist/ag-ui/sse.d.ts +0 -9
  46. package/dist/ag-ui/sse.js +0 -7
  47. package/dist/ag-ui/translate.d.ts +0 -38
  48. package/dist/ag-ui/translate.js +0 -222
  49. package/dist/derived-credentials.d.ts +0 -5
  50. package/dist/derived-credentials.js +0 -17
  51. package/dist/executor.d.ts +0 -20
  52. package/dist/executor.js +0 -417
@@ -0,0 +1,277 @@
1
+ /**
2
+ * `@nylorun/agents/browser`: a Runtime client for browsers and apps that hold no Tenant key
3
+ * (Host features `subject-tokens` and `browser-access`). The page ships a publishable key; the
4
+ * app server's token route returns a subject token for the signed-in person; the client keeps
5
+ * it in memory, fetches a new one a minute before it expires or when the Runtime answers
6
+ * `401 token_expired`, and never asks for two at once.
7
+ *
8
+ * No Node-only module is imported here or below.
9
+ */
10
+ import { PROTOCOL_HEADER, PROTOCOL_VERSION, PUBLISHABLE_KEY_HEADER, } from "@nylorun/core/compatibility";
11
+ import { AgentsClient } from "./session-client.js";
12
+ /** Fetch a new token this long before the current one expires. */
13
+ const REFRESH_EARLY_MS = 60_000;
14
+ /** The token's claims, read without verifying (the Runtime verifies). */
15
+ export function readClaims(token) {
16
+ const payload = token.split(".")[1];
17
+ if (!payload)
18
+ return {};
19
+ try {
20
+ const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
21
+ const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
22
+ const json = new TextDecoder().decode(Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)));
23
+ return JSON.parse(json);
24
+ }
25
+ catch {
26
+ return {};
27
+ }
28
+ }
29
+ /** A token kept in memory, fetched on demand, one fetch at a time. */
30
+ export class CachedToken {
31
+ fetchToken;
32
+ now;
33
+ current;
34
+ pending;
35
+ constructor(fetchToken, now = Date.now) {
36
+ this.fetchToken = fetchToken;
37
+ this.now = now;
38
+ }
39
+ get(signal) {
40
+ if (this.current && this.current.expiresAt - REFRESH_EARLY_MS > this.now())
41
+ return Promise.resolve(this.current.token);
42
+ this.pending ??= this.fetchToken(signal)
43
+ .then((reply) => {
44
+ const token = typeof reply === "string" ? reply : reply.token;
45
+ if (typeof token !== "string" || token === "")
46
+ throw new Error("The token callback returned no token");
47
+ const expiresAt = typeof reply !== "string" && reply.expiresAt
48
+ ? Date.parse(reply.expiresAt)
49
+ : (readClaims(token).exp ?? 0) * 1000;
50
+ this.current = { token, expiresAt };
51
+ return token;
52
+ })
53
+ .finally(() => {
54
+ this.pending = undefined;
55
+ });
56
+ return this.pending;
57
+ }
58
+ invalidate() {
59
+ this.current = undefined;
60
+ }
61
+ /** The subject of the current token, fetching one if needed. */
62
+ async subject(signal) {
63
+ const sub = readClaims(await this.get(signal)).sub;
64
+ if (!sub)
65
+ throw new Error("The subject token names no subject");
66
+ return sub;
67
+ }
68
+ }
69
+ /** Reattaching a run the Runtime ended for its token, at most this many times in a row. */
70
+ const MAX_REATTACH = 5;
71
+ function parseFrame(text) {
72
+ let id;
73
+ let data;
74
+ for (const line of text.split("\n")) {
75
+ if (line.startsWith("id: "))
76
+ id = line.slice(4);
77
+ else if (line.startsWith("data: "))
78
+ data = line.slice(6);
79
+ }
80
+ let event;
81
+ try {
82
+ event = data === undefined ? undefined : JSON.parse(data);
83
+ }
84
+ catch {
85
+ event = undefined;
86
+ }
87
+ return { text, ...(id ? { id } : {}), ...(event ? { event } : {}) };
88
+ }
89
+ /** The frames of an SSE body, as they arrive. */
90
+ async function* frames(body) {
91
+ const reader = body.getReader();
92
+ const decoder = new TextDecoder();
93
+ let buffer = "";
94
+ try {
95
+ for (;;) {
96
+ const { value, done } = await reader.read();
97
+ if (done)
98
+ break;
99
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
100
+ let end;
101
+ while ((end = buffer.indexOf("\n\n")) >= 0) {
102
+ const text = buffer.slice(0, end);
103
+ buffer = buffer.slice(end + 2);
104
+ if (text.trim() !== "")
105
+ yield parseFrame(text);
106
+ }
107
+ }
108
+ }
109
+ finally {
110
+ await reader.cancel().catch(() => { });
111
+ }
112
+ }
113
+ /** The signed-in person's view of the Runtime: their sessions and vaults, the agents they may use. */
114
+ export class BrowserClient {
115
+ /** The underlying Tenant API client, for anything this class does not wrap. */
116
+ client;
117
+ url;
118
+ publishableKey;
119
+ tokens;
120
+ fetcher;
121
+ constructor(options) {
122
+ this.url = options.url.replace(/\/$/, "");
123
+ this.publishableKey = options.publishableKey;
124
+ this.tokens = new CachedToken((signal) => options.token(signal));
125
+ const base = options.fetch ?? globalThis.fetch;
126
+ // Called as a method, a page's window.fetch would lose its receiver.
127
+ this.fetcher = options.fetch ? base : (input, init) => base(input, init);
128
+ this.client = new AgentsClient({
129
+ url: this.url,
130
+ publishableKey: options.publishableKey,
131
+ token: this.tokens,
132
+ ...(options.fetch ? { fetch: options.fetch } : {}),
133
+ });
134
+ }
135
+ /** The person the tokens are for. */
136
+ subject(signal) {
137
+ return this.tokens.subject(signal);
138
+ }
139
+ /** A current token, e.g. for a request this client does not make itself. */
140
+ token(signal) {
141
+ return this.tokens.get(signal);
142
+ }
143
+ /** The agents the person may use: id, name and description. */
144
+ listAgents(signal) {
145
+ return this.client.listAgents({ ...(signal ? { signal } : {}) });
146
+ }
147
+ listSessions(agentId, signal) {
148
+ return this.client.listSessions({
149
+ ...(agentId ? { agentId } : {}),
150
+ ...(signal ? { signal } : {}),
151
+ });
152
+ }
153
+ session(sessionId) {
154
+ return this.client.session(sessionId);
155
+ }
156
+ /** A session of the person's own; the owner is always the token's subject. */
157
+ async createSession(options) {
158
+ return this.client.createSession({
159
+ ...options,
160
+ ownerUserId: await this.subject(),
161
+ });
162
+ }
163
+ async createVault(options) {
164
+ return this.client.createVault({ ...options, ownerUserId: await this.subject() });
165
+ }
166
+ async listVaults(signal) {
167
+ return this.client.listVaults(await this.subject(signal), signal);
168
+ }
169
+ /** `fetch` with this client's credentials, retrying once after `401 token_expired`. */
170
+ async send(input, init = {}, retried = false) {
171
+ const headers = new Headers(init.headers);
172
+ headers.set("authorization", `Bearer ${await this.tokens.get(init.signal ?? undefined)}`);
173
+ headers.set(PUBLISHABLE_KEY_HEADER, this.publishableKey);
174
+ headers.set(PROTOCOL_HEADER, String(PROTOCOL_VERSION));
175
+ const response = await this.fetcher(input, { ...init, headers });
176
+ if (response.status !== 401 || retried)
177
+ return response;
178
+ const body = (await response.clone().json().catch(() => undefined));
179
+ if (body?.code !== "token_expired")
180
+ return response;
181
+ await response.body?.cancel().catch(() => { });
182
+ this.tokens.invalidate();
183
+ return this.send(input, init, true);
184
+ }
185
+ /**
186
+ * For `@ag-ui/client`'s `HttpAgent`: `new HttpAgent({ url, fetch })`. The `fetch` adds the
187
+ * publishable key and a current token, and when the Runtime ends a run's stream because the
188
+ * token expired or was revoked, it reattaches with a new token from the last event, so the
189
+ * agent sees one run.
190
+ */
191
+ agUi(agentId) {
192
+ const url = `${this.url}/v1/ag-ui/agents/${encodeURIComponent(agentId)}`;
193
+ const fetchRun = async (input, init = {}) => {
194
+ const target = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
195
+ const response = await this.send(target, init);
196
+ const isStream = response.headers.get("content-type")?.includes("text/event-stream");
197
+ const method = (init.method ?? "GET").toUpperCase();
198
+ if (!response.ok || !isStream || !response.body || method !== "POST" || target !== url)
199
+ return response;
200
+ let run = {};
201
+ try {
202
+ run = JSON.parse(String(init.body ?? "{}"));
203
+ }
204
+ catch {
205
+ /* not a RunAgentInput: pass the stream through as it is */
206
+ }
207
+ if (!run.threadId)
208
+ return response;
209
+ return new Response(this.stitch(response.body, url, run.threadId, run.runId, init.signal ?? undefined), {
210
+ status: response.status,
211
+ headers: response.headers,
212
+ });
213
+ };
214
+ return { url, fetch: fetchRun };
215
+ }
216
+ /** The thread's messages, for `HttpAgent`'s `initialMessages`. */
217
+ async agUiHistory(agentId, threadId, signal) {
218
+ const response = await this.send(`${this.url}/v1/ag-ui/agents/${encodeURIComponent(agentId)}/threads/${encodeURIComponent(threadId)}/messages`, { method: "GET", ...(signal ? { signal } : {}) });
219
+ if (!response.ok)
220
+ throw new Error(`Runtime HTTP ${response.status}`);
221
+ return (await response.json());
222
+ }
223
+ /**
224
+ * Passes a run's frames through; on `nylorun.stream_closed` before the run finished, fetches
225
+ * the rest from the last event id and continues without repeating `RUN_STARTED`.
226
+ */
227
+ stitch(body, url, threadId, runId, signal) {
228
+ const encoder = new TextEncoder();
229
+ const self = this;
230
+ return new ReadableStream({
231
+ async start(controller) {
232
+ let lastId;
233
+ let source = body;
234
+ let reattaching = false;
235
+ try {
236
+ for (let attempt = 0; source && attempt <= MAX_REATTACH; attempt += 1) {
237
+ let closed = false;
238
+ for await (const frame of frames(source)) {
239
+ if (frame.event?.type === "CUSTOM" && frame.event.name === "nylorun.stream_closed") {
240
+ if (frame.id)
241
+ lastId = frame.id;
242
+ closed = true;
243
+ break;
244
+ }
245
+ // The reattached stream starts its run again; the agent already has it.
246
+ if (reattaching && frame.event?.type === "RUN_STARTED")
247
+ continue;
248
+ if (frame.id)
249
+ lastId = frame.id;
250
+ controller.enqueue(encoder.encode(`${frame.text}\n\n`));
251
+ }
252
+ source = null;
253
+ if (!closed || !lastId || signal?.aborted)
254
+ break;
255
+ reattaching = true;
256
+ const query = runId ? `?runId=${encodeURIComponent(runId)}` : "";
257
+ const next = await self.send(`${url}/threads/${encodeURIComponent(threadId)}/events${query}`, {
258
+ method: "GET",
259
+ headers: { accept: "text/event-stream", "last-event-id": lastId },
260
+ ...(signal ? { signal } : {}),
261
+ });
262
+ // 204: the run ended with nothing after the last event; there is no more to send.
263
+ if (next.ok && next.status !== 204 && next.body)
264
+ source = next.body;
265
+ }
266
+ controller.close();
267
+ }
268
+ catch (error) {
269
+ controller.error(error);
270
+ }
271
+ },
272
+ });
273
+ }
274
+ }
275
+ export function createBrowserClient(options) {
276
+ return new BrowserClient(options);
277
+ }
package/dist/client.d.ts CHANGED
@@ -1,230 +1,6 @@
1
- import type { AgentManifest, BuiltAgent, BuiltWorkflow, JsonValue, SandboxToolName, WorkflowManifest } from "@nylorun/core/define";
2
- import { type SubjectScope, type CredentialInfo, type CredentialSelection, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
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
- };
8
- export interface AgentSource {
9
- readonly id: string;
10
- readonly manifest: AgentManifest | WorkflowManifest;
11
- build?(): BuiltAgent | BuiltWorkflow;
12
- getBinding?: BuiltAgent["getBinding"] | BuiltWorkflow["getBinding"];
13
- }
14
- /** Closures stay on the live binding. The published manifest cannot name them. */
15
- export declare function assertNoMiddlewareClosures(agent: AgentSource): void;
16
- export interface SessionView {
17
- id: string;
18
- agentId: string;
19
- ownerUserId: string;
20
- status: string;
21
- activeTurnId: string | null;
22
- waits?: unknown;
23
- [key: string]: unknown;
24
- }
25
- export interface ActAsOptions {
26
- /** What the subject may do; the Runtime grants nothing else. Default `["sessions:own"]`. */
27
- scopes?: readonly SubjectScope[];
28
- }
29
- export declare class AgentsClient {
30
- readonly transport: Transport;
31
- /** The subject this client acts for (`as`), or `undefined` for the principal itself. */
32
- readonly subject: string | undefined;
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[]>;
45
- listAgents(options?: {
46
- signal?: AbortSignal;
47
- }): Promise<{
48
- agents: {
49
- manifest: AgentManifest;
50
- }[];
51
- }>;
52
- listSessions(options?: {
53
- agentId?: string;
54
- signal?: AbortSignal;
55
- }): Promise<{
56
- sessions: SessionView[];
57
- }>;
58
- saveAgent(agent: AgentSource | BuiltWorkflow, options: {
59
- implementationVersion: string;
60
- requestId?: string;
61
- }): Promise<unknown>;
62
- createSession(options: {
63
- id?: string;
64
- agentId: string;
65
- ownerUserId: string;
66
- info?: Record<string, unknown>;
67
- requestId?: string;
68
- vaultIds?: readonly string[];
69
- credentialSelections?: readonly CredentialSelection[];
70
- /** Share another session's sandbox (public PutSession.sandbox). */
71
- sandbox?: {
72
- session: string;
73
- };
74
- }): Promise<SessionClient>;
75
- createVault(options: {
76
- name: string;
77
- ownerUserId: string;
78
- metadata?: Record<string, string>;
79
- idempotencyKey: string;
80
- requestId?: string;
81
- }): Promise<VaultInfo>;
82
- listVaults(ownerUserId: string, signal?: AbortSignal): Promise<{
83
- vaults: VaultInfo[];
84
- }>;
85
- getVault(vaultId: string, signal?: AbortSignal): Promise<VaultInfo>;
86
- deleteVault(vaultId: string): Promise<{
87
- id: string;
88
- }>;
89
- createCredential(vaultId: string, options: {
90
- name: string;
91
- idempotencyKey: string;
92
- requestId?: string;
93
- auth: {
94
- type: "bearer";
95
- url: string;
96
- token: string;
97
- } | {
98
- type: "oauth";
99
- url: string;
100
- accessToken: string;
101
- expiresAt?: string | null;
102
- refresh?: {
103
- tokenEndpoint: string;
104
- clientId: string;
105
- refreshToken: string;
106
- tokenEndpointAuth: {
107
- type: "none";
108
- } | {
109
- type: "client_secret_basic";
110
- clientSecret: string;
111
- } | {
112
- type: "client_secret_post";
113
- clientSecret: string;
114
- };
115
- };
116
- };
117
- }): Promise<CredentialInfo>;
118
- listCredentials(vaultId: string, signal?: AbortSignal): Promise<{
119
- credentials: CredentialInfo[];
120
- }>;
121
- getCredential(vaultId: string, credentialId: string, signal?: AbortSignal): Promise<CredentialInfo>;
122
- rotateCredential(vaultId: string, credentialId: string, options: {
123
- idempotencyKey: string;
124
- requestId?: string;
125
- auth: {
126
- type: "bearer";
127
- token: string;
128
- } | {
129
- type: "oauth";
130
- accessToken: string;
131
- expiresAt?: string | null;
132
- };
133
- }): Promise<CredentialInfo>;
134
- deleteCredential(vaultId: string, credentialId: string): Promise<{
135
- id: string;
136
- }>;
137
- session(sessionId: string): SessionClient;
138
- }
139
- export type CommandOptions = {
140
- idempotencyKey: string;
141
- requestId?: string;
142
- signal?: AbortSignal;
143
- };
144
- export declare class SessionClient {
145
- private readonly transport;
146
- readonly id: string;
147
- readonly sandbox: SessionSandbox;
148
- constructor(transport: Transport, id: string);
149
- private get path();
150
- inspect(signal?: AbortSignal): Promise<SessionView>;
151
- /** Pending waits / interactions from the session inspect view. */
152
- pending(signal?: AbortSignal): Promise<{}>;
153
- command(command: SessionCommand, signal?: AbortSignal): Promise<{
154
- status: "accepted";
155
- turnId: string | null;
156
- cursor: string | null;
157
- requestId: string;
158
- }>;
159
- /**
160
- * Start a turn. Strings become `message.content`; any other JSON value becomes
161
- * `message.data` (workflows.md §5).
162
- */
163
- input(value: string | JsonValue, options: CommandOptions): Promise<{
164
- status: "accepted";
165
- turnId: string | null;
166
- cursor: string | null;
167
- requestId: string;
168
- }>;
169
- approve(interactionId: string, approved: boolean, options: CommandOptions): Promise<{
170
- status: "accepted";
171
- turnId: string | null;
172
- cursor: string | null;
173
- requestId: string;
174
- }>;
175
- respond(interactionId: string, value: JsonValue, options: CommandOptions): Promise<{
176
- status: "accepted";
177
- turnId: string | null;
178
- cursor: string | null;
179
- requestId: string;
180
- }>;
181
- cancel(options: CommandOptions & {
182
- reason?: string;
183
- }): Promise<{
184
- status: "accepted";
185
- turnId: string | null;
186
- cursor: string | null;
187
- requestId: string;
188
- }>;
189
- /** `agent` narrows to one agent used as a tool: its delegationId or its path. */
190
- history(options?: {
191
- cursor?: string;
192
- agent?: string;
193
- signal?: AbortSignal;
194
- }): Promise<{
195
- items: {
196
- eventId: string;
197
- sessionId: string;
198
- tenantId: string;
199
- turnId: string | null;
200
- cursor: string;
201
- createdAt: string;
202
- type: string;
203
- payload: unknown;
204
- }[];
205
- cursor: string | null;
206
- }>;
207
- observe(options?: {
208
- cursor?: string;
209
- signal?: AbortSignal;
210
- follow?: boolean;
211
- }): AsyncGenerator<{
212
- eventId: string;
213
- sessionId: string;
214
- tenantId: string;
215
- turnId: string | null;
216
- cursor: string;
217
- createdAt: string;
218
- type: string;
219
- payload: unknown;
220
- }, void, unknown>;
221
- private observeOne;
222
- /**
223
- * Own session stream plus linked agent sessions announced in `node.agent`
224
- * events, merged by `createdAt` then arrival order (workflows.md §11).
225
- */
226
- private observeFollowing;
227
- }
1
+ export * from "./session-client.js";
2
+ import { AgentsClient } from "./session-client.js";
3
+ import type { Destination } from "./http.js";
228
4
  /**
229
5
  * Build a Tenant API client.
230
6
  * Explicit connection fields keep today's sync Transport resolution (including
@@ -233,6 +9,3 @@ export declare class SessionClient {
233
9
  */
234
10
  export declare function createClient(destination: Destination): AgentsClient;
235
11
  export declare function createClient(destination?: undefined): Promise<AgentsClient>;
236
- export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
237
- export type { Destination, IncompatibleReason } from "./http.js";
238
- export type { LiveEvent } from "@nylorun/core/contracts";