@nylorun/agents 0.12.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/CHANGELOG.md +72 -0
- package/README.md +104 -125
- package/dist/access.d.ts +6 -72
- package/dist/access.js +2 -78
- package/dist/ag-ui/handler.d.ts +3 -6
- package/dist/ag-ui/handler.js +2 -2
- package/dist/artifacts.d.ts +116 -0
- package/dist/artifacts.js +116 -0
- package/dist/delivery-token.js +3 -3
- package/dist/http.d.ts +6 -8
- package/dist/http.js +6 -9
- package/dist/index.d.ts +6 -6
- package/dist/index.js +3 -2
- package/dist/sandboxes.d.ts +77 -0
- package/dist/sandboxes.js +100 -0
- package/dist/session-client.d.ts +60 -29
- package/dist/session-client.js +41 -11
- package/dist/sse.js +1 -1
- package/dist/studio-embed.d.ts +1 -1
- package/dist/studio-embed.js +1 -1
- package/package.json +3 -7
- package/dist/browser.d.ts +0 -97
- package/dist/browser.js +0 -277
- package/dist/token-endpoint.d.ts +0 -23
- package/dist/token-endpoint.js +0 -30
package/dist/session-client.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Tenant API client classes, with no Node-only imports
|
|
3
|
-
*
|
|
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
|
-
import { type SubjectScope, type CredentialInfo, type CredentialSelection, type SandboxRequest, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
|
|
8
|
-
import { AccessClient
|
|
6
|
+
import { type SubjectScope, type CredentialInfo, type CredentialSelection, type MessagePart, type SandboxRequest, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
|
|
7
|
+
import { AccessClient } from "./access.js";
|
|
8
|
+
import { SandboxesClient } from "./sandboxes.js";
|
|
9
|
+
import { ArtifactsClient } from "./artifacts.js";
|
|
9
10
|
import { Transport, type Destination } from "./http.js";
|
|
10
11
|
/** Application-principal access to a session's sandbox built-ins (workflows.md §8). */
|
|
11
12
|
export type SessionSandbox = {
|
|
@@ -28,6 +29,24 @@ export interface SessionView {
|
|
|
28
29
|
waits?: unknown;
|
|
29
30
|
[key: string]: unknown;
|
|
30
31
|
}
|
|
32
|
+
/** `AgentsClient.createSession`'s options. */
|
|
33
|
+
export interface CreateSessionOptions {
|
|
34
|
+
id?: string;
|
|
35
|
+
agentId: string;
|
|
36
|
+
ownerUserId: string;
|
|
37
|
+
info?: Record<string, unknown>;
|
|
38
|
+
requestId?: string;
|
|
39
|
+
vaultIds?: readonly string[];
|
|
40
|
+
credentialSelections?: readonly CredentialSelection[];
|
|
41
|
+
/**
|
|
42
|
+
* The session's sandbox: omit for the Tenant default, `false` for none, `{ id }` to attach a
|
|
43
|
+
* sandbox resource (`client.sandboxes`, Host feature `sandboxes`), or an inline sandbox
|
|
44
|
+
* (`image`, `network.allow`, `resources`) checked against the Tenant's limits. `{ session }`
|
|
45
|
+
* shares another session's and is deprecated: use `client.sandboxes.forSession()` or
|
|
46
|
+
* `ensure()` and `{ id }`. Fixed once the session exists.
|
|
47
|
+
*/
|
|
48
|
+
sandbox?: SandboxRequest;
|
|
49
|
+
}
|
|
31
50
|
export interface ActAsOptions {
|
|
32
51
|
/** What the subject may do; the Runtime grants nothing else. Default `["sessions:own"]`. */
|
|
33
52
|
scopes?: readonly SubjectScope[];
|
|
@@ -44,13 +63,15 @@ export declare class AgentsClient {
|
|
|
44
63
|
* themselves and hold the key; nothing is minted, cached or refreshed.
|
|
45
64
|
*/
|
|
46
65
|
as(subject: string, options?: ActAsOptions): AgentsClient;
|
|
66
|
+
/** The signing keys and the public keys that verify delivery tokens. Application key only. */
|
|
67
|
+
get access(): AccessClient;
|
|
47
68
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
69
|
+
* Sandboxes as a resource (Host feature `sandboxes`): `ensure` one by id, `list` them by
|
|
70
|
+
* label, `delete` one, or give a session its own with `forSession`.
|
|
50
71
|
*/
|
|
51
|
-
get
|
|
52
|
-
/**
|
|
53
|
-
get
|
|
72
|
+
get sandboxes(): SandboxesClient;
|
|
73
|
+
/** File artifacts: upload, list, download with Range, capability links (protocol 6). */
|
|
74
|
+
get artifacts(): ArtifactsClient;
|
|
54
75
|
/** The Runtime's protocol features, including optional ones such as `transcript-events`. */
|
|
55
76
|
hostFeatures(options?: {
|
|
56
77
|
signal?: AbortSignal;
|
|
@@ -72,29 +93,26 @@ export declare class AgentsClient {
|
|
|
72
93
|
implementationVersion: string;
|
|
73
94
|
requestId?: string;
|
|
74
95
|
}): Promise<unknown>;
|
|
75
|
-
createSession(options:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
vaultIds?: readonly string[];
|
|
82
|
-
credentialSelections?: readonly CredentialSelection[];
|
|
83
|
-
/**
|
|
84
|
-
* The session's sandbox: omit for the Tenant default, `false` for none, `{ session }` to share
|
|
85
|
-
* another session's, or an inline sandbox (`image`, `network.allow`, `resources`) checked
|
|
86
|
-
* against the Tenant's limits. Fixed once the session exists.
|
|
87
|
-
*/
|
|
88
|
-
sandbox?: SandboxRequest;
|
|
89
|
-
}): Promise<SessionClient>;
|
|
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
|
+
*/
|
|
90
102
|
createVault(options: {
|
|
91
103
|
name: string;
|
|
92
|
-
ownerUserId: string;
|
|
93
104
|
metadata?: Record<string, string>;
|
|
94
105
|
idempotencyKey: string;
|
|
95
106
|
requestId?: string;
|
|
96
|
-
}
|
|
97
|
-
|
|
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<{
|
|
98
116
|
vaults: VaultInfo[];
|
|
99
117
|
}>;
|
|
100
118
|
getVault(vaultId: string, signal?: AbortSignal): Promise<VaultInfo>;
|
|
@@ -181,6 +199,17 @@ export declare class SessionClient {
|
|
|
181
199
|
cursor: string | null;
|
|
182
200
|
requestId: string;
|
|
183
201
|
}>;
|
|
202
|
+
/**
|
|
203
|
+
* Start a turn with text and file parts (protocol 6): a file part names an artifact (upload
|
|
204
|
+
* it with `client.artifacts.upload`), at a version or its latest, and the model reads it, an
|
|
205
|
+
* image as an image and a text file as text.
|
|
206
|
+
*/
|
|
207
|
+
inputParts(parts: readonly MessagePart[], options: CommandOptions): Promise<{
|
|
208
|
+
status: "accepted";
|
|
209
|
+
turnId: string | null;
|
|
210
|
+
cursor: string | null;
|
|
211
|
+
requestId: string;
|
|
212
|
+
}>;
|
|
184
213
|
approve(interactionId: string, approved: boolean, options: CommandOptions): Promise<{
|
|
185
214
|
status: "accepted";
|
|
186
215
|
turnId: string | null;
|
|
@@ -279,4 +308,6 @@ export declare class SessionClient {
|
|
|
279
308
|
export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
|
|
280
309
|
export type { Destination, IncompatibleReason } from "./http.js";
|
|
281
310
|
export type { LiveEvent } from "@nylorun/core/contracts";
|
|
282
|
-
export { AccessClient,
|
|
311
|
+
export { AccessClient, SigningKeysClient } from "./access.js";
|
|
312
|
+
export { SandboxesClient } from "./sandboxes.js";
|
|
313
|
+
export type { ForSessionOptions, SandboxSpec, SessionSandboxHandle } from "./sandboxes.js";
|
package/dist/session-client.js
CHANGED
|
@@ -2,7 +2,9 @@ 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
|
|
5
|
+
import { AccessClient } from "./access.js";
|
|
6
|
+
import { SandboxesClient } from "./sandboxes.js";
|
|
7
|
+
import { ArtifactsClient } from "./artifacts.js";
|
|
6
8
|
import { Transport, id, segment } from "./http.js";
|
|
7
9
|
import { observeSSE } from "./sse.js";
|
|
8
10
|
function sessionSandboxOf(transport, sessionId) {
|
|
@@ -93,16 +95,20 @@ export class AgentsClient {
|
|
|
93
95
|
subject,
|
|
94
96
|
});
|
|
95
97
|
}
|
|
98
|
+
/** The signing keys and the public keys that verify delivery tokens. Application key only. */
|
|
99
|
+
get access() {
|
|
100
|
+
return new AccessClient(this.transport);
|
|
101
|
+
}
|
|
96
102
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
103
|
+
* Sandboxes as a resource (Host feature `sandboxes`): `ensure` one by id, `list` them by
|
|
104
|
+
* label, `delete` one, or give a session its own with `forSession`.
|
|
99
105
|
*/
|
|
100
|
-
get
|
|
101
|
-
return new
|
|
106
|
+
get sandboxes() {
|
|
107
|
+
return new SandboxesClient(this.transport, (options) => this.createSession(options));
|
|
102
108
|
}
|
|
103
|
-
/**
|
|
104
|
-
get
|
|
105
|
-
return new
|
|
109
|
+
/** File artifacts: upload, list, download with Range, capability links (protocol 6). */
|
|
110
|
+
get artifacts() {
|
|
111
|
+
return new ArtifactsClient(this.transport);
|
|
106
112
|
}
|
|
107
113
|
/** The Runtime's protocol features, including optional ones such as `transcript-events`. */
|
|
108
114
|
hostFeatures(options = {}) {
|
|
@@ -172,17 +178,27 @@ export class AgentsClient {
|
|
|
172
178
|
});
|
|
173
179
|
return this.session(sessionId);
|
|
174
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
|
+
*/
|
|
175
186
|
createVault(options) {
|
|
176
187
|
return this.transport.json("/v1/vaults", "POST", {
|
|
177
188
|
requestId: options.requestId ?? id(),
|
|
178
189
|
idempotencyKey: options.idempotencyKey,
|
|
179
190
|
name: options.name,
|
|
180
|
-
|
|
191
|
+
...(options.scope === "installation"
|
|
192
|
+
? { scope: "installation" }
|
|
193
|
+
: { ownerUserId: options.ownerUserId }),
|
|
181
194
|
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
182
195
|
});
|
|
183
196
|
}
|
|
197
|
+
/** The installation vaults, after the vaults of `ownerUserId` when it is given. */
|
|
184
198
|
listVaults(ownerUserId, signal) {
|
|
185
|
-
return this.transport.json(
|
|
199
|
+
return this.transport.json(ownerUserId === undefined
|
|
200
|
+
? "/v1/vaults"
|
|
201
|
+
: `/v1/vaults?ownerUserId=${encodeURIComponent(ownerUserId)}`, "GET", undefined, signal);
|
|
186
202
|
}
|
|
187
203
|
getVault(vaultId, signal) {
|
|
188
204
|
return this.transport.json(`/v1/vaults/${segment(vaultId)}`, "GET", undefined, signal);
|
|
@@ -255,6 +271,19 @@ export class SessionClient {
|
|
|
255
271
|
? { ...base, content: value }
|
|
256
272
|
: { ...base, data: value }, options.signal);
|
|
257
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Start a turn with text and file parts (protocol 6): a file part names an artifact (upload
|
|
276
|
+
* it with `client.artifacts.upload`), at a version or its latest, and the model reads it, an
|
|
277
|
+
* image as an image and a text file as text.
|
|
278
|
+
*/
|
|
279
|
+
inputParts(parts, options) {
|
|
280
|
+
return this.command({
|
|
281
|
+
type: "message",
|
|
282
|
+
requestId: options.requestId ?? id(),
|
|
283
|
+
idempotencyKey: options.idempotencyKey,
|
|
284
|
+
parts: [...parts],
|
|
285
|
+
}, options.signal);
|
|
286
|
+
}
|
|
258
287
|
approve(interactionId, approved, options) {
|
|
259
288
|
return this.command({
|
|
260
289
|
type: "approve",
|
|
@@ -372,4 +401,5 @@ export class SessionClient {
|
|
|
372
401
|
}
|
|
373
402
|
}
|
|
374
403
|
export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
|
|
375
|
-
export { AccessClient,
|
|
404
|
+
export { AccessClient, SigningKeysClient } from "./access.js";
|
|
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
|
|
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) {
|
package/dist/studio-embed.d.ts
CHANGED
|
@@ -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
|
|
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/dist/studio-embed.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
46
|
+
"@nylorun/core": "0.13.0-beta",
|
|
51
47
|
"gray-matter": "^4.0.3"
|
|
52
48
|
},
|
|
53
49
|
"peerDependencies": {
|
|
@@ -56,7 +52,7 @@
|
|
|
56
52
|
"devDependencies": {
|
|
57
53
|
"@types/node": "^24.0.0",
|
|
58
54
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
59
|
-
"vitest": "^5.0.
|
|
55
|
+
"vitest": "^5.0.2"
|
|
60
56
|
},
|
|
61
57
|
"engines": {
|
|
62
58
|
"node": ">=24"
|
package/dist/browser.d.ts
DELETED
|
@@ -1,97 +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
|
-
/** What the token callback returns: the token, or the app server's `{ token, expiresAt }`. */
|
|
5
|
-
export type TokenReply = string | {
|
|
6
|
-
token: string;
|
|
7
|
-
expiresAt?: string;
|
|
8
|
-
};
|
|
9
|
-
export interface BrowserClientOptions {
|
|
10
|
-
/** The Runtime's URL, as the page reaches it. */
|
|
11
|
-
url: string;
|
|
12
|
-
/** The Tenant's publishable key for this app (`nr_pub_…`). */
|
|
13
|
-
publishableKey: string;
|
|
14
|
-
/** Returns a subject token for the signed-in person, usually from your app server. */
|
|
15
|
-
token(signal?: AbortSignal): Promise<TokenReply>;
|
|
16
|
-
fetch?: typeof fetch;
|
|
17
|
-
}
|
|
18
|
-
interface Claims {
|
|
19
|
-
sub?: string;
|
|
20
|
-
exp?: number;
|
|
21
|
-
}
|
|
22
|
-
/** The token's claims, read without verifying (the Runtime verifies). */
|
|
23
|
-
export declare function readClaims(token: string): Claims;
|
|
24
|
-
/** A token kept in memory, fetched on demand, one fetch at a time. */
|
|
25
|
-
export declare class CachedToken implements TokenSource {
|
|
26
|
-
private readonly fetchToken;
|
|
27
|
-
private readonly now;
|
|
28
|
-
private current;
|
|
29
|
-
private pending;
|
|
30
|
-
constructor(fetchToken: (signal?: AbortSignal) => Promise<TokenReply>, now?: () => number);
|
|
31
|
-
get(signal?: AbortSignal): Promise<string>;
|
|
32
|
-
invalidate(): void;
|
|
33
|
-
/** The subject of the current token, fetching one if needed. */
|
|
34
|
-
subject(signal?: AbortSignal): Promise<string>;
|
|
35
|
-
}
|
|
36
|
-
/** The signed-in person's view of the Runtime: their sessions and vaults, the agents they may use. */
|
|
37
|
-
export declare class BrowserClient {
|
|
38
|
-
/** The underlying Tenant API client, for anything this class does not wrap. */
|
|
39
|
-
readonly client: AgentsClient;
|
|
40
|
-
readonly url: string;
|
|
41
|
-
readonly publishableKey: string;
|
|
42
|
-
private readonly tokens;
|
|
43
|
-
private readonly fetcher;
|
|
44
|
-
constructor(options: BrowserClientOptions);
|
|
45
|
-
/** The person the tokens are for. */
|
|
46
|
-
subject(signal?: AbortSignal): Promise<string>;
|
|
47
|
-
/** A current token, e.g. for a request this client does not make itself. */
|
|
48
|
-
token(signal?: AbortSignal): Promise<string>;
|
|
49
|
-
/** The agents the person may use: id, name and description. */
|
|
50
|
-
listAgents(signal?: AbortSignal): Promise<{
|
|
51
|
-
agents: {
|
|
52
|
-
agentId: string;
|
|
53
|
-
name?: string;
|
|
54
|
-
description?: string;
|
|
55
|
-
}[];
|
|
56
|
-
}>;
|
|
57
|
-
listSessions(agentId?: string, signal?: AbortSignal): Promise<{
|
|
58
|
-
sessions: import("./session-client.js").SessionView[];
|
|
59
|
-
}>;
|
|
60
|
-
session(sessionId: string): SessionClient;
|
|
61
|
-
/** A session of the person's own; the owner is always the token's subject. */
|
|
62
|
-
createSession(options: {
|
|
63
|
-
id?: string;
|
|
64
|
-
agentId: string;
|
|
65
|
-
vaultIds?: readonly string[];
|
|
66
|
-
credentialSelections?: readonly CredentialSelection[];
|
|
67
|
-
}): Promise<SessionClient>;
|
|
68
|
-
createVault(options: {
|
|
69
|
-
name: string;
|
|
70
|
-
idempotencyKey: string;
|
|
71
|
-
metadata?: Record<string, string>;
|
|
72
|
-
}): Promise<VaultInfo>;
|
|
73
|
-
listVaults(signal?: AbortSignal): Promise<{
|
|
74
|
-
vaults: VaultInfo[];
|
|
75
|
-
}>;
|
|
76
|
-
/** `fetch` with this client's credentials, retrying once after `401 token_expired`. */
|
|
77
|
-
private send;
|
|
78
|
-
/**
|
|
79
|
-
* For `@ag-ui/client`'s `HttpAgent`: `new HttpAgent({ url, fetch })`. The `fetch` adds the
|
|
80
|
-
* publishable key and a current token, and when the Runtime ends a run's stream because the
|
|
81
|
-
* token expired or was revoked, it reattaches with a new token from the last event, so the
|
|
82
|
-
* agent sees one run.
|
|
83
|
-
*/
|
|
84
|
-
agUi(agentId: string): {
|
|
85
|
-
url: string;
|
|
86
|
-
fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
87
|
-
};
|
|
88
|
-
/** The thread's messages, for `HttpAgent`'s `initialMessages`. */
|
|
89
|
-
agUiHistory(agentId: string, threadId: string, signal?: AbortSignal): Promise<unknown[]>;
|
|
90
|
-
/**
|
|
91
|
-
* Passes a run's frames through; on `nylorun.stream_closed` before the run finished, fetches
|
|
92
|
-
* the rest from the last event id and continues without repeating `RUN_STARTED`.
|
|
93
|
-
*/
|
|
94
|
-
private stitch;
|
|
95
|
-
}
|
|
96
|
-
export declare function createBrowserClient(options: BrowserClientOptions): BrowserClient;
|
|
97
|
-
export {};
|
package/dist/browser.js
DELETED
|
@@ -1,277 +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
|
-
/** 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
|
-
}
|