@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.
- package/CHANGELOG.md +131 -0
- package/README.md +287 -69
- package/dist/a2a/handler.d.ts +59 -0
- package/dist/a2a/handler.js +178 -0
- package/dist/a2a/index.d.ts +8 -0
- package/dist/a2a/index.js +7 -0
- package/dist/access.d.ts +101 -0
- package/dist/access.js +111 -0
- package/dist/action-handler.d.ts +64 -0
- package/dist/action-handler.js +283 -0
- package/dist/ag-ui/handler.d.ts +12 -4
- package/dist/ag-ui/handler.js +87 -281
- package/dist/ag-ui/index.d.ts +2 -1
- package/dist/ag-ui/index.js +2 -1
- package/dist/browser.d.ts +97 -0
- package/dist/browser.js +277 -0
- package/dist/client.d.ts +3 -230
- package/dist/client.js +2 -357
- package/dist/connection.d.ts +0 -1
- package/dist/connection.js +3 -9
- package/dist/delivery-token.d.ts +57 -0
- package/dist/delivery-token.js +150 -0
- package/dist/execute-action.d.ts +6 -0
- package/dist/execute-action.js +24 -0
- package/dist/http.d.ts +31 -1
- package/dist/http.js +74 -14
- package/dist/index.d.ts +9 -7
- package/dist/index.js +3 -3
- package/dist/sandbox/client.d.ts +4 -5
- package/dist/sandbox/client.js +4 -8
- package/dist/sandbox/index.d.ts +0 -2
- package/dist/sandbox/index.js +0 -1
- package/dist/served-definitions.d.ts +20 -0
- package/dist/served-definitions.js +100 -0
- package/dist/session-client.d.ts +282 -0
- package/dist/session-client.js +375 -0
- package/dist/sse.js +12 -0
- package/dist/studio-embed.d.ts +9 -0
- package/dist/studio-embed.js +8 -0
- package/dist/token-endpoint.d.ts +23 -0
- package/dist/token-endpoint.js +30 -0
- package/package.json +15 -8
- package/dist/ag-ui/history.d.ts +0 -7
- package/dist/ag-ui/history.js +0 -60
- package/dist/ag-ui/sse.d.ts +0 -9
- package/dist/ag-ui/sse.js +0 -7
- package/dist/ag-ui/translate.d.ts +0 -38
- package/dist/ag-ui/translate.js +0 -222
- package/dist/derived-credentials.d.ts +0 -5
- package/dist/derived-credentials.js +0 -17
- package/dist/executor.d.ts +0 -20
- package/dist/executor.js +0 -417
|
@@ -0,0 +1,282 @@
|
|
|
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.
|
|
5
|
+
*/
|
|
6
|
+
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, TokensClient } from "./access.js";
|
|
9
|
+
import { Transport, type Destination } from "./http.js";
|
|
10
|
+
/** Application-principal access to a session's sandbox built-ins (workflows.md §8). */
|
|
11
|
+
export type SessionSandbox = {
|
|
12
|
+
readonly [K in SandboxToolName]: (args: Record<string, unknown>) => Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
export interface AgentSource {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly manifest: AgentManifest | WorkflowManifest;
|
|
17
|
+
build?(): BuiltAgent | BuiltWorkflow;
|
|
18
|
+
getBinding?: BuiltAgent["getBinding"] | BuiltWorkflow["getBinding"];
|
|
19
|
+
}
|
|
20
|
+
/** Closures stay on the live binding. The published manifest cannot name them. */
|
|
21
|
+
export declare function assertNoMiddlewareClosures(agent: AgentSource): void;
|
|
22
|
+
export interface SessionView {
|
|
23
|
+
id: string;
|
|
24
|
+
agentId: string;
|
|
25
|
+
ownerUserId: string;
|
|
26
|
+
status: string;
|
|
27
|
+
activeTurnId: string | null;
|
|
28
|
+
waits?: unknown;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
export interface ActAsOptions {
|
|
32
|
+
/** What the subject may do; the Runtime grants nothing else. Default `["sessions:own"]`. */
|
|
33
|
+
scopes?: readonly SubjectScope[];
|
|
34
|
+
}
|
|
35
|
+
export declare class AgentsClient {
|
|
36
|
+
readonly transport: Transport;
|
|
37
|
+
/** The subject this client acts for (`as`), or `undefined` for the principal itself. */
|
|
38
|
+
readonly subject: string | undefined;
|
|
39
|
+
constructor(destination?: Destination);
|
|
40
|
+
/**
|
|
41
|
+
* A client that acts for `subject`: every call sends `Nylorun-Subject` and
|
|
42
|
+
* `Nylorun-Scopes`, and the Runtime limits it to the scopes and to the subject's own sessions
|
|
43
|
+
* and vaults (Host feature `subject-headers`). For app servers, which authenticate the person
|
|
44
|
+
* themselves and hold the key; nothing is minted, cached or refreshed.
|
|
45
|
+
*/
|
|
46
|
+
as(subject: string, options?: ActAsOptions): AgentsClient;
|
|
47
|
+
/**
|
|
48
|
+
* Mints subject tokens for signed-in people (Host feature `subject-tokens`), so their
|
|
49
|
+
* browser or app calls the Runtime directly. Application key only.
|
|
50
|
+
*/
|
|
51
|
+
get tokens(): TokensClient;
|
|
52
|
+
/** The access policy, signing keys and revocations (Host feature `subject-tokens`). */
|
|
53
|
+
get access(): AccessClient;
|
|
54
|
+
/** The Runtime's protocol features, including optional ones such as `transcript-events`. */
|
|
55
|
+
hostFeatures(options?: {
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}): Promise<readonly string[]>;
|
|
58
|
+
listAgents(options?: {
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}): Promise<{
|
|
61
|
+
agents: {
|
|
62
|
+
manifest: AgentManifest;
|
|
63
|
+
}[];
|
|
64
|
+
}>;
|
|
65
|
+
listSessions(options?: {
|
|
66
|
+
agentId?: string;
|
|
67
|
+
signal?: AbortSignal;
|
|
68
|
+
}): Promise<{
|
|
69
|
+
sessions: SessionView[];
|
|
70
|
+
}>;
|
|
71
|
+
saveAgent(agent: AgentSource | BuiltWorkflow, options: {
|
|
72
|
+
implementationVersion: string;
|
|
73
|
+
requestId?: string;
|
|
74
|
+
}): Promise<unknown>;
|
|
75
|
+
createSession(options: {
|
|
76
|
+
id?: string;
|
|
77
|
+
agentId: string;
|
|
78
|
+
ownerUserId: string;
|
|
79
|
+
info?: Record<string, unknown>;
|
|
80
|
+
requestId?: string;
|
|
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>;
|
|
90
|
+
createVault(options: {
|
|
91
|
+
name: string;
|
|
92
|
+
ownerUserId: string;
|
|
93
|
+
metadata?: Record<string, string>;
|
|
94
|
+
idempotencyKey: string;
|
|
95
|
+
requestId?: string;
|
|
96
|
+
}): Promise<VaultInfo>;
|
|
97
|
+
listVaults(ownerUserId: string, signal?: AbortSignal): Promise<{
|
|
98
|
+
vaults: VaultInfo[];
|
|
99
|
+
}>;
|
|
100
|
+
getVault(vaultId: string, signal?: AbortSignal): Promise<VaultInfo>;
|
|
101
|
+
deleteVault(vaultId: string): Promise<{
|
|
102
|
+
id: string;
|
|
103
|
+
}>;
|
|
104
|
+
createCredential(vaultId: string, options: {
|
|
105
|
+
name: string;
|
|
106
|
+
idempotencyKey: string;
|
|
107
|
+
requestId?: string;
|
|
108
|
+
auth: {
|
|
109
|
+
type: "bearer";
|
|
110
|
+
url: string;
|
|
111
|
+
token: string;
|
|
112
|
+
} | {
|
|
113
|
+
type: "oauth";
|
|
114
|
+
url: string;
|
|
115
|
+
accessToken: string;
|
|
116
|
+
expiresAt?: string | null;
|
|
117
|
+
refresh?: {
|
|
118
|
+
tokenEndpoint: string;
|
|
119
|
+
clientId: string;
|
|
120
|
+
refreshToken: string;
|
|
121
|
+
tokenEndpointAuth: {
|
|
122
|
+
type: "none";
|
|
123
|
+
} | {
|
|
124
|
+
type: "client_secret_basic";
|
|
125
|
+
clientSecret: string;
|
|
126
|
+
} | {
|
|
127
|
+
type: "client_secret_post";
|
|
128
|
+
clientSecret: string;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
}): Promise<CredentialInfo>;
|
|
133
|
+
listCredentials(vaultId: string, signal?: AbortSignal): Promise<{
|
|
134
|
+
credentials: CredentialInfo[];
|
|
135
|
+
}>;
|
|
136
|
+
getCredential(vaultId: string, credentialId: string, signal?: AbortSignal): Promise<CredentialInfo>;
|
|
137
|
+
rotateCredential(vaultId: string, credentialId: string, options: {
|
|
138
|
+
idempotencyKey: string;
|
|
139
|
+
requestId?: string;
|
|
140
|
+
auth: {
|
|
141
|
+
type: "bearer";
|
|
142
|
+
token: string;
|
|
143
|
+
} | {
|
|
144
|
+
type: "oauth";
|
|
145
|
+
accessToken: string;
|
|
146
|
+
expiresAt?: string | null;
|
|
147
|
+
};
|
|
148
|
+
}): Promise<CredentialInfo>;
|
|
149
|
+
deleteCredential(vaultId: string, credentialId: string): Promise<{
|
|
150
|
+
id: string;
|
|
151
|
+
}>;
|
|
152
|
+
session(sessionId: string): SessionClient;
|
|
153
|
+
}
|
|
154
|
+
export type CommandOptions = {
|
|
155
|
+
idempotencyKey: string;
|
|
156
|
+
requestId?: string;
|
|
157
|
+
signal?: AbortSignal;
|
|
158
|
+
};
|
|
159
|
+
export declare class SessionClient {
|
|
160
|
+
private readonly transport;
|
|
161
|
+
readonly id: string;
|
|
162
|
+
readonly sandbox: SessionSandbox;
|
|
163
|
+
constructor(transport: Transport, id: string);
|
|
164
|
+
private get path();
|
|
165
|
+
inspect(signal?: AbortSignal): Promise<SessionView>;
|
|
166
|
+
/** Pending waits / interactions from the session inspect view. */
|
|
167
|
+
pending(signal?: AbortSignal): Promise<{}>;
|
|
168
|
+
command(command: SessionCommand, signal?: AbortSignal): Promise<{
|
|
169
|
+
status: "accepted";
|
|
170
|
+
turnId: string | null;
|
|
171
|
+
cursor: string | null;
|
|
172
|
+
requestId: string;
|
|
173
|
+
}>;
|
|
174
|
+
/**
|
|
175
|
+
* Start a turn. Strings become `message.content`; any other JSON value becomes
|
|
176
|
+
* `message.data` (workflows.md §5).
|
|
177
|
+
*/
|
|
178
|
+
input(value: string | JsonValue, options: CommandOptions): Promise<{
|
|
179
|
+
status: "accepted";
|
|
180
|
+
turnId: string | null;
|
|
181
|
+
cursor: string | null;
|
|
182
|
+
requestId: string;
|
|
183
|
+
}>;
|
|
184
|
+
approve(interactionId: string, approved: boolean, options: CommandOptions): Promise<{
|
|
185
|
+
status: "accepted";
|
|
186
|
+
turnId: string | null;
|
|
187
|
+
cursor: string | null;
|
|
188
|
+
requestId: string;
|
|
189
|
+
}>;
|
|
190
|
+
respond(interactionId: string, value: JsonValue, options: CommandOptions): Promise<{
|
|
191
|
+
status: "accepted";
|
|
192
|
+
turnId: string | null;
|
|
193
|
+
cursor: string | null;
|
|
194
|
+
requestId: string;
|
|
195
|
+
}>;
|
|
196
|
+
cancel(options: CommandOptions & {
|
|
197
|
+
reason?: string;
|
|
198
|
+
}): Promise<{
|
|
199
|
+
status: "accepted";
|
|
200
|
+
turnId: string | null;
|
|
201
|
+
cursor: string | null;
|
|
202
|
+
requestId: string;
|
|
203
|
+
}>;
|
|
204
|
+
/** `agent` narrows to one agent used as a tool: its delegationId or its path. */
|
|
205
|
+
history(options?: {
|
|
206
|
+
cursor?: string;
|
|
207
|
+
agent?: string;
|
|
208
|
+
signal?: AbortSignal;
|
|
209
|
+
}): Promise<{
|
|
210
|
+
items: ({
|
|
211
|
+
schema: "nylorun.event/2";
|
|
212
|
+
eventId: string;
|
|
213
|
+
tenantId: string;
|
|
214
|
+
sessionId: string;
|
|
215
|
+
runId: string | null;
|
|
216
|
+
turnId: string | null;
|
|
217
|
+
incarnation: number;
|
|
218
|
+
epoch: number;
|
|
219
|
+
seq: number;
|
|
220
|
+
cursor: string;
|
|
221
|
+
time: string;
|
|
222
|
+
schemaVersion: number;
|
|
223
|
+
source: {
|
|
224
|
+
kind: "api" | "controller" | "edge" | "egress-gate" | "evidence" | "harness" | "human" | "init" | "loop" | "model-gate" | "operator" | "sandboxd" | "supervisor" | "tool-gate";
|
|
225
|
+
id: string;
|
|
226
|
+
};
|
|
227
|
+
evidence: "asserted" | "attested" | "claimed" | "measured" | "observed";
|
|
228
|
+
trace?: {
|
|
229
|
+
traceId: string;
|
|
230
|
+
spanId: string;
|
|
231
|
+
parentSpanId?: string | undefined;
|
|
232
|
+
} | undefined;
|
|
233
|
+
visibility: "internal" | "public" | "restricted";
|
|
234
|
+
retention: "full" | "metadata";
|
|
235
|
+
type: string;
|
|
236
|
+
payload: unknown;
|
|
237
|
+
} | import("@nylorun/core/contracts").SessionEvent)[];
|
|
238
|
+
cursor: string | null;
|
|
239
|
+
}>;
|
|
240
|
+
observe(options?: {
|
|
241
|
+
cursor?: string;
|
|
242
|
+
signal?: AbortSignal;
|
|
243
|
+
follow?: boolean;
|
|
244
|
+
}): AsyncGenerator<{
|
|
245
|
+
schema: "nylorun.event/2";
|
|
246
|
+
eventId: string;
|
|
247
|
+
tenantId: string;
|
|
248
|
+
sessionId: string;
|
|
249
|
+
runId: string | null;
|
|
250
|
+
turnId: string | null;
|
|
251
|
+
incarnation: number;
|
|
252
|
+
epoch: number;
|
|
253
|
+
seq: number;
|
|
254
|
+
cursor: string;
|
|
255
|
+
time: string;
|
|
256
|
+
schemaVersion: number;
|
|
257
|
+
source: {
|
|
258
|
+
kind: "api" | "controller" | "edge" | "egress-gate" | "evidence" | "harness" | "human" | "init" | "loop" | "model-gate" | "operator" | "sandboxd" | "supervisor" | "tool-gate";
|
|
259
|
+
id: string;
|
|
260
|
+
};
|
|
261
|
+
evidence: "asserted" | "attested" | "claimed" | "measured" | "observed";
|
|
262
|
+
trace?: {
|
|
263
|
+
traceId: string;
|
|
264
|
+
spanId: string;
|
|
265
|
+
parentSpanId?: string | undefined;
|
|
266
|
+
} | undefined;
|
|
267
|
+
visibility: "internal" | "public" | "restricted";
|
|
268
|
+
retention: "full" | "metadata";
|
|
269
|
+
type: string;
|
|
270
|
+
payload: unknown;
|
|
271
|
+
}, void, unknown>;
|
|
272
|
+
private observeOne;
|
|
273
|
+
/**
|
|
274
|
+
* Own session stream plus linked agent sessions announced in `node.agent`
|
|
275
|
+
* events, merged by `time` then arrival order (workflows.md §11).
|
|
276
|
+
*/
|
|
277
|
+
private observeFollowing;
|
|
278
|
+
}
|
|
279
|
+
export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
|
|
280
|
+
export type { Destination, IncompatibleReason } from "./http.js";
|
|
281
|
+
export type { LiveEvent } from "@nylorun/core/contracts";
|
|
282
|
+
export { AccessClient, PublishableKeysClient, SigningKeysClient, TokensClient, } from "./access.js";
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { SANDBOX_TOOL_NAMES, delegateOf, isBuiltWorkflow, isSandboxToolName, } from "@nylorun/core/define";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { SCOPES_HEADER, SUBJECT_HEADER } from "@nylorun/core/compatibility";
|
|
4
|
+
import { parseSessionEvent, parseSubjectHeaders, } from "@nylorun/core/contracts";
|
|
5
|
+
import { AccessClient, TokensClient } from "./access.js";
|
|
6
|
+
import { Transport, id, segment } from "./http.js";
|
|
7
|
+
import { observeSSE } from "./sse.js";
|
|
8
|
+
function sessionSandboxOf(transport, sessionId) {
|
|
9
|
+
const call = (tool, args) => transport.json(`/v1/sessions/${segment(sessionId)}/sandbox/${segment(tool)}`, "POST", args);
|
|
10
|
+
const sandbox = {};
|
|
11
|
+
for (const tool of SANDBOX_TOOL_NAMES) {
|
|
12
|
+
if (!isSandboxToolName(tool))
|
|
13
|
+
continue;
|
|
14
|
+
sandbox[tool] = (args) => call(tool, args);
|
|
15
|
+
}
|
|
16
|
+
return sandbox;
|
|
17
|
+
}
|
|
18
|
+
function linkedSessionId(event) {
|
|
19
|
+
if (event.type !== "node.agent")
|
|
20
|
+
return undefined;
|
|
21
|
+
const payload = event.payload;
|
|
22
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
23
|
+
return undefined;
|
|
24
|
+
const sessionId = payload.sessionId;
|
|
25
|
+
return typeof sessionId === "string" && sessionId.length > 0
|
|
26
|
+
? sessionId
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
const middlewareClosure = "Hosted definitions support declarative capabilities and before/after hooks, not middleware closures";
|
|
30
|
+
/** The agent's declarations, then those of each agent it uses as a tool, keyed `<child>/<capability>`. */
|
|
31
|
+
function declarationsOf(agent) {
|
|
32
|
+
if (isBuiltWorkflow(agent) || agent.manifest.kind === "workflow")
|
|
33
|
+
return [];
|
|
34
|
+
const built = agent.build?.() ?? agent;
|
|
35
|
+
if (isBuiltWorkflow(built))
|
|
36
|
+
return [];
|
|
37
|
+
const binding = built.getBinding?.();
|
|
38
|
+
if (!binding || !("declarations" in binding))
|
|
39
|
+
return [];
|
|
40
|
+
const found = (binding.declarations ?? []).map((item) => ({ key: item.id, item }));
|
|
41
|
+
for (const tool of binding.tools ?? []) {
|
|
42
|
+
const delegate = delegateOf(tool);
|
|
43
|
+
const child = delegate?.agent;
|
|
44
|
+
for (const item of child?.getBinding().declarations ?? [])
|
|
45
|
+
found.push({ key: `${child.id}/${item.id}`, item });
|
|
46
|
+
// A flow agent used as a tool: its agents' declarations, keyed `<flow>/<agent>/<capability>`.
|
|
47
|
+
const flow = delegate?.workflow;
|
|
48
|
+
for (const leaf of Object.values(flow?.getBinding().agents ?? {}))
|
|
49
|
+
for (const item of leaf.declarations ?? [])
|
|
50
|
+
found.push({ key: `${flow.id}/${leaf.manifest.id}/${item.id}`, item });
|
|
51
|
+
}
|
|
52
|
+
return found;
|
|
53
|
+
}
|
|
54
|
+
function pluginRootsOf(agent) {
|
|
55
|
+
const roots = {};
|
|
56
|
+
for (const { key, item } of declarationsOf(agent))
|
|
57
|
+
if (item.pluginRoot)
|
|
58
|
+
roots[key] = item.pluginRoot;
|
|
59
|
+
return Object.keys(roots).length === 0 ? undefined : roots;
|
|
60
|
+
}
|
|
61
|
+
/** Closures stay on the live binding. The published manifest cannot name them. */
|
|
62
|
+
export function assertNoMiddlewareClosures(agent) {
|
|
63
|
+
if (declarationsOf(agent).some(({ item }) => item.hasMiddleware))
|
|
64
|
+
throw new Error(middlewareClosure);
|
|
65
|
+
}
|
|
66
|
+
export class AgentsClient {
|
|
67
|
+
transport;
|
|
68
|
+
/** The subject this client acts for (`as`), or `undefined` for the principal itself. */
|
|
69
|
+
subject;
|
|
70
|
+
constructor(destination = {}) {
|
|
71
|
+
this.transport = new Transport(destination);
|
|
72
|
+
this.subject = undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A client that acts for `subject`: every call sends `Nylorun-Subject` and
|
|
76
|
+
* `Nylorun-Scopes`, and the Runtime limits it to the scopes and to the subject's own sessions
|
|
77
|
+
* and vaults (Host feature `subject-headers`). For app servers, which authenticate the person
|
|
78
|
+
* themselves and hold the key; nothing is minted, cached or refreshed.
|
|
79
|
+
*/
|
|
80
|
+
as(subject, options = {}) {
|
|
81
|
+
if (this.subject !== undefined)
|
|
82
|
+
throw new Error(`This client already acts for ${this.subject}`);
|
|
83
|
+
const scopes = [...(options.scopes ?? ["sessions:own"])].join(" ");
|
|
84
|
+
const parsed = parseSubjectHeaders(subject, scopes);
|
|
85
|
+
if (!parsed.ok)
|
|
86
|
+
throw new TypeError(parsed.message);
|
|
87
|
+
const client = Object.create(AgentsClient.prototype);
|
|
88
|
+
return Object.assign(client, {
|
|
89
|
+
transport: this.transport.withHeaders({
|
|
90
|
+
[SUBJECT_HEADER]: subject,
|
|
91
|
+
[SCOPES_HEADER]: [...parsed.scopes].join(" "),
|
|
92
|
+
}),
|
|
93
|
+
subject,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Mints subject tokens for signed-in people (Host feature `subject-tokens`), so their
|
|
98
|
+
* browser or app calls the Runtime directly. Application key only.
|
|
99
|
+
*/
|
|
100
|
+
get tokens() {
|
|
101
|
+
return new TokensClient(this.transport);
|
|
102
|
+
}
|
|
103
|
+
/** The access policy, signing keys and revocations (Host feature `subject-tokens`). */
|
|
104
|
+
get access() {
|
|
105
|
+
return new AccessClient(this.transport);
|
|
106
|
+
}
|
|
107
|
+
/** The Runtime's protocol features, including optional ones such as `transcript-events`. */
|
|
108
|
+
hostFeatures(options = {}) {
|
|
109
|
+
return this.transport.hostFeatures(options.signal);
|
|
110
|
+
}
|
|
111
|
+
listAgents(options = {}) {
|
|
112
|
+
return this.transport.json("/v1/agents", "GET", undefined, options.signal);
|
|
113
|
+
}
|
|
114
|
+
listSessions(options = {}) {
|
|
115
|
+
const query = options.agentId
|
|
116
|
+
? `?agentId=${encodeURIComponent(options.agentId)}`
|
|
117
|
+
: "";
|
|
118
|
+
return this.transport.json(`/v1/sessions${query}`, "GET", undefined, options.signal);
|
|
119
|
+
}
|
|
120
|
+
async saveAgent(agent, options) {
|
|
121
|
+
if (isBuiltWorkflow(agent) && agent.manifest.workflowSchemaVersion === 2) {
|
|
122
|
+
// A v2 workflow embeds its agents: one document, carrying their plugin roots.
|
|
123
|
+
const pluginRoots = {};
|
|
124
|
+
for (const binding of Object.values(agent.getBinding().agents)) {
|
|
125
|
+
const leaf = { id: binding.manifest.id, manifest: binding.manifest, getBinding: () => binding };
|
|
126
|
+
assertNoMiddlewareClosures(leaf);
|
|
127
|
+
for (const [key, root] of Object.entries(pluginRootsOf(leaf) ?? {}))
|
|
128
|
+
pluginRoots[`${leaf.id}/${key}`] = root;
|
|
129
|
+
}
|
|
130
|
+
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
131
|
+
requestId: options.requestId ?? id(),
|
|
132
|
+
manifest: agent.manifest,
|
|
133
|
+
implementationVersion: options.implementationVersion,
|
|
134
|
+
...(Object.keys(pluginRoots).length === 0 ? {} : { pluginRoots }),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (isBuiltWorkflow(agent)) {
|
|
138
|
+
for (const binding of Object.values(agent.getBinding().agents)) {
|
|
139
|
+
await this.saveAgent({
|
|
140
|
+
id: binding.manifest.id,
|
|
141
|
+
manifest: binding.manifest,
|
|
142
|
+
getBinding: () => binding,
|
|
143
|
+
}, options);
|
|
144
|
+
}
|
|
145
|
+
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
146
|
+
requestId: options.requestId ?? id(),
|
|
147
|
+
manifest: agent.manifest,
|
|
148
|
+
implementationVersion: options.implementationVersion,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
assertNoMiddlewareClosures(agent);
|
|
152
|
+
const pluginRoots = pluginRootsOf(agent);
|
|
153
|
+
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
154
|
+
requestId: options.requestId ?? id(),
|
|
155
|
+
manifest: agent.manifest,
|
|
156
|
+
implementationVersion: options.implementationVersion,
|
|
157
|
+
...(pluginRoots === undefined ? {} : { pluginRoots }),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async createSession(options) {
|
|
161
|
+
const sessionId = options.id ?? id();
|
|
162
|
+
await this.transport.json(`/v1/sessions/${segment(sessionId)}`, "PUT", {
|
|
163
|
+
requestId: options.requestId ?? id(),
|
|
164
|
+
agentId: options.agentId,
|
|
165
|
+
ownerUserId: options.ownerUserId,
|
|
166
|
+
...(options.info ? { info: options.info } : {}),
|
|
167
|
+
...(options.vaultIds ? { vaultIds: options.vaultIds } : {}),
|
|
168
|
+
...(options.credentialSelections
|
|
169
|
+
? { credentialSelections: options.credentialSelections }
|
|
170
|
+
: {}),
|
|
171
|
+
...(options.sandbox === undefined ? {} : { sandbox: options.sandbox }),
|
|
172
|
+
});
|
|
173
|
+
return this.session(sessionId);
|
|
174
|
+
}
|
|
175
|
+
createVault(options) {
|
|
176
|
+
return this.transport.json("/v1/vaults", "POST", {
|
|
177
|
+
requestId: options.requestId ?? id(),
|
|
178
|
+
idempotencyKey: options.idempotencyKey,
|
|
179
|
+
name: options.name,
|
|
180
|
+
ownerUserId: options.ownerUserId,
|
|
181
|
+
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
listVaults(ownerUserId, signal) {
|
|
185
|
+
return this.transport.json(`/v1/vaults?ownerUserId=${encodeURIComponent(ownerUserId)}`, "GET", undefined, signal);
|
|
186
|
+
}
|
|
187
|
+
getVault(vaultId, signal) {
|
|
188
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}`, "GET", undefined, signal);
|
|
189
|
+
}
|
|
190
|
+
deleteVault(vaultId) {
|
|
191
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}`, "DELETE");
|
|
192
|
+
}
|
|
193
|
+
createCredential(vaultId, options) {
|
|
194
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials`, "POST", {
|
|
195
|
+
requestId: options.requestId ?? id(),
|
|
196
|
+
idempotencyKey: options.idempotencyKey,
|
|
197
|
+
name: options.name,
|
|
198
|
+
auth: options.auth,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
listCredentials(vaultId, signal) {
|
|
202
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials`, "GET", undefined, signal);
|
|
203
|
+
}
|
|
204
|
+
getCredential(vaultId, credentialId, signal) {
|
|
205
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials/${segment(credentialId)}`, "GET", undefined, signal);
|
|
206
|
+
}
|
|
207
|
+
rotateCredential(vaultId, credentialId, options) {
|
|
208
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials/${segment(credentialId)}`, "POST", {
|
|
209
|
+
requestId: options.requestId ?? id(),
|
|
210
|
+
idempotencyKey: options.idempotencyKey,
|
|
211
|
+
auth: options.auth,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
deleteCredential(vaultId, credentialId) {
|
|
215
|
+
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials/${segment(credentialId)}`, "DELETE");
|
|
216
|
+
}
|
|
217
|
+
session(sessionId) {
|
|
218
|
+
return new SessionClient(this.transport, sessionId);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export class SessionClient {
|
|
222
|
+
transport;
|
|
223
|
+
id;
|
|
224
|
+
sandbox;
|
|
225
|
+
constructor(transport, id) {
|
|
226
|
+
this.transport = transport;
|
|
227
|
+
this.id = id;
|
|
228
|
+
this.sandbox = sessionSandboxOf(transport, id);
|
|
229
|
+
}
|
|
230
|
+
get path() {
|
|
231
|
+
return `/v1/sessions/${segment(this.id)}`;
|
|
232
|
+
}
|
|
233
|
+
inspect(signal) {
|
|
234
|
+
return this.transport.json(this.path, "GET", undefined, signal);
|
|
235
|
+
}
|
|
236
|
+
/** Pending waits / interactions from the session inspect view. */
|
|
237
|
+
async pending(signal) {
|
|
238
|
+
const view = await this.inspect(signal);
|
|
239
|
+
return view.waits ?? [];
|
|
240
|
+
}
|
|
241
|
+
command(command, signal) {
|
|
242
|
+
return this.transport.json(`${this.path}/commands`, "POST", command, signal);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Start a turn. Strings become `message.content`; any other JSON value becomes
|
|
246
|
+
* `message.data` (workflows.md §5).
|
|
247
|
+
*/
|
|
248
|
+
input(value, options) {
|
|
249
|
+
const base = {
|
|
250
|
+
type: "message",
|
|
251
|
+
requestId: options.requestId ?? id(),
|
|
252
|
+
idempotencyKey: options.idempotencyKey,
|
|
253
|
+
};
|
|
254
|
+
return this.command(typeof value === "string"
|
|
255
|
+
? { ...base, content: value }
|
|
256
|
+
: { ...base, data: value }, options.signal);
|
|
257
|
+
}
|
|
258
|
+
approve(interactionId, approved, options) {
|
|
259
|
+
return this.command({
|
|
260
|
+
type: "approve",
|
|
261
|
+
interactionId,
|
|
262
|
+
approved,
|
|
263
|
+
requestId: options.requestId ?? id(),
|
|
264
|
+
idempotencyKey: options.idempotencyKey,
|
|
265
|
+
}, options.signal);
|
|
266
|
+
}
|
|
267
|
+
respond(interactionId, value, options) {
|
|
268
|
+
return this.command({
|
|
269
|
+
type: "respond",
|
|
270
|
+
interactionId,
|
|
271
|
+
value,
|
|
272
|
+
requestId: options.requestId ?? id(),
|
|
273
|
+
idempotencyKey: options.idempotencyKey,
|
|
274
|
+
}, options.signal);
|
|
275
|
+
}
|
|
276
|
+
cancel(options) {
|
|
277
|
+
return this.command({
|
|
278
|
+
type: "cancel",
|
|
279
|
+
requestId: options.requestId ?? id(),
|
|
280
|
+
idempotencyKey: options.idempotencyKey,
|
|
281
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
282
|
+
}, options.signal);
|
|
283
|
+
}
|
|
284
|
+
/** `agent` narrows to one agent used as a tool: its delegationId or its path. */
|
|
285
|
+
async history(options = {}) {
|
|
286
|
+
const query = new URLSearchParams();
|
|
287
|
+
if (options.cursor)
|
|
288
|
+
query.set("cursor", options.cursor);
|
|
289
|
+
if (options.agent)
|
|
290
|
+
query.set("agent", options.agent);
|
|
291
|
+
const search = query.toString();
|
|
292
|
+
const page = z
|
|
293
|
+
.object({ items: z.array(z.unknown()), cursor: z.string().nullable() })
|
|
294
|
+
.parse(await this.transport.json(`${this.path}/items${search ? `?${search}` : ""}`, "GET", undefined, options.signal));
|
|
295
|
+
// Typed when the catalog knows the type; a newer Runtime's type stays a bare envelope.
|
|
296
|
+
return { items: page.items.map(parseSessionEvent), cursor: page.cursor };
|
|
297
|
+
}
|
|
298
|
+
async *observe(options = {}) {
|
|
299
|
+
if (!options.follow) {
|
|
300
|
+
yield* this.observeOne(this.id, options);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
yield* this.observeFollowing(options);
|
|
304
|
+
}
|
|
305
|
+
async *observeOne(sessionId, options) {
|
|
306
|
+
for await (const frame of observeSSE(this.transport, `/v1/sessions/${segment(sessionId)}/events`, options)) {
|
|
307
|
+
if (frame.event === "heartbeat" || frame.event === "ready")
|
|
308
|
+
continue;
|
|
309
|
+
yield parseSessionEvent(JSON.parse(frame.data));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Own session stream plus linked agent sessions announced in `node.agent`
|
|
314
|
+
* events, merged by `time` then arrival order (workflows.md §11).
|
|
315
|
+
*/
|
|
316
|
+
async *observeFollowing(options) {
|
|
317
|
+
const signal = options.signal;
|
|
318
|
+
const linked = new Set();
|
|
319
|
+
const queue = [];
|
|
320
|
+
let wait;
|
|
321
|
+
let open = 1;
|
|
322
|
+
let failed;
|
|
323
|
+
const wake = () => {
|
|
324
|
+
wait?.();
|
|
325
|
+
wait = undefined;
|
|
326
|
+
};
|
|
327
|
+
const push = (event) => {
|
|
328
|
+
queue.push(event);
|
|
329
|
+
queue.sort((a, b) => {
|
|
330
|
+
const byTime = a.time.localeCompare(b.time);
|
|
331
|
+
return byTime !== 0 ? byTime : a.cursor.localeCompare(b.cursor);
|
|
332
|
+
});
|
|
333
|
+
wake();
|
|
334
|
+
};
|
|
335
|
+
const pump = async (sessionId, cursor) => {
|
|
336
|
+
try {
|
|
337
|
+
for await (const event of this.observeOne(sessionId, { cursor, signal })) {
|
|
338
|
+
push(event);
|
|
339
|
+
const link = linkedSessionId(event);
|
|
340
|
+
if (link && link !== this.id && !linked.has(link)) {
|
|
341
|
+
linked.add(link);
|
|
342
|
+
open += 1;
|
|
343
|
+
void pump(link).finally(() => {
|
|
344
|
+
open -= 1;
|
|
345
|
+
wake();
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (!signal?.aborted)
|
|
352
|
+
failed = error;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
void pump(this.id, options.cursor).finally(() => {
|
|
356
|
+
open -= 1;
|
|
357
|
+
wake();
|
|
358
|
+
});
|
|
359
|
+
while (!signal?.aborted) {
|
|
360
|
+
if (failed)
|
|
361
|
+
throw failed;
|
|
362
|
+
if (queue.length) {
|
|
363
|
+
yield queue.shift();
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (open <= 0)
|
|
367
|
+
return;
|
|
368
|
+
await new Promise((resolve) => {
|
|
369
|
+
wait = resolve;
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
|
|
375
|
+
export { AccessClient, PublishableKeysClient, SigningKeysClient, TokensClient, } from "./access.js";
|
package/dist/sse.js
CHANGED
|
@@ -65,7 +65,11 @@ 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
|
|
69
|
+
// at once (the transport fetches a new token), but only once in a row.
|
|
70
|
+
let closedByRuntime = 0;
|
|
68
71
|
while (!signal.aborted) {
|
|
72
|
+
let reconnectNow = false;
|
|
69
73
|
try {
|
|
70
74
|
const response = await transport.request(path, {
|
|
71
75
|
signal,
|
|
@@ -75,7 +79,13 @@ export async function* observeSSE(transport, path, options = {}) {
|
|
|
75
79
|
},
|
|
76
80
|
});
|
|
77
81
|
for await (const event of readSSE(response, signal)) {
|
|
82
|
+
if (event.event === "nylorun.closed") {
|
|
83
|
+
closedByRuntime += 1;
|
|
84
|
+
reconnectNow = closedByRuntime === 1;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
78
87
|
retry = 250;
|
|
88
|
+
closedByRuntime = 0;
|
|
79
89
|
if (event.id)
|
|
80
90
|
cursor = event.id;
|
|
81
91
|
yield event;
|
|
@@ -90,6 +100,8 @@ export async function* observeSSE(transport, path, options = {}) {
|
|
|
90
100
|
![408, 429].includes(error.status))
|
|
91
101
|
throw error;
|
|
92
102
|
}
|
|
103
|
+
if (reconnectNow)
|
|
104
|
+
continue;
|
|
93
105
|
if (!signal.aborted)
|
|
94
106
|
await delay(retry, signal).catch(() => { });
|
|
95
107
|
retry = Math.min(30000, retry * 2);
|