@nylorun/agents 0.5.0-beta → 0.7.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 +56 -0
- package/README.md +156 -13
- package/dist/client.d.ts +41 -9
- package/dist/client.js +145 -11
- package/dist/connection.d.ts +19 -0
- package/dist/connection.js +100 -0
- package/dist/derived-credentials.d.ts +5 -0
- package/dist/derived-credentials.js +17 -0
- package/dist/execute-action.d.ts +8 -2
- package/dist/execute-action.js +85 -8
- package/dist/executor.d.ts +7 -2
- package/dist/executor.js +145 -8
- package/dist/http.d.ts +21 -1
- package/dist/http.js +120 -3
- package/dist/index.d.ts +13 -6
- package/dist/index.js +6 -2
- package/dist/sandbox/client.d.ts +64 -0
- package/dist/sandbox/client.js +33 -0
- package/dist/sandbox/index.d.ts +2 -0
- package/dist/sandbox/index.js +1 -0
- package/dist/sandbox/sandbox.d.ts +1 -2
- package/dist/sandbox/sandbox.js +1 -2
- package/package.json +2 -2
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { ProjectCredentialsFileSchema, ProjectLinkFileSchema, } from "@nylorun/core/contracts";
|
|
4
|
+
import { env } from "./http.js";
|
|
5
|
+
export class ConnectionError extends Error {
|
|
6
|
+
code = "connection_missing";
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "ConnectionError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function missing(tried) {
|
|
13
|
+
throw new ConnectionError(`connection_missing: no Runtime connection found (tried ${tried.join(", ")}). ` +
|
|
14
|
+
`Start the local stack with "npx nylorun up" and link this project with "npx @nylorun/cli tenant create", ` +
|
|
15
|
+
`or set NYLORUN_RUNTIME_URL, NYLORUN_TENANT and NYLORUN_SERVER_KEY.`);
|
|
16
|
+
}
|
|
17
|
+
function stripTrailingSlash(url) {
|
|
18
|
+
return url.replace(/\/$/, "");
|
|
19
|
+
}
|
|
20
|
+
async function readProjectLink(startDir) {
|
|
21
|
+
let dir = resolve(startDir);
|
|
22
|
+
for (;;) {
|
|
23
|
+
const nylorun = join(dir, ".nylorun");
|
|
24
|
+
const linkPath = join(nylorun, "link.json");
|
|
25
|
+
let linkRaw;
|
|
26
|
+
try {
|
|
27
|
+
linkRaw = await readFile(linkPath, "utf8");
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code === "ENOENT") {
|
|
31
|
+
const parent = dirname(dir);
|
|
32
|
+
if (parent === dir)
|
|
33
|
+
return undefined;
|
|
34
|
+
dir = parent;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
const link = ProjectLinkFileSchema.parse(JSON.parse(linkRaw));
|
|
40
|
+
const credentials = ProjectCredentialsFileSchema.parse(JSON.parse(await readFile(join(nylorun, "credentials.json"), "utf8")));
|
|
41
|
+
return {
|
|
42
|
+
url: stripTrailingSlash(link.hostUrl),
|
|
43
|
+
tenant: link.tenantId,
|
|
44
|
+
key: credentials.applicationKey,
|
|
45
|
+
role: "application",
|
|
46
|
+
source: "project-link",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Resolve Tenant API connection: options → environment → project link (D§7.1). */
|
|
51
|
+
export async function resolveConnection(options) {
|
|
52
|
+
const tried = [];
|
|
53
|
+
const optionUrl = options?.url;
|
|
54
|
+
const optionTenant = options?.tenant;
|
|
55
|
+
const optionKey = options?.key;
|
|
56
|
+
const anyOption = optionUrl !== undefined ||
|
|
57
|
+
optionTenant !== undefined ||
|
|
58
|
+
optionKey !== undefined;
|
|
59
|
+
tried.push("options");
|
|
60
|
+
if (anyOption) {
|
|
61
|
+
if (optionUrl && optionTenant && optionKey) {
|
|
62
|
+
return {
|
|
63
|
+
url: stripTrailingSlash(optionUrl),
|
|
64
|
+
tenant: optionTenant,
|
|
65
|
+
key: optionKey,
|
|
66
|
+
role: "application",
|
|
67
|
+
source: "options",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
missing(tried.concat(["environment", "project-link"]));
|
|
71
|
+
}
|
|
72
|
+
const envUrl = env("NYLORUN_RUNTIME_URL");
|
|
73
|
+
const envTenant = env("NYLORUN_TENANT");
|
|
74
|
+
const envServerKey = env("NYLORUN_SERVER_KEY");
|
|
75
|
+
const envExecutorKey = env("NYLORUN_EXECUTOR_KEY");
|
|
76
|
+
const anyEnv = envUrl !== undefined ||
|
|
77
|
+
envTenant !== undefined ||
|
|
78
|
+
envServerKey !== undefined ||
|
|
79
|
+
envExecutorKey !== undefined;
|
|
80
|
+
tried.push("environment");
|
|
81
|
+
if (anyEnv) {
|
|
82
|
+
const key = envExecutorKey ?? envServerKey;
|
|
83
|
+
if (envUrl && envTenant && key) {
|
|
84
|
+
return {
|
|
85
|
+
url: stripTrailingSlash(envUrl),
|
|
86
|
+
tenant: envTenant,
|
|
87
|
+
key,
|
|
88
|
+
role: envExecutorKey !== undefined ? "executor" : "application",
|
|
89
|
+
source: "environment",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
missing(tried.concat(["project-link"]));
|
|
93
|
+
}
|
|
94
|
+
tried.push("project-link");
|
|
95
|
+
const cwd = options?.cwd ?? (typeof process !== "undefined" ? process.cwd() : ".");
|
|
96
|
+
const fromLink = await readProjectLink(cwd);
|
|
97
|
+
if (fromLink)
|
|
98
|
+
return fromLink;
|
|
99
|
+
missing(tried);
|
|
100
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic executor token from an application key (D§7.3).
|
|
4
|
+
* Same key, Tenant and agent always produce the same token.
|
|
5
|
+
*/
|
|
6
|
+
export function deriveExecutorToken(applicationKey, tenantId, agentId) {
|
|
7
|
+
const message = Buffer.concat([
|
|
8
|
+
Buffer.from("nylorun/executor/v1", "utf8"),
|
|
9
|
+
Buffer.from([0]),
|
|
10
|
+
Buffer.from(tenantId, "utf8"),
|
|
11
|
+
Buffer.from([0]),
|
|
12
|
+
Buffer.from(agentId, "utf8"),
|
|
13
|
+
]);
|
|
14
|
+
return createHmac("sha256", Buffer.from(applicationKey, "utf8"))
|
|
15
|
+
.update(message)
|
|
16
|
+
.digest("hex");
|
|
17
|
+
}
|
package/dist/execute-action.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import { type BuiltAgent } from "@nylorun/core/define";
|
|
1
|
+
import { type BuiltAgent, type BuiltWorkflow } from "@nylorun/core/define";
|
|
2
2
|
import type { Action, ActionOutcome } from "@nylorun/core/contracts";
|
|
3
|
+
import type { ActionSandbox } from "./sandbox/client.js";
|
|
4
|
+
export type ExecutableDefinition = BuiltAgent | BuiltWorkflow;
|
|
5
|
+
export type ExecuteActionOptions = {
|
|
6
|
+
/** Claim-scoped sandbox client; omitted when the session has no sandbox. */
|
|
7
|
+
readonly sandbox?: ActionSandbox;
|
|
8
|
+
};
|
|
3
9
|
/** Executes code only after a host-issued claim. No engine dependency. */
|
|
4
|
-
export declare function executeAction(action: Action, root:
|
|
10
|
+
export declare function executeAction(action: Action, root: ExecutableDefinition, signal: AbortSignal, options?: ExecuteActionOptions): Promise<ActionOutcome>;
|
package/dist/execute-action.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { delegateOf, implementationsFor, isToolError, normalizeToolDefinition, normalizedSchemasFor, runHookPoint, } from "@nylorun/core/define";
|
|
1
|
+
import { delegateOf, implementationsFor, isBuiltWorkflow, isToolError, normalizeToolDefinition, normalizedSchemasFor, runHookPoint, } from "@nylorun/core/define";
|
|
2
2
|
class Suspend {
|
|
3
3
|
outcome;
|
|
4
4
|
constructor(outcome) {
|
|
@@ -9,7 +9,18 @@ const object = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
9
9
|
? value
|
|
10
10
|
: {};
|
|
11
11
|
/** Executes code only after a host-issued claim. No engine dependency. */
|
|
12
|
-
export async function executeAction(action, root, signal) {
|
|
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
|
+
}
|
|
13
24
|
// Work for an agent used as a tool runs that agent's code, served from the root's binding.
|
|
14
25
|
const agent = action.agent ? delegatedAgent(root, action.agent.id) : root;
|
|
15
26
|
const ref = action.agent ?? { id: root.id, path: root.id };
|
|
@@ -20,6 +31,8 @@ export async function executeAction(action, root, signal) {
|
|
|
20
31
|
results: await runHookPoint(implementationsFor(agent), action.hook, action.input),
|
|
21
32
|
},
|
|
22
33
|
};
|
|
34
|
+
if (action.kind !== "tool" || !("capabilityId" in action))
|
|
35
|
+
throw new Error(`Unsupported action kind ${action.kind}`);
|
|
23
36
|
const impl = implementationsFor(agent)[action.capabilityId];
|
|
24
37
|
if (!impl)
|
|
25
38
|
throw new Error(`No capability ${action.capabilityId}`);
|
|
@@ -27,7 +40,25 @@ export async function executeAction(action, root, signal) {
|
|
|
27
40
|
if (!raw)
|
|
28
41
|
throw new Error("Missing tool implementation");
|
|
29
42
|
const tool = normalizeToolDefinition(raw);
|
|
30
|
-
|
|
43
|
+
return runTool(action, tool, ref, signal, sandbox);
|
|
44
|
+
}
|
|
45
|
+
async function executeWorkflowTool(action, binding, signal, sandbox) {
|
|
46
|
+
const impl = binding.nodes[action.key];
|
|
47
|
+
if (!impl || impl.kind !== "tool")
|
|
48
|
+
throw new Error(`No tool implementation for key ${action.key}`);
|
|
49
|
+
const path = "path" in action && typeof action.path === "string" ? action.path : action.key;
|
|
50
|
+
return runTool(action, impl.tool, { id: action.agentId, path }, signal, sandbox);
|
|
51
|
+
}
|
|
52
|
+
async function runTool(action, raw, ref, signal, sandbox) {
|
|
53
|
+
const tool = "execute" in raw && typeof raw.execute === "function" && "inputSchema" in raw
|
|
54
|
+
? raw
|
|
55
|
+
: normalizeToolDefinition(raw);
|
|
56
|
+
const schemas = "inputSchema" in tool && tool.inputSchema && "validate" in tool.inputSchema
|
|
57
|
+
? {
|
|
58
|
+
inputSchema: tool.inputSchema,
|
|
59
|
+
outputSchema: "outputSchema" in tool ? tool.outputSchema : undefined,
|
|
60
|
+
}
|
|
61
|
+
: normalizedSchemasFor(tool);
|
|
31
62
|
const input = schemas.inputSchema.validate(action.input);
|
|
32
63
|
if (!input.ok)
|
|
33
64
|
return {
|
|
@@ -82,6 +113,7 @@ export async function executeAction(action, root, signal) {
|
|
|
82
113
|
session: { id: action.sessionId },
|
|
83
114
|
agent: ref,
|
|
84
115
|
...(ctx.resume ? { resume: ctx.resume } : {}),
|
|
116
|
+
...(sandbox ? { sandbox } : {}),
|
|
85
117
|
state: {
|
|
86
118
|
get: (key) => state[key],
|
|
87
119
|
set: (key, value) => {
|
|
@@ -119,14 +151,17 @@ export async function executeAction(action, root, signal) {
|
|
|
119
151
|
};
|
|
120
152
|
try {
|
|
121
153
|
signal.throwIfAborted();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
154
|
+
const approval = "approval" in tool ? tool.approval : undefined;
|
|
155
|
+
if (approval && !gateApproved) {
|
|
156
|
+
const result = await approval(input.value);
|
|
157
|
+
if (result)
|
|
125
158
|
throw new Suspend({
|
|
126
159
|
kind: "interaction-required",
|
|
127
160
|
interaction: {
|
|
128
161
|
kind: "approval",
|
|
129
|
-
prompt: typeof
|
|
162
|
+
prompt: typeof result === "string"
|
|
163
|
+
? result
|
|
164
|
+
: `Approve ${"name" in tool ? tool.name : "tool"}?`,
|
|
130
165
|
},
|
|
131
166
|
token: durableToken({ approvalGate: true }),
|
|
132
167
|
});
|
|
@@ -134,7 +169,12 @@ export async function executeAction(action, root, signal) {
|
|
|
134
169
|
}
|
|
135
170
|
if (resume.kind === "approval" && resume.approved === false)
|
|
136
171
|
return { value: { kind: "denied", reason: "Approval denied" } };
|
|
137
|
-
const
|
|
172
|
+
const execute = "execute" in tool && typeof tool.execute === "function"
|
|
173
|
+
? tool.execute
|
|
174
|
+
: undefined;
|
|
175
|
+
if (!execute)
|
|
176
|
+
throw new Error("Missing tool implementation");
|
|
177
|
+
const value = await execute(input.value, context);
|
|
138
178
|
const tagged = object(value);
|
|
139
179
|
let outcome;
|
|
140
180
|
if ([
|
|
@@ -174,6 +214,43 @@ export async function executeAction(action, root, signal) {
|
|
|
174
214
|
};
|
|
175
215
|
}
|
|
176
216
|
}
|
|
217
|
+
async function executeWorkflowFn(action, binding, signal, sandbox) {
|
|
218
|
+
const impl = binding.nodes[action.key];
|
|
219
|
+
if (!impl || impl.kind !== action.kind)
|
|
220
|
+
throw new Error(`No ${action.kind} implementation for key ${action.key}`);
|
|
221
|
+
signal.throwIfAborted();
|
|
222
|
+
try {
|
|
223
|
+
// Verify may receive a tool-like context later (L3/L4); tracer passes input only.
|
|
224
|
+
const value = action.kind === "verify"
|
|
225
|
+
? await impl.fn(action.input, {
|
|
226
|
+
signal,
|
|
227
|
+
info: action.context.info,
|
|
228
|
+
session: { id: action.sessionId },
|
|
229
|
+
...(sandbox ? { sandbox } : {}),
|
|
230
|
+
step: async (_name, fn) => fn(),
|
|
231
|
+
approve: async () => {
|
|
232
|
+
throw new Error("Approvals in verify are not available in the tracer");
|
|
233
|
+
},
|
|
234
|
+
ask: async () => {
|
|
235
|
+
throw new Error("ask in verify is not available in the tracer");
|
|
236
|
+
},
|
|
237
|
+
progress() { },
|
|
238
|
+
})
|
|
239
|
+
: await impl.fn(action.input);
|
|
240
|
+
return { value };
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (signal.aborted)
|
|
244
|
+
throw signal.reason;
|
|
245
|
+
return {
|
|
246
|
+
value: {
|
|
247
|
+
kind: "failed",
|
|
248
|
+
code: action.kind === "verify" ? "loop.verify-failed" : "fn.failed",
|
|
249
|
+
message: message(error),
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
177
254
|
function delegatedAgent(root, id) {
|
|
178
255
|
for (const capability of Object.values(implementationsFor(root))) {
|
|
179
256
|
const child = delegateOf(capability.tools?.[id])?.agent;
|
package/dist/executor.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type BuiltWorkflow } from "@nylorun/core/define";
|
|
2
|
+
import { AgentsClient, type AgentSource } from "./client.js";
|
|
3
|
+
import { deriveExecutorToken } from "./derived-credentials.js";
|
|
2
4
|
import { type Destination } from "./http.js";
|
|
3
5
|
export interface ConnectOptions {
|
|
4
|
-
agents: readonly AgentSource[];
|
|
6
|
+
agents: readonly (AgentSource | BuiltWorkflow)[];
|
|
7
|
+
/** Application-mode client; when set, connectAgents uses application mode (D§7.2). */
|
|
8
|
+
application?: AgentsClient;
|
|
5
9
|
runtime?: Destination;
|
|
6
10
|
implementationVersion?: string;
|
|
7
11
|
onError?: (error: unknown) => void;
|
|
@@ -13,3 +17,4 @@ export interface AgentConnection {
|
|
|
13
17
|
close(): Promise<void>;
|
|
14
18
|
}
|
|
15
19
|
export declare function connectAgents(options: ConnectOptions): AgentConnection;
|
|
20
|
+
export { deriveExecutorToken };
|
package/dist/executor.js
CHANGED
|
@@ -1,24 +1,53 @@
|
|
|
1
1
|
import { AgentManifestSchema, ActionSchema, ActionClaimResponseSchema, } from "@nylorun/core/contracts";
|
|
2
|
-
import {
|
|
2
|
+
import { agentFrom, isBuiltWorkflow, } from "@nylorun/core/define";
|
|
3
|
+
import { AgentsClient, assertNoMiddlewareClosures, } from "./client.js";
|
|
4
|
+
import { resolveConnection } from "./connection.js";
|
|
5
|
+
import { deriveExecutorToken } from "./derived-credentials.js";
|
|
3
6
|
import { Transport, RuntimeError, delay, env, id, segment, } from "./http.js";
|
|
4
7
|
import { readSSE } from "./sse.js";
|
|
5
8
|
import { executeAction } from "./execute-action.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
import { createActionSandbox, definitionDeclaresSandbox, } from "./sandbox/client.js";
|
|
10
|
+
function implementationVersionOf(options) {
|
|
11
|
+
return (options.implementationVersion ??
|
|
9
12
|
env("NYLORUN_IMPLEMENTATION_VERSION") ??
|
|
10
|
-
"dev";
|
|
13
|
+
"dev");
|
|
14
|
+
}
|
|
15
|
+
function buildAgents(options) {
|
|
11
16
|
const agents = new Map();
|
|
12
|
-
|
|
13
|
-
const built = source.build?.() ?? source;
|
|
17
|
+
const addAgent = (built) => {
|
|
14
18
|
assertNoMiddlewareClosures(built);
|
|
15
19
|
AgentManifestSchema.parse(built.manifest);
|
|
16
20
|
if (agents.has(built.id))
|
|
17
21
|
throw new Error(`Duplicate connected agent ${built.id}`);
|
|
18
22
|
agents.set(built.id, built);
|
|
23
|
+
};
|
|
24
|
+
for (const source of options.agents) {
|
|
25
|
+
if (isBuiltWorkflow(source)) {
|
|
26
|
+
if (agents.has(source.id))
|
|
27
|
+
throw new Error(`Duplicate connected agent ${source.id}`);
|
|
28
|
+
agents.set(source.id, source);
|
|
29
|
+
for (const binding of Object.values(source.getBinding().agents))
|
|
30
|
+
addAgent(agentFrom(binding.manifest, binding.implementations));
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const built = source.build?.() ?? source;
|
|
34
|
+
if (isBuiltWorkflow(built)) {
|
|
35
|
+
if (agents.has(built.id))
|
|
36
|
+
throw new Error(`Duplicate connected agent ${built.id}`);
|
|
37
|
+
agents.set(built.id, built);
|
|
38
|
+
for (const binding of Object.values(built.getBinding().agents))
|
|
39
|
+
addAgent(agentFrom(binding.manifest, binding.implementations));
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
addAgent(built);
|
|
19
43
|
}
|
|
20
44
|
if (!agents.size)
|
|
21
45
|
throw new Error("connectAgents requires at least one agent");
|
|
46
|
+
return agents;
|
|
47
|
+
}
|
|
48
|
+
function connectExecutorMode(options, runtime, agents) {
|
|
49
|
+
const transport = new Transport(runtime, "executor");
|
|
50
|
+
const version = implementationVersionOf(options);
|
|
22
51
|
const controller = new AbortController();
|
|
23
52
|
const signal = controller.signal;
|
|
24
53
|
const active = new Map();
|
|
@@ -125,7 +154,17 @@ export function connectAgents(options) {
|
|
|
125
154
|
report(error);
|
|
126
155
|
}
|
|
127
156
|
});
|
|
128
|
-
const outcome = await executeAction(claim.action, agent, work.signal
|
|
157
|
+
const outcome = await executeAction(claim.action, agent, work.signal, {
|
|
158
|
+
sandbox: definitionDeclaresSandbox(agent.manifest)
|
|
159
|
+
? createActionSandbox({
|
|
160
|
+
transport,
|
|
161
|
+
actionId: action.actionId,
|
|
162
|
+
claimId: claim.claimId,
|
|
163
|
+
generation: claim.generation,
|
|
164
|
+
signal: work.signal,
|
|
165
|
+
})
|
|
166
|
+
: undefined,
|
|
167
|
+
});
|
|
129
168
|
work.signal.throwIfAborted();
|
|
130
169
|
const command = {
|
|
131
170
|
type: "action_result",
|
|
@@ -214,3 +253,101 @@ export function connectAgents(options) {
|
|
|
214
253
|
},
|
|
215
254
|
};
|
|
216
255
|
}
|
|
256
|
+
function connectApplicationMode(options, application, agents) {
|
|
257
|
+
const version = implementationVersionOf(options);
|
|
258
|
+
const children = [];
|
|
259
|
+
let resolveReady;
|
|
260
|
+
let rejectReady;
|
|
261
|
+
const ready = new Promise((resolve, reject) => {
|
|
262
|
+
resolveReady = resolve;
|
|
263
|
+
rejectReady = reject;
|
|
264
|
+
});
|
|
265
|
+
void ready.catch(() => { });
|
|
266
|
+
const boot = (async () => {
|
|
267
|
+
for (const agent of agents.values()) {
|
|
268
|
+
await application.saveAgent(agent, { implementationVersion: version });
|
|
269
|
+
}
|
|
270
|
+
const registrations = [...agents.values()].map((agent) => ({
|
|
271
|
+
agentId: agent.id,
|
|
272
|
+
token: deriveExecutorToken(application.transport.key, application.transport.tenant, agent.id),
|
|
273
|
+
implementationVersion: version,
|
|
274
|
+
}));
|
|
275
|
+
await application.transport.json("/v1/executors", "PUT", {
|
|
276
|
+
executors: registrations,
|
|
277
|
+
});
|
|
278
|
+
for (const agent of agents.values()) {
|
|
279
|
+
const token = deriveExecutorToken(application.transport.key, application.transport.tenant, agent.id);
|
|
280
|
+
const child = connectExecutorMode({
|
|
281
|
+
agents: [agent],
|
|
282
|
+
implementationVersion: version,
|
|
283
|
+
onError: options.onError,
|
|
284
|
+
}, {
|
|
285
|
+
url: application.transport.url,
|
|
286
|
+
tenant: application.transport.tenant,
|
|
287
|
+
key: token,
|
|
288
|
+
fetch: application.transport.fetcher,
|
|
289
|
+
}, new Map([[agent.id, agent]]));
|
|
290
|
+
children.push(child);
|
|
291
|
+
}
|
|
292
|
+
await Promise.all(children.map((child) => child.ready));
|
|
293
|
+
resolveReady();
|
|
294
|
+
})().catch((error) => {
|
|
295
|
+
rejectReady(error);
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
ready,
|
|
299
|
+
async close() {
|
|
300
|
+
await boot.catch(() => { });
|
|
301
|
+
await Promise.allSettled(children.map((child) => child.close()));
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
export function connectAgents(options) {
|
|
306
|
+
const agents = buildAgents(options);
|
|
307
|
+
// D§7.2 mode table: runtime → executor; application → application;
|
|
308
|
+
// neither → resolveConnection() role.
|
|
309
|
+
if (options.runtime) {
|
|
310
|
+
return connectExecutorMode(options, options.runtime, agents);
|
|
311
|
+
}
|
|
312
|
+
if (options.application) {
|
|
313
|
+
return connectApplicationMode(options, options.application, agents);
|
|
314
|
+
}
|
|
315
|
+
let resolveReady;
|
|
316
|
+
let rejectReady;
|
|
317
|
+
const ready = new Promise((resolve, reject) => {
|
|
318
|
+
resolveReady = resolve;
|
|
319
|
+
rejectReady = reject;
|
|
320
|
+
});
|
|
321
|
+
void ready.catch(() => { });
|
|
322
|
+
let handle;
|
|
323
|
+
const boot = (async () => {
|
|
324
|
+
const connection = await resolveConnection();
|
|
325
|
+
if (connection.role === "executor") {
|
|
326
|
+
handle = connectExecutorMode(options, {
|
|
327
|
+
url: connection.url,
|
|
328
|
+
tenant: connection.tenant,
|
|
329
|
+
key: connection.key,
|
|
330
|
+
}, agents);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
const application = new AgentsClient({
|
|
334
|
+
url: connection.url,
|
|
335
|
+
tenant: connection.tenant,
|
|
336
|
+
key: connection.key,
|
|
337
|
+
});
|
|
338
|
+
handle = connectApplicationMode(options, application, agents);
|
|
339
|
+
}
|
|
340
|
+
await handle.ready;
|
|
341
|
+
resolveReady();
|
|
342
|
+
})().catch((error) => {
|
|
343
|
+
rejectReady(error);
|
|
344
|
+
});
|
|
345
|
+
return {
|
|
346
|
+
ready,
|
|
347
|
+
async close() {
|
|
348
|
+
await boot.catch(() => { });
|
|
349
|
+
await handle?.close();
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
export { deriveExecutorToken };
|
package/dist/http.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { type Compatibility, type ProtocolRange } from "@nylorun/core/compatibility";
|
|
1
2
|
export interface Destination {
|
|
2
3
|
url?: string;
|
|
3
4
|
key?: string;
|
|
5
|
+
tenant?: string;
|
|
4
6
|
fetch?: typeof fetch;
|
|
5
7
|
}
|
|
6
8
|
export declare function env(name: string): string | undefined;
|
|
@@ -9,12 +11,30 @@ export declare class RuntimeError extends Error {
|
|
|
9
11
|
readonly body: unknown;
|
|
10
12
|
constructor(status: number, body: unknown);
|
|
11
13
|
}
|
|
14
|
+
export type IncompatibleReason = Extract<Compatibility, {
|
|
15
|
+
ok: false;
|
|
16
|
+
}>;
|
|
17
|
+
/** Host protocol is outside the client's supported range or missing required features. */
|
|
18
|
+
export declare class IncompatibleRuntimeError extends Error {
|
|
19
|
+
readonly compatibility: IncompatibleReason;
|
|
20
|
+
readonly remedy: string;
|
|
21
|
+
readonly host: ProtocolRange;
|
|
22
|
+
constructor(compatibility: IncompatibleReason);
|
|
23
|
+
}
|
|
12
24
|
export declare class Transport {
|
|
13
25
|
readonly url: string;
|
|
14
26
|
readonly key: string;
|
|
27
|
+
readonly tenant: string;
|
|
15
28
|
readonly fetcher: typeof fetch;
|
|
29
|
+
private compatible;
|
|
16
30
|
constructor(options?: Destination, role?: "server" | "executor");
|
|
17
|
-
|
|
31
|
+
/** Clears the cached Host compatibility result (used after a 426). */
|
|
32
|
+
clearCompatibilityCache(): void;
|
|
33
|
+
private ensureCompatible;
|
|
34
|
+
private authHeaders;
|
|
35
|
+
request(path: string, init?: RequestInit, options?: {
|
|
36
|
+
retried426?: boolean;
|
|
37
|
+
}): Promise<Response>;
|
|
18
38
|
json<T>(path: string, method?: string, body?: unknown, signal?: AbortSignal): Promise<T>;
|
|
19
39
|
}
|
|
20
40
|
export declare const id: () => `${string}-${string}-${string}-${string}-${string}`;
|