@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.
Files changed (50) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/README.md +220 -235
  3. package/dist/access.d.ts +6 -97
  4. package/dist/access.js +1 -103
  5. package/dist/ag-ui/handler.d.ts +3 -6
  6. package/dist/ag-ui/handler.js +2 -2
  7. package/dist/artifacts.d.ts +1 -1
  8. package/dist/files.d.ts +31 -0
  9. package/dist/files.js +60 -0
  10. package/dist/http.d.ts +6 -13
  11. package/dist/http.js +4 -16
  12. package/dist/index.d.ts +8 -16
  13. package/dist/index.js +4 -7
  14. package/dist/plugins/load.d.ts +3 -2
  15. package/dist/plugins/load.js +40 -100
  16. package/dist/plugins/plugin.d.ts +6 -1
  17. package/dist/plugins/plugin.js +20 -7
  18. package/dist/reads.d.ts +17 -0
  19. package/dist/reads.js +17 -0
  20. package/dist/sandboxes.d.ts +29 -0
  21. package/dist/sandboxes.js +10 -1
  22. package/dist/session-client.d.ts +94 -112
  23. package/dist/session-client.js +80 -107
  24. package/dist/skills/index.d.ts +1 -1
  25. package/dist/skills/load.d.ts +14 -3
  26. package/dist/skills/load.js +50 -32
  27. package/dist/skills/skills.d.ts +6 -4
  28. package/dist/skills/skills.js +17 -10
  29. package/dist/sse.js +1 -1
  30. package/dist/studio-embed.d.ts +1 -1
  31. package/dist/studio-embed.js +1 -1
  32. package/package.json +3 -7
  33. package/dist/action-handler.d.ts +0 -63
  34. package/dist/action-handler.js +0 -279
  35. package/dist/browser.d.ts +0 -104
  36. package/dist/browser.js +0 -281
  37. package/dist/delivery-token.d.ts +0 -62
  38. package/dist/delivery-token.js +0 -159
  39. package/dist/execute-action.d.ts +0 -16
  40. package/dist/execute-action.js +0 -288
  41. package/dist/plugins/launch.d.ts +0 -17
  42. package/dist/plugins/launch.js +0 -71
  43. package/dist/sandbox/client.d.ts +0 -63
  44. package/dist/sandbox/client.js +0 -29
  45. package/dist/sandbox/index.d.ts +0 -2
  46. package/dist/sandbox/index.js +0 -1
  47. package/dist/served-definitions.d.ts +0 -20
  48. package/dist/served-definitions.js +0 -100
  49. package/dist/token-endpoint.d.ts +0 -23
  50. package/dist/token-endpoint.js +0 -30
