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