@nylorun/agents 0.9.0-beta → 0.11.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 (52) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/README.md +296 -77
  3. package/dist/a2a/handler.d.ts +59 -0
  4. package/dist/a2a/handler.js +178 -0
  5. package/dist/a2a/index.d.ts +8 -0
  6. package/dist/a2a/index.js +7 -0
  7. package/dist/access.d.ts +101 -0
  8. package/dist/access.js +111 -0
  9. package/dist/action-handler.d.ts +63 -0
  10. package/dist/action-handler.js +279 -0
  11. package/dist/ag-ui/handler.d.ts +12 -4
  12. package/dist/ag-ui/handler.js +87 -281
  13. package/dist/ag-ui/index.d.ts +2 -1
  14. package/dist/ag-ui/index.js +2 -1
  15. package/dist/browser.d.ts +97 -0
  16. package/dist/browser.js +277 -0
  17. package/dist/client.d.ts +3 -230
  18. package/dist/client.js +3 -361
  19. package/dist/connection.d.ts +4 -4
  20. package/dist/connection.js +16 -25
  21. package/dist/delivery-token.d.ts +62 -0
  22. package/dist/delivery-token.js +159 -0
  23. package/dist/execute-action.d.ts +6 -0
  24. package/dist/execute-action.js +24 -0
  25. package/dist/http.d.ts +34 -3
  26. package/dist/http.js +68 -16
  27. package/dist/index.d.ts +10 -8
  28. package/dist/index.js +4 -4
  29. package/dist/sandbox/client.d.ts +4 -5
  30. package/dist/sandbox/client.js +4 -8
  31. package/dist/sandbox/index.d.ts +0 -2
  32. package/dist/sandbox/index.js +0 -1
  33. package/dist/served-definitions.d.ts +20 -0
  34. package/dist/served-definitions.js +100 -0
  35. package/dist/session-client.d.ts +282 -0
  36. package/dist/session-client.js +375 -0
  37. package/dist/sse.js +12 -0
  38. package/dist/studio-embed.d.ts +9 -0
  39. package/dist/studio-embed.js +8 -0
  40. package/dist/token-endpoint.d.ts +23 -0
  41. package/dist/token-endpoint.js +30 -0
  42. package/package.json +15 -8
  43. package/dist/ag-ui/history.d.ts +0 -7
  44. package/dist/ag-ui/history.js +0 -60
  45. package/dist/ag-ui/sse.d.ts +0 -9
  46. package/dist/ag-ui/sse.js +0 -7
  47. package/dist/ag-ui/translate.d.ts +0 -38
  48. package/dist/ag-ui/translate.js +0 -222
  49. package/dist/derived-credentials.d.ts +0 -5
  50. package/dist/derived-credentials.js +0 -17
  51. package/dist/executor.d.ts +0 -20
  52. package/dist/executor.js +0 -417
