@mantyx/sdk 0.2.0 → 0.3.0

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 CHANGED
@@ -5,6 +5,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ### Added
9
+
10
+ - Add support for a2a, mcp and local versions
11
+
12
+ ### Fixed
13
+
14
+ - Changelo
15
+
16
+ ## [0.2.0] — 2026-05-04
17
+
8
18
  ### Fixed
9
19
 
10
20
  - Use correct default base url
@@ -21,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
21
31
 
22
32
  ## [0.1.0] — 2026-05-02
23
33
 
24
- [unreleased]: https://github.com/mantyx-io/mantyx-sdk/compare/v0.1.1..HEAD
34
+ [unreleased]: https://github.com/mantyx-io/mantyx-sdk/compare/v0.2.0..HEAD
35
+ [0.2.0]: https://github.com/mantyx-io/mantyx-sdk/compare/v0.1.1..v0.2.0
25
36
  [0.1.1]: https://github.com/mantyx-io/mantyx-sdk/compare/v0.1.0..v0.1.1
26
37
  [0.1.0]: https://github.com/mantyx-io/mantyx-sdk/releases/tag/v0.1.0
package/README.md CHANGED
@@ -6,13 +6,20 @@ locally-executed tools, run them remotely, and stream events back into your
6
6
  process.
7
7
 
8
8
  - LLM loop runs on MANTYX (BYOK or platform-hosted models).
9
- - Server-side tools (`mantyx`, `mantyx_plugin`) execute inside MANTYX.
10
- - Local tools execute inside *your* process; the SDK shuttles inputs and
11
- outputs over an SSE stream + a tool-result POST.
9
+ - Server-resolved tools (`mantyx`, `mantyx_plugin`, `a2a`, `mcp`) execute
10
+ inside MANTYX including remote Agent2Agent peers and remote MCP servers.
11
+ - Client-resolved tools (`local`, `a2a_local`, `mcp_local`) execute inside
12
+ *your* process; the SDK shuttles inputs and outputs over an SSE stream +
13
+ a tool-result POST.
14
+ - Tunable provider thinking via `reasoningLevel` (string anchors or 0–100).
12
15
  - One-shot runs and multi-turn sessions, both with persisted observability.
13
16
  - Authenticated with a single workspace API key.
14
17
 
15
- For background, see the [agent-runs protocol spec](./docs/agent-runs-protocol.md).
18
+ For background, see the [agent-runs protocol spec](./docs/agent-runs-protocol.md)
19
+ and the messaging-layer reference in [`docs/wire-protocol.md`](./docs/wire-protocol.md)
20
+ — the latter pins down the exact `local_tool_call` event shape and the
21
+ resolved data structures (`a2a_local` Agent Card, `mcp_local` `Tool[]`)
22
+ that this SDK ships.
16
23
 
17
24
  ## Install
18
25
 
@@ -22,9 +29,11 @@ npm install @mantyx/sdk zod
22
29
  # or: yarn add @mantyx/sdk zod
