@mantyx/sdk 0.2.0 → 0.4.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/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
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
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -26,13 +36,21 @@ __export(index_exports, {
26
36
  MantyxClient: () => MantyxClient,
27
37
  MantyxError: () => MantyxError,
28
38
  MantyxNetworkError: () => MantyxNetworkError,
39
+ MantyxParseError: () => MantyxParseError,
29
40
  MantyxRunError: () => MantyxRunError,
30
41
  MantyxToolError: () => MantyxToolError,
31
42
  SDK_VERSION: () => SDK_VERSION,
43
+ defineLocalA2A: () => defineLocalA2A,
44
+ defineLocalMcp: () => defineLocalMcp,
32
45
  defineLocalTool: () => defineLocalTool,
46
+ isLocalA2ATool: () => isLocalA2ATool,
47
+ isLocalMcpServer: () => isLocalMcpServer,
33
48
  isLocalTool: () => isLocalTool,
49
+ mantyxA2A: () => mantyxA2A,
50
+ mantyxMcp: () => mantyxMcp,
34
51
  mantyxPluginTool: () => mantyxPluginTool,
35
52
  mantyxTool: () => mantyxTool,
53
+ parseRunOutput: () => parseRunOutput,
36
54
  readSseStream: () => readSseStream,
37
55
  toToolParametersWire: () => toToolParametersWire,
38
56
  zodToJsonSchema: () => zodToJsonSchema
@@ -87,6 +105,361 @@ var MantyxRunError = class extends MantyxError {
87
105
  this.subtype = subtype;
88
106
  }
89
107
  };
108
+ var MantyxParseError = class extends MantyxError {
109
+ text;
110
+ constructor(message, text, opts = {}) {
111
+ super(message, { code: "output_parse_failed" });
112
+ this.name = "MantyxParseError";
113
+ this.text = text;
114
+ if (opts.cause !== void 0) {
115
+ this.cause = opts.cause;
116
+ }
117
+ }
118
+ };
119
+
120
+ // src/tools.ts
121
+ function defineLocalTool(opts) {
122
+ assertToolName(opts.name);
123
+ return {
124
+ kind: "local",
125
+ name: opts.name,
126
+ description: opts.description ?? "",
127
+ parameters: opts.parameters,
128
+ execute: opts.execute
129
+ };
130
+ }
131
+ function mantyxTool(id) {
132
+ if (typeof id !== "string" || id.length === 0) {
133
+ throw new Error("mantyxTool(id): id must be a non-empty string");
134
+ }
135
+ return { kind: "mantyx", id };
136
+ }
137
+ function mantyxPluginTool(name) {
138
+ if (typeof name !== "string" || !name.startsWith("@") || !name.includes("/")) {
139
+ throw new Error(
140
+ `mantyxPluginTool(name): expected "@plugin-slug/tool-name", got ${JSON.stringify(name)}`
141
+ );
142
+ }
143
+ return { kind: "mantyx_plugin", name };
144
+ }
145
+ function mantyxA2A(opts) {
146
+ assertToolName(opts.name);
147
+ if (typeof opts.agentCardUrl !== "string" || opts.agentCardUrl.length === 0) {
148
+ throw new Error("mantyxA2A: agentCardUrl is required");
149
+ }
150
+ return {
151
+ kind: "a2a",
152
+ name: opts.name,
153
+ ...opts.description !== void 0 ? { description: opts.description } : {},
154
+ agentCardUrl: opts.agentCardUrl,
155
+ ...opts.headers ? { headers: { ...opts.headers } } : {},
156
+ ...opts.contextId ? { contextId: opts.contextId } : {}
157
+ };
158
+ }
159
+ function defineLocalA2A(opts) {
160
+ assertToolName(opts.name);
161
+ if (typeof opts.agentCardUrl !== "string" || opts.agentCardUrl.length === 0) {
162
+ throw new Error("defineLocalA2A: `agentCardUrl` is required");
163
+ }
164
+ return {
165
+ kind: "a2a_local",
166
+ name: opts.name,
167
+ agentCardUrl: opts.agentCardUrl,
168
+ headers: opts.headers ? { ...opts.headers } : void 0
169
+ };
170
+ }
171
+ function mantyxMcp(opts) {
172
+ assertToolName(opts.name);
173
+ if (typeof opts.url !== "string" || opts.url.length === 0) {
174
+ throw new Error("mantyxMcp: url is required");
175
+ }
176
+ return {
177
+ kind: "mcp",
178
+ name: opts.name,
179
+ url: opts.url,
180
+ ...opts.headers ? { headers: { ...opts.headers } } : {},
181
+ ...opts.toolFilter ? { toolFilter: [...opts.toolFilter] } : {}
182
+ };
183
+ }
184
+ function defineLocalMcp(opts) {
185
+ assertToolName(opts.name);
186
+ const hasHttp = typeof opts.url === "string" && opts.url.length > 0;
187
+ const hasStdio = typeof opts.command === "string" && opts.command.length > 0;
188
+ if (hasHttp && hasStdio) {
189
+ throw new Error(
190
+ "defineLocalMcp: pass either `url` (Streamable HTTP) or `command` (stdio), not both"
191
+ );
192
+ }
193
+ if (!hasHttp && !hasStdio) {
194
+ throw new Error(
195
+ "defineLocalMcp: one of `url` (Streamable HTTP) or `command` (stdio) is required"
196
+ );
197
+ }
198
+ if (hasHttp) {
199
+ const url = opts.url;
200
+ return {
201
+ kind: "mcp_local",
202
+ name: opts.name,
203
+ http: {
204
+ url,
205
+ ...opts.headers ? { headers: { ...opts.headers } } : {}
206
+ },
207
+ stdio: void 0
208
+ };
209
+ }
210
+ const command = opts.command;
211
+ return {
212
+ kind: "mcp_local",
213
+ name: opts.name,
214
+ http: void 0,
215
+ stdio: {
216
+ command,
217
+ ...opts.args ? { args: [...opts.args] } : {},
218
+ ...opts.env ? { env: { ...opts.env } } : {},
219
+ ...opts.cwd ? { cwd: opts.cwd } : {}
220
+ }
221
+ };
222
+ }
223
+ function isLocalTool(t) {
224
+ return t.kind === "local";
225
+ }
226
+ function isLocalA2ATool(t) {
227
+ return t.kind === "a2a_local";
228
+ }
229
+ function isLocalMcpServer(t) {
230
+ return t.kind === "mcp_local";
231
+ }
232
+ var TOOL_NAME_RE = /^[a-zA-Z0-9_]{1,64}$/;
233
+ function assertToolName(name) {
234
+ if (!TOOL_NAME_RE.test(name)) {
235
+ throw new Error(
236
+ `Invalid tool name ${JSON.stringify(name)}: must match /^[a-zA-Z0-9_]{1,64}$/`
237
+ );
238
+ }
239
+ }
240
+ function prefixedMcpToolName(serverName, toolName) {
241
+ const prefix = `${serverName}_`;
242
+ return toolName.startsWith(prefix) ? toolName : `${prefix}${toolName}`;
243
+ }
244
+
245
+ // src/local-resolver.ts
246
+ async function resolveLocalRefs(tools, opts = { fetch: globalThis.fetch }) {
247
+ if (!tools || tools.length === 0) return { newlyOpenedMcp: [] };
248
+ const newlyOpenedMcp = [];
249
+ const work = [];
250
+ for (const t of tools) {
251
+ if (isLocalA2ATool(t)) {
252
+ if (t._resolvedCard) continue;
253
+ work.push(resolveA2A(t, opts.fetch));
254
+ } else if (isLocalMcpServer(t)) {
255
+ if (t._resolved) continue;
256
+ work.push(
257
+ resolveMcp(t).then((resolved) => {
258
+ if (resolved) newlyOpenedMcp.push(t);
259
+ })
260
+ );
261
+ }
262
+ }
263
+ await Promise.all(work);
264
+ return { newlyOpenedMcp };
265
+ }
266
+ async function resolveA2A(t, fetchImpl) {
267
+ const headers = { Accept: "application/json", ...t.headers ?? {} };
268
+ const res = await fetchImpl(t.agentCardUrl, { method: "GET", headers });
269
+ if (!res.ok) {
270
+ throw new Error(
271
+ `defineLocalA2A(${JSON.stringify(t.name)}): GET ${t.agentCardUrl} returned ${res.status} ${res.statusText}`
272
+ );
273
+ }
274
+ const card = await res.json();
275
+ if (!card || typeof card !== "object" || typeof card.name !== "string" || !card.name) {
276
+ throw new Error(
277
+ `defineLocalA2A(${JSON.stringify(t.name)}): ${t.agentCardUrl} did not return a valid Agent Card (missing required \`name\` field)`
278
+ );
279
+ }
280
+ t._resolvedCard = card;
281
+ }
282
+ async function resolveMcp(t) {
283
+ const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
284
+ let transport;
285
+ let connect;
286
+ if (t.http) {
287
+ const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
288
+ const httpTransport = new StreamableHTTPClientTransport(new URL(t.http.url), {
289
+ requestInit: t.http.headers ? { headers: t.http.headers } : {}
290
+ });
291
+ transport = httpTransport;
292
+ connect = (c) => c.connect(httpTransport);
293
+ } else if (t.stdio) {
294
+ const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
295
+ const stdioTransport = new StdioClientTransport({
296
+ command: t.stdio.command,
297
+ ...t.stdio.args ? { args: t.stdio.args } : {},
298
+ ...t.stdio.env ? { env: t.stdio.env } : {},
299
+ ...t.stdio.cwd ? { cwd: t.stdio.cwd } : {}
300
+ });
301
+ transport = stdioTransport;
302
+ connect = (c) => c.connect(stdioTransport);
303
+ } else {
304
+ throw new Error(
305
+ `defineLocalMcp(${JSON.stringify(t.name)}): missing transport (no \`url\` or \`command\` was provided)`
306
+ );
307
+ }
308
+ const client = new Client({ name: "@mantyx/sdk", version: "0.3.0" }, { capabilities: {} });
309
+ try {
310
+ await connect(client);
311
+ } catch (err) {
312
+ throw new Error(
313
+ `defineLocalMcp(${JSON.stringify(t.name)}): failed to connect \u2014 ${err.message}`,
314
+ { cause: err }
315
+ );
316
+ }
317
+ const serverInfo = client.getServerVersion() ?? { name: t.name };
318
+ const listed = await client.listTools();
319
+ const tools = listed.tools.map((tool) => {
320
+ const out = {
321
+ name: tool.name,
322
+ inputSchema: tool.inputSchema
323
+ };
324
+ if (typeof tool.description === "string") out.description = tool.description;
325
+ if (tool.annotations) out.annotations = tool.annotations;
326
+ return out;
327
+ });
328
+ const close = async () => {
329
+ try {
330
+ await client.close();
331
+ } catch {
332
+ }
333
+ try {
334
+ const t2 = transport;
335
+ if (t2.close) await t2.close();
336
+ } catch {
337
+ }
338
+ };
339
+ t._resolved = {
340
+ serverInfo,
341
+ tools,
342
+ client,
343
+ close
344
+ };
345
+ return true;
346
+ }
347
+ async function closeMcpRefs(tools) {
348
+ if (!tools || tools.length === 0) return;
349
+ const closes = [];
350
+ for (const t of tools) {
351
+ if (!isLocalMcpServer(t)) continue;
352
+ const resolved = t._resolved;
353
+ if (!resolved) continue;
354
+ t._resolved = void 0;
355
+ closes.push(resolved.close());
356
+ }
357
+ await Promise.all(closes);
358
+ }
359
+ async function callA2A(t, args, opts = { fetch: globalThis.fetch }) {
360
+ const card = t._resolvedCard;
361
+ if (!card) {
362
+ throw new Error(
363
+ `defineLocalA2A(${JSON.stringify(t.name)}): agent card has not been resolved yet`
364
+ );
365
+ }
366
+ const url = typeof card.url === "string" && card.url.length > 0 ? card.url : t.agentCardUrl;
367
+ const body = {
368
+ jsonrpc: "2.0",
369
+ id: cryptoRandomId(),
370
+ method: "message/send",
371
+ params: {
372
+ message: {
373
+ kind: "message",
374
+ role: "user",
375
+ messageId: cryptoRandomId(),
376
+ parts: [{ kind: "text", text: args.message }]
377
+ }
378
+ }
379
+ };
380
+ const res = await opts.fetch(url, {
381
+ method: "POST",
382
+ headers: {
383
+ "Content-Type": "application/json",
384
+ Accept: "application/json",
385
+ ...t.headers ?? {}
386
+ },
387
+ body: JSON.stringify(body)
388
+ });
389
+ if (!res.ok) {
390
+ throw new Error(
391
+ `A2A message/send to ${url} returned ${res.status} ${res.statusText}`
392
+ );
393
+ }
394
+ const json = await res.json();
395
+ if (json.error) {
396
+ throw new Error(`A2A peer reported error ${json.error.code}: ${json.error.message}`);
397
+ }
398
+ return extractA2AReplyText(json.result);
399
+ }
400
+ function extractA2AReplyText(result) {
401
+ if (result == null) return "";
402
+ if (typeof result === "string") return result;
403
+ if (typeof result !== "object") return JSON.stringify(result);
404
+ const obj = result;
405
+ if (Array.isArray(obj.parts)) {
406
+ const text = textFromParts(obj.parts);
407
+ if (text) return text;
408
+ }
409
+ const status = obj.status;
410
+ const statusMessage = status?.message;
411
+ if (Array.isArray(statusMessage?.parts)) {
412
+ const text = textFromParts(statusMessage.parts);
413
+ if (text) return text;
414
+ }
415
+ const artifacts = obj.artifacts;
416
+ if (Array.isArray(artifacts) && artifacts.length > 0) {
417
+ const last = artifacts[artifacts.length - 1];
418
+ if (Array.isArray(last.parts)) {
419
+ const text = textFromParts(last.parts);
420
+ if (text) return text;
421
+ }
422
+ }
423
+ return JSON.stringify(result);
424
+ }
425
+ function textFromParts(parts) {
426
+ const out = [];
427
+ for (const part of parts) {
428
+ if (!part || typeof part !== "object") continue;
429
+ const p = part;
430
+ if ((p.kind === "text" || p.type === "text") && typeof p.text === "string") {
431
+ out.push(p.text);
432
+ }
433
+ }
434
+ return out.join("\n");
435
+ }
436
+ async function callMcpTool(server, toolName, args) {
437
+ const resolved = server._resolved;
438
+ if (!resolved) {
439
+ throw new Error(
440
+ `defineLocalMcp(${JSON.stringify(server.name)}): MCP server has not been initialised`
441
+ );
442
+ }
443
+ const result = await resolved.client.callTool({ name: toolName, arguments: args });
444
+ if (result.isError) {
445
+ const text = textFromMcpContent(result.content) || "MCP tool reported an error";
446
+ throw new Error(text);
447
+ }
448
+ return textFromMcpContent(result.content);
449
+ }
450
+ function textFromMcpContent(content) {
451
+ if (!content || content.length === 0) return "";
452
+ const out = [];
453
+ for (const block of content) {
454
+ if (block.type === "text" && typeof block.text === "string") out.push(block.text);
455
+ }
456
+ return out.join("\n");
457
+ }
458
+ function cryptoRandomId() {
459
+ const c = globalThis.crypto;
460
+ if (c?.randomUUID) return c.randomUUID();
461
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
462
+ }
90
463
 
91
464
  // src/sse.ts
92
465
  async function* readSseStream(body, opts = {}) {
@@ -163,39 +536,6 @@ function parseEventBlock(block) {
163
536
  };
164
537
  }
165
538
 
166
- // src/tools.ts
167
- function defineLocalTool(opts) {
168
- if (!/^[a-zA-Z0-9_]{1,64}$/.test(opts.name)) {
169
- throw new Error(
170
- `Invalid local tool name ${JSON.stringify(opts.name)}: must match /^[a-zA-Z0-9_]{1,64}$/`
171
- );
172
- }
173
- return {
174
- kind: "local",
175
- name: opts.name,
176
- description: opts.description ?? "",
177
- parameters: opts.parameters,
178
- execute: opts.execute
179
- };
180
- }
181
- function mantyxTool(id) {
182
- if (typeof id !== "string" || id.length === 0) {
183
- throw new Error("mantyxTool(id): id must be a non-empty string");
184
- }
185
- return { kind: "mantyx", id };
186
- }
187
- function mantyxPluginTool(name) {
188
- if (typeof name !== "string" || !name.startsWith("@") || !name.includes("/")) {
189
- throw new Error(
190
- `mantyxPluginTool(name): expected "@plugin-slug/tool-name", got ${JSON.stringify(name)}`
191
- );
192
- }
193
- return { kind: "mantyx_plugin", name };
194
- }
195
- function isLocalTool(t) {
196
- return t.kind === "local";
197
- }
198
-
199
539
  // src/zod-to-json-schema.ts
200
540
  var import_zod = require("zod");
201
541
  function zodToJsonSchema(schema) {
@@ -310,47 +650,79 @@ var MantyxClient = class {
310
650
  }
311
651
  // ------------------------------------------------------------- One-shot
312
652
  async runAgent(spec) {
313
- const handlers = collectLocalHandlers(spec.tools ?? []);
314
- const created = await this.request({
315
- method: "POST",
316
- path: "/agent-runs",
317
- body: serializeAgentSpec(spec, {
318
- prompt: spec.prompt,
319
- messages: spec.messages
320
- })
321
- });
322
- return this.driveRun(created.runId, handlers, {
323
- ...spec.onAssistantDelta ? { onAssistantDelta: spec.onAssistantDelta } : {},
324
- ...spec.onEvent ? { onEvent: spec.onEvent } : {},
325
- ...spec.signal ? { signal: spec.signal } : {}
326
- });
653
+ const tools = spec.tools ?? [];
654
+ await resolveLocalRefs(tools, { fetch: this.options.fetch });
655
+ const handlers = collectLocalHandlers(tools);
656
+ try {
657
+ const created = await this.request({
658
+ method: "POST",
659
+ path: "/agent-runs",
660
+ body: serializeAgentSpec(spec, {
661
+ prompt: spec.prompt,
662
+ messages: spec.messages
663
+ })
664
+ });
665
+ return await this.driveRun(created.runId, handlers, {
666
+ ...spec.onAssistantDelta ? { onAssistantDelta: spec.onAssistantDelta } : {},
667
+ ...spec.onEvent ? { onEvent: spec.onEvent } : {},
668
+ ...spec.signal ? { signal: spec.signal } : {}
669
+ });
670
+ } finally {
671
+ await closeMcpRefs(tools);
672
+ }
327
673
  }
328
674
  async *streamAgent(spec) {
329
- const handlers = collectLocalHandlers(spec.tools ?? []);
330
- const created = await this.request({
331
- method: "POST",
332
- path: "/agent-runs",
333
- body: serializeAgentSpec(spec, {
334
- prompt: spec.prompt,
335
- messages: spec.messages
336
- })
337
- });
338
- yield* this.streamRunEvents(created.runId, handlers, spec.signal);
675
+ const tools = spec.tools ?? [];
676
+ await resolveLocalRefs(tools, { fetch: this.options.fetch });
677
+ const handlers = collectLocalHandlers(tools);
678
+ try {
679
+ const created = await this.request({
680
+ method: "POST",
681
+ path: "/agent-runs",
682
+ body: serializeAgentSpec(spec, {
683
+ prompt: spec.prompt,
684
+ messages: spec.messages
685
+ })
686
+ });
687
+ yield* this.streamRunEvents(created.runId, handlers, spec.signal);
688
+ } finally {
689
+ await closeMcpRefs(tools);
690
+ }
691
+ }
692
+ /**
693
+ * Internal registry of client-resolved tool handlers. Exposed for callers
694
+ * who drive the run loop manually via `driveRun` / `streamRunEvents`.
695
+ */
696
+ collectHandlers(tools) {
697
+ return collectLocalHandlers(tools);
339
698
  }
340
699
  // ------------------------------------------------------------- Sessions
341
700
  async createSession(spec) {
342
- const handlers = collectLocalHandlers(spec.tools ?? []);
701
+ const tools = spec.tools ?? [];
702
+ await resolveLocalRefs(tools, { fetch: this.options.fetch });
703
+ const handlers = collectLocalHandlers(tools);
343
704
  const created = await this.request({
344
705
  method: "POST",
345
706
  path: "/agent-sessions",
346
707
  body: serializeAgentSpec(spec)
347
708
  });
348
- return new AgentSession(this, created.sessionId, handlers);
709
+ return new AgentSession(this, created.sessionId, handlers, tools);
710
+ }
711
+ /**
712
+ * Re-emit a `local_tool_call` event into the right local handler. Useful
713
+ * for tests and for users who consume events via `streamAgent` themselves.
714
+ */
715
+ async dispatchLocalToolFromEvent(runId, ev, handlers) {
716
+ return this.dispatchLocalTool(runId, ev, handlers);
349
717
  }
350
718
  async resumeSession(sessionId, opts = {}) {
351
719
  await this.getSessionInfo(sessionId);
352
- const handlers = collectLocalHandlers(opts.tools ?? []);
353
- return new AgentSession(this, sessionId, handlers, opts.tools);
720
+ const tools = opts.tools ?? [];
721
+ if (tools.length > 0) {
722
+ await resolveLocalRefs(tools, { fetch: this.options.fetch });
723
+ }
724
+ const handlers = collectLocalHandlers(tools);
725
+ return new AgentSession(this, sessionId, handlers, tools);
354
726
  }
355
727
  async endSession(sessionId) {
356
728
  await this.request({
@@ -448,22 +820,49 @@ var MantyxClient = class {
448
820
  }
449
821
  }
450
822
  async dispatchLocalTool(runId, ev, handlers) {
451
- const handler = handlers.get(ev.name);
452
- if (!handler) {
453
- await this.postToolResult(runId, ev.toolUseId, {
454
- error: `No local handler registered for tool ${JSON.stringify(ev.name)}`
455
- });
456
- return;
457
- }
823
+ const kind = ev.kind ?? "local";
458
824
  try {
459
- const args = handler.parameters ? handler.parameters.parse?.(ev.args) ?? ev.args : ev.args;
460
- const out = await handler.execute(args);
461
- const resultText = typeof out === "string" ? out : JSON.stringify(out);
462
- await this.postToolResult(runId, ev.toolUseId, { result: resultText });
825
+ let out;
826
+ if (kind === "a2a_local") {
827
+ const tool = handlers.a2aTools.get(ev.name);
828
+ if (!tool) {
829
+ await this.postToolResult(runId, ev.toolUseId, {
830
+ error: `No local A2A handler registered for tool ${JSON.stringify(ev.name)}`
831
+ });
832
+ return;
833
+ }
834
+ const message = typeof ev.args?.message === "string" ? ev.args.message : "";
835
+ out = await callA2A(tool, { message }, { fetch: this.options.fetch });
836
+ } else if (kind === "mcp_local") {
837
+ const serverName = ev.mcpServer ?? "";
838
+ const mcpToolName = ev.mcpToolName ?? "";
839
+ const server = handlers.mcpServers.get(serverName);
840
+ if (!server) {
841
+ await this.postToolResult(runId, ev.toolUseId, {
842
+ error: `No local MCP server registered as ${JSON.stringify(serverName)}`
843
+ });
844
+ return;
845
+ }
846
+ const upstreamName = mcpToolName.startsWith(`${serverName}_`) ? mcpToolName.slice(serverName.length + 1) : mcpToolName;
847
+ out = await callMcpTool(server, upstreamName, ev.args ?? {});
848
+ } else {
849
+ const handler = handlers.localTools.get(ev.name);
850
+ if (!handler) {
851
+ await this.postToolResult(runId, ev.toolUseId, {
852
+ error: `No local handler registered for tool ${JSON.stringify(ev.name)}`
853
+ });
854
+ return;
855
+ }
856
+ const args = handler.parameters ? handler.parameters.parse?.(ev.args) ?? ev.args : ev.args;
857
+ const result = await handler.execute(args);
858
+ out = typeof result === "string" ? result : JSON.stringify(result);
859
+ }
860
+ await this.postToolResult(runId, ev.toolUseId, { result: out });
463
861
  } catch (err) {
464
862
  const message = err instanceof Error ? err.message : String(err);
863
+ const handlerName = describeHandlerName(ev);
465
864
  await this.postToolResult(runId, ev.toolUseId, {
466
- error: new MantyxToolError(handler.name, message).message
865
+ error: new MantyxToolError(handlerName, message).message
467
866
  });
468
867
  }
469
868
  }
@@ -541,22 +940,18 @@ var AgentSession = class {
541
940
  id;
542
941
  client;
543
942
  handlers;
544
- toolsForResume;
545
- constructor(client, id, handlers, toolsForResume) {
943
+ tools;
944
+ constructor(client, id, handlers, tools) {
546
945
  this.client = client;
547
946
  this.id = id;
548
947
  this.handlers = handlers;
549
- this.toolsForResume = toolsForResume;
948
+ this.tools = tools ?? [];
550
949
  }
551
950
  async send(prompt, opts = {}) {
552
951
  const created = await this.client.request({
553
952
  method: "POST",
554
953
  path: `/agent-sessions/${encodeURIComponent(this.id)}/messages`,
555
- body: {
556
- prompt,
557
- ...this.toolsForResume ? { tools: serializeToolRefs(this.toolsForResume) } : {},
558
- ...opts.metadata && Object.keys(opts.metadata).length > 0 ? { metadata: opts.metadata } : {}
559
- }
954
+ body: this.buildSessionMessageBody(prompt, opts)
560
955
  });
561
956
  return this.client.driveRun(created.runId, this.handlers, {
562
957
  ...opts.onAssistantDelta ? { onAssistantDelta: opts.onAssistantDelta } : {},
@@ -567,14 +962,22 @@ var AgentSession = class {
567
962
  const created = await this.client.request({
568
963
  method: "POST",
569
964
  path: `/agent-sessions/${encodeURIComponent(this.id)}/messages`,
570
- body: {
571
- prompt,
572
- ...this.toolsForResume ? { tools: serializeToolRefs(this.toolsForResume) } : {},
573
- ...opts.metadata && Object.keys(opts.metadata).length > 0 ? { metadata: opts.metadata } : {}
574
- }
965
+ body: this.buildSessionMessageBody(prompt, opts)
575
966
  });
576
967
  yield* this.client.streamRunEvents(created.runId, this.handlers, opts.signal);
577
968
  }
969
+ buildSessionMessageBody(prompt, opts) {
970
+ const body = { prompt };
971
+ if (this.tools.length > 0) body.tools = serializeToolRefs(this.tools);
972
+ if (opts.metadata && Object.keys(opts.metadata).length > 0) body.metadata = opts.metadata;
973
+ if (opts.reasoningLevel !== void 0) {
974
+ body.reasoningLevel = normalizeReasoningLevel(opts.reasoningLevel);
975
+ }
976
+ if (opts.outputSchema !== void 0) {
977
+ body.outputSchema = normalizeOutputSchema(opts.outputSchema);
978
+ }
979
+ return body;
980
+ }
578
981
  async history() {
579
982
  const info = await this.client.getSessionInfo(this.id);
580
983
  return info.messages;
@@ -583,7 +986,11 @@ var AgentSession = class {
583
986
  return this.client.getSessionInfo(this.id);
584
987
  }
585
988
  async end() {
586
- await this.client.endSession(this.id);
989
+ try {
990
+ await this.client.endSession(this.id);
991
+ } finally {
992
+ await closeMcpRefs(this.tools);
993
+ }
587
994
  }
588
995
  };
589
996
  function serializeAgentSpec(spec, extra = {}) {
@@ -597,6 +1004,12 @@ function serializeAgentSpec(spec, extra = {}) {
597
1004
  if (spec.agentId) body.agentId = spec.agentId;
598
1005
  if (spec.name) body.name = spec.name;
599
1006
  if (spec.modelId) body.modelId = spec.modelId;
1007
+ if (spec.reasoningLevel !== void 0) {
1008
+ body.reasoningLevel = normalizeReasoningLevel(spec.reasoningLevel);
1009
+ }
1010
+ if (spec.outputSchema !== void 0) {
1011
+ body.outputSchema = normalizeOutputSchema(spec.outputSchema);
1012
+ }
600
1013
  if (spec.budgets) body.budgets = spec.budgets;
601
1014
  if (spec.metadata && Object.keys(spec.metadata).length > 0) body.metadata = spec.metadata;
602
1015
  if (extra.prompt !== void 0) body.prompt = extra.prompt;
@@ -605,29 +1018,183 @@ function serializeAgentSpec(spec, extra = {}) {
605
1018
  }
606
1019
  function serializeToolRefs(tools) {
607
1020
  return tools.map((t) => {
608
- if (t.kind === "mantyx") return { kind: "mantyx", id: t.id };
609
- if (t.kind === "mantyx_plugin") return { kind: "mantyx_plugin", name: t.name };
610
- return {
611
- kind: "local",
612
- name: t.name,
613
- description: t.description,
614
- parameters: toToolParametersWire(t.parameters)
615
- };
1021
+ switch (t.kind) {
1022
+ case "mantyx":
1023
+ return { kind: "mantyx", id: t.id };
1024
+ case "mantyx_plugin":
1025
+ return { kind: "mantyx_plugin", name: t.name };
1026
+ case "local":
1027
+ return {
1028
+ kind: "local",
1029
+ name: t.name,
1030
+ description: t.description,
1031
+ parameters: toToolParametersWire(t.parameters)
1032
+ };
1033
+ case "a2a":
1034
+ return {
1035
+ kind: "a2a",
1036
+ name: t.name,
1037
+ ...t.description !== void 0 ? { description: t.description } : {},
1038
+ agentCardUrl: t.agentCardUrl,
1039
+ ...t.headers ? { headers: { ...t.headers } } : {},
1040
+ ...t.contextId ? { contextId: t.contextId } : {}
1041
+ };
1042
+ case "a2a_local": {
1043
+ const card = t._resolvedCard;
1044
+ if (!card) {
1045
+ throw new MantyxError(
1046
+ `defineLocalA2A(${JSON.stringify(t.name)}): agent card has not been resolved yet (was \`runAgent\` / \`createSession\` skipped?)`
1047
+ );
1048
+ }
1049
+ return {
1050
+ kind: "a2a_local",
1051
+ name: t.name,
1052
+ // The wire ships the resolved A2A Agent Card. Shallow-clone so
1053
+ // consumers can mutate the input later without affecting the
1054
+ // wire payload.
1055
+ agentCard: { ...card }
1056
+ };
1057
+ }
1058
+ case "mcp":
1059
+ return {
1060
+ kind: "mcp",
1061
+ name: t.name,
1062
+ url: t.url,
1063
+ ...t.headers ? { headers: { ...t.headers } } : {},
1064
+ ...t.toolFilter ? { toolFilter: [...t.toolFilter] } : {}
1065
+ };
1066
+ case "mcp_local": {
1067
+ const resolved = t._resolved;
1068
+ if (!resolved) {
1069
+ throw new MantyxError(
1070
+ `defineLocalMcp(${JSON.stringify(t.name)}): MCP server has not been initialised yet`
1071
+ );
1072
+ }
1073
+ const tools2 = resolved.tools.map((tool) => {
1074
+ const wire = {
1075
+ name: prefixedMcpToolName(t.name, tool.name),
1076
+ inputSchema: tool.inputSchema
1077
+ };
1078
+ if (typeof tool.description === "string") wire.description = tool.description;
1079
+ if (tool.annotations) wire.annotations = tool.annotations;
1080
+ return wire;
1081
+ });
1082
+ return {
1083
+ kind: "mcp_local",
1084
+ name: t.name,
1085
+ serverInfo: { ...resolved.serverInfo },
1086
+ tools: tools2
1087
+ };
1088
+ }
1089
+ }
616
1090
  });
617
1091
  }
618
1092
  function collectLocalHandlers(tools) {
619
- const map = /* @__PURE__ */ new Map();
1093
+ const localTools = /* @__PURE__ */ new Map();
1094
+ const a2aTools = /* @__PURE__ */ new Map();
1095
+ const mcpServers = /* @__PURE__ */ new Map();
620
1096
  for (const t of tools) {
621
- if (isLocalTool(t)) map.set(t.name, t);
1097
+ if (isLocalTool(t)) {
1098
+ localTools.set(t.name, t);
1099
+ } else if (isLocalA2ATool(t)) {
1100
+ a2aTools.set(t.name, t);
1101
+ } else if (isLocalMcpServer(t)) {
1102
+ mcpServers.set(t.name, t);
1103
+ }
1104
+ }
1105
+ return { localTools, a2aTools, mcpServers };
1106
+ }
1107
+ function describeHandlerName(ev) {
1108
+ if (ev.kind === "mcp_local" && ev.mcpServer && ev.mcpToolName) {
1109
+ return `${ev.mcpServer}/${ev.mcpToolName}`;
1110
+ }
1111
+ return ev.name;
1112
+ }
1113
+ function normalizeReasoningLevel(level) {
1114
+ if (typeof level === "number") {
1115
+ if (!Number.isFinite(level) || level < 0 || level > 100) {
1116
+ throw new MantyxError(
1117
+ `reasoningLevel must be a string anchor or an integer in 0..100, got ${level}`
1118
+ );
1119
+ }
1120
+ return Math.trunc(level);
1121
+ }
1122
+ if (level === "off" || level === "low" || level === "medium" || level === "high") {
1123
+ return level;
1124
+ }
1125
+ throw new MantyxError(
1126
+ `reasoningLevel must be one of "off" | "low" | "medium" | "high" or a number 0..100, got ${JSON.stringify(level)}`
1127
+ );
1128
+ }
1129
+ var OUTPUT_SCHEMA_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
1130
+ var OUTPUT_SCHEMA_MAX_BYTES = 32 * 1024;
1131
+ function normalizeOutputSchema(value) {
1132
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1133
+ throw new MantyxError(
1134
+ `outputSchema must be an object of shape { name?, schema }, got ${JSON.stringify(value)}`
1135
+ );
1136
+ }
1137
+ const out = {};
1138
+ if (value.name !== void 0) {
1139
+ if (typeof value.name !== "string" || !OUTPUT_SCHEMA_NAME_RE.test(value.name)) {
1140
+ throw new MantyxError(
1141
+ `outputSchema.name must match /^[a-zA-Z0-9_-]{1,64}$/, got ${JSON.stringify(value.name)}`
1142
+ );
1143
+ }
1144
+ out.name = value.name;
1145
+ }
1146
+ const schema = value.schema;
1147
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
1148
+ throw new MantyxError(
1149
+ `outputSchema.schema must be a non-null JSON object (the JSON Schema root)`
1150
+ );
1151
+ }
1152
+ out.schema = schema;
1153
+ let serialized;
1154
+ try {
1155
+ serialized = JSON.stringify(out);
1156
+ } catch (err) {
1157
+ throw new MantyxError(
1158
+ `outputSchema is not JSON-serialisable: ${err.message ?? String(err)}`
1159
+ );
1160
+ }
1161
+ if (serialized.length > OUTPUT_SCHEMA_MAX_BYTES) {
1162
+ throw new MantyxError(
1163
+ `outputSchema serialised JSON is ${serialized.length} bytes; the server enforces a 32 KB limit`
1164
+ );
1165
+ }
1166
+ return out;
1167
+ }
1168
+ function parseRunOutput(result, validator) {
1169
+ let parsed;
1170
+ try {
1171
+ parsed = JSON.parse(result.text);
1172
+ } catch (err) {
1173
+ throw new MantyxParseError(
1174
+ `Run ${result.runId} returned non-JSON text; cannot satisfy outputSchema`,
1175
+ result.text,
1176
+ { cause: err }
1177
+ );
1178
+ }
1179
+ if (validator) {
1180
+ try {
1181
+ return validator(parsed);
1182
+ } catch (err) {
1183
+ throw new MantyxParseError(
1184
+ `Run ${result.runId} output failed validation: ${err.message ?? String(err)}`,
1185
+ result.text,
1186
+ { cause: err }
1187
+ );
1188
+ }
622
1189
  }
623
- return map;
1190
+ return parsed;
624
1191
  }
625
1192
  function sleep(ms) {
626
1193
  return new Promise((r) => setTimeout(r, ms));
627
1194
  }
628
1195
 
629
1196
  // src/version.ts
630
- var SDK_VERSION = "0.2.0";
1197
+ var SDK_VERSION = "0.4.0";
631
1198
  // Annotate the CommonJS export names for ESM import in node:
632
1199
  0 && (module.exports = {
633
1200
  AgentSession,
@@ -636,13 +1203,21 @@ var SDK_VERSION = "0.2.0";
636
1203
  MantyxClient,
637
1204
  MantyxError,
638
1205
  MantyxNetworkError,
1206
+ MantyxParseError,
639
1207
  MantyxRunError,
640
1208
  MantyxToolError,
641
1209
  SDK_VERSION,
1210
+ defineLocalA2A,
1211
+ defineLocalMcp,
642
1212
  defineLocalTool,
1213
+ isLocalA2ATool,
1214
+ isLocalMcpServer,
643
1215
  isLocalTool,
1216
+ mantyxA2A,
1217
+ mantyxMcp,
644
1218
  mantyxPluginTool,
645
1219
  mantyxTool,
1220
+ parseRunOutput,
646
1221
  readSseStream,
647
1222
  toToolParametersWire,
648
1223
  zodToJsonSchema