@nylorun/agents 0.29.1-beta → 0.31.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.
@@ -1,8 +1,11 @@
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 type { A2aCaller, A2aCardOptions, A2aHandler, A2aHandlerOptions, } from "./handler.js";
8
9
  export { toNodeListener } from "../ag-ui/node.js";
10
+ export * from "./client-api.js";
11
+ export type { AgentCapabilities, AgentCard, AgentExtension, AgentInterface, AgentSkill, Artifact, ListTasksResult, Message, Metadata, Part, PushNotificationConfig, Role, StreamResponse, Task, TaskArtifactUpdateEvent, TaskState, TaskStatus, TaskStatusUpdateEvent, } from "./types.js";
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/files.d.ts CHANGED
@@ -17,14 +17,26 @@ export declare class FilesClient {
17
17
  signal?: AbortSignal;
18
18
  }): Promise<boolean>;
19
19
  /**
20
- * Uploads one file, at most 2 GiB. The Runtime checks its bytes hash to `sha256` (default:
21
- * their hash) and keeps a file it already holds as it is.
20
+ * Uploads one file, at most the Tenant's skill limit (2 GiB by default). The Runtime checks its
21
+ * bytes hash to `sha256` (default: their hash) and keeps a file it already holds as it is.
22
22
  */
23
23
  upload(bytes: Uint8Array, options?: {
24
24
  sha256?: string;
25
25
  signal?: AbortSignal;
26
26
  }): Promise<DefinitionFileView>;
27
- /** Uploads each of `files` the Runtime does not hold yet. */
27
+ /**
28
+ * Uploads one file from a stream, so a large file never sits in memory (ADR-93). `sha256` is
29
+ * required: the bytes aren't read before they are sent. The Runtime refuses a body with another
30
+ * hash and stores nothing.
31
+ */
32
+ uploadStream(stream: ReadableStream<Uint8Array>, options: {
33
+ sha256: string;
34
+ signal?: AbortSignal;
35
+ }): Promise<DefinitionFileView>;
36
+ /**
37
+ * Uploads each of `files` the Runtime does not hold yet: from its `stream()` when the source has
38
+ * one, else from `read()`.
39
+ */
28
40
  ensure(files: ReadonlyMap<string, SkillFileSource>, options?: {
29
41
  signal?: AbortSignal;
30
42
  }): Promise<void>;
package/dist/files.js CHANGED
@@ -37,8 +37,8 @@ export class FilesClient {
37
37
  }
38
38
  }
39
39
  /**
40
- * Uploads one file, at most 2 GiB. The Runtime checks its bytes hash to `sha256` (default:
41
- * their hash) and keeps a file it already holds as it is.
40
+ * Uploads one file, at most the Tenant's skill limit (2 GiB by default). The Runtime checks its
41
+ * bytes hash to `sha256` (default: their hash) and keeps a file it already holds as it is.
42
42
  */
43
43
  async upload(bytes, options = {}) {
44
44
  const response = await this.transport.request(pathOf(options.sha256 ?? (await definitionFileHash(bytes))), {
@@ -49,12 +49,44 @@ export class FilesClient {
49
49
  });
50
50
  return (await response.json());
51
51
  }
52
- /** Uploads each of `files` the Runtime does not hold yet. */
52
+ /**
53
+ * Uploads one file from a stream, so a large file never sits in memory (ADR-93). `sha256` is
54
+ * required: the bytes aren't read before they are sent. The Runtime refuses a body with another
55
+ * hash and stores nothing.
56
+ */
57
+ async uploadStream(stream, options) {
58
+ const response = await this.transport.request(pathOf(options.sha256), {
59
+ method: "PUT",
60
+ body: stream,
61
+ headers: { "content-type": "application/octet-stream" },
62
+ // Node's fetch sends a stream body only half-duplex.
63
+ duplex: "half",
64
+ ...(options.signal ? { signal: options.signal } : {}),
65
+ });
66
+ return (await response.json());
67
+ }
68
+ /**
69
+ * Uploads each of `files` the Runtime does not hold yet: from its `stream()` when the source has
70
+ * one, else from `read()`.
71
+ */
53
72
  async ensure(files, options = {}) {
54
73
  for (const [sha256, source] of files) {
55
74
  if (await this.has(sha256, options))
56
75
  continue;
57
- await this.upload(await source.read(), { sha256, ...options });
76
+ if (!source.stream) {
77
+ await this.upload(await source.read(), { sha256, ...options });
78
+ continue;
79
+ }
80
+ try {
81
+ await this.uploadStream(source.stream(), { sha256, ...options });
82
+ }
83
+ catch (error) {
84
+ // The transport doesn't resend a stream: once more with a new one after an expired token
85
+ // or a protocol check, which the transport has handled for the next request.
86
+ if (!(error instanceof RuntimeError) || (error.status !== 401 && error.status !== 426))
87
+ throw error;
88
+ await this.uploadStream(source.stream(), { sha256, ...options });
89
+ }
58
90
  }
59
91
  }
60
92
  }
package/dist/http.js CHANGED
@@ -138,18 +138,22 @@ export class Transport {
138
138
  headers: await this.authHeaders(init),
139
139
  redirect: "error",
140
140
  });
141
+ // A stream body can't be sent twice: its caller sends a new one (FilesClient.ensure).
142
+ const resendable = !(typeof ReadableStream !== "undefined" && init.body instanceof ReadableStream);
141
143
  // An expired token: get a new one and try once more.
142
144
  if (response.status === 401 && this.token && !options.retried401) {
143
145
  const body = (await response.clone().json().catch(() => undefined));
144
146
  if (body?.code === "token_expired") {
145
- await response.body?.cancel().catch(() => { });
146
147
  this.token.invalidate();
147
- return this.request(path, init, { ...options, retried401: true });
148
+ if (resendable) {
149
+ await response.body?.cancel().catch(() => { });
150
+ return this.request(path, init, { ...options, retried401: true });
151
+ }
148
152
  }
149
153
  }
150
154
  if (response.status === 426) {
151
155
  this.clearCompatibilityCache();
152
- if (!options.retried426) {
156
+ if (!options.retried426 && resendable) {
153
157
  await this.ensureCompatible(init.signal === null ? undefined : init.signal);
154
158
  return this.request(path, init, { retried426: true });
155
159
  }
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";