@@ -1,288 +0,0 @@
1
- import { delegateOf, implementationsFor, isBuiltWorkflow, isToolError, normalizeToolDefinition, normalizedSchemasFor, runHookPoint, } from "@nylorun/core/define";
2
- class Suspend {
3
- outcome;
4
- constructor(outcome) {
5
- this.outcome = outcome;
6
- }
7
- }
8
- const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
9
- ? value
10
- : {};
11
- /** Executes code only after a host-issued claim. No engine dependency. */
12
- export async function executeAction(action, root, signal, options = {}) {
13
- const sandbox = options.sandbox;
14
- if (action.kind === "fn" || action.kind === "verify") {
15
- if (!isBuiltWorkflow(root))
16
- throw new Error(`Action ${action.kind} requires a workflow definition`);
17
- return executeWorkflowFn(action, root.getBinding(), signal, sandbox);
18
- }
19
- if (isBuiltWorkflow(root)) {
20
- if (action.kind === "tool" && "key" in action && typeof action.key === "string")
21
- return executeWorkflowTool(action, root.getBinding(), signal, sandbox);
22
- throw new Error(`Unsupported action kind ${action.kind} on workflow`);
23
- }
24
- // Work for an agent used as a tool runs that agent's code, served from the root's binding.
25
- const agent = action.agent ? delegatedAgent(root, action.agent.id) : root;
26
- const ref = action.agent ?? { id: root.id, path: root.id };
27
- if (action.kind === "hook")
28
- // All capabilities at this hook point run concurrently in one action. Never throws.
29
- return {
30
- value: {
31
- results: await runHookPoint(implementationsFor(agent), action.hook, action.input),
32
- },
33
- };
34
- if (action.kind !== "tool" || !("capabilityId" in action))
35
- throw new Error(`Unsupported action kind ${action.kind}`);
36
- const impl = implementationsFor(agent)[action.capabilityId];
37
- if (!impl)
38
- throw new Error(`No capability ${action.capabilityId}`);
39
- const raw = impl.tools?.[action.toolName];
40
- if (!raw)
41
- throw new Error("Missing tool implementation");
42
- const tool = normalizeToolDefinition(raw);
43
- return runTool(action, tool, ref, signal, sandbox);
44
- }
45
- /**
46
- * True when `action` runs a tool marked `background` (`ToolDefinition.background`): an Action
47
- * endpoint answers its delivery at once and posts the outcome later. False for anything it
48
- * cannot find; `executeAction` reports those.
49
- */
50
- export function runsInBackground(action, root) {
51
- if (action.kind !== "tool")
52
- return false;
53
- try {
54
- if (isBuiltWorkflow(root)) {
55
- const key = action.key;
56
- const impl = typeof key === "string" ? root.getBinding().nodes[key] : undefined;
57
- return impl?.kind === "tool" && impl.tool.background === true;
58
- }
59
- if (!("capabilityId" in action))
60
- return false;
61
- const agent = action.agent ? delegatedAgent(root, action.agent.id) : root;
62
- const raw = implementationsFor(agent)[action.capabilityId]?.tools?.[action.toolName];
63
- return raw !== undefined && normalizeToolDefinition(raw).background === true;
64
- }
65
- catch {
66
- return false;
67
- }
68
- }
69
- async function executeWorkflowTool(action, binding, signal, sandbox) {
70
- const impl = binding.nodes[action.key];
71
- if (!impl || impl.kind !== "tool")
72
- throw new Error(`No tool implementation for key ${action.key}`);
73
- const path = "path" in action && typeof action.path === "string" ? action.path : action.key;
74
- return runTool(action, impl.tool, { id: action.agentId, path }, signal, sandbox);
75
- }
76
- async function runTool(action, raw, ref, signal, sandbox) {
77
- const tool = "execute" in raw && typeof raw.execute === "function" && "inputSchema" in raw
78
- ? raw
79
- : normalizeToolDefinition(raw);
80
- const schemas = "inputSchema" in tool && tool.inputSchema && "validate" in tool.inputSchema
81
- ? {
82
- inputSchema: tool.inputSchema,
83
- outputSchema: "outputSchema" in tool ? tool.outputSchema : undefined,
84
- }
85
- : normalizedSchemasFor(tool);
86
- const input = schemas.inputSchema.validate(action.input);
87
- if (!input.ok)
88
- return {
89
- value: {
90
- kind: "failed",
91
- code: "tool.invalid-input",
92
- message: JSON.stringify(input.issues),
93
- },
94
- };
95
- const ctx = action.context;
96
- const state = { ...object(ctx.state) };
97
- const statePatch = {};
98
- const resume = object(ctx.resume);
99
- const token = object(resume.token);
100
- const memo = object(token._nylorun);
101
- const steps = { ...object(memo.steps) };
102
- const answers = { ...object(memo.answers) };
103
- let waitIndex = 0;
104
- let gateApproved = Boolean(memo.gateApproved) ||
105
- Boolean(memo.approvalGate && resume.kind === "approval" && resume.approved);
106
- const durableToken = (extra = {}) => ({
107
- _nylorun: { steps, answers, waitIndex, gateApproved, ...extra },
108
- });
109
- const wait = async (kind, prompt, metadata) => {
110
- const index = String(waitIndex++);
111
- if (index in answers)
112
- return answers[index];
113
- if (!memo.approvalGate &&
114
- resume.kind === kind &&
115
- Number(memo.waitIndex ?? 0) === Number(index)) {
116
- const answer = kind === "approval" ? Boolean(resume.approved) : resume.value;
117
- answers[index] = answer;
118
- return answer;
119
- }
120
- throw new Suspend({
121
- kind: "interaction-required",
122
- interaction: { kind, prompt, ...(metadata ? { metadata } : {}) },
123
- token: {
124
- _nylorun: { steps, answers, waitIndex: Number(index), gateApproved },
125
- },
126
- });
127
- };
128
- const context = {
129
- executionId: String(ctx.executionId ?? action.sessionId),
130
- turnId: String(ctx.turnId ?? action.turnId),
131
- stepId: String(ctx.stepId ?? ""),
132
- callId: String(ctx.callId ?? action.actionId),
133
- invocationId: String(ctx.invocationId ?? action.actionId),
134
- idempotencyKey: String(ctx.idempotencyKey ?? action.actionId),
135
- signal,
136
- info: ctx.info,
137
- session: { id: action.sessionId },
138
- agent: ref,
139
- ...(ctx.resume ? { resume: ctx.resume } : {}),
140
- ...(sandbox ? { sandbox } : {}),
141
- state: {
142
- get: (key) => state[key],
143
- set: (key, value) => {
144
- state[key] = value;
145
- statePatch[key] = value;
146
- },
147
- entries: () => ({ ...state }),
148
- },
149
- progress() {
150
- /* Canonical events are host-owned; progress transport is deferred. */
151
- },
152
- ask: (prompt, options) => wait("response", prompt, options),
153
- approve: (prompt) => wait("approval", prompt),
154
- sleep: async (duration) => {
155
- throw new Suspend({
156
- kind: "deferred",
157
- token: durableToken({ wait: { kind: "sleep", duration } }),
158
- });
159
- },
160
- waitFor: async (event, options) => {
161
- throw new Suspend({
162
- kind: "deferred",
163
- token: durableToken({ wait: { kind: "waitFor", event, ...options } }),
164
- });
165
- },
166
- step: async (name, fn) => {
167
- if (Object.prototype.hasOwnProperty.call(steps, name))
168
- return steps[name];
169
- signal.throwIfAborted();
170
- const value = await fn();
171
- // Memoized values are durable only with a persisted action outcome. Interrupted actions stay uncertain.
172
- steps[name] = JSON.parse(JSON.stringify(value ?? null));
173
- return value;
174
- },
175
- };
176
- try {
177
- signal.throwIfAborted();
178
- const approval = "approval" in tool ? tool.approval : undefined;
179
- if (approval && !gateApproved) {
180
- const result = await approval(input.value);
181
- if (result)
182
- throw new Suspend({
183
- kind: "interaction-required",
184
- interaction: {
185
- kind: "approval",
186
- prompt: typeof result === "string"
187
- ? result
188
- : `Approve ${"name" in tool ? tool.name : "tool"}?`,
189
- },
190
- token: durableToken({ approvalGate: true }),
191
- });
192
- gateApproved = true;
193
- }
194
- if (resume.kind === "approval" && resume.approved === false)
195
- return { value: { kind: "denied", reason: "Approval denied" } };
196
- const execute = "execute" in tool && typeof tool.execute === "function"
197
- ? tool.execute
198
- : undefined;
199
- if (!execute)
200
- throw new Error("Missing tool implementation");
201
- const value = await execute(input.value, context);
202
- const tagged = object(value);
203
- let outcome;
204
- if ([
205
- "completed",
206
- "failed",
207
- "denied",
208
- "interaction-required",
209
- "deferred",
210
- ].includes(tagged.kind))
211
- outcome = value;
212
- else
213
- outcome = { kind: "completed", output: value ?? null };
214
- if (outcome.kind === "completed" && schemas.outputSchema) {
215
- const parsed = schemas.outputSchema.validate(outcome.output);
216
- outcome = parsed.ok
217
- ? { ...outcome, output: parsed.value }
218
- : {
219
- kind: "failed",
220
- code: "tool.invalid-output",
221
- message: JSON.stringify(parsed.issues),
222
- };
223
- }
224
- return { value: outcome, statePatch };
225
- }
226
- catch (error) {
227
- if (signal.aborted)
228
- throw signal.reason;
229
- return {
230
- value: error instanceof Suspend
231
- ? error.outcome
232
- : {
233
- kind: "failed",
234
- code: isToolError(error) ? error.code : "tool.execution-failed",
235
- message: message(error),
236
- },
237
- statePatch,
238
- };
239
- }
240
- }
241
- async function executeWorkflowFn(action, binding, signal, sandbox) {
242
- const impl = binding.nodes[action.key];
243
- if (!impl || impl.kind !== action.kind)
244
- throw new Error(`No ${action.kind} implementation for key ${action.key}`);
245
- signal.throwIfAborted();
246
- try {
247
- // Verify may receive a tool-like context later (L3/L4); tracer passes input only.
248
- const value = action.kind === "verify"
249
- ? await impl.fn(action.input, {
250
- signal,
251
- info: action.context.info,
252
- session: { id: action.sessionId },
253
- ...(sandbox ? { sandbox } : {}),
254
- step: async (_name, fn) => fn(),
255
- approve: async () => {
256
- throw new Error("Approvals in verify are not available in the tracer");
257
- },
258
- ask: async () => {
259
- throw new Error("ask in verify is not available in the tracer");
260
- },
261
- progress() { },
262
- })
263
- : await impl.fn(action.input);
264
- return { value };
265
- }
266
- catch (error) {
267
- if (signal.aborted)
268
- throw signal.reason;
269
- return {
270
- value: {
271
- kind: "failed",
272
- code: action.kind === "verify" ? "loop.verify-failed" : "fn.failed",
273
- message: message(error),
274
- },
275
- };
276
- }
277
- }
278
- function delegatedAgent(root, id) {
279
- for (const capability of Object.values(implementationsFor(root))) {
280
- const child = delegateOf(capability.tools?.[id])?.agent;
281
- if (child)
282
- return child;
283
- }
284
- throw new Error(`Agent '${root.id}' does not use '${id}' as a tool`);
285
- }
286
- function message(error) {
287
- return error instanceof Error ? error.message : String(error);
288
- }
@@ -1,17 +0,0 @@
1
- import type { McpServerManifest } from "@nylorun/core/define";
2
- /** Single-pass replacement. Text inserted for a token is not scanned again. */
3
- export declare function expandPluginPlaceholders(value: string, pluginRoot: string, pluginData: string): string;
4
- export interface StdioLaunch {
5
- readonly command: string;
6
- readonly args: readonly string[];
7
- readonly cwd: string;
8
- readonly env: Readonly<Record<string, string>>;
9
- }
10
- export declare function prepareStdioLaunch(server: Extract<McpServerManifest, {
11
- type: "stdio";
12
- }>, options: {
13
- readonly pluginRoot: string;
14
- readonly pluginData: string;
15
- readonly baseEnv?: Readonly<Record<string, string | undefined>>;
16
- }): StdioLaunch;
17
- export declare function resolveContainedCwd(cwd: string, pluginRoot: string, pluginData: string): string;
@@ -1,71 +0,0 @@
1
- import { mkdirSync } from "node:fs";
2
- import { isAbsolute, relative, resolve, sep } from "node:path";
3
- const ROOT_TOKEN = "${PLUGIN_ROOT}";
4
- const DATA_TOKEN = "${PLUGIN_DATA}";
5
- /** Single-pass replacement. Text inserted for a token is not scanned again. */
6
- export function expandPluginPlaceholders(value, pluginRoot, pluginData) {
7
- let out = "";
8
- for (let index = 0; index < value.length;) {
9
- if (value.startsWith(ROOT_TOKEN, index)) {
10
- out += pluginRoot;
11
- index += ROOT_TOKEN.length;
12
- continue;
13
- }
14
- if (value.startsWith(DATA_TOKEN, index)) {
15
- out += pluginData;
16
- index += DATA_TOKEN.length;
17
- continue;
18
- }
19
- out += value[index];
20
- index += 1;
21
- }
22
- return out;
23
- }
24
- export function prepareStdioLaunch(server, options) {
25
- const pluginRoot = options.pluginRoot;
26
- const pluginData = options.pluginData;
27
- mkdirSync(pluginData, { recursive: true });
28
- const command = resolveCommand(server.command, pluginRoot);
29
- const args = (server.args ?? []).map((arg) => expandPluginPlaceholders(arg, pluginRoot, pluginData));
30
- const cwd = server.cwd
31
- ? resolveContainedCwd(server.cwd, pluginRoot, pluginData)
32
- : pluginRoot;
33
- const env = {};
34
- for (const [key, value] of Object.entries(options.baseEnv ?? {})) {
35
- if (typeof value === "string")
36
- setEnv(env, key, value);
37
- }
38
- for (const [key, value] of Object.entries(server.env ?? {}))
39
- setEnv(env, key, expandPluginPlaceholders(value, pluginRoot, pluginData));
40
- setEnv(env, "PLUGIN_ROOT", pluginRoot);
41
- setEnv(env, "PLUGIN_DATA", pluginData);
42
- return { command, args, cwd, env };
43
- }
44
- function resolveCommand(command, pluginRoot) {
45
- if (command.startsWith("./"))
46
- return resolve(pluginRoot, command);
47
- return command;
48
- }
49
- export function resolveContainedCwd(cwd, pluginRoot, pluginData) {
50
- const expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginData);
51
- const absolute = isAbsolute(expanded) ? expanded : resolve(pluginRoot, expanded);
52
- const root = cwdTargetsData(cwd) ? pluginData : pluginRoot;
53
- if (!isInside(root, absolute))
54
- throw new Error(`Working directory escapes ${root}`);
55
- return absolute;
56
- }
57
- function cwdTargetsData(cwd) {
58
- return cwd === DATA_TOKEN || cwd.startsWith(`${DATA_TOKEN}/`);
59
- }
60
- function isInside(root, target) {
61
- const rel = relative(root, target);
62
- return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
63
- }
64
- function setEnv(env, key, value) {
65
- if (process.platform === "win32") {
66
- const existing = Object.keys(env).find((name) => name.toLowerCase() === key.toLowerCase());
67
- if (existing && existing !== key)
68
- delete env[existing];
69
- }
70
- env[key] = value;
71
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * Action-scoped sandbox client (`ctx.sandbox`). Calls `POST /v1/actions/:id/sandbox/:tool`
3
- * over a transport whose credential is the Action's delivery token.
4
- */
5
- import { type SandboxToolName } from "@nylorun/core/define";
6
- import { type Transport } from "../http.js";
7
- export type ActionSandboxToolResult = {
8
- readonly kind: "completed";
9
- readonly output: unknown;
10
- } | {
11
- readonly kind: "failed";
12
- readonly code: string;
13
- readonly message: string;
14
- };
15
- export type ActionSandbox = {
16
- readonly bash: (input: {
17
- command: string;
18
- timeout?: number;
19
- }) => Promise<ActionSandboxToolResult>;
20
- readonly read: (input: {
21
- path: string;
22
- offset?: number;
23
- limit?: number;
24
- }) => Promise<ActionSandboxToolResult>;
25
- readonly write: (input: {
26
- path: string;
27
- content: string;
28
- }) => Promise<ActionSandboxToolResult>;
29
- readonly edit: (input: {
30
- path: string;
31
- old_string: string;
32
- new_string: string;
33
- replace_all?: boolean;
34
- }) => Promise<ActionSandboxToolResult>;
35
- readonly grep: (input: {
36
- pattern: string;
37
- path?: string;
38
- glob?: string;
39
- ignore_case?: boolean;
40
- max_results?: number;
41
- }) => Promise<ActionSandboxToolResult>;
42
- readonly glob: (input: {
43
- pattern: string;
44
- path?: string;
45
- }) => Promise<ActionSandboxToolResult>;
46
- };
47
- export type CreateActionSandboxOptions = {
48
- /** The transport's key is the delivery token, which names the Action and its generation. */
49
- readonly transport: Transport;
50
- readonly actionId: string;
51
- readonly signal?: AbortSignal;
52
- };
53
- /** Build `ctx.sandbox` for one delivered action. Callers skip it when the session has none. */
54
- export declare function createActionSandbox(options: CreateActionSandboxOptions): ActionSandbox;
55
- export declare function isActionSandboxTool(name: string): name is SandboxToolName;
56
- /** True when the executable declares a sandbox the Runtime can share. */
57
- export declare function definitionDeclaresSandbox(manifest: {
58
- kind?: string;
59
- sandbox?: unknown;
60
- capabilities?: readonly {
61
- sandbox?: unknown;
62
- }[];
63
- }): boolean;
@@ -1,29 +0,0 @@
1
- /**
2
- * Action-scoped sandbox client (`ctx.sandbox`). Calls `POST /v1/actions/:id/sandbox/:tool`
3
- * over a transport whose credential is the Action's delivery token.
4
- */
5
- import { SANDBOX_TOOL_NAMES, } from "@nylorun/core/define";
6
- import { segment } from "../http.js";
7
- /** Build `ctx.sandbox` for one delivered action. Callers skip it when the session has none. */
8
- export function createActionSandbox(options) {
9
- const call = (tool, input) => options.transport.json(`/v1/actions/${segment(options.actionId)}/sandbox/${tool}`, "POST", input, options.signal);
10
- const sandbox = {
11
- bash: (input) => call("bash", input),
12
- read: (input) => call("read", input),
13
- write: (input) => call("write", input),
14
- edit: (input) => call("edit", input),
15
- grep: (input) => call("grep", input),
16
- glob: (input) => call("glob", input),
17
- };
18
- // Freeze so tools cannot replace built-ins.
19
- return Object.freeze(sandbox);
20
- }
21
- export function isActionSandboxTool(name) {
22
- return SANDBOX_TOOL_NAMES.includes(name);
23
- }
24
- /** True when the executable declares a sandbox the Runtime can share. */
25
- export function definitionDeclaresSandbox(manifest) {
26
- if (manifest.kind === "workflow")
27
- return manifest.sandbox !== undefined;
28
- return (manifest.capabilities ?? []).some((item) => item.sandbox !== undefined);
29
- }
@@ -1,2 +0,0 @@
1
- export { createActionSandbox, definitionDeclaresSandbox, isActionSandboxTool, } from "./client.js";
2
- export type { ActionSandbox, ActionSandboxToolResult, CreateActionSandboxOptions, } from "./client.js";
@@ -1 +0,0 @@
1
- export { createActionSandbox, definitionDeclaresSandbox, isActionSandboxTool, } from "./client.js";
@@ -1,20 +0,0 @@
1
- import { type BuiltWorkflow } from "@nylorun/core/define";
2
- import { type AgentSource } from "./client.js";
3
- import type { ExecutableDefinition } from "./execute-action.js";
4
- /** The implementation version to register: the option, `NYLORUN_IMPLEMENTATION_VERSION`, or `dev`. */
5
- export declare function implementationVersionOf(options: {
6
- implementationVersion?: string;
7
- }): string;
8
- /** The ids among `agents` that are saved only as part of another definition. */
9
- export declare function embeddedIn(agents: Map<string, ExecutableDefinition>): ReadonlySet<string>;
10
- /**
11
- * Every definition `sources` serves, by id: the agents and workflows passed in, the agents
12
- * a v2 workflow embeds, and flow agents used as tools. `owner` names the caller in errors.
13
- */
14
- export declare function buildAgents(sources: readonly (AgentSource | BuiltWorkflow)[], owner: string): Map<string, ExecutableDefinition>;
15
- /**
16
- * The manifest hash served for a workflow's flow actions. A run is pinned to one workflow
17
- * manifest, and its stage keys may shift between deploys, so flow actions for another hash
18
- * are left for whoever serves it.
19
- */
20
- export declare function flowManifestHash(agent: ExecutableDefinition): string | undefined;
@@ -1,100 +0,0 @@
1
- /** The definitions an application serves Actions for (`createActionHandler`). */
2
- import { AgentManifestSchema } from "@nylorun/core/contracts";
3
- import { agentFrom, delegateOf, hashManifest, isFlowDelegate, isBuiltWorkflow, } from "@nylorun/core/define";
4
- import { assertNoMiddlewareClosures } from "./client.js";
5
- import { env } from "./http.js";
6
- /** The implementation version to register: the option, `NYLORUN_IMPLEMENTATION_VERSION`, or `dev`. */
7
- export function implementationVersionOf(options) {
8
- return (options.implementationVersion ??
9
- env("NYLORUN_IMPLEMENTATION_VERSION") ??
10
- "dev");
11
- }
12
- /** Agents embedded in a v2 workflow: served here, but saved only as part of the workflow. */
13
- const embeddedAgents = new WeakMap();
14
- /** The ids among `agents` that are saved only as part of another definition. */
15
- export function embeddedIn(agents) {
16
- return embeddedAgents.get(agents) ?? new Set();
17
- }
18
- /**
19
- * Every definition `sources` serves, by id: the agents and workflows passed in, the agents
20
- * a v2 workflow embeds, and flow agents used as tools. `owner` names the caller in errors.
21
- */
22
- export function buildAgents(sources, owner) {
23
- const agents = new Map();
24
- const embedded = new Set();
25
- /**
26
- * Agents embedded in another definition (a v2 flow's agents, a flow agent used as a
27
- * tool) may be reached from more than one place; the same definition is served once.
28
- */
29
- const sameAsServed = (id, manifest) => {
30
- const served = agents.get(id);
31
- if (!served)
32
- return false;
33
- if (hashManifest(served.manifest) === hashManifest(manifest))
34
- return true;
35
- throw new Error(`Duplicate connected agent ${id}`);
36
- };
37
- const addAgent = (built, shared = false) => {
38
- assertNoMiddlewareClosures(built);
39
- AgentManifestSchema.parse(built.manifest);
40
- if (shared ? sameAsServed(built.id, built.manifest) : agents.has(built.id)) {
41
- if (!shared)
42
- throw new Error(`Duplicate connected agent ${built.id}`);
43
- return;
44
- }
45
- agents.set(built.id, built);
46
- for (const tool of built.getBinding().tools) {
47
- const delegate = delegateOf(tool);
48
- if (!delegate || !isFlowDelegate(delegate))
49
- continue;
50
- // A flow agent used as a tool runs in its own linked session: serve its code too.
51
- if (!delegate.workflow)
52
- throw new Error(`Flow agent '${delegate.manifest.id}' is used as a tool by '${built.id}' but has no local implementation`);
53
- addWorkflow(delegate.workflow, true);
54
- embedded.add(delegate.workflow.id);
55
- }
56
- };
57
- const addWorkflow = (workflow, shared = false) => {
58
- if (shared ? sameAsServed(workflow.id, workflow.manifest) : agents.has(workflow.id)) {
59
- if (!shared)
60
- throw new Error(`Duplicate connected agent ${workflow.id}`);
61
- return;
62
- }
63
- agents.set(workflow.id, workflow);
64
- const v2 = workflow.manifest.workflowSchemaVersion === 2;
65
- for (const binding of Object.values(workflow.getBinding().agents)) {
66
- addAgent(agentFrom(binding.manifest, binding.implementations), v2);
67
- if (v2)
68
- embedded.add(binding.manifest.id);
69
- }
70
- };
71
- for (const source of sources) {
72
- if (isBuiltWorkflow(source)) {
73
- addWorkflow(source);
74
- continue;
75
- }
76
- const built = source.build?.() ?? source;
77
- if (isBuiltWorkflow(built)) {
78
- addWorkflow(built);
79
- continue;
80
- }
81
- addAgent(built);
82
- }
83
- if (!agents.size)
84
- throw new Error(`${owner} requires at least one agent`);
85
- // A definition passed in directly is saved on its own, even if another one embeds it.
86
- for (const source of sources)
87
- embedded.delete(source.id);
88
- embeddedAgents.set(agents, embedded);
89
- return agents;
90
- }
91
- /**
92
- * The manifest hash served for a workflow's flow actions. A run is pinned to one workflow
93
- * manifest, and its stage keys may shift between deploys, so flow actions for another hash
94
- * are left for whoever serves it.
95
- */
96
- export function flowManifestHash(agent) {
97
- return isBuiltWorkflow(agent) && agent.manifest.workflowSchemaVersion === 2
98
- ? hashManifest(agent.manifest)
99
- : undefined;
100
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * A route for app servers that mints a subject token for the signed-in person (Host feature
3
- * `subject-tokens`). The browser client calls it whenever it needs a token. Mount it on your
4
- * app's own origin behind your sign-in: `POST` only, no CORS, never cached.
5
- *
6
- * export const POST = createTokenEndpoint({
7
- * client, role: "user", subject: (request) => userFromCookie(request)?.id,
8
- * });
9
- */
10
- import type { TokenScope } from "@nylorun/core/contracts";
11
- import type { AgentsClient } from "./session-client.js";
12
- export interface TokenEndpointOptions {
13
- /** The application client (the Tenant key stays on the server). */
14
- client: AgentsClient | Promise<AgentsClient>;
15
- /** The signed-in person the token is for; `undefined` answers 401. */
16
- subject(request: Request): string | undefined | Promise<string | undefined>;
17
- /** The access policy role to mint for, or a function of the request and subject. */
18
- role: string | ((request: Request, subject: string) => string | Promise<string>);
19
- scopes?: readonly TokenScope[];
20
- agents?: readonly string[];
21
- ttlSeconds?: number;
22
- }
23
- export declare function createTokenEndpoint(options: TokenEndpointOptions): (request: Request) => Promise<Response>;
@@ -1,30 +0,0 @@
1
- import { RuntimeError } from "./http.js";
2
- const NO_STORE = { "cache-control": "no-store" };
3
- export function createTokenEndpoint(options) {
4
- return async (request) => {
5
- if (request.method !== "POST")
6
- return new Response(null, { status: 405, headers: { allow: "POST", ...NO_STORE } });
7
- const subject = await options.subject(request);
8
- if (!subject)
9
- return Response.json({ error: "Sign in required" }, { status: 401, headers: NO_STORE });
10
- const role = typeof options.role === "string"
11
- ? options.role
12
- : await options.role(request, subject);
13
- try {
14
- const client = await options.client;
15
- const minted = await client.tokens.create({
16
- subject,
17
- role,
18
- ...(options.scopes ? { scopes: options.scopes } : {}),
19
- ...(options.agents ? { agents: options.agents } : {}),
20
- ...(options.ttlSeconds === undefined ? {} : { ttlSeconds: options.ttlSeconds }),
21
- });
22
- return Response.json({ token: minted.token, expiresAt: minted.expiresAt }, { headers: NO_STORE });
23
- }
24
- catch (error) {
25
- // The person sees that a token could not be issued, never the Runtime's details.
26
- const status = error instanceof RuntimeError && error.status === 400 ? 403 : 502;
27
- return Response.json({ error: "Could not issue a token" }, { status, headers: NO_STORE });
28
- }
29
- };
30
- }