@@ -1,222 +0,0 @@
1
- /**
2
- * Runtime transcript events → AG-UI 1.0 events for one run. Ids: an assistant message is its
3
- * model call's `invocationId`, a tool call is the model's `callId`, a tool result is
4
- * `<callId>:result`, so live runs and `messagesFromEvents` agree.
5
- */
6
- import { EventType } from "@ag-ui/core";
7
- import { parseTranscriptEvent, } from "@nylorun/core/contracts";
8
- /** The text a tool result shows; `undefined` while there is no result yet. */
9
- export function toolResultText(result) {
10
- const value = result;
11
- switch (value?.kind) {
12
- case "completed":
13
- return outputText(value.output ?? null);
14
- case "failed":
15
- return JSON.stringify({ error: value.code, message: value.message });
16
- case "denied":
17
- return JSON.stringify({ denied: true, reason: value.reason });
18
- default:
19
- // interaction-required and deferred: the call is still open.
20
- return undefined;
21
- }
22
- }
23
- export function outputText(output) {
24
- return typeof output === "string" ? output : JSON.stringify(output);
25
- }
26
- const none = { events: [], finished: false };
27
- /** A tool result from any of the three places a call can end. */
28
- export function toolResultOf(event) {
29
- switch (event.type) {
30
- case "action.completed": {
31
- const p = event.payload;
32
- if (p.kind !== "tool" || !p.callId || p.agent)
33
- return undefined;
34
- const content = toolResultText(p.result);
35
- return content === undefined ? undefined : { callId: p.callId, content };
36
- }
37
- case "tool.completed": {
38
- const p = event.payload;
39
- if (p.agent)
40
- return undefined;
41
- return {
42
- callId: p.callId,
43
- content: p.error !== undefined
44
- ? JSON.stringify({ error: p.error.code, message: p.error.message })
45
- : outputText(p.output ?? null),
46
- };
47
- }
48
- case "delegation.completed": {
49
- // An agent used as a tool: its own work stays inside it; the parent sees the result.
50
- const p = event.payload;
51
- if (!p.callId)
52
- return undefined;
53
- const content = toolResultText(p.outcome);
54
- return content === undefined ? undefined : { callId: p.callId, content };
55
- }
56
- default:
57
- return undefined;
58
- }
59
- }
60
- export class RunTranslator {
61
- threadId;
62
- runId;
63
- /** Tool invocation → the model's call id, for interrupts that name the invocation. */
64
- callIds = new Map();
65
- closed = new Set();
66
- sawAssistant;
67
- /**
68
- * `reattached`: the run continues from a cursor, so earlier assistant messages already
69
- * reached the client and a workflow's output is not repeated as text.
70
- */
71
- constructor(threadId, runId, options = {}) {
72
- this.threadId = threadId;
73
- this.runId = runId;
74
- this.sawAssistant = options.reattached ?? false;
75
- }
76
- translate(raw) {
77
- const event = parseTranscriptEvent(raw);
78
- if (!event)
79
- return none;
80
- if (event.type === "action.pending" || event.type === "action.completed") {
81
- const { callId, invocationId } = event.payload;
82
- if (callId && invocationId)
83
- this.callIds.set(invocationId, callId);
84
- }
85
- const result = toolResultOf(event);
86
- if (result) {
87
- if (this.closed.has(result.callId))
88
- return none;
89
- this.closed.add(result.callId);
90
- return {
91
- events: [
92
- {
93
- type: EventType.TOOL_CALL_RESULT,
94
- messageId: `${result.callId}:result`,
95
- toolCallId: result.callId,
96
- content: result.content,
97
- role: "tool",
98
- },
99
- ],
100
- finished: false,
101
- };
102
- }
103
- switch (event.type) {
104
- case "message.assistant": {
105
- const p = event.payload;
106
- if (p.agent)
107
- return none;
108
- this.sawAssistant = true;
109
- return { events: assistantEvents(p), finished: false };
110
- }
111
- case "turn.completed": {
112
- const p = event.payload;
113
- // Workflows finish without a model step of their own: show their output.
114
- const text = this.sawAssistant || p.output === undefined || p.output === null
115
- ? []
116
- : textEvents(raw.eventId, outputText(p.output));
117
- return {
118
- events: [...text, this.finished({ type: "success" }, p.output)],
119
- finished: true,
120
- };
121
- }
122
- case "turn.paused": {
123
- const interrupts = event.payload.interactions.map((w) => {
124
- const interaction = w.interaction;
125
- const toolCallId = this.callIds.get(w.invocationId);
126
- return {
127
- id: interaction.id,
128
- reason: interaction.kind === "approval" ? "tool_approval" : "input_required",
129
- ...(typeof interaction.prompt === "string"
130
- ? { message: interaction.prompt }
131
- : {}),
132
- ...(toolCallId ? { toolCallId } : {}),
133
- ...(interaction.kind === "approval"
134
- ? {
135
- responseSchema: {
136
- type: "object",
137
- properties: { approved: { type: "boolean" } },
138
- required: ["approved"],
139
- },
140
- }
141
- : {}),
142
- metadata: { nylorun: { kind: interaction.kind } },
143
- };
144
- });
145
- return {
146
- events: [this.finished({ type: "interrupt", interrupts })],
147
- finished: true,
148
- };
149
- }
150
- case "turn.cancelled":
151
- return { events: [this.finished({ type: "cancelled" })], finished: true };
152
- case "turn.failed": {
153
- const p = event.payload;
154
- return {
155
- events: [
156
- {
157
- type: EventType.RUN_ERROR,
158
- message: p.error?.message ?? p.message ?? "The agent failed",
159
- code: p.error?.code ?? "turn.failed",
160
- },
161
- ],
162
- finished: true,
163
- };
164
- }
165
- case "effect.uncertain":
166
- case "action.uncertain":
167
- // The Runtime cannot tell whether a side effect happened; the run goes on.
168
- return {
169
- events: [
170
- {
171
- type: EventType.CUSTOM,
172
- name: "nylorun.uncertain",
173
- value: event.payload,
174
- },
175
- ],
176
- finished: false,
177
- };
178
- default:
179
- return none;
180
- }
181
- }
182
- started() {
183
- return {
184
- type: EventType.RUN_STARTED,
185
- threadId: this.threadId,
186
- runId: this.runId,
187
- };
188
- }
189
- finished(outcome, result) {
190
- return {
191
- type: EventType.RUN_FINISHED,
192
- threadId: this.threadId,
193
- runId: this.runId,
194
- outcome,
195
- ...(result === undefined ? {} : { result }),
196
- };
197
- }
198
- }
199
- function textEvents(messageId, text) {
200
- if (!text)
201
- return [];
202
- return [
203
- { type: EventType.TEXT_MESSAGE_START, messageId, role: "assistant" },
204
- { type: EventType.TEXT_MESSAGE_CONTENT, messageId, delta: text },
205
- { type: EventType.TEXT_MESSAGE_END, messageId },
206
- ];
207
- }
208
- function assistantEvents(p) {
209
- const events = textEvents(p.invocationId, p.text);
210
- for (const call of p.toolCalls)
211
- events.push({
212
- type: EventType.TOOL_CALL_START,
213
- toolCallId: call.callId,
214
- toolCallName: call.name,
215
- parentMessageId: p.invocationId,
216
- }, {
217
- type: EventType.TOOL_CALL_ARGS,
218
- toolCallId: call.callId,
219
- delta: JSON.stringify(call.input ?? {}),
220
- }, { type: EventType.TOOL_CALL_END, toolCallId: call.callId });
221
- return events;
222
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Deterministic executor token from an application key (D§7.3).
3
- * Same key, Tenant and agent always produce the same token.
4
- */
5
- export declare function deriveExecutorToken(applicationKey: string, tenantId: string, agentId: string): string;
@@ -1,17 +0,0 @@
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
- }
@@ -1,20 +0,0 @@
1
- import { type BuiltWorkflow } from "@nylorun/core/define";
2
- import { AgentsClient, type AgentSource } from "./client.js";
3
- import { deriveExecutorToken } from "./derived-credentials.js";
4
- import { type Destination } from "./http.js";
5
- export interface ConnectOptions {
6
- agents: readonly (AgentSource | BuiltWorkflow)[];
7
- /** Application-mode client; when set, connectAgents uses application mode (D§7.2). */
8
- application?: AgentsClient;
9
- runtime?: Destination;
10
- implementationVersion?: string;
11
- onError?: (error: unknown) => void;
12
- }
13
- export interface AgentConnection {
14
- /** Settles after authenticated SSE is established and initial discovery succeeds. */
15
- readonly ready: Promise<void>;
16
- /** Abort subscriptions and leases. Running user code receives an AbortSignal. */
17
- close(): Promise<void>;
18
- }
19
- export declare function connectAgents(options: ConnectOptions): AgentConnection;
20
- export { deriveExecutorToken };
package/dist/executor.js DELETED
@@ -1,417 +0,0 @@
1
- import { AgentManifestSchema, ActionSchema, ActionClaimResponseSchema, } from "@nylorun/core/contracts";
2
- import { agentFrom, delegateOf, hashManifest, isFlowDelegate, 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";
6
- import { Transport, RuntimeError, delay, env, id, segment, } from "./http.js";
7
- import { readSSE } from "./sse.js";
8
- import { executeAction } from "./execute-action.js";
9
- import { createActionSandbox, definitionDeclaresSandbox, } from "./sandbox/client.js";
10
- function implementationVersionOf(options) {
11
- return (options.implementationVersion ??
12
- env("NYLORUN_IMPLEMENTATION_VERSION") ??
13
- "dev");
14
- }
15
- /** Agents embedded in a v2 workflow: served here, but saved only as part of the workflow. */
16
- const embeddedAgents = new WeakMap();
17
- function buildAgents(options) {
18
- const agents = new Map();
19
- const embedded = new Set();
20
- /**
21
- * Agents embedded in another definition (a v2 flow's agents, a flow agent used as a
22
- * tool) may be reached from more than one place; the same definition is served once.
23
- */
24
- const sameAsServed = (id, manifest) => {
25
- const served = agents.get(id);
26
- if (!served)
27
- return false;
28
- if (hashManifest(served.manifest) === hashManifest(manifest))
29
- return true;
30
- throw new Error(`Duplicate connected agent ${id}`);
31
- };
32
- const addAgent = (built, shared = false) => {
33
- assertNoMiddlewareClosures(built);
34
- AgentManifestSchema.parse(built.manifest);
35
- if (shared ? sameAsServed(built.id, built.manifest) : agents.has(built.id)) {
36
- if (!shared)
37
- throw new Error(`Duplicate connected agent ${built.id}`);
38
- return;
39
- }
40
- agents.set(built.id, built);
41
- for (const tool of built.getBinding().tools) {
42
- const delegate = delegateOf(tool);
43
- if (!delegate || !isFlowDelegate(delegate))
44
- continue;
45
- // A flow agent used as a tool runs in its own linked session: serve its code too.
46
- if (!delegate.workflow)
47
- throw new Error(`Flow agent '${delegate.manifest.id}' is used as a tool by '${built.id}' but has no local implementation`);
48
- addWorkflow(delegate.workflow, true);
49
- embedded.add(delegate.workflow.id);
50
- }
51
- };
52
- const addWorkflow = (workflow, shared = false) => {
53
- if (shared ? sameAsServed(workflow.id, workflow.manifest) : agents.has(workflow.id)) {
54
- if (!shared)
55
- throw new Error(`Duplicate connected agent ${workflow.id}`);
56
- return;
57
- }
58
- agents.set(workflow.id, workflow);
59
- const v2 = workflow.manifest.workflowSchemaVersion === 2;
60
- for (const binding of Object.values(workflow.getBinding().agents)) {
61
- addAgent(agentFrom(binding.manifest, binding.implementations), v2);
62
- if (v2)
63
- embedded.add(binding.manifest.id);
64
- }
65
- };
66
- for (const source of options.agents) {
67
- if (isBuiltWorkflow(source)) {
68
- addWorkflow(source);
69
- continue;
70
- }
71
- const built = source.build?.() ?? source;
72
- if (isBuiltWorkflow(built)) {
73
- addWorkflow(built);
74
- continue;
75
- }
76
- addAgent(built);
77
- }
78
- if (!agents.size)
79
- throw new Error("connectAgents requires at least one agent");
80
- // A definition passed in directly is saved on its own, even if another one embeds it.
81
- for (const source of options.agents)
82
- embedded.delete(source.id);
83
- embeddedAgents.set(agents, embedded);
84
- return agents;
85
- }
86
- /**
87
- * The manifest hash an executor serves for a workflow's flow actions. A run is pinned to
88
- * one workflow manifest, and its stage keys may shift between deploys, so flow actions
89
- * for another hash are left for the executor that serves it.
90
- */
91
- function flowManifestHash(agent) {
92
- return isBuiltWorkflow(agent) && agent.manifest.workflowSchemaVersion === 2
93
- ? hashManifest(agent.manifest)
94
- : undefined;
95
- }
96
- function connectExecutorMode(options, runtime, agents) {
97
- const transport = new Transport(runtime, "executor");
98
- const version = implementationVersionOf(options);
99
- const controller = new AbortController();
100
- const signal = controller.signal;
101
- const active = new Map();
102
- let resolveReady;
103
- let rejectReady;
104
- const ready = new Promise((resolve, reject) => {
105
- resolveReady = resolve;
106
- rejectReady = reject;
107
- });
108
- // Avoid an unhandled rejection for callers that immediately close the handle.
109
- void ready.catch(() => { });
110
- const report = (error) => {
111
- try {
112
- options.onError?.(error);
113
- }
114
- catch { }
115
- };
116
- const skipped = new Set();
117
- let discovering = false, dirty = false;
118
- const discover = async () => {
119
- dirty = true;
120
- if (discovering)
121
- return;
122
- discovering = true;
123
- try {
124
- while (dirty && !signal.aborted) {
125
- dirty = false;
126
- const response = await transport.json("/v1/actions", "GET", undefined, signal);
127
- for (const item of response.actions) {
128
- // Skip actions this SDK cannot run (e.g. from an older Runtime) instead of stalling discovery.
129
- const parsed = ActionSchema.safeParse(item);
130
- if (!parsed.success) {
131
- report(parsed.error);
132
- continue;
133
- }
134
- const action = parsed.data;
135
- const agent = agents.get(action.agentId);
136
- if (!agent || action.status !== "pending" || active.has(action.actionId))
137
- continue;
138
- const served = flowManifestHash(agent);
139
- if (served !== undefined && "key" in action && action.manifestHash !== served) {
140
- if (!skipped.has(action.actionId)) {
141
- skipped.add(action.actionId);
142
- report(new Error(`Skipped action ${action.actionId}: it belongs to manifest ${action.manifestHash} of '${action.agentId}', and this executor serves ${served}`));
143
- }
144
- continue;
145
- }
146
- const work = new AbortController();
147
- active.set(action.actionId, work);
148
- let delivered = false;
149
- void processAction(action, agent, work)
150
- .then(() => {
151
- delivered = true;
152
- })
153
- .catch((error) => {
154
- if (!signal.aborted)
155
- report(error);
156
- })
157
- .finally(() => {
158
- active.delete(action.actionId);
159
- // Completion-driven discovery drains bounded server pages without periodic polling.
160
- if (delivered && !signal.aborted)
161
- void discover().catch(report);
162
- });
163
- }
164
- }
165
- }
166
- finally {
167
- discovering = false;
168
- }
169
- };
170
- const processAction = async (action, agent, work) => {
171
- const stop = () => work.abort(signal.reason);
172
- signal.addEventListener("abort", stop, { once: true });
173
- let renewal;
174
- let renewing = true;
175
- let wakeRenewal;
176
- try {
177
- const claim = ActionClaimResponseSchema.parse(await transport.json(`/v1/actions/${segment(action.actionId)}/claim`, "POST", {
178
- requestId: id(),
179
- implementationVersion: version,
180
- }, work.signal));
181
- if (claim.action.actionId !== action.actionId)
182
- throw new Error("Claim definition mismatch");
183
- let expires = Date.parse(claim.leaseExpiresAt);
184
- renewal = (async () => {
185
- while (renewing && !work.signal.aborted) {
186
- const remaining = expires - Date.now();
187
- if (!Number.isFinite(remaining) || remaining <= 0)
188
- throw new Error("Executor lease expired");
189
- await new Promise((resolve) => {
190
- const finish = () => {
191
- clearTimeout(timer);
192
- work.signal.removeEventListener("abort", finish);
193
- resolve();
194
- };
195
- const timer = setTimeout(finish, Math.max(50, Math.floor(remaining / 3)));
196
- wakeRenewal = finish;
197
- work.signal.addEventListener("abort", finish, { once: true });
198
- });
199
- if (!renewing || work.signal.aborted)
200
- return;
201
- const renewed = await transport.json(`/v1/actions/${segment(action.actionId)}/heartbeat`, "POST", {
202
- requestId: id(),
203
- claimId: claim.claimId,
204
- generation: claim.generation,
205
- }, work.signal);
206
- expires = Date.parse(renewed.leaseExpiresAt);
207
- }
208
- })().catch((error) => {
209
- if (renewing && !work.signal.aborted) {
210
- work.abort(error);
211
- report(error);
212
- }
213
- });
214
- const outcome = await executeAction(claim.action, agent, work.signal, {
215
- sandbox: definitionDeclaresSandbox(agent.manifest)
216
- ? createActionSandbox({
217
- transport,
218
- actionId: action.actionId,
219
- claimId: claim.claimId,
220
- generation: claim.generation,
221
- signal: work.signal,
222
- })
223
- : undefined,
224
- });
225
- work.signal.throwIfAborted();
226
- const command = {
227
- type: "action_result",
228
- requestId: id(),
229
- idempotencyKey: `action:${action.actionId}:${claim.generation}`,
230
- actionId: action.actionId,
231
- claimId: claim.claimId,
232
- generation: claim.generation,
233
- outcome,
234
- };
235
- // Retrying delivery never re-invokes the tool/hook. Lost acknowledgements use the same receipt key.
236
- for (let attempt = 0;; attempt++) {
237
- try {
238
- await transport.json(`/v1/sessions/${segment(action.sessionId)}/commands`, "POST", command, work.signal);
239
- break;
240
- }
241
- catch (error) {
242
- if (work.signal.aborted ||
243
- attempt >= 4 ||
244
- (error instanceof RuntimeError &&
245
- error.status < 500 &&
246
- ![408, 429].includes(error.status)))
247
- throw error;
248
- await delay(Math.min(4000, 250 * 2 ** attempt), work.signal);
249
- }
250
- }
251
- }
252
- finally {
253
- renewing = false;
254
- wakeRenewal?.();
255
- await renewal;
256
- signal.removeEventListener("abort", stop);
257
- }
258
- };
259
- const loop = (async () => {
260
- let retry = 250;
261
- while (!signal.aborted) {
262
- const subscription = new AbortController();
263
- const stop = () => subscription.abort(signal.reason);
264
- signal.addEventListener("abort", stop, { once: true });
265
- try {
266
- const response = await transport.request("/v1/executors/connect", {
267
- signal: subscription.signal,
268
- headers: { Accept: "text/event-stream" },
269
- });
270
- if (!response.headers.get("content-type")?.includes("text/event-stream"))
271
- throw new Error("Executor endpoint did not return SSE");
272
- // Subscription exists before initial discovery; notifications can safely accumulate in its stream.
273
- await discover();
274
- resolveReady();
275
- for await (const frame of readSSE(response, subscription.signal)) {
276
- retry = 250;
277
- if (frame.event === "work_available" ||
278
- (frame.data && JSON.parse(frame.data).type === "work_available"))
279
- await discover();
280
- }
281
- }
282
- catch (error) {
283
- if (signal.aborted)
284
- break;
285
- report(error);
286
- if (error instanceof RuntimeError &&
287
- [400, 401, 403, 404].includes(error.status)) {
288
- rejectReady(error);
289
- controller.abort(error);
290
- break;
291
- }
292
- }
293
- finally {
294
- subscription.abort();
295
- signal.removeEventListener("abort", stop);
296
- }
297
- if (!signal.aborted)
298
- await delay(retry, signal).catch(() => { });
299
- retry = Math.min(30000, retry * 2);
300
- }
301
- rejectReady(signal.reason ?? new Error("Executor connection closed"));
302
- })();
303
- return {
304
- ready,
305
- async close() {
306
- controller.abort(new Error("Executor connection closed"));
307
- for (const work of active.values())
308
- work.abort(signal.reason);
309
- await loop;
310
- },
311
- };
312
- }
313
- function connectApplicationMode(options, application, agents) {
314
- const version = implementationVersionOf(options);
315
- const children = [];
316
- let resolveReady;
317
- let rejectReady;
318
- const ready = new Promise((resolve, reject) => {
319
- resolveReady = resolve;
320
- rejectReady = reject;
321
- });
322
- void ready.catch(() => { });
323
- const boot = (async () => {
324
- const embedded = embeddedAgents.get(agents) ?? new Set();
325
- for (const agent of agents.values()) {
326
- if (embedded.has(agent.id))
327
- continue;
328
- await application.saveAgent(agent, { implementationVersion: version });
329
- }
330
- const registrations = [...agents.values()].map((agent) => {
331
- const manifestHash = flowManifestHash(agent);
332
- return {
333
- agentId: agent.id,
334
- token: deriveExecutorToken(application.transport.key, application.transport.tenant, agent.id),
335
- implementationVersion: version,
336
- ...(manifestHash === undefined ? {} : { manifestHash }),
337
- };
338
- });
339
- await application.transport.json("/v1/executors", "PUT", {
340
- executors: registrations,
341
- });
342
- for (const agent of agents.values()) {
343
- const token = deriveExecutorToken(application.transport.key, application.transport.tenant, agent.id);
344
- const child = connectExecutorMode({
345
- agents: [agent],
346
- implementationVersion: version,
347
- onError: options.onError,
348
- }, {
349
- url: application.transport.url,
350
- tenant: application.transport.tenant,
351
- key: token,
352
- fetch: application.transport.fetcher,
353
- }, new Map([[agent.id, agent]]));
354
- children.push(child);
355
- }
356
- await Promise.all(children.map((child) => child.ready));
357
- resolveReady();
358
- })().catch((error) => {
359
- rejectReady(error);
360
- });
361
- return {
362
- ready,
363
- async close() {
364
- await boot.catch(() => { });
365
- await Promise.allSettled(children.map((child) => child.close()));
366
- },
367
- };
368
- }
369
- export function connectAgents(options) {
370
- const agents = buildAgents(options);
371
- // D§7.2 mode table: runtime → executor; application → application;
372
- // neither → resolveConnection() role.
373
- if (options.runtime) {
374
- return connectExecutorMode(options, options.runtime, agents);
375
- }
376
- if (options.application) {
377
- return connectApplicationMode(options, options.application, agents);
378
- }
379
- let resolveReady;
380
- let rejectReady;
381
- const ready = new Promise((resolve, reject) => {
382
- resolveReady = resolve;
383
- rejectReady = reject;
384
- });
385
- void ready.catch(() => { });
386
- let handle;
387
- const boot = (async () => {
388
- const connection = await resolveConnection();
389
- if (connection.role === "executor") {
390
- handle = connectExecutorMode(options, {
391
- url: connection.url,
392
- tenant: connection.tenant,
393
- key: connection.key,
394
- }, agents);
395
- }
396
- else {
397
- const application = new AgentsClient({
398
- url: connection.url,
399
- tenant: connection.tenant,
400
- key: connection.key,
401
- });
402
- handle = connectApplicationMode(options, application, agents);
403
- }
404
- await handle.ready;
405
- resolveReady();
406
- })().catch((error) => {
407
- rejectReady(error);
408
- });
409
- return {
410
- ready,
411
- async close() {
412
- await boot.catch(() => { });
413
- await handle?.close();
414
- },
415
- };
416
- }
417
- export { deriveExecutorToken };