@nylorun/agents 0.4.0-beta → 0.6.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 +45 -0
- package/README.md +174 -8
- package/dist/client.d.ts +43 -9
- package/dist/client.js +166 -16
- package/dist/connection.d.ts +19 -0
- package/dist/connection.js +98 -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 +97 -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/package.json +2 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ErrorCode } from "@nylorun/core/compatibility";
|
|
2
|
+
export interface ResolvedConnection {
|
|
3
|
+
url: string;
|
|
4
|
+
tenant: string;
|
|
5
|
+
key: string;
|
|
6
|
+
role: "application" | "executor";
|
|
7
|
+
source: "options" | "environment" | "project-link";
|
|
8
|
+
}
|
|
9
|
+
export declare class ConnectionError extends Error {
|
|
10
|
+
readonly code: ErrorCode;
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/** Resolve Tenant API connection: options → environment → project link (D§7.1). */
|
|
14
|
+
export declare function resolveConnection(options?: {
|
|
15
|
+
url?: string;
|
|
16
|
+
tenant?: string;
|
|
17
|
+
key?: string;
|
|
18
|
+
cwd?: string;
|
|
19
|
+
}): Promise<ResolvedConnection>;
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
}
|
|
15
|
+
function stripTrailingSlash(url) {
|
|
16
|
+
return url.replace(/\/$/, "");
|
|
17
|
+
}
|
|
18
|
+
async function readProjectLink(startDir) {
|
|
19
|
+
let dir = resolve(startDir);
|
|
20
|
+
for (;;) {
|
|
21
|
+
const nylorun = join(dir, ".nylorun");
|
|
22
|
+
const linkPath = join(nylorun, "link.json");
|
|
23
|
+
let linkRaw;
|
|
24
|
+
try {
|
|
25
|
+
linkRaw = await readFile(linkPath, "utf8");
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error.code === "ENOENT") {
|
|
29
|
+
const parent = dirname(dir);
|
|
30
|
+
if (parent === dir)
|
|
31
|
+
return undefined;
|
|
32
|
+
dir = parent;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
const link = ProjectLinkFileSchema.parse(JSON.parse(linkRaw));
|
|
38
|
+
const credentials = ProjectCredentialsFileSchema.parse(JSON.parse(await readFile(join(nylorun, "credentials.json"), "utf8")));
|
|
39
|
+
return {
|
|
40
|
+
url: stripTrailingSlash(link.hostUrl),
|
|
41
|
+
tenant: link.tenantId,
|
|
42
|
+
key: credentials.applicationKey,
|
|
43
|
+
role: "application",
|
|
44
|
+
source: "project-link",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Resolve Tenant API connection: options → environment → project link (D§7.1). */
|
|
49
|
+
export async function resolveConnection(options) {
|
|
50
|
+
const tried = [];
|
|
51
|
+
const optionUrl = options?.url;
|
|
52
|
+
const optionTenant = options?.tenant;
|
|
53
|
+
const optionKey = options?.key;
|
|
54
|
+
const anyOption = optionUrl !== undefined ||
|
|
55
|
+
optionTenant !== undefined ||
|
|
56
|
+
optionKey !== undefined;
|
|
57
|
+
tried.push("options");
|
|
58
|
+
if (anyOption) {
|
|
59
|
+
if (optionUrl && optionTenant && optionKey) {
|
|
60
|
+
return {
|
|
61
|
+
url: stripTrailingSlash(optionUrl),
|
|
62
|
+
tenant: optionTenant,
|
|
63
|
+
key: optionKey,
|
|
64
|
+
role: "application",
|
|
65
|
+
source: "options",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
missing(tried.concat(["environment", "project-link"]));
|
|
69
|
+
}
|
|
70
|
+
const envUrl = env("NYLORUN_RUNTIME_URL");
|
|
71
|
+
const envTenant = env("NYLORUN_TENANT");
|
|
72
|
+
const envServerKey = env("NYLORUN_SERVER_KEY");
|
|
73
|
+
const envExecutorKey = env("NYLORUN_EXECUTOR_KEY");
|
|
74
|
+
const anyEnv = envUrl !== undefined ||
|
|
75
|
+
envTenant !== undefined ||
|
|
76
|
+
envServerKey !== undefined ||
|
|
77
|
+
envExecutorKey !== undefined;
|
|
78
|
+
tried.push("environment");
|
|
79
|
+
if (anyEnv) {
|
|
80
|
+
const key = envExecutorKey ?? envServerKey;
|
|
81
|
+
if (envUrl && envTenant && key) {
|
|
82
|
+
return {
|
|
83
|
+
url: stripTrailingSlash(envUrl),
|
|
84
|
+
tenant: envTenant,
|
|
85
|
+
key,
|
|
86
|
+
role: envExecutorKey !== undefined ? "executor" : "application",
|
|
87
|
+
source: "environment",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
missing(tried.concat(["project-link"]));
|
|
91
|
+
}
|
|
92
|
+
tried.push("project-link");
|
|
93
|
+
const cwd = options?.cwd ?? (typeof process !== "undefined" ? process.cwd() : ".");
|
|
94
|
+
const fromLink = await readProjectLink(cwd);
|
|
95
|
+
if (fromLink)
|
|
96
|
+
return fromLink;
|
|
97
|
+
missing(tried);
|
|
98
|
+
}
|
|
@@ -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,
|
|
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 { 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,21 @@ 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,
|
|
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 };
|
|
13
27
|
if (action.kind === "hook")
|
|
14
28
|
// All capabilities at this hook point run concurrently in one action. Never throws.
|
|
15
29
|
return {
|
|
@@ -17,6 +31,8 @@ export async function executeAction(action, agent, signal) {
|
|
|
17
31
|
results: await runHookPoint(implementationsFor(agent), action.hook, action.input),
|
|
18
32
|
},
|
|
19
33
|
};
|
|
34
|
+
if (action.kind !== "tool" || !("capabilityId" in action))
|
|
35
|
+
throw new Error(`Unsupported action kind ${action.kind}`);
|
|
20
36
|
const impl = implementationsFor(agent)[action.capabilityId];
|
|
21
37
|
if (!impl)
|
|
22
38
|
throw new Error(`No capability ${action.capabilityId}`);
|
|
@@ -24,7 +40,25 @@ export async function executeAction(action, agent, signal) {
|
|
|
24
40
|
if (!raw)
|
|
25
41
|
throw new Error("Missing tool implementation");
|
|
26
42
|
const tool = normalizeToolDefinition(raw);
|
|
27
|
-
|
|
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);
|
|
28
62
|
const input = schemas.inputSchema.validate(action.input);
|
|
29
63
|
if (!input.ok)
|
|
30
64
|
return {
|
|
@@ -77,7 +111,9 @@ export async function executeAction(action, agent, signal) {
|
|
|
77
111
|
signal,
|
|
78
112
|
info: ctx.info,
|
|
79
113
|
session: { id: action.sessionId },
|
|
114
|
+
agent: ref,
|
|
80
115
|
...(ctx.resume ? { resume: ctx.resume } : {}),
|
|
116
|
+
...(sandbox ? { sandbox } : {}),
|
|
81
117
|
state: {
|
|
82
118
|
get: (key) => state[key],
|
|
83
119
|
set: (key, value) => {
|
|
@@ -115,14 +151,17 @@ export async function executeAction(action, agent, signal) {
|
|
|
115
151
|
};
|
|
116
152
|
try {
|
|
117
153
|
signal.throwIfAborted();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
154
|
+
const approval = "approval" in tool ? tool.approval : undefined;
|
|
155
|
+
if (approval && !gateApproved) {
|
|
156
|
+
const result = await approval(input.value);
|
|
157
|
+
if (result)
|
|
121
158
|
throw new Suspend({
|
|
122
159
|
kind: "interaction-required",
|
|
123
160
|
interaction: {
|
|
124
161
|
kind: "approval",
|
|
125
|
-
prompt: typeof
|
|
162
|
+
prompt: typeof result === "string"
|
|
163
|
+
? result
|
|
164
|
+
: `Approve ${"name" in tool ? tool.name : "tool"}?`,
|
|
126
165
|
},
|
|
127
166
|
token: durableToken({ approvalGate: true }),
|
|
128
167
|
});
|
|
@@ -130,7 +169,12 @@ export async function executeAction(action, agent, signal) {
|
|
|
130
169
|
}
|
|
131
170
|
if (resume.kind === "approval" && resume.approved === false)
|
|
132
171
|
return { value: { kind: "denied", reason: "Approval denied" } };
|
|
133
|
-
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);
|
|
134
178
|
const tagged = object(value);
|
|
135
179
|
let outcome;
|
|
136
180
|
if ([
|
|
@@ -170,6 +214,51 @@ export async function executeAction(action, agent, signal) {
|
|
|
170
214
|
};
|
|
171
215
|
}
|
|
172
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
|
+
}
|
|
254
|
+
function delegatedAgent(root, id) {
|
|
255
|
+
for (const capability of Object.values(implementationsFor(root))) {
|
|
256
|
+
const child = delegateOf(capability.tools?.[id])?.agent;
|
|
257
|
+
if (child)
|
|
258
|
+
return child;
|
|
259
|
+
}
|
|
260
|
+
throw new Error(`Agent '${root.id}' does not use '${id}' as a tool`);
|
|
261
|
+
}
|
|
173
262
|
function message(error) {
|
|
174
263
|
return error instanceof Error ? error.message : String(error);
|
|
175
264
|
}
|
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}`;
|