23
30
  ```
24
31
 
25
- Requires Node.js 18.17+ (for `fetch` and `ReadableStream`). `zod` is the only
26
- runtime dependency the SDK adds; the rest is the standard library and your
27
- own modules.
32
+ Requires Node.js 18.17+ (for `fetch` and `ReadableStream`). The SDK depends
33
+ on `zod` (parameter schemas) and `@modelcontextprotocol/sdk` (the official
34
+ MCP TypeScript SDK that powers `defineLocalMcp`'s stdio + Streamable HTTP
35
+ transports). The MCP SDK is loaded lazily — apps that never use
36
+ `defineLocalMcp` don't pay its startup cost.
28
37
 
29
38
  ## Quickstart
30
39
 
@@ -103,6 +112,190 @@ Notes:
103
112
  The same `agentId` field works on `client.createSession({ ... })` for
104
113
  multi-turn conversations against a persisted agent.
105
114
 
115
+ ## Agent2Agent delegation
116
+
117
+ Hand a turn off to another agent — either a remote peer MANTYX dials directly
118
+ (`mantyxA2A`) or a peer that only the SDK can reach (`defineLocalA2A`). The
119
+ model addresses both with the same `{ message: string }` argument shape, so
120
+ an agent prompt that uses one works unchanged with the other.
121
+
122
+ `defineLocalA2A` is fully URL-driven: pass the Agent Card URL and the SDK
123
+ takes care of the rest — fetching the card on the first run, shipping it
124
+ inline as part of the spec, and POSTing JSON-RPC `message/send` to the
125
+ card's `url` whenever MANTYX emits a `local_tool_call`. You don't write any
126
+ A2A code yourself.
127
+
128
+ ```ts
129
+ import { MantyxClient, defineLocalA2A, mantyxA2A } from "@mantyx/sdk";
130
+
131
+ const client = new MantyxClient({ apiKey: "...", workspaceSlug: "acme" });
132
+
133
+ await client.runAgent({
134
+ systemPrompt: "You are a helpful router. Delegate billing to billing_agent.",
135
+ prompt: "Why was I charged twice last month?",
136
+ tools: [
137
+ // Public peer MANTYX dials directly.
138
+ mantyxA2A({
139
+ name: "billing_agent",
140
+ description: "Delegate billing questions to the Acme billing agent.",
141
+ agentCardUrl: "https://billing.acme.com/.well-known/agent-card.json",
142
+ headers: { Authorization: `Bearer ${process.env.BILLING_TOKEN}` },
143
+ }),
144
+ // Intranet peer the SDK reaches on MANTYX's behalf — URL only.
145
+ defineLocalA2A({
146
+ name: "intranet_hr",
147
+ agentCardUrl: "https://hr.intranet.acme/.well-known/agent-card.json",
148
+ headers: { Authorization: `Bearer ${process.env.HR_TOKEN}` },
149
+ }),
150
+ ],
151
+ });
152
+ ```
153
+
154
+ The same `headers` are sent on both the card fetch *and* every subsequent
155
+ `message/send` POST, which is typically what intranet peers want. The SDK
156
+ caches the resolved card on the tool ref for the duration of the run /
157
+ session — re-construct the ref to force a refetch.
158
+
159
+ > **Headers and secrets.** The `headers` you pass to `mantyxA2A` are forwarded
160
+ > as-is. For long-lived credentials, register the peer as a workspace
161
+ > `ExternalAgent` instead — those headers support `{{secret:NAME}}`
162
+ > placeholders. Use `mantyxA2A` for short-lived, per-run tokens minted by
163
+ > your application.
164
+
165
+ ### Exposing an agent over A2A
166
+
167
+ The inverse direction also works: wrap a MANTYX agent (ephemeral spec or a
168
+ persisted `agentId`) and serve it as an Agent2Agent peer using the official
169
+ [`@a2a-js/sdk`](https://www.npmjs.com/package/@a2a-js/sdk) library. Other
170
+ agents can then discover it at `/.well-known/agent-card.json` and call
171
+ `message/send` over JSON-RPC — including MANTYX agents elsewhere in your
172
+ estate consuming this one via `mantyxA2A` or `defineLocalA2A`.
173
+
174
+ ```ts
175
+ import { MantyxClient } from "@mantyx/sdk";
176
+ import { serveAgentOverA2A } from "@mantyx/sdk/a2a-server";
177
+
178
+ const client = new MantyxClient({ apiKey: "...", workspaceSlug: "acme" });
179
+
180
+ const handle = await serveAgentOverA2A({
181
+ client,
182
+ agent: { agentId: "agent_cm6abc123" }, // or { systemPrompt, modelId, tools }
183
+ port: 4000,
184
+ agentCard: {
185
+ name: "Acme Support",
186
+ description: "Customer support questions.",
187
+ protocolVersion: "0.3.0",
188
+ version: "1.0.0",
189
+ url: "http://localhost:4000",
190
+ skills: [{ id: "support", name: "Support", tags: ["support"] }],
191
+ capabilities: { streaming: true, pushNotifications: false },
192
+ defaultInputModes: ["text"],
193
+ defaultOutputModes: ["text"],
194
+ },
195
+ });
196
+
197
+ console.log(`A2A peer up on ${handle.url}`);
198
+ // later: await handle.close();
199
+ ```
200
+
201
+ `@a2a-js/sdk` and `express` are declared as **optional peer dependencies**,
202
+ so apps that don't expose an A2A server pay zero bundle cost. Install them
203
+ on demand:
204
+
205
+ ```bash
206
+ npm install @a2a-js/sdk express
207
+ ```
208
+
209
+ Each unique A2A `contextId` opens a long-lived MANTYX session by default, so
210
+ multi-turn `message/send` calls share conversational history. Pass
211
+ `conversation: "stateless"` to reduce every A2A request to a one-shot
212
+ `runAgent` call.
213
+
214
+ For lower-level integration (mounting the executor in your own Express /
215
+ Fastify / Connect app), `@mantyx/sdk/a2a-server` also exports a
216
+ `MantyxAgentExecutor` class implementing `@a2a-js/sdk/server`'s
217
+ `AgentExecutor` interface.
218
+
219
+ ## MCP connectors
220
+
221
+ Expose every tool published by an MCP server to the agent loop in one go,
222
+ without listing them individually.
223
+
224
+ ```ts
225
+ import { MantyxClient, mantyxMcp, defineLocalMcp } from "@mantyx/sdk";
226
+
227
+ const client = new MantyxClient({ apiKey: "...", workspaceSlug: "acme" });
228
+
229
+ await client.runAgent({
230
+ systemPrompt: "You are a developer assistant with GitHub + filesystem access.",
231
+ prompt: "Summarize the latest 5 issues on octocat/hello-world.",
232
+ tools: [
233
+ // Remote MCP server (Streamable HTTP) — MANTYX lists the catalog at run
234
+ // start and proxies every call. Tools surface as `github_<tool>`.
235
+ mantyxMcp({
236
+ name: "github",
237
+ url: "https://mcp.github.com/v1",
238
+ headers: { Authorization: `Bearer ${process.env.GH_PAT}` },
239
+ toolFilter: ["search_issues", "get_repo"],
240
+ }),
241
+ // Local MCP server — fully managed by the SDK. Pass either a
242
+ // Streamable HTTP `url` *or* an stdio `command`; the SDK opens the
243
+ // transport, runs `Initialize` + `tools/list`, ships the resolved
244
+ // catalog inline, and forwards every invocation to `tools/call`. The
245
+ // model sees `<server>_<tool>` (`fs_read_file`, `fs_list_dir`, …) —
246
+ // same shape as `mantyxMcp` above.
247
+
248
+ // (a) Streamable HTTP MCP server.
249
+ defineLocalMcp({
250
+ name: "fs",
251
+ url: "http://localhost:8080/mcp",
252
+ headers: { Authorization: `Bearer ${process.env.FS_TOKEN}` },
253
+ }),
254
+
255
+ // (b) stdio MCP server — the SDK spawns the process for you.
256
+ // defineLocalMcp({
257
+ // name: "fs",
258
+ // command: "mcp-server-filesystem",
259
+ // args: ["/workspace"],
260
+ // env: { LOG_LEVEL: "info" },
261
+ // }),
262
+ ],
263
+ });
264
+ ```
265
+
266
+ The MCP transport is opened lazily on the first `runAgent` / first
267
+ `session.send`, kept warm for subsequent calls within the same run /
268
+ session, and closed when the run completes or `session.end()` is called.
269
+ If the MCP server can't be reached, the SDK throws before submitting the
270
+ spec — you get the failure synchronously rather than mid-conversation.
271
+
272
+ If a remote (`kind: "mcp"`) MCP server is unreachable when the run starts,
273
+ MANTYX still exposes a single `<server>_unavailable` stub so the model can
274
+ tell the user why the connector is missing.
275
+
276
+ ## Reasoning effort (`reasoningLevel`)
277
+
278
+ Crank up provider thinking on reasoning models without writing
279
+ provider-specific code:
280
+
281
+ ```ts
282
+ await client.runAgent({
283
+ systemPrompt: "...",
284
+ prompt: "Plan a multi-week migration.",
285
+ reasoningLevel: "high", // or 80, etc.
286
+ });
287
+ ```
288
+
289
+ | Form | Values | Notes |
290
+ | ------------ | -------------------------------------------- | ----- |
291
+ | String | `"off"`, `"low"`, `"medium"`, `"high"` | Snaps to the same anchors the web composer uses (Fast=30, Moderate=50, Smart=80; off=0). |
292
+ | Number | integer `0`–`100` | `0` explicitly disables provider thinking on reasoning models. |
293
+
294
+ The server maps this onto each LLM's native dial — `reasoning.effort` for
295
+ OpenAI, `thinkingConfig` for Gemini, extended-thinking budget for Anthropic.
296
+ Non-reasoning models silently ignore it. On sessions, `reasoningLevel`
297
+ inherits from the session and can be overridden per `session.send`.
298
+
106
299
  ## Picking a model
107
300
 
108
301
  ```ts
