@nylorun/agents 0.30.0-beta → 0.31.1-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/dist/a2a/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * `@nylorun/agents/a2a`: serve the Tenant's agents to A2A clients through the app's own server
3
- * (gateway mode). The handler forwards A2A requests to the Runtime, which speaks the protocol;
4
- * this entry point loads no A2A package.
3
+ * (gateway mode), and call A2A agents with the Nylorun profile v2 (the client, push
4
+ * verification, the extensions' readers and the change feed). The handler forwards A2A requests
5
+ * to the Runtime, which speaks the protocol; this entry point loads no A2A package.
5
6
  */
6
7
  export { createA2aHandler } from "./handler.js";
7
8
  export { toNodeListener } from "../ag-ui/node.js";
9
+ export * from "./client-api.js";
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The Nylorun A2A profile, v2: the five extensions' URIs, the data each carries, and readers
3
+ * that find it in whatever a client holds (a task, a push's `StreamResponse`, a status update, a
4
+ * message, an artifact). A reader answers `undefined` (or `[]`) when the data is missing or not
5
+ * the profile's shape, so a core-tier agent outside Nylorun reads as plain A2A.
6
+ */
7
+ import type { Artifact } from "./types.js";
8
+ /** The profile's extension URIs, exactly as agents declare them on the card. */
9
+ export declare const A2A_EXTENSIONS: {
10
+ /** An open interaction on a status message; the caller's answer on its reply. */
11
+ readonly interactions: "https://docs.nylorun.com/a2a/interactions/v1";
12
+ /** `uncertain`: the effect whose outcome was lost. */
13
+ readonly effects: "https://docs.nylorun.com/a2a/effects/v1";
14
+ /** The agent's plan, whole each time. */
15
+ readonly progress: "https://docs.nylorun.com/a2a/progress/v1";
16
+ /** Totals and the trace id, on the terminal status update. */
17
+ readonly telemetry: "https://docs.nylorun.com/a2a/telemetry/v1";
18
+ /** Each file artifact's kind, path, version, size and digest. */
19
+ readonly files: "https://docs.nylorun.com/a2a/files/v1";
20
+ };
21
+ export type A2aExtensionUri = (typeof A2A_EXTENSIONS)[keyof typeof A2A_EXTENSIONS];
22
+ /** Every profile v2 extension: what a client activates by default. */
23
+ export declare const A2A_PROFILE_EXTENSIONS: readonly A2aExtensionUri[];
24
+ export type A2aPlanStepState = "pending" | "active" | "done" | "blocked" | "skipped";
25
+ export interface A2aPlanStep {
26
+ label: string;
27
+ state: A2aPlanStepState;
28
+ note?: string;
29
+ }
30
+ /** `progress/v1`: the whole plan, never a diff. */
31
+ export interface A2aProgress {
32
+ steps: A2aPlanStep[];
33
+ }
34
+ /** `telemetry/v1`, on the terminal status update. */
35
+ export interface A2aTelemetry {
36
+ totals?: {
37
+ steps: number;
38
+ tokens: number;
39
+ costUsd: number;
40
+ };
41
+ /** The caller's `traceparent` trace id when one was sent, else the Runtime's own. */
42
+ traceId?: string;
43
+ }
44
+ /** `interactions/v1`: what the agent waits for. `approval` and `connection` pause as `AUTH_REQUIRED`. */
45
+ export interface A2aInteraction {
46
+ interactionId: string;
47
+ kind: "approval" | "question" | "connection" | (string & {});
48
+ prompt?: string;
49
+ detail?: unknown;
50
+ }
51
+ /** `effects/v1`. */
52
+ export interface A2aEffects {
53
+ /** The effect id whose outcome was lost; the task stays `WORKING`. */
54
+ uncertain?: string;
55
+ }
56
+ /** `files/v1`, on an artifact that comes from a file the agent wrote. */
57
+ export interface A2aFile {
58
+ kind: string;
59
+ /** Its path in the agent's outputs. */
60
+ path?: string;
61
+ version?: number;
62
+ size?: number;
63
+ sha256?: string;
64
+ }
65
+ /** An artifact with its `files/v1` data. */
66
+ export interface A2aFileArtifact {
67
+ artifact: Artifact;
68
+ file: A2aFile;
69
+ }
70
+ /**
71
+ * The data under extension `uri` in `source`'s metadata: an update event's own first, then its
72
+ * status message's. `source` may be a `StreamResponse`, a task, a status update, a status, a
73
+ * message or an artifact.
74
+ */
75
+ export declare function readExtension(source: unknown, uri: string): unknown;
76
+ /** `progress/v1`'s plan; steps that are not the profile's shape are left out. */
77
+ export declare function readProgress(source: unknown): A2aProgress | undefined;
78
+ /** `telemetry/v1`'s totals and trace id. */
79
+ export declare function readTelemetry(source: unknown): A2aTelemetry | undefined;
80
+ /** `interactions/v1`'s open interaction, from a status (update) or its message. */
81
+ export declare function readInteraction(source: unknown): A2aInteraction | undefined;
82
+ /** `effects/v1`: the effect whose outcome was lost. */
83
+ export declare function readEffects(source: unknown): A2aEffects | undefined;
84
+ /**
85
+ * The artifacts with `files/v1` data: a task's, an artifact update's, or one artifact. Others
86
+ * (the turn's `output`, a core-tier agent's) are left out.
87
+ */
88
+ export declare function readFiles(source: unknown): A2aFileArtifact[];
@@ -0,0 +1,133 @@
1
+ /** The profile's extension URIs, exactly as agents declare them on the card. */
2
+ export const A2A_EXTENSIONS = {
3
+ /** An open interaction on a status message; the caller's answer on its reply. */
4
+ interactions: "https://docs.nylorun.com/a2a/interactions/v1",
5
+ /** `uncertain`: the effect whose outcome was lost. */
6
+ effects: "https://docs.nylorun.com/a2a/effects/v1",
7
+ /** The agent's plan, whole each time. */
8
+ progress: "https://docs.nylorun.com/a2a/progress/v1",
9
+ /** Totals and the trace id, on the terminal status update. */
10
+ telemetry: "https://docs.nylorun.com/a2a/telemetry/v1",
11
+ /** Each file artifact's kind, path, version, size and digest. */
12
+ files: "https://docs.nylorun.com/a2a/files/v1",
13
+ };
14
+ /** Every profile v2 extension: what a client activates by default. */
15
+ export const A2A_PROFILE_EXTENSIONS = Object.values(A2A_EXTENSIONS);
16
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
17
+ /** The objects whose `metadata` may hold extension data, most specific first. */
18
+ function holders(source) {
19
+ if (!isObject(source))
20
+ return [];
21
+ for (const key of ["statusUpdate", "artifactUpdate", "task", "message"])
22
+ if (isObject(source[key]))
23
+ return holders(source[key]);
24
+ const found = [source];
25
+ const status = isObject(source.status) ? source.status : undefined;
26
+ if (status && isObject(status.message))
27
+ found.push(status.message);
28
+ if (isObject(source.artifact))
29
+ found.push(source.artifact);
30
+ return found;
31
+ }
32
+ /**
33
+ * The data under extension `uri` in `source`'s metadata: an update event's own first, then its
34
+ * status message's. `source` may be a `StreamResponse`, a task, a status update, a status, a
35
+ * message or an artifact.
36
+ */
37
+ export function readExtension(source, uri) {
38
+ for (const holder of holders(source)) {
39
+ const metadata = holder.metadata;
40
+ if (isObject(metadata) && metadata[uri] !== undefined)
41
+ return metadata[uri];
42
+ }
43
+ return undefined;
44
+ }
45
+ const STEP_STATES = new Set(["pending", "active", "done", "blocked", "skipped"]);
46
+ const finite = (value) => typeof value === "number" && Number.isFinite(value);
47
+ /** `progress/v1`'s plan; steps that are not the profile's shape are left out. */
48
+ export function readProgress(source) {
49
+ const data = readExtension(source, A2A_EXTENSIONS.progress);
50
+ if (!isObject(data) || !Array.isArray(data.steps))
51
+ return undefined;
52
+ const steps = [];
53
+ for (const step of data.steps) {
54
+ if (!isObject(step) || typeof step.label !== "string" || typeof step.state !== "string")
55
+ continue;
56
+ if (!STEP_STATES.has(step.state))
57
+ continue;
58
+ steps.push({
59
+ label: step.label,
60
+ state: step.state,
61
+ ...(typeof step.note === "string" ? { note: step.note } : {}),
62
+ });
63
+ }
64
+ return { steps };
65
+ }
66
+ /** `telemetry/v1`'s totals and trace id. */
67
+ export function readTelemetry(source) {
68
+ const data = readExtension(source, A2A_EXTENSIONS.telemetry);
69
+ if (!isObject(data))
70
+ return undefined;
71
+ const totals = isObject(data.totals) ? data.totals : undefined;
72
+ const result = {};
73
+ if (totals && finite(totals.steps) && finite(totals.tokens) && finite(totals.costUsd))
74
+ result.totals = { steps: totals.steps, tokens: totals.tokens, costUsd: totals.costUsd };
75
+ if (typeof data.traceId === "string")
76
+ result.traceId = data.traceId;
77
+ return result.totals || result.traceId ? result : undefined;
78
+ }
79
+ /** `interactions/v1`'s open interaction, from a status (update) or its message. */
80
+ export function readInteraction(source) {
81
+ const data = readExtension(source, A2A_EXTENSIONS.interactions);
82
+ if (!isObject(data) || typeof data.interactionId !== "string" || data.interactionId === "")
83
+ return undefined;
84
+ return {
85
+ interactionId: data.interactionId,
86
+ kind: typeof data.kind === "string" ? data.kind : "question",
87
+ ...(typeof data.prompt === "string" ? { prompt: data.prompt } : {}),
88
+ ...(data.detail !== undefined ? { detail: data.detail } : {}),
89
+ };
90
+ }
91
+ /** `effects/v1`: the effect whose outcome was lost. */
92
+ export function readEffects(source) {
93
+ const data = readExtension(source, A2A_EXTENSIONS.effects);
94
+ if (!isObject(data))
95
+ return undefined;
96
+ return typeof data.uncertain === "string" ? { uncertain: data.uncertain } : {};
97
+ }
98
+ function fileOf(artifact) {
99
+ if (!isObject(artifact) || typeof artifact.artifactId !== "string")
100
+ return undefined;
101
+ const metadata = artifact.metadata;
102
+ const data = isObject(metadata) ? metadata[A2A_EXTENSIONS.files] : undefined;
103
+ if (!isObject(data) || typeof data.kind !== "string")
104
+ return undefined;
105
+ return {
106
+ artifact: artifact,
107
+ file: {
108
+ kind: data.kind,
109
+ ...(typeof data.path === "string" ? { path: data.path } : {}),
110
+ ...(finite(data.version) ? { version: data.version } : {}),
111
+ ...(finite(data.size) ? { size: data.size } : {}),
112
+ ...(typeof data.sha256 === "string" ? { sha256: data.sha256 } : {}),
113
+ },
114
+ };
115
+ }
116
+ /**
117
+ * The artifacts with `files/v1` data: a task's, an artifact update's, or one artifact. Others
118
+ * (the turn's `output`, a core-tier agent's) are left out.
119
+ */
120
+ export function readFiles(source) {
121
+ if (!isObject(source))
122
+ return [];
123
+ if (isObject(source.task))
124
+ return readFiles(source.task);
125
+ if (isObject(source.artifactUpdate))
126
+ return readFiles(source.artifactUpdate);
127
+ const artifacts = Array.isArray(source.artifacts)
128
+ ? source.artifacts
129
+ : isObject(source.artifact)
130
+ ? [source.artifact]
131
+ : [source];
132
+ return artifacts.flatMap((artifact) => fileOf(artifact) ?? []);
133
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Verifying a push delivery from a Nylorun agent (profile v2): Standard Webhooks signatures over
3
+ * the raw body with the installation's `whsec_` secret. Web Crypto only, so it runs wherever
4
+ * `fetch` does; HMAC comparison is `crypto.subtle.verify`'s, in constant time.
5
+ */
6
+ import type { StreamResponse } from "./types.js";
7
+ export type A2aPushFailure = "missing_headers" | "invalid_secret" | "invalid_timestamp" | "timestamp_out_of_range" | "invalid_signature" | "invalid_token" | "invalid_body";
8
+ /** A delivery that is not provably from the agent: answer it `400` or `401` and drop it. */
9
+ export declare class A2aPushVerificationError extends Error {
10
+ readonly reason: A2aPushFailure;
11
+ constructor(reason: A2aPushFailure, message: string);
12
+ }
13
+ /** Request headers as `Headers`, a `node:http` `IncomingHttpHeaders`, or a plain record. */
14
+ export type PushHeaders = Headers | Readonly<Record<string, string | readonly string[] | undefined>>;
15
+ export interface VerifyA2aPushOptions {
16
+ /** The installation's webhook secret, `whsec_<base64>`. */
17
+ secret: string;
18
+ headers: PushHeaders;
19
+ /** The request body exactly as received: verify before parsing. */
20
+ body: string | Uint8Array;
21
+ /** How far `webhook-timestamp` may be from now, either way. Default 300. */
22
+ toleranceSeconds?: number;
23
+ /**
24
+ * The push config's `token`, when the client gave one: the delivery's
25
+ * `X-A2A-Notification-Token` must match it.
26
+ */
27
+ token?: string;
28
+ }
29
+ /**
30
+ * Checks one push delivery and returns its body, an A2A `StreamResponse`. Throws
31
+ * `A2aPushVerificationError` when a header is missing, the timestamp is more than
32
+ * `toleranceSeconds` from now, no `v1` signature matches, or the token differs. Several
33
+ * signatures (space-separated) are allowed, as during a secret's rotation.
34
+ */
35
+ export declare function verifyA2aPush(options: VerifyA2aPushOptions): Promise<StreamResponse>;
@@ -0,0 +1,96 @@
1
+ /** A delivery that is not provably from the agent: answer it `400` or `401` and drop it. */
2
+ export class A2aPushVerificationError extends Error {
3
+ reason;
4
+ constructor(reason, message) {
5
+ super(message);
6
+ this.reason = reason;
7
+ this.name = "A2aPushVerificationError";
8
+ }
9
+ }
10
+ function header(headers, name) {
11
+ if (typeof headers.get === "function")
12
+ return headers.get(name) ?? undefined;
13
+ const record = headers;
14
+ const key = Object.keys(record).find((candidate) => candidate.toLowerCase() === name);
15
+ const value = key === undefined ? undefined : record[key];
16
+ return Array.isArray(value) ? value.join(" ") : value;
17
+ }
18
+ function base64Bytes(value) {
19
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 === 1)
20
+ return undefined;
21
+ try {
22
+ const binary = atob(value);
23
+ const bytes = new Uint8Array(binary.length);
24
+ for (let i = 0; i < binary.length; i++)
25
+ bytes[i] = binary.charCodeAt(i);
26
+ return bytes;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ const encoder = new TextEncoder();
33
+ /** Equal strings, compared without stopping at the first difference. */
34
+ function sameText(a, b) {
35
+ const left = encoder.encode(a);
36
+ const right = encoder.encode(b);
37
+ let diff = left.length ^ right.length;
38
+ for (let i = 0; i < Math.max(left.length, right.length); i++)
39
+ diff |= (left[i] ?? 0) ^ (right[i] ?? 0);
40
+ return diff === 0;
41
+ }
42
+ /**
43
+ * Checks one push delivery and returns its body, an A2A `StreamResponse`. Throws
44
+ * `A2aPushVerificationError` when a header is missing, the timestamp is more than
45
+ * `toleranceSeconds` from now, no `v1` signature matches, or the token differs. Several
46
+ * signatures (space-separated) are allowed, as during a secret's rotation.
47
+ */
48
+ export async function verifyA2aPush(options) {
49
+ const id = header(options.headers, "webhook-id");
50
+ const timestamp = header(options.headers, "webhook-timestamp");
51
+ const signatures = header(options.headers, "webhook-signature");
52
+ if (!id || !timestamp || !signatures)
53
+ throw new A2aPushVerificationError("missing_headers", "webhook-id, webhook-timestamp and webhook-signature are required");
54
+ const secret = base64Bytes(options.secret.replace(/^whsec_/, ""));
55
+ if (!secret || secret.length === 0)
56
+ throw new A2aPushVerificationError("invalid_secret", "The secret must be whsec_<base64>");
57
+ if (!/^\d{1,15}$/.test(timestamp))
58
+ throw new A2aPushVerificationError("invalid_timestamp", "webhook-timestamp must be unix seconds");
59
+ const tolerance = options.toleranceSeconds ?? 300;
60
+ if (Math.abs(Date.now() / 1000 - Number(timestamp)) > tolerance)
61
+ throw new A2aPushVerificationError("timestamp_out_of_range", `webhook-timestamp is more than ${tolerance} seconds from now`);
62
+ const body = typeof options.body === "string" ? options.body : new TextDecoder().decode(options.body);
63
+ const key = await crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
64
+ const signed = encoder.encode(`${id}.${timestamp}.${body}`);
65
+ let verified = false;
66
+ for (const entry of signatures.split(" ")) {
67
+ const comma = entry.indexOf(",");
68
+ if (comma < 0 || entry.slice(0, comma) !== "v1")
69
+ continue;
70
+ const signature = base64Bytes(entry.slice(comma + 1));
71
+ if (!signature)
72
+ continue;
73
+ // Every candidate is checked, so the time taken says nothing about which one matched.
74
+ if (await crypto.subtle.verify("HMAC", key, signature, signed))
75
+ verified = true;
76
+ }
77
+ if (!verified)
78
+ throw new A2aPushVerificationError("invalid_signature", "No webhook-signature matches");
79
+ if (options.token !== undefined) {
80
+ const token = header(options.headers, "x-a2a-notification-token");
81
+ if (token === undefined || !sameText(token, options.token))
82
+ throw new A2aPushVerificationError("invalid_token", "X-A2A-Notification-Token does not match the push config's token");
83
+ }
84
+ let parsed;
85
+ try {
86
+ parsed = JSON.parse(body);
87
+ }
88
+ catch {
89
+ throw new A2aPushVerificationError("invalid_body", "The body is not JSON");
90
+ }
91
+ if (typeof parsed !== "object" ||
92
+ parsed === null ||
93
+ !["task", "message", "statusUpdate", "artifactUpdate"].some((key) => key in parsed))
94
+ throw new A2aPushVerificationError("invalid_body", "The body is not an A2A StreamResponse");
95
+ return parsed;
96
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * The A2A 1.0 wire types the client reads and sends, as ProtoJSON (camelCase fields, enum
3
+ * values such as `TASK_STATE_WORKING`). They follow the Runtime's own (`api/a2a/protocol.ts`)
4
+ * and add what profile v2 serves: metadata on tasks and artifacts, the update events a push
5
+ * carries, `ListTasks` and the card's extensions. Only types: nothing here runs.
6
+ */
7
+ export type TaskState = "TASK_STATE_SUBMITTED" | "TASK_STATE_WORKING" | "TASK_STATE_INPUT_REQUIRED" | "TASK_STATE_AUTH_REQUIRED" | "TASK_STATE_COMPLETED" | "TASK_STATE_FAILED" | "TASK_STATE_CANCELED" | "TASK_STATE_REJECTED";
8
+ export type Metadata = Record<string, unknown>;
9
+ /** Exactly one of `text`, `data`, `raw` (base64 bytes) or `url` (A2A §4.1.6). */
10
+ export type Part = {
11
+ text: string;
12
+ mediaType?: string;
13
+ metadata?: Metadata;
14
+ } | {
15
+ data: unknown;
16
+ mediaType?: string;
17
+ metadata?: Metadata;
18
+ } | {
19
+ raw: string;
20
+ mediaType?: string;
21
+ filename?: string;
22
+ metadata?: Metadata;
23
+ } | {
24
+ url: string;
25
+ mediaType?: string;
26
+ filename?: string;
27
+ metadata?: Metadata;
28
+ };
29
+ export type Role = "ROLE_USER" | "ROLE_AGENT";
30
+ export interface Message {
31
+ messageId: string;
32
+ role: Role;
33
+ parts: Part[];
34
+ contextId?: string;
35
+ taskId?: string;
36
+ /** The extension URIs whose data this message carries in `metadata`. */
37
+ extensions?: string[];
38
+ metadata?: Metadata;
39
+ referenceTaskIds?: string[];
40
+ }
41
+ export interface Artifact {
42
+ artifactId: string;
43
+ name?: string;
44
+ description?: string;
45
+ parts: Part[];
46
+ extensions?: string[];
47
+ metadata?: Metadata;
48
+ }
49
+ export interface TaskStatus {
50
+ state: TaskState;
51
+ message?: Message;
52
+ /** ISO 8601. Order updates by it. */
53
+ timestamp?: string;
54
+ }
55
+ export interface Task {
56
+ id: string;
57
+ contextId: string;
58
+ status: TaskStatus;
59
+ artifacts?: Artifact[];
60
+ history?: Message[];
61
+ metadata?: Metadata;
62
+ }
63
+ export interface TaskStatusUpdateEvent {
64
+ taskId: string;
65
+ contextId: string;
66
+ status: TaskStatus;
67
+ metadata?: Metadata;
68
+ }
69
+ export interface TaskArtifactUpdateEvent {
70
+ taskId: string;
71
+ contextId: string;
72
+ artifact: Artifact;
73
+ append?: boolean;
74
+ lastChunk?: boolean;
75
+ metadata?: Metadata;
76
+ }
77
+ /** One update: the body of a push delivery, or one frame of a stream. Exactly one field is set. */
78
+ export type StreamResponse = {
79
+ task: Task;
80
+ } | {
81
+ message: Message;
82
+ } | {
83
+ statusUpdate: TaskStatusUpdateEvent;
84
+ } | {
85
+ artifactUpdate: TaskArtifactUpdateEvent;
86
+ };
87
+ /** Where an agent sends push deliveries for a task. */
88
+ export interface PushNotificationConfig {
89
+ url: string;
90
+ /** Sent back on every delivery as `X-A2A-Notification-Token`. */
91
+ token?: string;
92
+ id?: string;
93
+ }
94
+ export interface ListTasksResult {
95
+ tasks: Task[];
96
+ /** Empty when this is the last page. */
97
+ nextPageToken: string;
98
+ pageSize: number;
99
+ totalSize: number;
100
+ }
101
+ export interface AgentExtension {
102
+ uri: string;
103
+ description?: string;
104
+ required?: boolean;
105
+ params?: Metadata;
106
+ }
107
+ export interface AgentInterface {
108
+ url: string;
109
+ protocolBinding: string;
110
+ protocolVersion?: string;
111
+ }
112
+ export interface AgentCapabilities {
113
+ streaming?: boolean;
114
+ pushNotifications?: boolean;
115
+ extendedAgentCard?: boolean;
116
+ extensions?: AgentExtension[];
117
+ }
118
+ export interface AgentSkill {
119
+ id: string;
120
+ name: string;
121
+ description: string;
122
+ tags: string[];
123
+ examples?: string[];
124
+ inputModes?: string[];
125
+ outputModes?: string[];
126
+ }
127
+ export interface AgentCard {
128
+ name: string;
129
+ description: string;
130
+ supportedInterfaces: AgentInterface[];
131
+ version: string;
132
+ capabilities: AgentCapabilities;
133
+ defaultInputModes: string[];
134
+ defaultOutputModes: string[];
135
+ skills: AgentSkill[];
136
+ provider?: {
137
+ organization: string;
138
+ url: string;
139
+ };
140
+ documentationUrl?: string;
141
+ iconUrl?: string;
142
+ securitySchemes?: Record<string, unknown>;
143
+ securityRequirements?: unknown[];
144
+ [field: string]: unknown;
145
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The A2A 1.0 wire types the client reads and sends, as ProtoJSON (camelCase fields, enum
3
+ * values such as `TASK_STATE_WORKING`). They follow the Runtime's own (`api/a2a/protocol.ts`)
4
+ * and add what profile v2 serves: metadata on tasks and artifacts, the update events a push
5
+ * carries, `ListTasks` and the card's extensions. Only types: nothing here runs.
6
+ */
7
+ export {};
package/dist/compose.js CHANGED
@@ -199,6 +199,7 @@ function agentHarness(file, base, where, files, warnings) {
199
199
  ...(file.outputSchema === undefined ? {} : { outputSchema: file.outputSchema }),
200
200
  ...(file.loop === undefined ? {} : { loop: file.loop }),
201
201
  ...(file.memory === undefined ? {} : { memory: file.memory }),
202
+ ...(file.model === undefined ? {} : { model: file.model }),
202
203
  };
203
204
  }
204
205
  /**
package/dist/index.d.ts CHANGED
@@ -33,3 +33,5 @@ export type { SkillDiagnostic } from "./skills/index.js";
33
33
  export { McpError } from "./mcp/index.js";
34
34
  export type { McpServerSpec } from "./mcp/index.js";
35
35
  export type { SessionPageOptions } from "./reads.js";
36
+ export * from "./a2a/client-api.js";
37
+ export type * as A2a from "./a2a/types.js";
package/dist/index.js CHANGED
@@ -23,3 +23,5 @@ export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
23
23
  export { loadMcpServers, loadPlugin, PluginError } from "./plugins/load.js";
24
24
  export { hashFolder, loadSkillsFromDirectory, resolveSkillsRoot, parseSkill, SkillsError, } from "./skills/index.js";
25
25
  export { McpError } from "./mcp/index.js";
26
+ // The A2A client (`@nylorun/agents/a2a` also has the gateway handler).
27
+ export * from "./a2a/client-api.js";
@@ -439,10 +439,15 @@ export declare class SessionClient {
439
439
  agent?: string;
440
440
  signal?: AbortSignal;
441
441
  }): Promise<SessionItemsResponse>;
442
+ /**
443
+ * The session's events from `cursor` on, reconnecting from the last one when the connection
444
+ * drops or sends nothing, keepalives included, for `idleTimeoutMs` (default 45 s; `0` never).
445
+ */
442
446
  observe(options?: {
443
447
  cursor?: string;
444
448
  signal?: AbortSignal;
445
449
  follow?: boolean;
450
+ idleTimeoutMs?: number;
446
451
  }): AsyncGenerator<{
447
452
  schema: "nylorun.event/2";
448
453
  eventId: string;
@@ -279,6 +279,10 @@ export class SessionClient {
279
279
  ...("tail" in page ? { tail: page.tail } : {}),
280
280
  };
281
281
  }
282
+ /**
283
+ * The session's events from `cursor` on, reconnecting from the last one when the connection
284
+ * drops or sends nothing, keepalives included, for `idleTimeoutMs` (default 45 s; `0` never).
285
+ */
282
286
  async *observe(options = {}) {
283
287
  if (!options.follow) {
284
288
  yield* this.observeOne(this.id, options);
@@ -318,7 +322,11 @@ export class SessionClient {
318
322
  };
319
323
  const pump = async (sessionId, cursor) => {
320
324
  try {
321
- for await (const event of this.observeOne(sessionId, { cursor, signal })) {
325
+ for await (const event of this.observeOne(sessionId, {
326
+ cursor,
327
+ signal,
328
+ ...(options.idleTimeoutMs !== undefined ? { idleTimeoutMs: options.idleTimeoutMs } : {}),
329
+ })) {
322
330
  push(event);
323
331
  const link = linkedSessionId(event);
324
332
  if (link && link !== this.id && !linked.has(link)) {
package/dist/sse.d.ts CHANGED
@@ -4,9 +4,26 @@ export interface SSEEvent {
4
4
  data: string;
5
5
  id?: string;
6
6
  }
7
- /** Fetch-based SSE supports Authorization headers and works in browsers and Node. */
8
- export declare function readSSE(response: Response, signal: AbortSignal): AsyncGenerator<SSEEvent>;
7
+ /**
8
+ * How long a stream may send nothing before the client takes it for dead: the Runtime sends a
9
+ * `: keepalive` comment every 15 s, so three missed ones.
10
+ */
11
+ export declare const SSE_IDLE_TIMEOUT_MS = 45000;
12
+ /** A stream sent no byte, not even a keepalive, for `idleTimeoutMs`. */
13
+ export declare class SSEIdleTimeoutError extends Error {
14
+ readonly idleTimeoutMs: number;
15
+ constructor(idleTimeoutMs: number);
16
+ }
17
+ /**
18
+ * Fetch-based SSE supports Authorization headers and works in browsers and Node. Any byte,
19
+ * keepalive comments included, counts as liveness; after `idleTimeoutMs` without one the read
20
+ * stops with `SSEIdleTimeoutError` (`0` waits forever).
21
+ */
22
+ export declare function readSSE(response: Response, signal: AbortSignal, options?: {
23
+ idleTimeoutMs?: number;
24
+ }): AsyncGenerator<SSEEvent>;
9
25
  export declare function observeSSE(transport: Transport, path: string, options?: {
10
26
  signal?: AbortSignal;
11
27
  cursor?: string;
28
+ idleTimeoutMs?: number;
12
29
  }): AsyncGenerator<SSEEvent>;