@nylorun/agents 0.7.0-beta → 0.9.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 +67 -0
- package/README.md +199 -109
- package/dist/ag-ui/handler.d.ts +42 -0
- package/dist/ag-ui/handler.js +397 -0
- package/dist/ag-ui/history.d.ts +7 -0
- package/dist/ag-ui/history.js +60 -0
- package/dist/ag-ui/index.d.ts +7 -0
- package/dist/ag-ui/index.js +6 -0
- package/dist/ag-ui/node.d.ts +5 -0
- package/dist/ag-ui/node.js +51 -0
- package/dist/ag-ui/sse.d.ts +9 -0
- package/dist/ag-ui/sse.js +7 -0
- package/dist/ag-ui/translate.d.ts +38 -0
- package/dist/ag-ui/translate.js +222 -0
- package/dist/builder.d.ts +20 -0
- package/dist/builder.js +26 -0
- package/dist/client.d.ts +18 -1
- package/dist/client.js +54 -2
- package/dist/executor.js +83 -19
- package/dist/http.d.ts +10 -1
- package/dist/http.js +24 -4
- package/dist/index.d.ts +8 -5
- package/dist/index.js +4 -2
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js +1 -1
- package/dist/sandbox/index.d.ts +2 -2
- package/dist/sandbox/index.js +1 -1
- package/package.json +8 -3
- package/dist/mcp/mcp.d.ts +0 -30
- package/dist/mcp/mcp.js +0 -80
- package/dist/sandbox/sandbox.d.ts +0 -27
- package/dist/sandbox/sandbox.js +0 -92
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Agent as CoreAgent, AgentBuilder as CoreAgentBuilder } from "@nylorun/core/define";
|
|
2
|
+
import type { AgentOptions, ToolSchemaSource } from "@nylorun/core/define";
|
|
3
|
+
import { type SkillsOptions } from "./skills/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* The agent builder with the capabilities that read files: `.skills()` and `.plugin()`.
|
|
6
|
+
* `@nylorun/agents/define` exports the portable builder from core, without them.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AgentBuilder<Info = unknown, Schema extends ToolSchemaSource | undefined = undefined, Id extends string = string> extends CoreAgentBuilder<Info, Schema, Id> {
|
|
9
|
+
/** Load an Agent Skills folder. */
|
|
10
|
+
skills(directory: string, options?: SkillsOptions): this;
|
|
11
|
+
/** Attach an Agent Plugin package. */
|
|
12
|
+
plugin(directory: string): this;
|
|
13
|
+
output<S extends ToolSchemaSource>(schema: S): AgentBuilder<Info, S, Id>;
|
|
14
|
+
}
|
|
15
|
+
export declare function Agent<Info = unknown, Schema extends ToolSchemaSource | undefined = undefined, const Id extends string = string>(options: AgentOptions<Schema> & {
|
|
16
|
+
readonly id: Id;
|
|
17
|
+
}): AgentBuilder<Info, Schema, Id>;
|
|
18
|
+
export declare namespace Agent {
|
|
19
|
+
const from: typeof CoreAgent.from;
|
|
20
|
+
}
|
package/dist/builder.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Agent as CoreAgent, AgentBuilder as CoreAgentBuilder, } from "@nylorun/core/define";
|
|
2
|
+
import { skills } from "./skills/index.js";
|
|
3
|
+
import { plugin } from "./plugins/plugin.js";
|
|
4
|
+
/**
|
|
5
|
+
* The agent builder with the capabilities that read files: `.skills()` and `.plugin()`.
|
|
6
|
+
* `@nylorun/agents/define` exports the portable builder from core, without them.
|
|
7
|
+
*/
|
|
8
|
+
export class AgentBuilder extends CoreAgentBuilder {
|
|
9
|
+
/** Load an Agent Skills folder. */
|
|
10
|
+
skills(directory, options) {
|
|
11
|
+
return this.capability(skills(directory, options));
|
|
12
|
+
}
|
|
13
|
+
/** Attach an Agent Plugin package. */
|
|
14
|
+
plugin(directory) {
|
|
15
|
+
return this.capability(plugin(directory));
|
|
16
|
+
}
|
|
17
|
+
output(schema) {
|
|
18
|
+
return super.output(schema);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function Agent(options) {
|
|
22
|
+
return new AgentBuilder(options);
|
|
23
|
+
}
|
|
24
|
+
(function (Agent) {
|
|
25
|
+
Agent.from = CoreAgent.from;
|
|
26
|
+
})(Agent || (Agent = {}));
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentManifest, BuiltAgent, BuiltWorkflow, JsonValue, SandboxToolName, WorkflowManifest } from "@nylorun/core/define";
|
|
2
|
-
import { type CredentialInfo, type CredentialSelection, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
|
|
2
|
+
import { type SubjectScope, type CredentialInfo, type CredentialSelection, type SessionCommand, type VaultInfo } from "@nylorun/core/contracts";
|
|
3
3
|
import { Transport, type Destination } from "./http.js";
|
|
4
4
|
/** Application-principal access to a session's sandbox built-ins (workflows.md §8). */
|
|
5
5
|
export type SessionSandbox = {
|
|
@@ -22,9 +22,26 @@ export interface SessionView {
|
|
|
22
22
|
waits?: unknown;
|
|
23
23
|
[key: string]: unknown;
|
|
24
24
|
}
|
|
25
|
+
export interface ActAsOptions {
|
|
26
|
+
/** What the subject may do; the Runtime grants nothing else. Default `["sessions:own"]`. */
|
|
27
|
+
scopes?: readonly SubjectScope[];
|
|
28
|
+
}
|
|
25
29
|
export declare class AgentsClient {
|
|
26
30
|
readonly transport: Transport;
|
|
31
|
+
/** The subject this client acts for (`as`), or `undefined` for the principal itself. */
|
|
32
|
+
readonly subject: string | undefined;
|
|
27
33
|
constructor(destination?: Destination);
|
|
34
|
+
/**
|
|
35
|
+
* A client that acts for `subject`: every call sends `Nylorun-Subject` and
|
|
36
|
+
* `Nylorun-Scopes`, and the Runtime limits it to the scopes and to the subject's own sessions
|
|
37
|
+
* and vaults (Host feature `subject-headers`). For app servers, which authenticate the person
|
|
38
|
+
* themselves and hold the key; nothing is minted, cached or refreshed.
|
|
39
|
+
*/
|
|
40
|
+
as(subject: string, options?: ActAsOptions): AgentsClient;
|
|
41
|
+
/** The Runtime's protocol features, including optional ones such as `transcript-events`. */
|
|
42
|
+
hostFeatures(options?: {
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
}): Promise<readonly string[]>;
|
|
28
45
|
listAgents(options?: {
|
|
29
46
|
signal?: AbortSignal;
|
|
30
47
|
}): Promise<{
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SANDBOX_TOOL_NAMES, delegateOf, isBuiltWorkflow, isSandboxToolName, } from "@nylorun/core/define";
|
|
2
|
-
import {
|
|
2
|
+
import { SCOPES_HEADER, SUBJECT_HEADER } from "@nylorun/core/compatibility";
|
|
3
|
+
import { LiveEventSchema, SessionItemsResponseSchema, parseSubjectHeaders, } from "@nylorun/core/contracts";
|
|
3
4
|
import { resolveConnection } from "./connection.js";
|
|
4
5
|
import { Transport, id, segment } from "./http.js";
|
|
5
6
|
import { observeSSE } from "./sse.js";
|
|
@@ -37,9 +38,15 @@ function declarationsOf(agent) {
|
|
|
37
38
|
return [];
|
|
38
39
|
const found = (binding.declarations ?? []).map((item) => ({ key: item.id, item }));
|
|
39
40
|
for (const tool of binding.tools ?? []) {
|
|
40
|
-
const
|
|
41
|
+
const delegate = delegateOf(tool);
|
|
42
|
+
const child = delegate?.agent;
|
|
41
43
|
for (const item of child?.getBinding().declarations ?? [])
|
|
42
44
|
found.push({ key: `${child.id}/${item.id}`, item });
|
|
45
|
+
// A flow agent used as a tool: its agents' declarations, keyed `<flow>/<agent>/<capability>`.
|
|
46
|
+
const flow = delegate?.workflow;
|
|
47
|
+
for (const leaf of Object.values(flow?.getBinding().agents ?? {}))
|
|
48
|
+
for (const item of leaf.declarations ?? [])
|
|
49
|
+
found.push({ key: `${flow.id}/${leaf.manifest.id}/${item.id}`, item });
|
|
43
50
|
}
|
|
44
51
|
return found;
|
|
45
52
|
}
|
|
@@ -57,8 +64,37 @@ export function assertNoMiddlewareClosures(agent) {
|
|
|
57
64
|
}
|
|
58
65
|
export class AgentsClient {
|
|
59
66
|
transport;
|
|
67
|
+
/** The subject this client acts for (`as`), or `undefined` for the principal itself. */
|
|
68
|
+
subject;
|
|
60
69
|
constructor(destination = {}) {
|
|
61
70
|
this.transport = new Transport(destination);
|
|
71
|
+
this.subject = undefined;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A client that acts for `subject`: every call sends `Nylorun-Subject` and
|
|
75
|
+
* `Nylorun-Scopes`, and the Runtime limits it to the scopes and to the subject's own sessions
|
|
76
|
+
* and vaults (Host feature `subject-headers`). For app servers, which authenticate the person
|
|
77
|
+
* themselves and hold the key; nothing is minted, cached or refreshed.
|
|
78
|
+
*/
|
|
79
|
+
as(subject, options = {}) {
|
|
80
|
+
if (this.subject !== undefined)
|
|
81
|
+
throw new Error(`This client already acts for ${this.subject}`);
|
|
82
|
+
const scopes = [...(options.scopes ?? ["sessions:own"])].join(" ");
|
|
83
|
+
const parsed = parseSubjectHeaders(subject, scopes);
|
|
84
|
+
if (!parsed.ok)
|
|
85
|
+
throw new TypeError(parsed.message);
|
|
86
|
+
const client = Object.create(AgentsClient.prototype);
|
|
87
|
+
return Object.assign(client, {
|
|
88
|
+
transport: this.transport.withHeaders({
|
|
89
|
+
[SUBJECT_HEADER]: subject,
|
|
90
|
+
[SCOPES_HEADER]: [...parsed.scopes].join(" "),
|
|
91
|
+
}),
|
|
92
|
+
subject,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** The Runtime's protocol features, including optional ones such as `transcript-events`. */
|
|
96
|
+
hostFeatures(options = {}) {
|
|
97
|
+
return this.transport.hostFeatures(options.signal);
|
|
62
98
|
}
|
|
63
99
|
listAgents(options = {}) {
|
|
64
100
|
return this.transport.json("/v1/agents", "GET", undefined, options.signal);
|
|
@@ -70,6 +106,22 @@ export class AgentsClient {
|
|
|
70
106
|
return this.transport.json(`/v1/sessions${query}`, "GET", undefined, options.signal);
|
|
71
107
|
}
|
|
72
108
|
async saveAgent(agent, options) {
|
|
109
|
+
if (isBuiltWorkflow(agent) && agent.manifest.workflowSchemaVersion === 2) {
|
|
110
|
+
// A v2 workflow embeds its agents: one document, carrying their plugin roots.
|
|
111
|
+
const pluginRoots = {};
|
|
112
|
+
for (const binding of Object.values(agent.getBinding().agents)) {
|
|
113
|
+
const leaf = { id: binding.manifest.id, manifest: binding.manifest, getBinding: () => binding };
|
|
114
|
+
assertNoMiddlewareClosures(leaf);
|
|
115
|
+
for (const [key, root] of Object.entries(pluginRootsOf(leaf) ?? {}))
|
|
116
|
+
pluginRoots[`${leaf.id}/${key}`] = root;
|
|
117
|
+
}
|
|
118
|
+
return this.transport.json(`/v1/agents/${segment(agent.id)}`, "PUT", {
|
|
119
|
+
requestId: options.requestId ?? id(),
|
|
120
|
+
manifest: agent.manifest,
|
|
121
|
+
implementationVersion: options.implementationVersion,
|
|
122
|
+
...(Object.keys(pluginRoots).length === 0 ? {} : { pluginRoots }),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
73
125
|
if (isBuiltWorkflow(agent)) {
|
|
74
126
|
for (const binding of Object.values(agent.getBinding().agents)) {
|
|
75
127
|
await this.saveAgent({
|
package/dist/executor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentManifestSchema, ActionSchema, ActionClaimResponseSchema, } from "@nylorun/core/contracts";
|
|
2
|
-
import { agentFrom, isBuiltWorkflow, } from "@nylorun/core/define";
|
|
2
|
+
import { agentFrom, delegateOf, hashManifest, isFlowDelegate, isBuiltWorkflow, } from "@nylorun/core/define";
|
|
3
3
|
import { AgentsClient, assertNoMiddlewareClosures, } from "./client.js";
|
|
4
4
|
import { resolveConnection } from "./connection.js";
|
|
5
5
|
import { deriveExecutorToken } from "./derived-credentials.js";
|
|
@@ -12,39 +12,87 @@ function implementationVersionOf(options) {
|
|
|
12
12
|
env("NYLORUN_IMPLEMENTATION_VERSION") ??
|
|
13
13
|
"dev");
|
|
14
14
|
}
|
|
15
|
+
/** Agents embedded in a v2 workflow: served here, but saved only as part of the workflow. */
|
|
16
|
+
const embeddedAgents = new WeakMap();
|
|
15
17
|
function buildAgents(options) {
|
|
16
18
|
const agents = new Map();
|
|
17
|
-
const
|
|
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) => {
|
|
18
33
|
assertNoMiddlewareClosures(built);
|
|
19
34
|
AgentManifestSchema.parse(built.manifest);
|
|
20
|
-
if (agents.has(built.id))
|
|
21
|
-
|
|
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
|
+
}
|
|
22
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
|
+
}
|
|
23
65
|
};
|
|
24
66
|
for (const source of options.agents) {
|
|
25
67
|
if (isBuiltWorkflow(source)) {
|
|
26
|
-
|
|
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));
|
|
68
|
+
addWorkflow(source);
|
|
31
69
|
continue;
|
|
32
70
|
}
|
|
33
71
|
const built = source.build?.() ?? source;
|
|
34
72
|
if (isBuiltWorkflow(built)) {
|
|
35
|
-
|
|
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));
|
|
73
|
+
addWorkflow(built);
|
|
40
74
|
continue;
|
|
41
75
|
}
|
|
42
76
|
addAgent(built);
|
|
43
77
|
}
|
|
44
78
|
if (!agents.size)
|
|
45
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);
|
|
46
84
|
return agents;
|
|
47
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
|
+
}
|
|
48
96
|
function connectExecutorMode(options, runtime, agents) {
|
|
49
97
|
const transport = new Transport(runtime, "executor");
|
|
50
98
|
const version = implementationVersionOf(options);
|
|
@@ -65,6 +113,7 @@ function connectExecutorMode(options, runtime, agents) {
|
|
|
65
113
|
}
|
|
66
114
|
catch { }
|
|
67
115
|
};
|
|
116
|
+
const skipped = new Set();
|
|
68
117
|
let discovering = false, dirty = false;
|
|
69
118
|
const discover = async () => {
|
|
70
119
|
dirty = true;
|
|
@@ -86,6 +135,14 @@ function connectExecutorMode(options, runtime, agents) {
|
|
|
86
135
|
const agent = agents.get(action.agentId);
|
|
87
136
|
if (!agent || action.status !== "pending" || active.has(action.actionId))
|
|
88
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
|
+
}
|
|
89
146
|
const work = new AbortController();
|
|
90
147
|
active.set(action.actionId, work);
|
|
91
148
|
let delivered = false;
|
|
@@ -264,14 +321,21 @@ function connectApplicationMode(options, application, agents) {
|
|
|
264
321
|
});
|
|
265
322
|
void ready.catch(() => { });
|
|
266
323
|
const boot = (async () => {
|
|
324
|
+
const embedded = embeddedAgents.get(agents) ?? new Set();
|
|
267
325
|
for (const agent of agents.values()) {
|
|
326
|
+
if (embedded.has(agent.id))
|
|
327
|
+
continue;
|
|
268
328
|
await application.saveAgent(agent, { implementationVersion: version });
|
|
269
329
|
}
|
|
270
|
-
const registrations = [...agents.values()].map((agent) =>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
+
});
|
|
275
339
|
await application.transport.json("/v1/executors", "PUT", {
|
|
276
340
|
executors: registrations,
|
|
277
341
|
});
|
package/dist/http.d.ts
CHANGED
|
@@ -26,11 +26,20 @@ export declare class Transport {
|
|
|
26
26
|
readonly key: string;
|
|
27
27
|
readonly tenant: string;
|
|
28
28
|
readonly fetcher: typeof fetch;
|
|
29
|
-
|
|
29
|
+
/** Sent on every request, e.g. `Nylorun-Subject` and `Nylorun-Scopes` (`withHeaders`). */
|
|
30
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
31
|
+
private readonly check;
|
|
30
32
|
constructor(options?: Destination, role?: "server" | "executor");
|
|
33
|
+
/**
|
|
34
|
+
* The same destination with `headers` added to every request. The copy shares this
|
|
35
|
+
* transport's compatibility check, so a copy made per request does not repeat `/health`.
|
|
36
|
+
*/
|
|
37
|
+
withHeaders(headers: Readonly<Record<string, string>>): Transport;
|
|
31
38
|
/** Clears the cached Host compatibility result (used after a 426). */
|
|
32
39
|
clearCompatibilityCache(): void;
|
|
33
40
|
private ensureCompatible;
|
|
41
|
+
/** The Host's protocol features, including optional ones, from its `/health`. */
|
|
42
|
+
hostFeatures(signal?: AbortSignal): Promise<readonly string[]>;
|
|
34
43
|
private authHeaders;
|
|
35
44
|
request(path: string, init?: RequestInit, options?: {
|
|
36
45
|
retried426?: boolean;
|
package/dist/http.js
CHANGED
|
@@ -57,7 +57,9 @@ export class Transport {
|
|
|
57
57
|
key;
|
|
58
58
|
tenant;
|
|
59
59
|
fetcher;
|
|
60
|
-
|
|
60
|
+
/** Sent on every request, e.g. `Nylorun-Subject` and `Nylorun-Scopes` (`withHeaders`). */
|
|
61
|
+
headers = {};
|
|
62
|
+
check = { compatible: false, features: [] };
|
|
61
63
|
constructor(options = {}, role = "server") {
|
|
62
64
|
const url = options.url ?? env("NYLORUN_RUNTIME_URL");
|
|
63
65
|
const key = options.key ??
|
|
@@ -75,12 +77,22 @@ export class Transport {
|
|
|
75
77
|
this.tenant = tenant;
|
|
76
78
|
this.fetcher = options.fetch ?? globalThis.fetch;
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The same destination with `headers` added to every request. The copy shares this
|
|
82
|
+
* transport's compatibility check, so a copy made per request does not repeat `/health`.
|
|
83
|
+
*/
|
|
84
|
+
withHeaders(headers) {
|
|
85
|
+
const copy = Object.create(Transport.prototype);
|
|
86
|
+
return Object.assign(copy, this, {
|
|
87
|
+
headers: Object.freeze({ ...this.headers, ...headers }),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
78
90
|
/** Clears the cached Host compatibility result (used after a 426). */
|
|
79
91
|
clearCompatibilityCache() {
|
|
80
|
-
this.compatible = false;
|
|
92
|
+
this.check.compatible = false;
|
|
81
93
|
}
|
|
82
94
|
async ensureCompatible(signal) {
|
|
83
|
-
if (this.compatible)
|
|
95
|
+
if (this.check.compatible)
|
|
84
96
|
return;
|
|
85
97
|
const response = await this.fetcher(`${this.url}/health`, {
|
|
86
98
|
method: "GET",
|
|
@@ -110,10 +122,18 @@ export class Transport {
|
|
|
110
122
|
const result = checkCompatibility({ version: PROTOCOL_VERSION, required: [...PROTOCOL_FEATURES] }, protocol);
|
|
111
123
|
if (!result.ok)
|
|
112
124
|
throw new IncompatibleRuntimeError(result);
|
|
113
|
-
this.
|
|
125
|
+
this.check.features = [...protocol.features];
|
|
126
|
+
this.check.compatible = true;
|
|
127
|
+
}
|
|
128
|
+
/** The Host's protocol features, including optional ones, from its `/health`. */
|
|
129
|
+
async hostFeatures(signal) {
|
|
130
|
+
await this.ensureCompatible(signal);
|
|
131
|
+
return this.check.features;
|
|
114
132
|
}
|
|
115
133
|
authHeaders(init = {}) {
|
|
116
134
|
const headers = new Headers(init.headers);
|
|
135
|
+
for (const [name, value] of Object.entries(this.headers))
|
|
136
|
+
headers.set(name, value);
|
|
117
137
|
headers.set("Authorization", `Bearer ${this.key}`);
|
|
118
138
|
headers.set(TENANT_HEADER, this.tenant);
|
|
119
139
|
headers.set(PROTOCOL_HEADER, String(PROTOCOL_VERSION));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
1
|
+
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
2
2
|
export type { ProtocolFeature, ProtocolRange, Compatibility, ErrorCode, } from "@nylorun/core/compatibility";
|
|
3
|
-
export { Agent, AgentBuilder
|
|
3
|
+
export { Agent, AgentBuilder } from "./builder.js";
|
|
4
|
+
export { AgentBuildError, AgentLifecycleError, Loop, Chain, Switch, Parallel, Map, VerdictSchema, isVerdict, withInstructions, withoutTools, WorkflowBuildError, isBuiltWorkflow, capability, CapabilityBuilder, flow, tool, defineSchema, ToolError, } from "@nylorun/core/define";
|
|
5
|
+
export type { FlowAgentBuilder, Flow, Named, StageArgs, NestedStageArgs, CapabilityIdentity, } from "@nylorun/core/define";
|
|
4
6
|
export type { AgentOptions, AgentManifest, BuiltAgent, BuiltWorkflow, LoopOptions, LoopDecideArgs, LoopVerifyArgs, LoopDecision, ChainOptions, SwitchOptions, ParallelOptions, MapOptions, Verdict, WorkflowBinding, WorkflowManifest, ToolDefinition, ToolExecutionContext, ToolOutcome, Patch, Decision, TurnDecision, HookScope, BeforeHook, AfterHook, JsonValue, JsonObject, } from "@nylorun/core/define";
|
|
5
|
-
export type { LiveEvent, SessionCommand, Action, ActionOutcome, CredentialInfo, CredentialSelection, VaultInfo, } from "@nylorun/core/contracts";
|
|
7
|
+
export type { LiveEvent, SessionCommand, Action, ActionOutcome, CredentialInfo, CredentialSelection, VaultInfo, SubjectScope, } from "@nylorun/core/contracts";
|
|
8
|
+
export { SUBJECT_SCOPES } from "@nylorun/core/contracts";
|
|
6
9
|
export { AgentsClient, SessionClient, createClient } from "./client.js";
|
|
7
|
-
export type { AgentSource, SessionView, CommandOptions, SessionSandbox, } from "./client.js";
|
|
10
|
+
export type { ActAsOptions, AgentSource, SessionView, CommandOptions, SessionSandbox, } from "./client.js";
|
|
8
11
|
export { connectAgents } from "./executor.js";
|
|
9
12
|
export type { ConnectOptions, AgentConnection } from "./executor.js";
|
|
10
13
|
export type { ExecuteActionOptions, ExecutableDefinition, } from "./execute-action.js";
|
|
@@ -22,7 +25,7 @@ export type { StdioLaunch } from "./plugins/launch.js";
|
|
|
22
25
|
export { skills, loadSkillsFromDirectory, resolveSkillsRoot, parseSkill, SkillsError, formatSkillCatalog, SKILLS_USAGE, } from "./skills/index.js";
|
|
23
26
|
export type { SkillsCapability, SkillsOptions, SkillDiagnostic, } from "./skills/index.js";
|
|
24
27
|
export { mcp, McpError } from "./mcp/index.js";
|
|
25
|
-
export type { McpCapability, McpOptions } from "./mcp/index.js";
|
|
28
|
+
export type { McpCapability, McpOptions, McpServerSpec } from "./mcp/index.js";
|
|
26
29
|
export { sandbox, SandboxError } from "./sandbox/index.js";
|
|
27
30
|
export type { SandboxCapability, SandboxCapabilityOptions, SandboxOptions, ActionSandbox, ActionSandboxToolResult, CreateActionSandboxOptions, } from "./sandbox/index.js";
|
|
28
31
|
export { createActionSandbox, definitionDeclaresSandbox, isActionSandboxTool, } from "./sandbox/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Definition authoring and wire contracts are the only harness runtime imports.
|
|
2
|
-
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
3
|
-
export { Agent, AgentBuilder
|
|
2
|
+
export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
|
|
3
|
+
export { Agent, AgentBuilder } from "./builder.js";
|
|
4
|
+
export { AgentBuildError, AgentLifecycleError, Loop, Chain, Switch, Parallel, Map, VerdictSchema, isVerdict, withInstructions, withoutTools, WorkflowBuildError, isBuiltWorkflow, capability, CapabilityBuilder, flow, tool, defineSchema, ToolError, } from "@nylorun/core/define";
|
|
5
|
+
export { SUBJECT_SCOPES } from "@nylorun/core/contracts";
|
|
4
6
|
export { AgentsClient, SessionClient, createClient } from "./client.js";
|
|
5
7
|
export { connectAgents } from "./executor.js";
|
|
6
8
|
export { resolveConnection, ConnectionError } from "./connection.js";
|