@nylorun/agents 0.13.0-beta → 0.14.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/dist/http.js CHANGED
@@ -1,4 +1,4 @@
1
- import { PROTOCOL_FEATURES, PROTOCOL_HEADER, PROTOCOL_VERSION, PUBLISHABLE_KEY_HEADER, checkCompatibility, } from "@nylorun/core/compatibility";
1
+ import { PROTOCOL_FEATURES, PROTOCOL_HEADER, PROTOCOL_VERSION, checkCompatibility, } from "@nylorun/core/compatibility";
2
2
  export function env(name) {
3
3
  return typeof process !== "undefined" ? process.env[name] : undefined;
4
4
  }
@@ -54,10 +54,9 @@ function parseProtocolRange(value) {
54
54
  }
55
55
  export class Transport {
56
56
  url;
57
- /** The Tenant key; empty for a client that uses subject tokens. */
57
+ /** The Tenant key; empty for a client that uses issuer tokens. */
58
58
  key;
59
59
  token;
60
- publishableKey;
61
60
  fetcher;
62
61
  /** Sent on every request, e.g. `Nylorun-Subject` and `Nylorun-Scopes` (`withHeaders`). */
63
62
  headers = {};
@@ -69,14 +68,13 @@ export class Transport {
69
68
  if (!url || (!token && !key))
70
69
  throw new Error("Set Runtime url and server key explicitly or via NYLORUN_RUNTIME_URL / NYLORUN_SERVER_KEY");
71
70
  if (token && options.key)
72
- throw new Error("Use a Tenant key or subject tokens, not both");
71
+ throw new Error("Use a Tenant key or issuer tokens, not both");
73
72
  const parsed = new URL(url);
74
73
  if (!["http:", "https:"].includes(parsed.protocol))
75
74
  throw new Error("Runtime requires an HTTP(S) URL");
76
75
  this.url = url.replace(/\/$/, "");
77
76
  this.key = key;
78
77
  this.token = token;
79
- this.publishableKey = options.publishableKey;
80
78
  // Browsers may not call `/health` (it refuses `Origin`); token clients rely on `426`.
81
79
  if (token)
82
80
  this.check.compatible = true;
@@ -165,8 +163,6 @@ export class Transport {
165
163
  ? await this.token.get(init.signal ?? undefined)
166
164
  : this.key;
167
165
  headers.set("Authorization", `Bearer ${bearer}`);
168
- if (this.publishableKey)
169
- headers.set(PUBLISHABLE_KEY_HEADER, this.publishableKey);
170
166
  headers.set(PROTOCOL_HEADER, String(PROTOCOL_VERSION));
171
167
  // JSON unless the caller says otherwise (an artifact upload sends the file's type).
172
168
  if (init.body && !headers.has("content-type"))
@@ -180,7 +176,7 @@ export class Transport {
180
176
  headers: await this.authHeaders(init),
181
177
  redirect: "error",
182
178
  });
183
- // An expired or revoked subject token: get a new one and try once more.
179
+ // An expired token: get a new one and try once more.
184
180
  if (response.status === 401 && this.token && !options.retried401) {
185
181
  const body = (await response.clone().json().catch(() => undefined));
186
182
  if (body?.code === "token_expired") {
package/dist/index.d.ts CHANGED
@@ -11,10 +11,7 @@ export { ArtifactsClient } from "./artifacts.js";
11
11
  export type { ArtifactBody, ArtifactLinkWithUrl, UploadArtifactOptions, } from "./artifacts.js";
12
12
  export type { ActAsOptions, AgentSource, SessionView, CommandOptions, CreateSessionOptions, SessionSandbox, ForSessionOptions, SandboxSpec, SessionSandboxHandle, } from "./client.js";
13
13
  export { SandboxesClient } from "./client.js";
14
- export { AccessClient, PublishableKeysClient, SigningKeysClient, TokensClient, } from "./access.js";
15
- export type { CreateTokenOptions } from "./access.js";
16
- export { createTokenEndpoint } from "./token-endpoint.js";
17
- export type { TokenEndpointOptions } from "./token-endpoint.js";
14
+ export { AccessClient, SigningKeysClient } from "./access.js";
18
15
  export { createActionHandler } from "./action-handler.js";
19
16
  export type { ActionHandler, ActionHandlerOptions, RegisterOptions, } from "./action-handler.js";
20
17
  export type { ExecuteActionOptions, ExecutableDefinition, } from "./execute-action.js";
package/dist/index.js CHANGED
@@ -6,8 +6,7 @@ export { SUBJECT_SCOPES } from "@nylorun/core/contracts";
6
6
  export { AgentsClient, SessionClient, createClient } from "./client.js";
7
7
  export { ArtifactsClient } from "./artifacts.js";
8
8
  export { SandboxesClient } from "./client.js";
9
- export { AccessClient, PublishableKeysClient, SigningKeysClient, TokensClient, } from "./access.js";
10
- export { createTokenEndpoint } from "./token-endpoint.js";
9
+ export { AccessClient, SigningKeysClient } from "./access.js";
11
10
  export { createActionHandler } from "./action-handler.js";
12
11
  export { resolveConnection, ConnectionError } from "./connection.js";
13
12
  export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
@@ -1,11 +1,10 @@
1
1
  /**
2
- * The Tenant API client classes, with no Node-only imports so a browser bundle can load them
3
- * (`@nylorun/agents/browser`). `client.ts` adds `createClient`, which resolves the
4
- * connection from the environment or the Project link.
2
+ * The Tenant API client classes, with no Node-only imports. `client.ts` adds `createClient`,
3
+ * which resolves the connection from the environment or the Project link.
5
4
  */
6
5
  import type { AgentManifest, BuiltAgent, BuiltWorkflow, JsonValue, SandboxToolName, WorkflowManifest } from "@nylorun/core/define";
7
6
  import { type SubjectScope, type CredentialInfo, type CredentialSelection, type MessagePart, type SandboxRequest, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
8
- import { AccessClient, TokensClient } from "./access.js";
7
+ import { AccessClient } from "./access.js";
9
8
  import { SandboxesClient } from "./sandboxes.js";
10
9
  import { ArtifactsClient } from "./artifacts.js";
11
10
  import { Transport, type Destination } from "./http.js";
@@ -64,12 +63,7 @@ export declare class AgentsClient {
64
63
  * themselves and hold the key; nothing is minted, cached or refreshed.
65
64
  */
66
65
  as(subject: string, options?: ActAsOptions): AgentsClient;
67
- /**
68
- * Mints subject tokens for signed-in people (Host feature `subject-tokens`), so their
69
- * browser or app calls the Runtime directly. Application key only.
70
- */
71
- get tokens(): TokensClient;
72
- /** The access policy, signing keys and revocations (Host feature `subject-tokens`). */
66
+ /** The signing keys and the public keys that verify delivery tokens. Application key only. */
73
67
  get access(): AccessClient;
74
68
  /**
75
69
  * Sandboxes as a resource (Host feature `sandboxes`): `ensure` one by id, `list` them by
@@ -100,14 +94,25 @@ export declare class AgentsClient {
100
94
  requestId?: string;
101
95
  }): Promise<unknown>;
102
96
  createSession(options: CreateSessionOptions): Promise<SessionClient>;
97
+ /**
98
+ * Creates a vault: the installation's own (`scope: "installation"`), which any session may
99
+ * attach, or one person's (`ownerUserId`), which only that person's sessions attach. Vault
100
+ * routes take the application key acting for no one (protocol 7): not a client made with `as`.
101
+ */
103
102
  createVault(options: {
104
103
  name: string;
105
- ownerUserId: string;
106
104
  metadata?: Record<string, string>;
107
105
  idempotencyKey: string;
108
106
  requestId?: string;
109
- }): Promise<VaultInfo>;
110
- listVaults(ownerUserId: string, signal?: AbortSignal): Promise<{
107
+ } & ({
108
+ scope: "installation";
109
+ ownerUserId?: undefined;
110
+ } | {
111
+ scope?: "user";
112
+ ownerUserId: string;
113
+ })): Promise<VaultInfo>;
114
+ /** The installation vaults, after the vaults of `ownerUserId` when it is given. */
115
+ listVaults(ownerUserId?: string, signal?: AbortSignal): Promise<{
111
116
  vaults: VaultInfo[];
112
117
  }>;
113
118
  getVault(vaultId: string, signal?: AbortSignal): Promise<VaultInfo>;
@@ -303,6 +308,6 @@ export declare class SessionClient {
303
308
  export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
304
309
  export type { Destination, IncompatibleReason } from "./http.js";
305
310
  export type { LiveEvent } from "@nylorun/core/contracts";
306
- export { AccessClient, PublishableKeysClient, SigningKeysClient, TokensClient, } from "./access.js";
311
+ export { AccessClient, SigningKeysClient } from "./access.js";
307
312
  export { SandboxesClient } from "./sandboxes.js";
308
313
  export type { ForSessionOptions, SandboxSpec, SessionSandboxHandle } from "./sandboxes.js";
@@ -2,7 +2,7 @@ import { SANDBOX_TOOL_NAMES, delegateOf, isBuiltWorkflow, isSandboxToolName, } f
2
2
  import { z } from "zod";
3
3
  import { SCOPES_HEADER, SUBJECT_HEADER } from "@nylorun/core/compatibility";
4
4
  import { parseSessionEvent, parseSubjectHeaders, } from "@nylorun/core/contracts";
5
- import { AccessClient, TokensClient } from "./access.js";
5
+ import { AccessClient } from "./access.js";
6
6
  import { SandboxesClient } from "./sandboxes.js";
7
7
  import { ArtifactsClient } from "./artifacts.js";
8
8
  import { Transport, id, segment } from "./http.js";
@@ -95,14 +95,7 @@ export class AgentsClient {
95
95
  subject,
96
96
  });
97
97
  }
98
- /**
99
- * Mints subject tokens for signed-in people (Host feature `subject-tokens`), so their
100
- * browser or app calls the Runtime directly. Application key only.
101
- */
102
- get tokens() {
103
- return new TokensClient(this.transport);
104
- }
105
- /** The access policy, signing keys and revocations (Host feature `subject-tokens`). */
98
+ /** The signing keys and the public keys that verify delivery tokens. Application key only. */
106
99
  get access() {
107
100
  return new AccessClient(this.transport);
108
101
  }
@@ -185,17 +178,27 @@ export class AgentsClient {
185
178
  });
186
179
  return this.session(sessionId);
187
180
  }
181
+ /**
182
+ * Creates a vault: the installation's own (`scope: "installation"`), which any session may
183
+ * attach, or one person's (`ownerUserId`), which only that person's sessions attach. Vault
184
+ * routes take the application key acting for no one (protocol 7): not a client made with `as`.
185
+ */
188
186
  createVault(options) {
189
187
  return this.transport.json("/v1/vaults", "POST", {
190
188
  requestId: options.requestId ?? id(),
191
189
  idempotencyKey: options.idempotencyKey,
192
190
  name: options.name,
193
- ownerUserId: options.ownerUserId,
191
+ ...(options.scope === "installation"
192
+ ? { scope: "installation" }
193
+ : { ownerUserId: options.ownerUserId }),
194
194
  ...(options.metadata ? { metadata: options.metadata } : {}),
195
195
  });
196
196
  }
197
+ /** The installation vaults, after the vaults of `ownerUserId` when it is given. */
197
198
  listVaults(ownerUserId, signal) {
198
- return this.transport.json(`/v1/vaults?ownerUserId=${encodeURIComponent(ownerUserId)}`, "GET", undefined, signal);
199
+ return this.transport.json(ownerUserId === undefined
200
+ ? "/v1/vaults"
201
+ : `/v1/vaults?ownerUserId=${encodeURIComponent(ownerUserId)}`, "GET", undefined, signal);
199
202
  }
200
203
  getVault(vaultId, signal) {
201
204
  return this.transport.json(`/v1/vaults/${segment(vaultId)}`, "GET", undefined, signal);
@@ -398,5 +401,5 @@ export class SessionClient {
398
401
  }
399
402
  }
400
403
  export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
401
- export { AccessClient, PublishableKeysClient, SigningKeysClient, TokensClient, } from "./access.js";
404
+ export { AccessClient, SigningKeysClient } from "./access.js";
402
405
  export { SandboxesClient } from "./sandboxes.js";
package/dist/sse.js CHANGED
@@ -65,7 +65,7 @@ export async function* observeSSE(transport, path, options = {}) {
65
65
  const signal = options.signal ?? controller.signal;
66
66
  let cursor = options.cursor;
67
67
  let retry = 250;
68
- // The Runtime ended the stream because its subject token expired or was revoked: reconnect
68
+ // The Runtime ended the stream because its token expired: reconnect
69
69
  // at once (the transport fetches a new token), but only once in a row.
70
70
  let closedByRuntime = 0;
71
71
  while (!signal.aborted) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `@nylorun/agents/studio-embed`: the contract between Studio and an app that embeds it in an
3
- * iframe (Studio design §8). Studio's web app and embedders such as Babai import it from here,
3
+ * iframe (Studio design §8). Studio's web app and the apps that embed it import it from here,
4
4
  * because Studio may not depend on `@nylorun/core` directly.
5
5
  *
6
6
  * No Node-only module is imported here or below.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `@nylorun/agents/studio-embed`: the contract between Studio and an app that embeds it in an
3
- * iframe (Studio design §8). Studio's web app and embedders such as Babai import it from here,
3
+ * iframe (Studio design §8). Studio's web app and the apps that embed it import it from here,
4
4
  * because Studio may not depend on `@nylorun/core` directly.
5
5
  *
6
6
  * No Node-only module is imported here or below.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/agents",
3
- "version": "0.13.0-beta",
3
+ "version": "0.14.0-beta",
4
4
  "description": "Nylorun agent definitions, session client and Action endpoint",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -28,10 +28,6 @@
28
28
  "types": "./dist/ag-ui/index.d.ts",
29
29
  "import": "./dist/ag-ui/index.js"
30
30
  },
31
- "./browser": {
32
- "types": "./dist/browser.d.ts",
33
- "import": "./dist/browser.js"
34
- },
35
31
  "./a2a": {
36
32
  "types": "./dist/a2a/index.d.ts",
37
33
  "import": "./dist/a2a/index.js"
@@ -47,7 +43,7 @@
47
43
  "test": "vitest run --passWithNoTests"
48
44
  },
49
45
  "dependencies": {
50
- "@nylorun/core": "0.12.0-beta",
46
+ "@nylorun/core": "0.13.0-beta",
51
47
  "gray-matter": "^4.0.3"
52
48
  },
53
49
  "peerDependencies": {
package/dist/browser.d.ts DELETED
@@ -1,104 +0,0 @@
1
- import type { CredentialSelection, VaultInfo } from "@nylorun/core/contracts";
2
- import type { TokenSource } from "./http.js";
3
- import { AgentsClient, type SessionClient } from "./session-client.js";
4
- import type { ArtifactsClient } from "./artifacts.js";
5
- /** What the token callback returns: the token, or the app server's `{ token, expiresAt }`. */
6
- export type TokenReply = string | {
7
- token: string;
8
- expiresAt?: string;
9
- };
10
- export interface BrowserClientOptions {
11
- /** The Runtime's URL, as the page reaches it. */
12
- url: string;
13
- /** The Tenant's publishable key for this app (`nr_pub_…`). */
14
- publishableKey: string;
15
- /** Returns a subject token for the signed-in person, usually from your app server. */
16
- token(signal?: AbortSignal): Promise<TokenReply>;
17
- fetch?: typeof fetch;
18
- }
19
- interface Claims {
20
- sub?: string;
21
- exp?: number;
22
- }
23
- /** The token's claims, read without verifying (the Runtime verifies). */
24
- export declare function readClaims(token: string): Claims;
25
- /** A token kept in memory, fetched on demand, one fetch at a time. */
26
- export declare class CachedToken implements TokenSource {
27
- private readonly fetchToken;
28
- private readonly now;
29
- private current;
30
- private pending;
31
- constructor(fetchToken: (signal?: AbortSignal) => Promise<TokenReply>, now?: () => number);
32
- get(signal?: AbortSignal): Promise<string>;
33
- invalidate(): void;
34
- /** The subject of the current token, fetching one if needed. */
35
- subject(signal?: AbortSignal): Promise<string>;
36
- }
37
- /** The signed-in person's view of the Runtime: their sessions and vaults, the agents they may use. */
38
- export declare class BrowserClient {
39
- /** The underlying Tenant API client, for anything this class does not wrap. */
40
- readonly client: AgentsClient;
41
- readonly url: string;
42
- readonly publishableKey: string;
43
- private readonly tokens;
44
- private readonly fetcher;
45
- constructor(options: BrowserClientOptions);
46
- /** The person the tokens are for. */
47
- subject(signal?: AbortSignal): Promise<string>;
48
- /** A current token, e.g. for a request this client does not make itself. */
49
- token(signal?: AbortSignal): Promise<string>;
50
- /** The agents the person may use: id, name and description. */
51
- listAgents(signal?: AbortSignal): Promise<{
52
- agents: {
53
- agentId: string;
54
- name?: string;
55
- description?: string;
56
- }[];
57
- }>;
58
- listSessions(agentId?: string, signal?: AbortSignal): Promise<{
59
- sessions: import("./session-client.js").SessionView[];
60
- }>;
61
- session(sessionId: string): SessionClient;
62
- /** The person's file artifacts, in their own sessions (protocol 6). */
63
- get artifacts(): ArtifactsClient;
64
- /** A session of the person's own; the owner is always the token's subject. */
65
- createSession(options: {
66
- id?: string;
67
- agentId: string;
68
- vaultIds?: readonly string[];
69
- credentialSelections?: readonly CredentialSelection[];
70
- /** A sandbox the token's `sandboxes` grants reach (Host feature `sandboxes`). */
71
- sandbox?: {
72
- id: string;
73
- };
74
- }): Promise<SessionClient>;
75
- createVault(options: {
76
- name: string;
77
- idempotencyKey: string;
78
- metadata?: Record<string, string>;
79
- }): Promise<VaultInfo>;
80
- listVaults(signal?: AbortSignal): Promise<{
81
- vaults: VaultInfo[];
82
- }>;
83
- /** `fetch` with this client's credentials, retrying once after `401 token_expired`. */
84
- private send;
85
- /**
86
- * For `@ag-ui/client`'s `HttpAgent`: `new HttpAgent({ url, fetch })`. The `fetch` adds the
87
- * publishable key and a current token, and when the Runtime ends a run's stream because the
88
- * token expired or was revoked, it reattaches with a new token from the last event, so the
89
- * agent sees one run.
90
- */
91
- agUi(agentId: string): {
92
- url: string;
93
- fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
94
- };
95
- /** The thread's messages, for `HttpAgent`'s `initialMessages`. */
96
- agUiHistory(agentId: string, threadId: string, signal?: AbortSignal): Promise<unknown[]>;
97
- /**
98
- * Passes a run's frames through; on `nylorun.stream_closed` before the run finished, fetches
99
- * the rest from the last event id and continues without repeating `RUN_STARTED`.
100
- */
101
- private stitch;
102
- }
103
- export declare function createBrowserClient(options: BrowserClientOptions): BrowserClient;
104
- export {};
package/dist/browser.js DELETED
@@ -1,281 +0,0 @@
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
- /** The person's file artifacts, in their own sessions (protocol 6). */
157
- get artifacts() {
158
- return this.client.artifacts;
159
- }
160
- /** A session of the person's own; the owner is always the token's subject. */
161
- async createSession(options) {
162
- return this.client.createSession({
163
- ...options,
164
- ownerUserId: await this.subject(),
165
- });
166
- }
167
- async createVault(options) {
168
- return this.client.createVault({ ...options, ownerUserId: await this.subject() });
169
- }
170
- async listVaults(signal) {
171
- return this.client.listVaults(await this.subject(signal), signal);
172
- }
173
- /** `fetch` with this client's credentials, retrying once after `401 token_expired`. */
174
- async send(input, init = {}, retried = false) {
175
- const headers = new Headers(init.headers);
176
- headers.set("authorization", `Bearer ${await this.tokens.get(init.signal ?? undefined)}`);
177
- headers.set(PUBLISHABLE_KEY_HEADER, this.publishableKey);
178
- headers.set(PROTOCOL_HEADER, String(PROTOCOL_VERSION));
179
- const response = await this.fetcher(input, { ...init, headers });
180
- if (response.status !== 401 || retried)
181
- return response;
182
- const body = (await response.clone().json().catch(() => undefined));
183
- if (body?.code !== "token_expired")
184
- return response;
185
- await response.body?.cancel().catch(() => { });
186
- this.tokens.invalidate();
187
- return this.send(input, init, true);
188
- }
189
- /**
190
- * For `@ag-ui/client`'s `HttpAgent`: `new HttpAgent({ url, fetch })`. The `fetch` adds the
191
- * publishable key and a current token, and when the Runtime ends a run's stream because the
192
- * token expired or was revoked, it reattaches with a new token from the last event, so the
193
- * agent sees one run.
194
- */
195
- agUi(agentId) {
196
- const url = `${this.url}/v1/ag-ui/agents/${encodeURIComponent(agentId)}`;
197
- const fetchRun = async (input, init = {}) => {
198
- const target = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
199
- const response = await this.send(target, init);
200
- const isStream = response.headers.get("content-type")?.includes("text/event-stream");
201
- const method = (init.method ?? "GET").toUpperCase();
202
- if (!response.ok || !isStream || !response.body || method !== "POST" || target !== url)
203
- return response;
204
- let run = {};
205
- try {
206
- run = JSON.parse(String(init.body ?? "{}"));
207
- }
208
- catch {
209
- /* not a RunAgentInput: pass the stream through as it is */
210
- }
211
- if (!run.threadId)
212
- return response;
213
- return new Response(this.stitch(response.body, url, run.threadId, run.runId, init.signal ?? undefined), {
214
- status: response.status,
215
- headers: response.headers,
216
- });
217
- };
218
- return { url, fetch: fetchRun };
219
- }
220
- /** The thread's messages, for `HttpAgent`'s `initialMessages`. */
221
- async agUiHistory(agentId, threadId, signal) {
222
- const response = await this.send(`${this.url}/v1/ag-ui/agents/${encodeURIComponent(agentId)}/threads/${encodeURIComponent(threadId)}/messages`, { method: "GET", ...(signal ? { signal } : {}) });
223
- if (!response.ok)
224
- throw new Error(`Runtime HTTP ${response.status}`);
225
- return (await response.json());
226
- }
227
- /**
228
- * Passes a run's frames through; on `nylorun.stream_closed` before the run finished, fetches
229
- * the rest from the last event id and continues without repeating `RUN_STARTED`.
230
- */
231
- stitch(body, url, threadId, runId, signal) {
232
- const encoder = new TextEncoder();
233
- const self = this;
234
- return new ReadableStream({
235
- async start(controller) {
236
- let lastId;
237
- let source = body;
238
- let reattaching = false;
239
- try {
240
- for (let attempt = 0; source && attempt <= MAX_REATTACH; attempt += 1) {
241
- let closed = false;
242
- for await (const frame of frames(source)) {
243
- if (frame.event?.type === "CUSTOM" && frame.event.name === "nylorun.stream_closed") {
244
- if (frame.id)
245
- lastId = frame.id;
246
- closed = true;
247
- break;
248
- }
249
- // The reattached stream starts its run again; the agent already has it.
250
- if (reattaching && frame.event?.type === "RUN_STARTED")
251
- continue;
252
- if (frame.id)
253
- lastId = frame.id;
254
- controller.enqueue(encoder.encode(`${frame.text}\n\n`));
255
- }
256
- source = null;
257
- if (!closed || !lastId || signal?.aborted)
258
- break;
259
- reattaching = true;
260
- const query = runId ? `?runId=${encodeURIComponent(runId)}` : "";
261
- const next = await self.send(`${url}/threads/${encodeURIComponent(threadId)}/events${query}`, {
262
- method: "GET",
263
- headers: { accept: "text/event-stream", "last-event-id": lastId },
264
- ...(signal ? { signal } : {}),
265
- });
266
- // 204: the run ended with nothing after the last event; there is no more to send.
267
- if (next.ok && next.status !== 204 && next.body)
268
- source = next.body;
269
- }
270
- controller.close();
271
- }
272
- catch (error) {
273
- controller.error(error);
274
- }
275
- },
276
- });
277
- }
278
- }
279
- export function createBrowserClient(options) {
280
- return new BrowserClient(options);
281
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * A route for app servers that mints a subject token for the signed-in person (Host feature
3
- * `subject-tokens`). The browser client calls it whenever it needs a token. Mount it on your
4
- * app's own origin behind your sign-in: `POST` only, no CORS, never cached.
5
- *
6
- * export const POST = createTokenEndpoint({
7
- * client, role: "user", subject: (request) => userFromCookie(request)?.id,
8
- * });
9
- */
10
- import type { TokenScope } from "@nylorun/core/contracts";
11
- import type { AgentsClient } from "./session-client.js";
12
- export interface TokenEndpointOptions {
13
- /** The application client (the Tenant key stays on the server). */
14
- client: AgentsClient | Promise<AgentsClient>;
15
- /** The signed-in person the token is for; `undefined` answers 401. */
16
- subject(request: Request): string | undefined | Promise<string | undefined>;
17
- /** The access policy role to mint for, or a function of the request and subject. */
18
- role: string | ((request: Request, subject: string) => string | Promise<string>);
19
- scopes?: readonly TokenScope[];
20
- agents?: readonly string[];
21
- ttlSeconds?: number;
22
- }
23
- export declare function createTokenEndpoint(options: TokenEndpointOptions): (request: Request) => Promise<Response>;