@@ -248,11 +441,15 @@ interface MantyxClientOptions {
248
441
 
249
442
  ### Tools
250
443
 
251
- | Helper | Use case |
252
- | -------------------------- | ------------------------------------------------------------ |
253
- | `defineLocalTool(opts)` | Define a local tool with a Zod parameter schema and handler. |
254
- | `mantyxTool(id)` | Reference an existing MANTYX tool by id. |
255
- | `mantyxPluginTool(name)` | Reference an installed platform plugin tool by name. |
444
+ | Helper | Use case |
445
+ | -------------------------- | ----------------------------------------------------------------------- |
446
+ | `defineLocalTool(opts)` | Define a local tool with a Zod parameter schema and handler. |
447
+ | `defineLocalA2A(opts)` | Local Agent2Agent peer — pass an `agentCardUrl`; the SDK fetches the card and speaks `message/send` for you. |
448
+ | `defineLocalMcp(opts)` | Local MCP server — pass either a Streamable HTTP `url` or an stdio `command`; the SDK runs `Initialize` + `tools/list` + `tools/call` for you. |
449
+ | `mantyxTool(id)` | Reference an existing MANTYX tool by id. |
450
+ | `mantyxPluginTool(name)` | Reference an installed platform plugin tool by name. |
451
+ | `mantyxA2A(opts)` | Remote Agent2Agent peer reachable from MANTYX (server-resolved). |
452
+ | `mantyxMcp(opts)` | Remote MCP server (Streamable HTTP) MANTYX dials and proxies for you. |
256
453
 
257
454
  ### Errors
258
455
 
@@ -272,6 +469,8 @@ Self-contained example projects live under [`examples/`](./examples/):
272
469
  - `examples/mixed-tools` — combines local, MANTYX, and plugin tools.
273
470
  - `examples/streaming` — token streaming to stdout.
274
471
  - `examples/list-models` — model catalog + pick-and-run.
472
+ - `examples/a2a-tools` — remote (`mantyxA2A`) + local (`defineLocalA2A`) Agent2Agent peers.
473
+ - `examples/mcp-tools` — remote (`mantyxMcp`) + local (`defineLocalMcp`) MCP servers.
275
474
 
276
475
  Each example is its own project (`package.json`, `tsconfig.json`, `README.md`)
277
476
  so you can copy any one of them out of the repo and run it standalone.
@@ -0,0 +1,404 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/a2a-server.ts
31
+ var a2a_server_exports = {};
32
+ __export(a2a_server_exports, {
33
+ MantyxAgentExecutor: () => MantyxAgentExecutor,
34
+ serveAgentOverA2A: () => serveAgentOverA2A
35
+ });
36
+ module.exports = __toCommonJS(a2a_server_exports);
37
+
38
+ // src/errors.ts
39
+ var MantyxError = class extends Error {
40
+ code;
41
+ status;
42
+ hint;
43
+ constructor(message, opts = {}) {
44
+ super(message);
45
+ this.name = "MantyxError";
46
+ this.code = opts.code ?? "mantyx_error";
47
+ this.status = opts.status;
48
+ this.hint = opts.hint;
49
+ }
50
+ };
51
+ var MantyxRunError = class extends MantyxError {
52
+ runId;
53
+ subtype;
54
+ constructor(runId, subtype, message) {
55
+ super(message, { code: subtype });
56
+ this.name = "MantyxRunError";
57
+ this.runId = runId;
58
+ this.subtype = subtype;
59
+ }
60
+ };
61
+
62
+ // src/zod-to-json-schema.ts
63
+ var import_zod = require("zod");
64
+
65
+ // src/a2a-server.ts
66
+ var MantyxAgentExecutor = class {
67
+ client;
68
+ agent;
69
+ conversation;
70
+ maxSessions;
71
+ onAssistantDelta;
72
+ /** contextId -> live MANTYX session. Maintained as an LRU map. */
73
+ sessions = /* @__PURE__ */ new Map();
74
+ /** taskIds we've been asked to cancel; checked between turns. */
75
+ cancelled = /* @__PURE__ */ new Set();
76
+ /** Pending AbortControllers per task, used for cooperative cancel. */
77
+ inFlight = /* @__PURE__ */ new Map();
78
+ constructor(options) {
79
+ if (!options.client) {
80
+ throw new MantyxError("MantyxAgentExecutor: `client` is required");
81
+ }
82
+ validateAgentSpec(options.agent);
83
+ this.client = options.client;
84
+ this.agent = options.agent;
85
+ this.conversation = options.conversation ?? "auto";
86
+ this.maxSessions = options.maxSessions ?? 1024;
87
+ if (options.onAssistantDelta) this.onAssistantDelta = options.onAssistantDelta;
88
+ }
89
+ async execute(requestContext, eventBus) {
90
+ const { userMessage, taskId, contextId, task } = requestContext;
91
+ const userText = extractText(userMessage);
92
+ const abort = new AbortController();
93
+ this.inFlight.set(taskId, abort);
94
+ try {
95
+ if (!task) {
96
+ eventBus.publish({
97
+ kind: "task",
98
+ id: taskId,
99
+ contextId,
100
+ status: { state: "submitted", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
101
+ history: [userMessage]
102
+ });
103
+ }
104
+ eventBus.publish(statusUpdate(taskId, contextId, "working", false));
105
+ if (this.cancelled.has(taskId)) {
106
+ eventBus.publish(statusUpdate(taskId, contextId, "canceled", true));
107
+ eventBus.finished();
108
+ return;
109
+ }
110
+ const onDelta = (delta) => {
111
+ if (this.onAssistantDelta) {
112
+ this.onAssistantDelta(delta, requestContext, eventBus);
113
+ return;
114
+ }
115
+ eventBus.publish(deltaStatusUpdate(taskId, contextId, delta));
116
+ };
117
+ let result;
118
+ try {
119
+ result = await this.runOnce(contextId, userText, onDelta, abort.signal);
120
+ } catch (err) {
121
+ eventBus.publish(
122
+ completedStatusUpdate(
123
+ taskId,
124
+ contextId,
125
+ this.cancelled.has(taskId) ? "canceled" : "failed",
126
+ errorText(err)
127
+ )
128
+ );
129
+ eventBus.finished();
130
+ return;
131
+ }
132
+ eventBus.publish(completedStatusUpdate(taskId, contextId, "completed", result.text ?? ""));
133
+ eventBus.finished();
134
+ } finally {
135
+ this.inFlight.delete(taskId);
136
+ this.cancelled.delete(taskId);
137
+ }
138
+ }
139
+ async cancelTask(taskId, eventBus) {
140
+ this.cancelled.add(taskId);
141
+ const ctrl = this.inFlight.get(taskId);
142
+ if (ctrl) ctrl.abort();
143
+ void eventBus;
144
+ }
145
+ /**
146
+ * Close every cached session. Idempotent. Safe to call from server shutdown
147
+ * paths.
148
+ */
149
+ async close() {
150
+ const sessions = Array.from(this.sessions.values());
151
+ this.sessions.clear();
152
+ await Promise.allSettled(sessions.map((s) => s.end()));
153
+ }
154
+ // -------------------------------------------------- private session helpers
155
+ async runOnce(contextId, prompt, onAssistantDelta, signal) {
156
+ if (this.conversation === "stateless") {
157
+ const runSpec = {
158
+ ...specForRun(this.agent),
159
+ prompt,
160
+ onAssistantDelta,
161
+ signal
162
+ };
163
+ return this.client.runAgent(runSpec);
164
+ }
165
+ const session = await this.getOrCreateSession(contextId);
166
+ return session.send(prompt, { onAssistantDelta, signal });
167
+ }
168
+ async getOrCreateSession(contextId) {
169
+ const existing = this.sessions.get(contextId);
170
+ if (existing) {
171
+ this.sessions.delete(contextId);
172
+ this.sessions.set(contextId, existing);
173
+ return existing;
174
+ }
175
+ const sessionSpec = specForSession(this.agent, contextId);
176
+ const session = await this.client.createSession(sessionSpec);
177
+ this.sessions.set(contextId, session);
178
+ await this.evictIfNeeded();
179
+ return session;
180
+ }
181
+ async evictIfNeeded() {
182
+ while (this.sessions.size > this.maxSessions) {
183
+ const oldestKey = this.sessions.keys().next().value;
184
+ if (!oldestKey) break;
185
+ const oldest = this.sessions.get(oldestKey);
186
+ this.sessions.delete(oldestKey);
187
+ try {
188
+ await oldest.end();
189
+ } catch {
190
+ }
191
+ }
192
+ }
193
+ };
194
+ async function serveAgentOverA2A(options) {
195
+ const a2a = await loadServerSdk();
196
+ const expressMod = await loadExpress();
197
+ const executor = new MantyxAgentExecutor(options);
198
+ const requestHandler = new a2a.DefaultRequestHandler(
199
+ options.agentCard,
200
+ new a2a.InMemoryTaskStore(),
201
+ executor
202
+ );
203
+ const app = expressMod();
204
+ app.use(expressMod.json());
205
+ const cardPath = options.agentCardPath ?? "/.well-known/agent-card.json";
206
+ const jsonRpcPath = options.jsonRpcPath ?? "/";
207
+ const restPath = options.restPath === void 0 ? "/v1" : options.restPath;
208
+ app.use(
209
+ cardPath,
210
+ a2a.expressApp.agentCardHandler({ agentCardProvider: requestHandler })
211
+ );
212
+ if (restPath !== false) {
213
+ app.use(
214
+ restPath,
215
+ a2a.expressApp.restHandler({
216
+ requestHandler,
217
+ userBuilder: a2a.expressApp.UserBuilder.noAuthentication
218
+ })
219
+ );
220
+ }
221
+ app.use(
222
+ jsonRpcPath,
223
+ a2a.expressApp.jsonRpcHandler({
224
+ requestHandler,
225
+ userBuilder: a2a.expressApp.UserBuilder.noAuthentication
226
+ })
227
+ );
228
+ const port = options.port ?? 0;
229
+ const host = options.host ?? "0.0.0.0";
230
+ const server = app.listen(port, host);
231
+ await new Promise((resolve, reject) => {
232
+ server.once("listening", resolve);
233
+ server.once("error", reject);
234
+ });
235
+ const address = server.address();
236
+ if (!address || typeof address === "string") {
237
+ server.close();
238
+ throw new MantyxError("serveAgentOverA2A: failed to bind HTTP listener");
239
+ }
240
+ return {
241
+ port: address.port,
242
+ url: `http://${displayHost(host)}:${address.port}`,
243
+ close: async () => {
244
+ await new Promise(
245
+ (resolve, reject) => server.close((err) => err ? reject(err) : resolve())
246
+ );
247
+ await executor.close();
248
+ }
249
+ };
250
+ }
251
+ function statusUpdate(taskId, contextId, state, final) {
252
+ return {
253
+ kind: "status-update",
254
+ taskId,
255
+ contextId,
256
+ status: { state, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
257
+ final
258
+ };
259
+ }
260
+ function deltaStatusUpdate(taskId, contextId, delta) {
261
+ return {
262
+ kind: "status-update",
263
+ taskId,
264
+ contextId,
265
+ status: {
266
+ state: "working",
267
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
268
+ message: {
269
+ kind: "message",
270
+ messageId: randomMessageId(),
271
+ role: "agent",
272
+ parts: [{ kind: "text", text: delta }],
273
+ contextId,
274
+ taskId
275
+ }
276
+ },
277
+ final: false
278
+ };
279
+ }
280
+ function completedStatusUpdate(taskId, contextId, state, text) {
281
+ return {
282
+ kind: "status-update",
283
+ taskId,
284
+ contextId,
285
+ status: {
286
+ state,
287
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
288
+ message: {
289
+ kind: "message",
290
+ messageId: randomMessageId(),
291
+ role: "agent",
292
+ parts: [{ kind: "text", text }],
293
+ contextId,
294
+ taskId
295
+ }
296
+ },
297
+ final: true
298
+ };
299
+ }
300
+ function extractText(message) {
301
+ if (!message) return "";
302
+ const parts = message.parts ?? [];
303
+ const out = [];
304
+ for (const p of parts) {
305
+ if (p.kind === "text") {
306
+ const t = p.text;
307
+ if (typeof t === "string") out.push(t);
308
+ }
309
+ }
310
+ return out.join("\n");
311
+ }
312
+ function specForRun(spec) {
313
+ const out = {};
314
+ if (spec.agentId) out.agentId = spec.agentId;
315
+ if (spec.systemPrompt) out.systemPrompt = spec.systemPrompt;
316
+ if (spec.modelId) out.modelId = spec.modelId;
317
+ if (spec.tools) out.tools = spec.tools;
318
+ if (spec.reasoningLevel !== void 0) out.reasoningLevel = spec.reasoningLevel;
319
+ if (spec.metadata) out.metadata = spec.metadata;
320
+ if (spec.budgets) out.budgets = spec.budgets;
321
+ if (spec.name) out.name = spec.name;
322
+ return out;
323
+ }
324
+ function specForSession(spec, contextId) {
325
+ const out = {};
326
+ if (spec.agentId) out.agentId = spec.agentId;
327
+ if (spec.systemPrompt) out.systemPrompt = spec.systemPrompt;
328
+ if (spec.modelId) out.modelId = spec.modelId;
329
+ if (spec.tools) out.tools = spec.tools;
330
+ if (spec.reasoningLevel !== void 0) out.reasoningLevel = spec.reasoningLevel;
331
+ const meta = { ...spec.metadata ?? {} };
332
+ if (!meta.a2a_context_id) meta.a2a_context_id = contextId;
333
+ out.metadata = meta;
334
+ if (spec.budgets) out.budgets = spec.budgets;
335
+ if (spec.name) out.name = spec.name;
336
+ return out;
337
+ }
338
+ function validateAgentSpec(spec) {
339
+ if (!spec.agentId && (!spec.systemPrompt || spec.systemPrompt.length === 0)) {
340
+ throw new MantyxError(
341
+ "MantyxAgentExecutor: `agent.agentId` or `agent.systemPrompt` is required"
342
+ );
343
+ }
344
+ }
345
+ function errorText(err) {
346
+ if (err instanceof MantyxRunError) {
347
+ return `MANTYX run failed (${err.subtype ?? "unknown"}): ${err.message}`;
348
+ }
349
+ if (err instanceof Error) return err.message;
350
+ try {
351
+ return String(err);
352
+ } catch {
353
+ return "unknown error";
354
+ }
355
+ }
356
+ function randomMessageId() {
357
+ if (typeof globalThis.crypto?.randomUUID === "function") {
358
+ return globalThis.crypto.randomUUID();
359
+ }
360
+ return `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
361
+ }
362
+ function displayHost(host) {
363
+ if (host === "0.0.0.0" || host === "::") return "localhost";
364
+ return host;
365
+ }
366
+ async function loadExpress() {
367
+ try {
368
+ const mod = await import("express");
369
+ return "default" in mod ? mod.default : mod;
370
+ } catch (err) {
371
+ throw new MantyxError(
372
+ "serveAgentOverA2A: `express` is required but not installed. Run `npm install express @a2a-js/sdk` to enable the A2A server."
373
+ );
374
+ }
375
+ }
376
+ async function loadServerSdk() {
377
+ let server;
378
+ let express;
379
+ try {
380
+ server = await import("@a2a-js/sdk/server");
381
+ } catch (err) {
382
+ throw new MantyxError(
383
+ "serveAgentOverA2A: `@a2a-js/sdk` is required but not installed. Run `npm install @a2a-js/sdk express` to enable the A2A server."
384
+ );
385
+ }
386
+ try {
387
+ express = await import("@a2a-js/sdk/server/express");
388
+ } catch (err) {
389
+ throw new MantyxError(
390
+ "serveAgentOverA2A: `@a2a-js/sdk/server/express` could not be loaded; ensure the installed `@a2a-js/sdk` is at least v0.3."
391
+ );
392
+ }
393
+ return {
394
+ DefaultRequestHandler: server.DefaultRequestHandler,
395
+ InMemoryTaskStore: server.InMemoryTaskStore,
396
+ expressApp: express
397
+ };
398
+ }
399
+ // Annotate the CommonJS export names for ESM import in node:
400
+ 0 && (module.exports = {
401
+ MantyxAgentExecutor,
402
+ serveAgentOverA2A
403
+ });
404
+ //# sourceMappingURL=a2a-server.cjs.map