@nylorun/agents 0.13.0-beta → 0.16.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 +139 -0
- package/README.md +220 -235
- package/dist/access.d.ts +6 -97
- package/dist/access.js +1 -103
- package/dist/ag-ui/handler.d.ts +3 -6
- package/dist/ag-ui/handler.js +2 -2
- package/dist/artifacts.d.ts +1 -1
- package/dist/files.d.ts +31 -0
- package/dist/files.js +60 -0
- package/dist/http.d.ts +6 -13
- package/dist/http.js +4 -16
- package/dist/index.d.ts +8 -16
- package/dist/index.js +4 -7
- package/dist/plugins/load.d.ts +3 -2
- package/dist/plugins/load.js +40 -100
- package/dist/plugins/plugin.d.ts +6 -1
- package/dist/plugins/plugin.js +20 -7
- package/dist/reads.d.ts +17 -0
- package/dist/reads.js +17 -0
- package/dist/sandboxes.d.ts +29 -0
- package/dist/sandboxes.js +10 -1
- package/dist/session-client.d.ts +94 -112
- package/dist/session-client.js +80 -107
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/load.d.ts +14 -3
- package/dist/skills/load.js +50 -32
- package/dist/skills/skills.d.ts +6 -4
- package/dist/skills/skills.js +17 -10
- 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/action-handler.d.ts +0 -63
- package/dist/action-handler.js +0 -279
- package/dist/browser.d.ts +0 -104
- package/dist/browser.js +0 -281
- package/dist/delivery-token.d.ts +0 -62
- package/dist/delivery-token.js +0 -159
- package/dist/execute-action.d.ts +0 -16
- package/dist/execute-action.js +0 -288
- package/dist/plugins/launch.d.ts +0 -17
- package/dist/plugins/launch.js +0 -71
- package/dist/sandbox/client.d.ts +0 -63
- package/dist/sandbox/client.js +0 -29
- package/dist/sandbox/index.d.ts +0 -2
- package/dist/sandbox/index.js +0 -1
- package/dist/served-definitions.d.ts +0 -20
- package/dist/served-definitions.js +0 -100
- package/dist/token-endpoint.d.ts +0 -23
- package/dist/token-endpoint.js +0 -30
package/dist/session-client.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { SANDBOX_TOOL_NAMES, delegateOf, isBuiltWorkflow, isSandboxToolName, } from "@nylorun/core/define";
|
|
1
|
+
import { SANDBOX_TOOL_NAMES, codeToolRefusal, delegateOf, isBuiltWorkflow, isSandboxToolName, } from "@nylorun/core/define";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { SCOPES_HEADER, SUBJECT_HEADER } from "@nylorun/core/compatibility";
|
|
4
|
-
import { parseSessionEvent, parseSubjectHeaders, } from "@nylorun/core/contracts";
|
|
5
|
-
import {
|
|
4
|
+
import { SessionManifestViewSchema, SessionUsageTotalsSchema, ModelCallsPageSchema, parseSessionEvent, parseSubjectHeaders, } from "@nylorun/core/contracts";
|
|
5
|
+
import { SessionsReadClient } from "./reads.js";
|
|
6
|
+
import { AccessClient } from "./access.js";
|
|
6
7
|
import { SandboxesClient } from "./sandboxes.js";
|
|
7
8
|
import { ArtifactsClient } from "./artifacts.js";
|
|
8
|
-
import {
|
|
9
|
+
import { FilesClient } from "./files.js";
|
|
10
|
+
import { Transport, env, id, segment } from "./http.js";
|
|
9
11
|
import { observeSSE } from "./sse.js";
|
|
10
12
|
function sessionSandboxOf(transport, sessionId) {
|
|
11
13
|
const call = (tool, args) => transport.json(`/v1/sessions/${segment(sessionId)}/sandbox/${segment(tool)}`, "POST", args);
|
|
@@ -28,8 +30,8 @@ function linkedSessionId(event) {
|
|
|
28
30
|
? sessionId
|
|
29
31
|
: undefined;
|
|
30
32
|
}
|
|
31
|
-
const middlewareClosure = "Hosted definitions support declarative capabilities
|
|
32
|
-
/** The agent's declarations, then those of each agent it uses as a tool
|
|
33
|
+
const middlewareClosure = "Hosted definitions support declarative capabilities, not middleware closures";
|
|
34
|
+
/** The agent's declarations, then those of each agent it uses as a tool. */
|
|
33
35
|
function declarationsOf(agent) {
|
|
34
36
|
if (isBuiltWorkflow(agent) || agent.manifest.kind === "workflow")
|
|
35
37
|
return [];
|
|
@@ -39,30 +41,26 @@ function declarationsOf(agent) {
|
|
|
39
41
|
const binding = built.getBinding?.();
|
|
40
42
|
if (!binding || !("declarations" in binding))
|
|
41
43
|
return [];
|
|
42
|
-
const found = (binding.declarations ?? [])
|
|
44
|
+
const found = [...(binding.declarations ?? [])];
|
|
43
45
|
for (const tool of binding.tools ?? []) {
|
|
44
46
|
const delegate = delegateOf(tool);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const flow = delegate?.workflow;
|
|
50
|
-
for (const leaf of Object.values(flow?.getBinding().agents ?? {}))
|
|
51
|
-
for (const item of leaf.declarations ?? [])
|
|
52
|
-
found.push({ key: `${flow.id}/${leaf.manifest.id}/${item.id}`, item });
|
|
47
|
+
found.push(...(delegate?.agent?.getBinding().declarations ?? []));
|
|
48
|
+
// A flow agent used as a tool: its agents' declarations.
|
|
49
|
+
for (const leaf of Object.values(delegate?.workflow?.getBinding().agents ?? {}))
|
|
50
|
+
found.push(...(leaf.declarations ?? []));
|
|
53
51
|
}
|
|
54
52
|
return found;
|
|
55
53
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
for (const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return
|
|
54
|
+
/** The bytes of every skill file the agent's definition names, by `sha256:<hex>`. */
|
|
55
|
+
function skillFilesOf(agent, into = new Map()) {
|
|
56
|
+
for (const item of declarationsOf(agent))
|
|
57
|
+
for (const [sha256, source] of Object.entries(item.skillFiles ?? {}))
|
|
58
|
+
into.set(sha256, source);
|
|
59
|
+
return into;
|
|
62
60
|
}
|
|
63
61
|
/** Closures stay on the live binding. The published manifest cannot name them. */
|
|
64
62
|
export function assertNoMiddlewareClosures(agent) {
|
|
65
|
-
if (declarationsOf(agent).some((
|
|
63
|
+
if (declarationsOf(agent).some((item) => item.hasMiddleware))
|
|
66
64
|
throw new Error(middlewareClosure);
|
|
67
65
|
}
|
|
68
66
|
export class AgentsClient {
|
|
@@ -95,14 +93,7 @@ export class AgentsClient {
|
|
|
95
93
|
subject,
|
|
96
94
|
});
|
|
97
95
|
}
|
|
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`). */
|
|
96
|
+
/** The public keys that verify the tokens the Tenant signs. */
|
|
106
97
|
get access() {
|
|
107
98
|
return new AccessClient(this.transport);
|
|
108
99
|
}
|
|
@@ -117,10 +108,17 @@ export class AgentsClient {
|
|
|
117
108
|
get artifacts() {
|
|
118
109
|
return new ArtifactsClient(this.transport);
|
|
119
110
|
}
|
|
111
|
+
/** Definition files: the skill files a definition names, by content hash. */
|
|
112
|
+
get files() {
|
|
113
|
+
return new FilesClient(this.transport);
|
|
114
|
+
}
|
|
120
115
|
/** The Runtime's protocol features, including optional ones such as `transcript-events`. */
|
|
121
116
|
hostFeatures(options = {}) {
|
|
122
117
|
return this.transport.hostFeatures(options.signal);
|
|
123
118
|
}
|
|
119
|
+
get sessions() {
|
|
120
|
+
return new SessionsReadClient(this.transport);
|
|
121
|
+
}
|
|
124
122
|
listAgents(options = {}) {
|
|
125
123
|
return this.transport.json("/v1/agents", "GET", undefined, options.signal);
|
|
126
124
|
}
|
|
@@ -130,44 +128,35 @@ export class AgentsClient {
|
|
|
130
128
|
: "";
|
|
131
129
|
return this.transport.json(`/v1/sessions${query}`, "GET", undefined, options.signal);
|
|
132
130
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Registers the agent's definition (`PUT /v1/agents/{id}`), first uploading the skill files
|
|
133
|
+
* it names that the Runtime does not hold. A definition with a tool that runs your code
|
|
134
|
+
* (`tool({ run })`, a flow's tool stage) is refused before anything is sent: the Runtime runs
|
|
135
|
+
* no code of yours during a session. `implementationVersion` defaults to
|
|
136
|
+
* `NYLORUN_IMPLEMENTATION_VERSION`, else `dev`.
|
|
137
|
+
*/
|
|
138
|
+
async saveAgent(agent, options = {}) {
|
|
139
|
+
const files = new Map();
|
|
140
|
+
if (isBuiltWorkflow(agent)) {
|
|
141
|
+
// A flow agent embeds its agents: one document, carrying their files.
|
|
137
142
|
for (const binding of Object.values(agent.getBinding().agents)) {
|
|
138
143
|
const leaf = { id: binding.manifest.id, manifest: binding.manifest, getBinding: () => binding };
|
|
139
144
|
assertNoMiddlewareClosures(leaf);
|
|
140
|
-
|
|
141
|
-
pluginRoots[`${leaf.id}/${key}`] = root;
|
|
145
|
+
skillFilesOf(leaf, files);
|
|
142
146
|
}
|
|
143
|
-
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
144
|
-
requestId: options.requestId ?? id(),
|
|
145
|
-
manifest: agent.manifest,
|
|
146
|
-
implementationVersion: options.implementationVersion,
|
|
147
|
-
...(Object.keys(pluginRoots).length === 0 ? {} : { pluginRoots }),
|
|
148
|
-
});
|
|
149
147
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
id: binding.manifest.id,
|
|
154
|
-
manifest: binding.manifest,
|
|
155
|
-
getBinding: () => binding,
|
|
156
|
-
}, options);
|
|
157
|
-
}
|
|
158
|
-
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
159
|
-
requestId: options.requestId ?? id(),
|
|
160
|
-
manifest: agent.manifest,
|
|
161
|
-
implementationVersion: options.implementationVersion,
|
|
162
|
-
});
|
|
148
|
+
else {
|
|
149
|
+
assertNoMiddlewareClosures(agent);
|
|
150
|
+
skillFilesOf(agent, files);
|
|
163
151
|
}
|
|
164
|
-
|
|
165
|
-
|
|
152
|
+
const refusal = codeToolRefusal(agent.manifest);
|
|
153
|
+
if (refusal)
|
|
154
|
+
throw new Error(refusal);
|
|
155
|
+
await this.files.ensure(files);
|
|
166
156
|
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
167
157
|
requestId: options.requestId ?? id(),
|
|
168
158
|
manifest: agent.manifest,
|
|
169
|
-
implementationVersion: options.implementationVersion,
|
|
170
|
-
...(pluginRoots === undefined ? {} : { pluginRoots }),
|
|
159
|
+
implementationVersion: options.implementationVersion ?? env("NYLORUN_IMPLEMENTATION_VERSION") ?? "dev",
|
|
171
160
|
});
|
|
172
161
|
}
|
|
173
162
|
async createSession(options) {
|
|
@@ -185,48 +174,6 @@ export class AgentsClient {
|
|
|
185
174
|
});
|
|
186
175
|
return this.session(sessionId);
|
|
187
176
|
}
|
|
188
|
-
createVault(options) {
|
|
189
|
-
return this.transport.json("/v1/vaults", "POST", {
|
|
190
|
-
requestId: options.requestId ?? id(),
|
|
191
|
-
idempotencyKey: options.idempotencyKey,
|
|
192
|
-
name: options.name,
|
|
193
|
-
ownerUserId: options.ownerUserId,
|
|
194
|
-
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
listVaults(ownerUserId, signal) {
|
|
198
|
-
return this.transport.json(`/v1/vaults?ownerUserId=${encodeURIComponent(ownerUserId)}`, "GET", undefined, signal);
|
|
199
|
-
}
|
|
200
|
-
getVault(vaultId, signal) {
|
|
201
|
-
return this.transport.json(`/v1/vaults/${segment(vaultId)}`, "GET", undefined, signal);
|
|
202
|
-
}
|
|
203
|
-
deleteVault(vaultId) {
|
|
204
|
-
return this.transport.json(`/v1/vaults/${segment(vaultId)}`, "DELETE");
|
|
205
|
-
}
|
|
206
|
-
createCredential(vaultId, options) {
|
|
207
|
-
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials`, "POST", {
|
|
208
|
-
requestId: options.requestId ?? id(),
|
|
209
|
-
idempotencyKey: options.idempotencyKey,
|
|
210
|
-
name: options.name,
|
|
211
|
-
auth: options.auth,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
listCredentials(vaultId, signal) {
|
|
215
|
-
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials`, "GET", undefined, signal);
|
|
216
|
-
}
|
|
217
|
-
getCredential(vaultId, credentialId, signal) {
|
|
218
|
-
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials/${segment(credentialId)}`, "GET", undefined, signal);
|
|
219
|
-
}
|
|
220
|
-
rotateCredential(vaultId, credentialId, options) {
|
|
221
|
-
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials/${segment(credentialId)}`, "POST", {
|
|
222
|
-
requestId: options.requestId ?? id(),
|
|
223
|
-
idempotencyKey: options.idempotencyKey,
|
|
224
|
-
auth: options.auth,
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
deleteCredential(vaultId, credentialId) {
|
|
228
|
-
return this.transport.json(`/v1/vaults/${segment(vaultId)}/credentials/${segment(credentialId)}`, "DELETE");
|
|
229
|
-
}
|
|
230
177
|
session(sessionId) {
|
|
231
178
|
return new SessionClient(this.transport, sessionId);
|
|
232
179
|
}
|
|
@@ -307,19 +254,45 @@ export class SessionClient {
|
|
|
307
254
|
...(options.reason ? { reason: options.reason } : {}),
|
|
308
255
|
}, options.signal);
|
|
309
256
|
}
|
|
310
|
-
|
|
257
|
+
async manifest(options = {}) {
|
|
258
|
+
await this.transport.requireFeature("session-reads", options.signal);
|
|
259
|
+
return SessionManifestViewSchema.parse(await this.transport.json(`${this.path}/manifest`, "GET", undefined, options.signal));
|
|
260
|
+
}
|
|
261
|
+
async usage(options = {}) {
|
|
262
|
+
await this.transport.requireFeature("session-reads", options.signal);
|
|
263
|
+
const query = new URLSearchParams();
|
|
264
|
+
if (options.turnId !== undefined)
|
|
265
|
+
query.set("turnId", options.turnId);
|
|
266
|
+
return SessionUsageTotalsSchema.parse(await this.transport.json(`${this.path}/usage${query.size ? `?${query}` : ""}`, "GET", undefined, options.signal));
|
|
267
|
+
}
|
|
268
|
+
async modelCalls(options = {}) {
|
|
269
|
+
await this.transport.requireFeature("session-reads", options.signal);
|
|
270
|
+
const query = new URLSearchParams({ limit: String(options.limit ?? 50) });
|
|
271
|
+
if (options.turnId !== undefined)
|
|
272
|
+
query.set("turnId", options.turnId);
|
|
273
|
+
if (options.cursor !== undefined)
|
|
274
|
+
query.set("cursor", options.cursor);
|
|
275
|
+
return ModelCallsPageSchema.parse(await this.transport.json(`${this.path}/calls/model?${query}`, "GET", undefined, options.signal));
|
|
276
|
+
}
|
|
311
277
|
async history(options = {}) {
|
|
312
278
|
const query = new URLSearchParams();
|
|
313
279
|
if (options.cursor)
|
|
314
280
|
query.set("cursor", options.cursor);
|
|
315
281
|
if (options.agent)
|
|
316
282
|
query.set("agent", options.agent);
|
|
283
|
+
if (options.limit !== undefined) {
|
|
284
|
+
await this.transport.requireFeature("session-reads", options.signal);
|
|
285
|
+
query.set("limit", String(options.limit));
|
|
286
|
+
}
|
|
317
287
|
const search = query.toString();
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
.parse(await this.transport.json(`${this.path}/items${search ? `?${search}` : ""}`, "GET", undefined, options.signal));
|
|
288
|
+
const schema = z.object({ items: z.array(z.unknown()), cursor: z.string().nullable() });
|
|
289
|
+
const page = (options.limit === undefined ? schema : schema.extend({ tail: z.boolean() })).parse(await this.transport.json(`${this.path}/items${search ? `?${search}` : ""}`, "GET", undefined, options.signal));
|
|
321
290
|
// Typed when the catalog knows the type; a newer Runtime's type stays a bare envelope.
|
|
322
|
-
return {
|
|
291
|
+
return {
|
|
292
|
+
items: page.items.map(parseSessionEvent),
|
|
293
|
+
cursor: page.cursor,
|
|
294
|
+
...("tail" in page ? { tail: page.tail } : {}),
|
|
295
|
+
};
|
|
323
296
|
}
|
|
324
297
|
async *observe(options = {}) {
|
|
325
298
|
if (!options.follow) {
|
|
@@ -398,5 +371,5 @@ export class SessionClient {
|
|
|
398
371
|
}
|
|
399
372
|
}
|
|
400
373
|
export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
|
|
401
|
-
export { AccessClient
|
|
374
|
+
export { AccessClient } from "./access.js";
|
|
402
375
|
export { SandboxesClient } from "./sandboxes.js";
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { skills } from "./skills.js";
|
|
2
2
|
export type { SkillsCapability, SkillsOptions } from "./skills.js";
|
|
3
3
|
export { loadSkillsFromDirectory, resolveSkillsRoot, parseSkill, SkillsError, } from "./load.js";
|
|
4
|
-
export type { SkillDiagnostic } from "./load.js";
|
|
4
|
+
export type { LoadedSkill, SkillDiagnostic } from "./load.js";
|
|
5
5
|
export { formatSkillCatalog, SKILLS_USAGE } from "./catalog.js";
|
package/dist/skills/load.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SkillFileSource } from "@nylorun/core/define";
|
|
2
2
|
export interface SkillDiagnostic {
|
|
3
3
|
readonly severity: "info" | "warning";
|
|
4
4
|
readonly code: string;
|
|
@@ -10,16 +10,27 @@ export declare class SkillsError extends Error {
|
|
|
10
10
|
readonly diagnostics: readonly SkillDiagnostic[];
|
|
11
11
|
constructor(code: string, message: string, diagnostics?: readonly SkillDiagnostic[]);
|
|
12
12
|
}
|
|
13
|
+
/** One skill read from its folder: what the manifest names, and where each file's bytes are. */
|
|
14
|
+
export interface LoadedSkill {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly description: string;
|
|
17
|
+
/** Every file of the folder by its `/`-separated path, as `sha256:<hex>`. */
|
|
18
|
+
readonly files: Readonly<Record<string, string>>;
|
|
19
|
+
/** Each file's bytes, by `sha256:<hex>`, for the client to upload. */
|
|
20
|
+
readonly sources: Readonly<Record<string, SkillFileSource>>;
|
|
21
|
+
}
|
|
13
22
|
/** Resolve a skills catalog directory; throws when missing or not a directory. */
|
|
14
23
|
export declare function resolveSkillsRoot(directory: string): string;
|
|
15
24
|
/**
|
|
16
25
|
* Load Agent Skills (agentskills.io) from a catalog directory.
|
|
17
|
-
* Each immediate subdirectory with a valid SKILL.md becomes one skill
|
|
26
|
+
* Each immediate subdirectory with a valid SKILL.md becomes one skill, with every file of its
|
|
27
|
+
* folder (text or binary) hashed. Throws `SkillsError` for a skill the Runtime cannot hold: a
|
|
28
|
+
* file over 10 MiB, more than 500 files, or a path a manifest cannot name.
|
|
18
29
|
*/
|
|
19
30
|
export declare function loadSkillsFromDirectory(directory: string, diagnostics: SkillDiagnostic[], options?: {
|
|
20
31
|
readonly boundary?: string;
|
|
21
32
|
readonly codePrefix?: string;
|
|
22
|
-
}): Readonly<Record<string,
|
|
33
|
+
}): Readonly<Record<string, LoadedSkill>>;
|
|
23
34
|
export declare function parseSkill(text: string, directoryName: string): {
|
|
24
35
|
name: string;
|
|
25
36
|
description: string;
|
package/dist/skills/load.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { existsSync, readdirSync, readFileSync, realpathSync, statSync, } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
2
4
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { DEFINITION_FILE_MAX_BYTES, SKILL_ENTRY, SKILL_FILES_MAX, skillFilePathIssue, } from "@nylorun/core/define";
|
|
3
6
|
import matter from "gray-matter";
|
|
4
7
|
const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
8
|
+
/** Version control and operating system files, never part of a skill. */
|
|
9
|
+
const SKIPPED_DIRECTORIES = new Set([".git", ".hg", ".svn", "node_modules"]);
|
|
10
|
+
const SKIPPED_FILES = new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
|
|
11
|
+
/** Environment files hold secrets: never uploaded (`.env`, `.env.local`, …). */
|
|
12
|
+
const isEnvFile = (name) => name === ".env" || name.startsWith(".env.");
|
|
5
13
|
export class SkillsError extends Error {
|
|
6
14
|
code;
|
|
7
15
|
diagnostics;
|
|
@@ -24,7 +32,9 @@ export function resolveSkillsRoot(directory) {
|
|
|
24
32
|
}
|
|
25
33
|
/**
|
|
26
34
|
* Load Agent Skills (agentskills.io) from a catalog directory.
|
|
27
|
-
* Each immediate subdirectory with a valid SKILL.md becomes one skill
|
|
35
|
+
* Each immediate subdirectory with a valid SKILL.md becomes one skill, with every file of its
|
|
36
|
+
* folder (text or binary) hashed. Throws `SkillsError` for a skill the Runtime cannot hold: a
|
|
37
|
+
* file over 10 MiB, more than 500 files, or a path a manifest cannot name.
|
|
28
38
|
*/
|
|
29
39
|
export function loadSkillsFromDirectory(directory, diagnostics, options = {}) {
|
|
30
40
|
const root = resolveSkillsRoot(directory);
|
|
@@ -34,7 +44,7 @@ export function loadSkillsFromDirectory(directory, diagnostics, options = {}) {
|
|
|
34
44
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
35
45
|
if (!entry.isDirectory() || entry.name.startsWith("."))
|
|
36
46
|
continue;
|
|
37
|
-
if (
|
|
47
|
+
if (SKIPPED_DIRECTORIES.has(entry.name))
|
|
38
48
|
continue;
|
|
39
49
|
const skillDir = join(root, entry.name);
|
|
40
50
|
const directoryReal = realInside(boundary, skillDir);
|
|
@@ -47,7 +57,7 @@ export function loadSkillsFromDirectory(directory, diagnostics, options = {}) {
|
|
|
47
57
|
});
|
|
48
58
|
continue;
|
|
49
59
|
}
|
|
50
|
-
const skillFile = join(directoryReal,
|
|
60
|
+
const skillFile = join(directoryReal, SKILL_ENTRY);
|
|
51
61
|
if (!existsSync(skillFile))
|
|
52
62
|
continue;
|
|
53
63
|
const skillReal = realInside(boundary, skillFile);
|
|
@@ -79,12 +89,10 @@ export function loadSkillsFromDirectory(directory, diagnostics, options = {}) {
|
|
|
79
89
|
});
|
|
80
90
|
continue;
|
|
81
91
|
}
|
|
82
|
-
const resources = readResources(boundary, directoryReal, diagnostics, prefix);
|
|
83
92
|
skills[parsed.name] = {
|
|
84
93
|
name: parsed.name,
|
|
85
94
|
description: parsed.description,
|
|
86
|
-
|
|
87
|
-
...(Object.keys(resources).length === 0 ? {} : { resources }),
|
|
95
|
+
...readFiles(parsed.name, boundary, directoryReal, diagnostics, prefix),
|
|
88
96
|
};
|
|
89
97
|
}
|
|
90
98
|
return skills;
|
|
@@ -112,47 +120,57 @@ export function parseSkill(text, directoryName) {
|
|
|
112
120
|
function asTrimmedString(value) {
|
|
113
121
|
return typeof value === "string" ? value.trim() : "";
|
|
114
122
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
123
|
+
/** Every file of the skill's folder, hashed, and where to read each one's bytes. */
|
|
124
|
+
function readFiles(name, boundary, directory, diagnostics, prefix) {
|
|
125
|
+
const files = {};
|
|
126
|
+
const sources = {};
|
|
127
|
+
const visited = new Set();
|
|
128
|
+
// `at` is the folder's path in the skill: symbolic links inside the package are followed.
|
|
129
|
+
const walk = (current, at) => {
|
|
130
|
+
if (visited.has(current))
|
|
119
131
|
return;
|
|
132
|
+
visited.add(current);
|
|
120
133
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
121
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
122
|
-
continue;
|
|
123
134
|
const full = join(current, entry.name);
|
|
124
|
-
if (entry.isDirectory()) {
|
|
125
|
-
const real = realInside(boundary, full);
|
|
126
|
-
if (!real)
|
|
127
|
-
continue;
|
|
128
|
-
walk(real, depth + 1);
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
132
|
-
continue;
|
|
133
|
-
if (depth === 0 && entry.name === "SKILL.md")
|
|
134
|
-
continue;
|
|
135
135
|
const real = realInside(boundary, full);
|
|
136
|
-
if (!real
|
|
136
|
+
if (!real) {
|
|
137
137
|
diagnostics.push({
|
|
138
138
|
severity: "warning",
|
|
139
139
|
code: `${prefix}.resource-denied`,
|
|
140
|
-
message: `Denied
|
|
140
|
+
message: `Denied a file of skill '${name}' outside the package: ${entry.name}`,
|
|
141
141
|
path: full,
|
|
142
142
|
});
|
|
143
143
|
continue;
|
|
144
144
|
}
|
|
145
|
-
const
|
|
146
|
-
|
|
145
|
+
const stats = statSync(real);
|
|
146
|
+
const path = at === "" ? entry.name : `${at}/${entry.name}`;
|
|
147
|
+
if (stats.isDirectory()) {
|
|
148
|
+
if (!SKIPPED_DIRECTORIES.has(entry.name))
|
|
149
|
+
walk(real, path);
|
|
147
150
|
continue;
|
|
148
|
-
|
|
149
|
-
if (!
|
|
151
|
+
}
|
|
152
|
+
if (!stats.isFile() || SKIPPED_FILES.has(entry.name) || isEnvFile(entry.name))
|
|
150
153
|
continue;
|
|
151
|
-
|
|
154
|
+
const issue = skillFilePathIssue(path);
|
|
155
|
+
if (issue)
|
|
156
|
+
throw new SkillsError(`${prefix}.invalid-path`, `Skill '${name}' has a file whose path ${issue}: ${path}`);
|
|
157
|
+
if (stats.size > DEFINITION_FILE_MAX_BYTES)
|
|
158
|
+
throw new SkillsError(`${prefix}.file-too-large`, `Skill '${name}' file ${path} is ${stats.size} bytes; a skill file may be at most ${DEFINITION_FILE_MAX_BYTES} (10 MiB)`);
|
|
159
|
+
const hash = `sha256:${createHash("sha256").update(readFileSync(real)).digest("hex")}`;
|
|
160
|
+
files[path] = hash;
|
|
161
|
+
sources[hash] = {
|
|
162
|
+
size: stats.size,
|
|
163
|
+
read: async () => new Uint8Array(await readFile(real)),
|
|
164
|
+
};
|
|
165
|
+
if (Object.keys(files).length > SKILL_FILES_MAX)
|
|
166
|
+
throw new SkillsError(`${prefix}.too-many-files`, `Skill '${name}' has more than ${SKILL_FILES_MAX} files; a skill may have at most ${SKILL_FILES_MAX}`);
|
|
152
167
|
}
|
|
153
168
|
};
|
|
154
|
-
walk(directory,
|
|
155
|
-
return
|
|
169
|
+
walk(directory, "");
|
|
170
|
+
return { files: sorted(files), sources };
|
|
171
|
+
}
|
|
172
|
+
function sorted(files) {
|
|
173
|
+
return Object.fromEntries(Object.entries(files).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
|
|
156
174
|
}
|
|
157
175
|
function realInside(root, candidate) {
|
|
158
176
|
if (!existsSync(candidate))
|
package/dist/skills/skills.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CapabilityDeclaration } from "@nylorun/core/define";
|
|
2
|
-
import { type SkillDiagnostic } from "./load.js";
|
|
2
|
+
import { type LoadedSkill, type SkillDiagnostic } from "./load.js";
|
|
3
3
|
export interface SkillsOptions {
|
|
4
4
|
/** Capability id. Defaults to the catalog directory basename. */
|
|
5
5
|
readonly id?: string;
|
|
@@ -21,8 +21,10 @@ export interface SkillsCapability extends CapabilityDeclaration {
|
|
|
21
21
|
* references/policy.md
|
|
22
22
|
* ```
|
|
23
23
|
*
|
|
24
|
-
* Sets
|
|
25
|
-
*
|
|
26
|
-
* `load_skill` / `read_skill_resource` from
|
|
24
|
+
* Sets `skills`: each skill's name, description and files (every file of its folder, binary
|
|
25
|
+
* included, by content hash). The client uploads the files the Runtime lacks when it registers
|
|
26
|
+
* the agent, and the Runtime serves `load_skill` / `read_skill_resource` from them.
|
|
27
27
|
*/
|
|
28
28
|
export declare function skills(directory: string, options?: SkillsOptions): SkillsCapability;
|
|
29
|
+
/** The manifest's skills and the bytes behind their files, for a capability declaration. */
|
|
30
|
+
export declare function skillsDeclaration(listed: readonly LoadedSkill[]): Required<Pick<CapabilityDeclaration, "skills" | "skillFiles">>;
|
package/dist/skills/skills.js
CHANGED
|
@@ -13,23 +13,20 @@ import { loadSkillsFromDirectory, resolveSkillsRoot, } from "./load.js";
|
|
|
13
13
|
* references/policy.md
|
|
14
14
|
* ```
|
|
15
15
|
*
|
|
16
|
-
* Sets
|
|
17
|
-
*
|
|
18
|
-
* `load_skill` / `read_skill_resource` from
|
|
16
|
+
* Sets `skills`: each skill's name, description and files (every file of its folder, binary
|
|
17
|
+
* included, by content hash). The client uploads the files the Runtime lacks when it registers
|
|
18
|
+
* the agent, and the Runtime serves `load_skill` / `read_skill_resource` from them.
|
|
19
19
|
*/
|
|
20
20
|
export function skills(directory, options = {}) {
|
|
21
21
|
const root = resolveSkillsRoot(directory);
|
|
22
22
|
const diagnostics = [];
|
|
23
|
-
const
|
|
24
|
-
const listed = Object.values(
|
|
23
|
+
const loaded = loadSkillsFromDirectory(root, diagnostics);
|
|
24
|
+
const listed = Object.values(loaded);
|
|
25
25
|
const id = options.id ?? basename(root);
|
|
26
26
|
if (listed.length === 0) {
|
|
27
27
|
return { id, root, diagnostics };
|
|
28
28
|
}
|
|
29
|
-
const skillManifest =
|
|
30
|
-
skill.name,
|
|
31
|
-
{ name: skill.name, description: skill.description },
|
|
32
|
-
]));
|
|
29
|
+
const { skills: skillManifest, skillFiles } = skillsDeclaration(listed);
|
|
33
30
|
return {
|
|
34
31
|
id,
|
|
35
32
|
root,
|
|
@@ -41,7 +38,17 @@ export function skills(directory, options = {}) {
|
|
|
41
38
|
}))),
|
|
42
39
|
],
|
|
43
40
|
skills: skillManifest,
|
|
44
|
-
|
|
41
|
+
skillFiles,
|
|
45
42
|
diagnostics,
|
|
46
43
|
};
|
|
47
44
|
}
|
|
45
|
+
/** The manifest's skills and the bytes behind their files, for a capability declaration. */
|
|
46
|
+
export function skillsDeclaration(listed) {
|
|
47
|
+
return {
|
|
48
|
+
skills: Object.fromEntries(listed.map((skill) => [
|
|
49
|
+
skill.name,
|
|
50
|
+
{ name: skill.name, description: skill.description, files: skill.files },
|
|
51
|
+
])),
|
|
52
|
+
skillFiles: Object.assign({}, ...listed.map((skill) => skill.sources)),
|
|
53
|
+
};
|
|
54
|
+
}
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/agents",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Nylorun agent definitions
|
|
3
|
+
"version": "0.16.0-beta",
|
|
4
|
+
"description": "Nylorun agent definitions and session client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"files": [
|
|
@@ -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.15.0-beta",
|
|
51
47
|
"gray-matter": "^4.0.3"
|
|
52
48
|
},
|
|
53
49
|
"peerDependencies": {
|