@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.
@@ -0,0 +1,1162 @@
1
+ // src/errors.ts
2
+ var MantyxError = class extends Error {
3
+ code;
4
+ status;
5
+ hint;
6
+ constructor(message, opts = {}) {
7
+ super(message);
8
+ this.name = "MantyxError";
9
+ this.code = opts.code ?? "mantyx_error";
10
+ this.status = opts.status;
11
+ this.hint = opts.hint;
12
+ }
13
+ };
14
+ var MantyxNetworkError = class extends MantyxError {
15
+ constructor(message, opts = {}) {
16
+ super(message, { code: "network" });
17
+ this.name = "MantyxNetworkError";
18
+ if (opts.cause !== void 0) {
19
+ this.cause = opts.cause;
20
+ }
21
+ }
22
+ };
23
+ var MantyxAuthError = class extends MantyxError {
24
+ constructor(message = "Invalid or missing API key") {
25
+ super(message, { code: "unauthorized", status: 401 });
26
+ this.name = "MantyxAuthError";
27
+ }
28
+ };
29
+ var MantyxToolError = class extends MantyxError {
30
+ toolName;
31
+ constructor(toolName, message) {
32
+ super(`Local tool ${JSON.stringify(toolName)} failed: ${message}`, {
33
+ code: "local_tool_failed"
34
+ });
35
+ this.name = "MantyxToolError";
36
+ this.toolName = toolName;
37
+ }
38
+ };
39
+ var MantyxRunError = class extends MantyxError {
40
+ runId;
41
+ subtype;
42
+ constructor(runId, subtype, message) {
43
+ super(message, { code: subtype });
44
+ this.name = "MantyxRunError";
45
+ this.runId = runId;
46
+ this.subtype = subtype;
47
+ }
48
+ };
49
+ var MantyxParseError = class extends MantyxError {
50
+ text;
51
+ constructor(message, text, opts = {}) {
52
+ super(message, { code: "output_parse_failed" });
53
+ this.name = "MantyxParseError";
54
+ this.text = text;
55
+ if (opts.cause !== void 0) {
56
+ this.cause = opts.cause;
57
+ }
58
+ }
59
+ };
60
+
61
+ // src/tools.ts
62
+ function defineLocalTool(opts) {
63
+ assertToolName(opts.name);
64
+ return {
65
+ kind: "local",
66
+ name: opts.name,
67
+ description: opts.description ?? "",
68
+ parameters: opts.parameters,
69
+ execute: opts.execute
70
+ };
71
+ }
72
+ function mantyxTool(id) {
73
+ if (typeof id !== "string" || id.length === 0) {
74
+ throw new Error("mantyxTool(id): id must be a non-empty string");
75
+ }
76
+ return { kind: "mantyx", id };
77
+ }
78
+ function mantyxPluginTool(name) {
79
+ if (typeof name !== "string" || !name.startsWith("@") || !name.includes("/")) {
80
+ throw new Error(
81
+ `mantyxPluginTool(name): expected "@plugin-slug/tool-name", got ${JSON.stringify(name)}`
82
+ );
83
+ }
84
+ return { kind: "mantyx_plugin", name };
85
+ }
86
+ function mantyxA2A(opts) {
87
+ assertToolName(opts.name);
88
+ if (typeof opts.agentCardUrl !== "string" || opts.agentCardUrl.length === 0) {
89
+ throw new Error("mantyxA2A: agentCardUrl is required");
90
+ }
91
+ return {
92
+ kind: "a2a",
93
+ name: opts.name,
94
+ ...opts.description !== void 0 ? { description: opts.description } : {},
95
+ agentCardUrl: opts.agentCardUrl,
96
+ ...opts.headers ? { headers: { ...opts.headers } } : {},
97
+ ...opts.contextId ? { contextId: opts.contextId } : {}
98
+ };
99
+ }
100
+ function defineLocalA2A(opts) {
101
+ assertToolName(opts.name);
102
+ if (typeof opts.agentCardUrl !== "string" || opts.agentCardUrl.length === 0) {
103
+ throw new Error("defineLocalA2A: `agentCardUrl` is required");
104
+ }
105
+ return {
106
+ kind: "a2a_local",
107
+ name: opts.name,
108
+ agentCardUrl: opts.agentCardUrl,
109
+ headers: opts.headers ? { ...opts.headers } : void 0
110
+ };
111
+ }
112
+ function mantyxMcp(opts) {
113
+ assertToolName(opts.name);
114
+ if (typeof opts.url !== "string" || opts.url.length === 0) {
115
+ throw new Error("mantyxMcp: url is required");
116
+ }
117
+ return {
118
+ kind: "mcp",
119
+ name: opts.name,
120
+ url: opts.url,
121
+ ...opts.headers ? { headers: { ...opts.headers } } : {},
122
+ ...opts.toolFilter ? { toolFilter: [...opts.toolFilter] } : {}
123
+ };
124
+ }
125
+ function defineLocalMcp(opts) {
126
+ assertToolName(opts.name);
127
+ const hasHttp = typeof opts.url === "string" && opts.url.length > 0;
128
+ const hasStdio = typeof opts.command === "string" && opts.command.length > 0;
129
+ if (hasHttp && hasStdio) {
130
+ throw new Error(
131
+ "defineLocalMcp: pass either `url` (Streamable HTTP) or `command` (stdio), not both"
132
+ );
133
+ }
134
+ if (!hasHttp && !hasStdio) {
135
+ throw new Error(
136
+ "defineLocalMcp: one of `url` (Streamable HTTP) or `command` (stdio) is required"
137
+ );
138
+ }
139
+ if (hasHttp) {
140
+ const url = opts.url;
141
+ return {
142
+ kind: "mcp_local",
143
+ name: opts.name,
144
+ http: {
145
+ url,
146
+ ...opts.headers ? { headers: { ...opts.headers } } : {}
147
+ },
148
+ stdio: void 0
149
+ };
150
+ }
151
+ const command = opts.command;
152
+ return {
153
+ kind: "mcp_local",
154
+ name: opts.name,
155
+ http: void 0,
156
+ stdio: {
157
+ command,
158
+ ...opts.args ? { args: [...opts.args] } : {},
159
+ ...opts.env ? { env: { ...opts.env } } : {},
160
+ ...opts.cwd ? { cwd: opts.cwd } : {}
161
+ }
162
+ };
163
+ }
164
+ function isLocalTool(t) {
165
+ return t.kind === "local";
166
+ }
167
+ function isLocalA2ATool(t) {
168
+ return t.kind === "a2a_local";
169
+ }
170
+ function isLocalMcpServer(t) {
171
+ return t.kind === "mcp_local";
172
+ }
173
+ var TOOL_NAME_RE = /^[a-zA-Z0-9_]{1,64}$/;
174
+ function assertToolName(name) {
175
+ if (!TOOL_NAME_RE.test(name)) {
176
+ throw new Error(
177
+ `Invalid tool name ${JSON.stringify(name)}: must match /^[a-zA-Z0-9_]{1,64}$/`
178
+ );
179
+ }
180
+ }
181
+ function prefixedMcpToolName(serverName, toolName) {
182
+ const prefix = `${serverName}_`;
183
+ return toolName.startsWith(prefix) ? toolName : `${prefix}${toolName}`;
184
+ }
185
+
186
+ // src/sse.ts
187
+ async function* readSseStream(body, opts = {}) {
188
+ if (!body) return;
189
+ const reader = body.getReader();
190
+ const decoder = new TextDecoder("utf-8");
191
+ let buffer = "";
192
+ let cancelled = false;
193
+ const onAbort = () => {
194
+ cancelled = true;
195
+ try {
196
+ void reader.cancel();
197
+ } catch {
198
+ }
199
+ };
200
+ if (opts.signal) {
201
+ if (opts.signal.aborted) {
202
+ onAbort();
203
+ } else {
204
+ opts.signal.addEventListener("abort", onAbort, { once: true });
205
+ }
206
+ }
207
+ try {
208
+ while (!cancelled) {
209
+ const { done, value } = await reader.read();
210
+ if (done) break;
211
+ buffer += decoder.decode(value, { stream: true });
212
+ let sepIdx;
213
+ while ((sepIdx = findSeparator(buffer)) !== -1) {
214
+ const raw = buffer.slice(0, sepIdx);
215
+ buffer = buffer.slice(sepIdx + (buffer.startsWith("\r", sepIdx) ? 4 : 2));
216
+ const ev = parseEventBlock(raw);
217
+ if (ev) yield ev;
218
+ }
219
+ }
220
+ } finally {
221
+ if (opts.signal) opts.signal.removeEventListener("abort", onAbort);
222
+ try {
223
+ reader.releaseLock();
224
+ } catch {
225
+ }
226
+ }
227
+ }
228
+ function findSeparator(s) {
229
+ const lf = s.indexOf("\n\n");
230
+ const crlf = s.indexOf("\r\n\r\n");
231
+ if (lf === -1) return crlf;
232
+ if (crlf === -1) return lf;
233
+ return Math.min(lf, crlf);
234
+ }
235
+ function parseEventBlock(block) {
236
+ const lines = block.split(/\r?\n/);
237
+ let id;
238
+ let event;
239
+ const dataLines = [];
240
+ for (const line of lines) {
241
+ if (line.length === 0) continue;
242
+ if (line.startsWith(":")) continue;
243
+ const colonIdx = line.indexOf(":");
244
+ const field = colonIdx === -1 ? line : line.slice(0, colonIdx);
245
+ let value = colonIdx === -1 ? "" : line.slice(colonIdx + 1);
246
+ if (value.startsWith(" ")) value = value.slice(1);
247
+ if (field === "id") id = value;
248
+ else if (field === "event") event = value;
249
+ else if (field === "data") dataLines.push(value);
250
+ }
251
+ if (dataLines.length === 0 && id === void 0 && event === void 0) {
252
+ return null;
253
+ }
254
+ return {
255
+ ...id !== void 0 ? { id } : {},
256
+ ...event !== void 0 ? { event } : {},
257
+ data: dataLines.join("\n")
258
+ };
259
+ }
260
+
261
+ // src/zod-to-json-schema.ts
262
+ import { z } from "zod";
263
+ function zodToJsonSchema(schema) {
264
+ const builtIn = z.toJSONSchema;
265
+ if (typeof builtIn === "function") {
266
+ try {
267
+ const out = builtIn.call(z, schema);
268
+ if (out && typeof out === "object") return out;
269
+ } catch {
270
+ }
271
+ }
272
+ return convertNode(schema);
273
+ }
274
+ function convertNode(schema) {
275
+ const def = schema._def;
276
+ const typeName = def?.typeName;
277
+ switch (typeName) {
278
+ case "ZodString":
279
+ return { type: "string" };
280
+ case "ZodNumber":
281
+ return { type: "number" };
282
+ case "ZodBoolean":
283
+ return { type: "boolean" };
284
+ case "ZodNull":
285
+ return { type: "null" };
286
+ case "ZodLiteral": {
287
+ const value = def.value;
288
+ return { const: value, type: typeof value };
289
+ }
290
+ case "ZodEnum": {
291
+ const values = def.values ?? [];
292
+ return { type: "string", enum: [...values] };
293
+ }
294
+ case "ZodArray": {
295
+ const inner = def.type;
296
+ return {
297
+ type: "array",
298
+ items: inner ? convertNode(inner) : {}
299
+ };
300
+ }
301
+ case "ZodOptional":
302
+ case "ZodNullable": {
303
+ const inner = def.innerType;
304
+ return inner ? convertNode(inner) : {};
305
+ }
306
+ case "ZodDefault": {
307
+ const inner = def.innerType;
308
+ return inner ? convertNode(inner) : {};
309
+ }
310
+ case "ZodObject": {
311
+ const shape = def.shape;
312
+ const fields = typeof shape === "function" ? shape() : shape;
313
+ const properties = {};
314
+ const required = [];
315
+ if (fields) {
316
+ for (const [key, value] of Object.entries(fields)) {
317
+ properties[key] = convertNode(value);
318
+ const innerDef = value._def;
319
+ const innerTypeName = innerDef?.typeName;
320
+ if (innerTypeName !== "ZodOptional" && innerTypeName !== "ZodDefault") {
321
+ required.push(key);
322
+ }
323
+ }
324
+ }
325
+ const out = { type: "object", properties };
326
+ if (required.length > 0) out.required = required;
327
+ return out;
328
+ }
329
+ default:
330
+ return {};
331
+ }
332
+ }
333
+ function toToolParametersWire(parameters) {
334
+ if (!parameters) return { type: "object", properties: {} };
335
+ if (typeof parameters._def !== "undefined") {
336
+ return zodToJsonSchema(parameters);
337
+ }
338
+ return parameters;
339
+ }
340
+
341
+ // src/local-resolver.ts
342
+ async function resolveLocalRefs(tools, opts = { fetch: globalThis.fetch }) {
343
+ if (!tools || tools.length === 0) return { newlyOpenedMcp: [] };
344
+ const newlyOpenedMcp = [];
345
+ const work = [];
346
+ for (const t of tools) {
347
+ if (isLocalA2ATool(t)) {
348
+ if (t._resolvedCard) continue;
349
+ work.push(resolveA2A(t, opts.fetch));
350
+ } else if (isLocalMcpServer(t)) {
351
+ if (t._resolved) continue;
352
+ work.push(
353
+ resolveMcp(t).then((resolved) => {
354
+ if (resolved) newlyOpenedMcp.push(t);
355
+ })
356
+ );
357
+ }
358
+ }
359
+ await Promise.all(work);
360
+ return { newlyOpenedMcp };
361
+ }
362
+ async function resolveA2A(t, fetchImpl) {
363
+ const headers = { Accept: "application/json", ...t.headers ?? {} };
364
+ const res = await fetchImpl(t.agentCardUrl, { method: "GET", headers });
365
+ if (!res.ok) {
366
+ throw new Error(
367
+ `defineLocalA2A(${JSON.stringify(t.name)}): GET ${t.agentCardUrl} returned ${res.status} ${res.statusText}`
368
+ );
369
+ }
370
+ const card = await res.json();
371
+ if (!card || typeof card !== "object" || typeof card.name !== "string" || !card.name) {
372
+ throw new Error(
373
+ `defineLocalA2A(${JSON.stringify(t.name)}): ${t.agentCardUrl} did not return a valid Agent Card (missing required \`name\` field)`
374
+ );
375
+ }
376
+ t._resolvedCard = card;
377
+ }
378
+ async function resolveMcp(t) {
379
+ const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
380
+ let transport;
381
+ let connect;
382
+ if (t.http) {
383
+ const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
384
+ const httpTransport = new StreamableHTTPClientTransport(new URL(t.http.url), {
385
+ requestInit: t.http.headers ? { headers: t.http.headers } : {}
386
+ });
387
+ transport = httpTransport;
388
+ connect = (c) => c.connect(httpTransport);
389
+ } else if (t.stdio) {
390
+ const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
391
+ const stdioTransport = new StdioClientTransport({
392
+ command: t.stdio.command,
393
+ ...t.stdio.args ? { args: t.stdio.args } : {},
394
+ ...t.stdio.env ? { env: t.stdio.env } : {},
395
+ ...t.stdio.cwd ? { cwd: t.stdio.cwd } : {}
396
+ });
397
+ transport = stdioTransport;
398
+ connect = (c) => c.connect(stdioTransport);
399
+ } else {
400
+ throw new Error(
401
+ `defineLocalMcp(${JSON.stringify(t.name)}): missing transport (no \`url\` or \`command\` was provided)`
402
+ );
403
+ }
404
+ const client = new Client({ name: "@mantyx/sdk", version: "0.3.0" }, { capabilities: {} });
405
+ try {
406
+ await connect(client);
407
+ } catch (err) {
408
+ throw new Error(
409
+ `defineLocalMcp(${JSON.stringify(t.name)}): failed to connect \u2014 ${err.message}`,
410
+ { cause: err }
411
+ );
412
+ }
413
+ const serverInfo = client.getServerVersion() ?? { name: t.name };
414
+ const listed = await client.listTools();
415
+ const tools = listed.tools.map((tool) => {
416
+ const out = {
417
+ name: tool.name,
418
+ inputSchema: tool.inputSchema
419
+ };
420
+ if (typeof tool.description === "string") out.description = tool.description;
421
+ if (tool.annotations) out.annotations = tool.annotations;
422
+ return out;
423
+ });
424
+ const close = async () => {
425
+ try {
426
+ await client.close();
427
+ } catch {
428
+ }
429
+ try {
430
+ const t2 = transport;
431
+ if (t2.close) await t2.close();
432
+ } catch {
433
+ }
434
+ };
435
+ t._resolved = {
436
+ serverInfo,
437
+ tools,
438
+ client,
439
+ close
440
+ };
441
+ return true;
442
+ }
443
+ async function closeMcpRefs(tools) {
444
+ if (!tools || tools.length === 0) return;
445
+ const closes = [];
446
+ for (const t of tools) {
447
+ if (!isLocalMcpServer(t)) continue;
448
+ const resolved = t._resolved;
449
+ if (!resolved) continue;
450
+ t._resolved = void 0;
451
+ closes.push(resolved.close());
452
+ }
453
+ await Promise.all(closes);
454
+ }
455
+ async function callA2A(t, args, opts = { fetch: globalThis.fetch }) {
456
+ const card = t._resolvedCard;
457
+ if (!card) {
458
+ throw new Error(
459
+ `defineLocalA2A(${JSON.stringify(t.name)}): agent card has not been resolved yet`
460
+ );
461
+ }
462
+ const url = typeof card.url === "string" && card.url.length > 0 ? card.url : t.agentCardUrl;
463
+ const body = {
464
+ jsonrpc: "2.0",
465
+ id: cryptoRandomId(),
466
+ method: "message/send",
467
+ params: {
468
+ message: {
469
+ kind: "message",
470
+ role: "user",
471
+ messageId: cryptoRandomId(),
472
+ parts: [{ kind: "text", text: args.message }]
473
+ }
474
+ }
475
+ };
476
+ const res = await opts.fetch(url, {
477
+ method: "POST",
478
+ headers: {
479
+ "Content-Type": "application/json",
480
+ Accept: "application/json",
481
+ ...t.headers ?? {}
482
+ },
483
+ body: JSON.stringify(body)
484
+ });
485
+ if (!res.ok) {
486
+ throw new Error(
487
+ `A2A message/send to ${url} returned ${res.status} ${res.statusText}`
488
+ );
489
+ }
490
+ const json = await res.json();
491
+ if (json.error) {
492
+ throw new Error(`A2A peer reported error ${json.error.code}: ${json.error.message}`);
493
+ }
494
+ return extractA2AReplyText(json.result);
495
+ }
496
+ function extractA2AReplyText(result) {
497
+ if (result == null) return "";
498
+ if (typeof result === "string") return result;
499
+ if (typeof result !== "object") return JSON.stringify(result);
500
+ const obj = result;
501
+ if (Array.isArray(obj.parts)) {
502
+ const text = textFromParts(obj.parts);
503
+ if (text) return text;
504
+ }
505
+ const status = obj.status;
506
+ const statusMessage = status?.message;
507
+ if (Array.isArray(statusMessage?.parts)) {
508
+ const text = textFromParts(statusMessage.parts);
509
+ if (text) return text;
510
+ }
511
+ const artifacts = obj.artifacts;
512
+ if (Array.isArray(artifacts) && artifacts.length > 0) {
513
+ const last = artifacts[artifacts.length - 1];
514
+ if (Array.isArray(last.parts)) {
515
+ const text = textFromParts(last.parts);
516
+ if (text) return text;
517
+ }
518
+ }
519
+ return JSON.stringify(result);
520
+ }
521
+ function textFromParts(parts) {
522
+ const out = [];
523
+ for (const part of parts) {
524
+ if (!part || typeof part !== "object") continue;
525
+ const p = part;
526
+ if ((p.kind === "text" || p.type === "text") && typeof p.text === "string") {
527
+ out.push(p.text);
528
+ }
529
+ }
530
+ return out.join("\n");
531
+ }
532
+ async function callMcpTool(server, toolName, args) {
533
+ const resolved = server._resolved;
534
+ if (!resolved) {
535
+ throw new Error(
536
+ `defineLocalMcp(${JSON.stringify(server.name)}): MCP server has not been initialised`
537
+ );
538
+ }
539
+ const result = await resolved.client.callTool({ name: toolName, arguments: args });
540
+ if (result.isError) {
541
+ const text = textFromMcpContent(result.content) || "MCP tool reported an error";
542
+ throw new Error(text);
543
+ }
544
+ return textFromMcpContent(result.content);
545
+ }
546
+ function textFromMcpContent(content) {
547
+ if (!content || content.length === 0) return "";
548
+ const out = [];
549
+ for (const block of content) {
550
+ if (block.type === "text" && typeof block.text === "string") out.push(block.text);
551
+ }
552
+ return out.join("\n");
553
+ }
554
+ function cryptoRandomId() {
555
+ const c = globalThis.crypto;
556
+ if (c?.randomUUID) return c.randomUUID();
557
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
558
+ }
559
+
560
+ // src/client.ts
561
+ var DEFAULT_BASE_URL = "https://app.mantyx.io";
562
+ var MantyxClient = class {
563
+ options;
564
+ constructor(opts) {
565
+ if (!opts.apiKey || typeof opts.apiKey !== "string") {
566
+ throw new MantyxError("apiKey is required");
567
+ }
568
+ if (!opts.workspaceSlug || typeof opts.workspaceSlug !== "string") {
569
+ throw new MantyxError("workspaceSlug is required");
570
+ }
571
+ const f = opts.fetch ?? globalThis.fetch;
572
+ if (typeof f !== "function") {
573
+ throw new MantyxError(
574
+ "Global fetch is not available; pass a custom `fetch` implementation in MantyxClientOptions."
575
+ );
576
+ }
577
+ this.options = {
578
+ apiKey: opts.apiKey,
579
+ workspaceSlug: opts.workspaceSlug,
580
+ baseUrl: (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
581
+ fetch: f,
582
+ timeoutMs: opts.timeoutMs ?? 6e4
583
+ };
584
+ }
585
+ // -------------------------------------------------------------- Models
586
+ async listModels() {
587
+ return this.request({
588
+ method: "GET",
589
+ path: "/models"
590
+ });
591
+ }
592
+ // ------------------------------------------------------------- One-shot
593
+ async runAgent(spec) {
594
+ const tools = spec.tools ?? [];
595
+ await resolveLocalRefs(tools, { fetch: this.options.fetch });
596
+ const handlers = collectLocalHandlers(tools);
597
+ try {
598
+ const created = await this.request({
599
+ method: "POST",
600
+ path: "/agent-runs",
601
+ body: serializeAgentSpec(spec, {
602
+ prompt: spec.prompt,
603
+ messages: spec.messages
604
+ })
605
+ });
606
+ return await this.driveRun(created.runId, handlers, {
607
+ ...spec.onAssistantDelta ? { onAssistantDelta: spec.onAssistantDelta } : {},
608
+ ...spec.onEvent ? { onEvent: spec.onEvent } : {},
609
+ ...spec.signal ? { signal: spec.signal } : {}
610
+ });
611
+ } finally {
612
+ await closeMcpRefs(tools);
613
+ }
614
+ }
615
+ async *streamAgent(spec) {
616
+ const tools = spec.tools ?? [];
617
+ await resolveLocalRefs(tools, { fetch: this.options.fetch });
618
+ const handlers = collectLocalHandlers(tools);
619
+ try {
620
+ const created = await this.request({
621
+ method: "POST",
622
+ path: "/agent-runs",
623
+ body: serializeAgentSpec(spec, {
624
+ prompt: spec.prompt,
625
+ messages: spec.messages
626
+ })
627
+ });
628
+ yield* this.streamRunEvents(created.runId, handlers, spec.signal);
629
+ } finally {
630
+ await closeMcpRefs(tools);
631
+ }
632
+ }
633
+ /**
634
+ * Internal registry of client-resolved tool handlers. Exposed for callers
635
+ * who drive the run loop manually via `driveRun` / `streamRunEvents`.
636
+ */
637
+ collectHandlers(tools) {
638
+ return collectLocalHandlers(tools);
639
+ }
640
+ // ------------------------------------------------------------- Sessions
641
+ async createSession(spec) {
642
+ const tools = spec.tools ?? [];
643
+ await resolveLocalRefs(tools, { fetch: this.options.fetch });
644
+ const handlers = collectLocalHandlers(tools);
645
+ const created = await this.request({
646
+ method: "POST",
647
+ path: "/agent-sessions",
648
+ body: serializeAgentSpec(spec)
649
+ });
650
+ return new AgentSession(this, created.sessionId, handlers, tools);
651
+ }
652
+ /**
653
+ * Re-emit a `local_tool_call` event into the right local handler. Useful
654
+ * for tests and for users who consume events via `streamAgent` themselves.
655
+ */
656
+ async dispatchLocalToolFromEvent(runId, ev, handlers) {
657
+ return this.dispatchLocalTool(runId, ev, handlers);
658
+ }
659
+ async resumeSession(sessionId, opts = {}) {
660
+ await this.getSessionInfo(sessionId);
661
+ const tools = opts.tools ?? [];
662
+ if (tools.length > 0) {
663
+ await resolveLocalRefs(tools, { fetch: this.options.fetch });
664
+ }
665
+ const handlers = collectLocalHandlers(tools);
666
+ return new AgentSession(this, sessionId, handlers, tools);
667
+ }
668
+ async endSession(sessionId) {
669
+ await this.request({
670
+ method: "DELETE",
671
+ path: `/agent-sessions/${encodeURIComponent(sessionId)}`
672
+ });
673
+ }
674
+ async getSessionInfo(sessionId) {
675
+ return this.request({
676
+ method: "GET",
677
+ path: `/agent-sessions/${encodeURIComponent(sessionId)}`
678
+ });
679
+ }
680
+ // ----------------------------------------------------------- Internals
681
+ /** Drive an existing run to completion (collect events, dispatch local tools). */
682
+ async driveRun(runId, handlers, opts = {}) {
683
+ const collected = [];
684
+ let finalText = "";
685
+ for await (const ev of this.streamRunEvents(runId, handlers, opts.signal)) {
686
+ collected.push(ev);
687
+ if (opts.onEvent) opts.onEvent(ev);
688
+ if (ev.type === "assistant_delta" && opts.onAssistantDelta) {
689
+ opts.onAssistantDelta(ev.text);
690
+ }
691
+ if (ev.type === "result") {
692
+ const r = ev;
693
+ if (r.subtype === "success") {
694
+ finalText = typeof r.text === "string" ? r.text : "";
695
+ } else {
696
+ throw new MantyxRunError(runId, r.subtype, r.error ?? r.subtype);
697
+ }
698
+ } else if (ev.type === "error") {
699
+ const e = ev;
700
+ throw new MantyxRunError(runId, e.code ?? "error", e.error);
701
+ } else if (ev.type === "cancelled") {
702
+ throw new MantyxRunError(runId, "cancelled", "Run was cancelled");
703
+ }
704
+ }
705
+ return { runId, text: finalText, events: collected };
706
+ }
707
+ async *streamRunEvents(runId, handlers, signal) {
708
+ const url = this.absoluteUrl(`/agent-runs/${encodeURIComponent(runId)}/stream`);
709
+ let lastSeq = 0;
710
+ while (true) {
711
+ const reqUrl = lastSeq > 0 ? `${url}?lastSeq=${lastSeq}` : url;
712
+ const res = await this.options.fetch(reqUrl, {
713
+ method: "GET",
714
+ headers: {
715
+ ...this.authHeaders(),
716
+ Accept: "text/event-stream",
717
+ ...lastSeq > 0 ? { "Last-Event-ID": String(lastSeq) } : {}
718
+ },
719
+ ...signal ? { signal } : {}
720
+ }).catch((err) => {
721
+ throw new MantyxNetworkError(`Failed to open SSE stream: ${err.message}`, {
722
+ cause: err
723
+ });
724
+ });
725
+ if (!res.ok) {
726
+ throw await this.errorFromResponse(res);
727
+ }
728
+ let terminal = false;
729
+ try {
730
+ for await (const sseEvent of readSseStream(res.body, { ...signal ? { signal } : {} })) {
731
+ let data = {};
732
+ try {
733
+ data = JSON.parse(sseEvent.data || "{}");
734
+ } catch {
735
+ data = {};
736
+ }
737
+ const evType = sseEvent.event ?? data.type ?? "message";
738
+ const seq = typeof data.seq === "number" ? data.seq : lastSeq;
739
+ if (typeof seq === "number" && seq > lastSeq) lastSeq = seq;
740
+ const ev = { seq, type: evType, ...data };
741
+ yield ev;
742
+ if (evType === "local_tool_call") {
743
+ const localEv = ev;
744
+ void this.dispatchLocalTool(runId, localEv, handlers).catch((err) => {
745
+ console.error("[mantyx-sdk] local tool dispatch failed:", err);
746
+ });
747
+ }
748
+ if (evType === "result" || evType === "error" || evType === "cancelled") {
749
+ terminal = true;
750
+ return;
751
+ }
752
+ }
753
+ } catch (err) {
754
+ if (signal?.aborted) {
755
+ throw new MantyxRunError(runId, "cancelled", "Run was cancelled by the client");
756
+ }
757
+ await sleep(500);
758
+ continue;
759
+ }
760
+ if (terminal) return;
761
+ }
762
+ }
763
+ async dispatchLocalTool(runId, ev, handlers) {
764
+ const kind = ev.kind ?? "local";
765
+ try {
766
+ let out;
767
+ if (kind === "a2a_local") {
768
+ const tool = handlers.a2aTools.get(ev.name);
769
+ if (!tool) {
770
+ await this.postToolResult(runId, ev.toolUseId, {
771
+ error: `No local A2A handler registered for tool ${JSON.stringify(ev.name)}`
772
+ });
773
+ return;
774
+ }
775
+ const message = typeof ev.args?.message === "string" ? ev.args.message : "";
776
+ out = await callA2A(tool, { message }, { fetch: this.options.fetch });
777
+ } else if (kind === "mcp_local") {
778
+ const serverName = ev.mcpServer ?? "";
779
+ const mcpToolName = ev.mcpToolName ?? "";
780
+ const server = handlers.mcpServers.get(serverName);
781
+ if (!server) {
782
+ await this.postToolResult(runId, ev.toolUseId, {
783
+ error: `No local MCP server registered as ${JSON.stringify(serverName)}`
784
+ });
785
+ return;
786
+ }
787
+ const upstreamName = mcpToolName.startsWith(`${serverName}_`) ? mcpToolName.slice(serverName.length + 1) : mcpToolName;
788
+ out = await callMcpTool(server, upstreamName, ev.args ?? {});
789
+ } else {
790
+ const handler = handlers.localTools.get(ev.name);
791
+ if (!handler) {
792
+ await this.postToolResult(runId, ev.toolUseId, {
793
+ error: `No local handler registered for tool ${JSON.stringify(ev.name)}`
794
+ });
795
+ return;
796
+ }
797
+ const args = handler.parameters ? handler.parameters.parse?.(ev.args) ?? ev.args : ev.args;
798
+ const result = await handler.execute(args);
799
+ out = typeof result === "string" ? result : JSON.stringify(result);
800
+ }
801
+ await this.postToolResult(runId, ev.toolUseId, { result: out });
802
+ } catch (err) {
803
+ const message = err instanceof Error ? err.message : String(err);
804
+ const handlerName = describeHandlerName(ev);
805
+ await this.postToolResult(runId, ev.toolUseId, {
806
+ error: new MantyxToolError(handlerName, message).message
807
+ });
808
+ }
809
+ }
810
+ async postToolResult(runId, toolUseId, payload) {
811
+ await this.request({
812
+ method: "POST",
813
+ path: `/agent-runs/${encodeURIComponent(runId)}/tool-results`,
814
+ body: { toolUseId, ...payload }
815
+ });
816
+ }
817
+ async cancelRun(runId) {
818
+ await this.request({
819
+ method: "POST",
820
+ path: `/agent-runs/${encodeURIComponent(runId)}/cancel`
821
+ });
822
+ }
823
+ // -------------------------------------------------------------- HTTP
824
+ absoluteUrl(path) {
825
+ return `${this.options.baseUrl}/api/v1/workspaces/${encodeURIComponent(this.options.workspaceSlug)}${path}`;
826
+ }
827
+ authHeaders() {
828
+ return { Authorization: `Bearer ${this.options.apiKey}` };
829
+ }
830
+ async request(args) {
831
+ const url = this.absoluteUrl(args.path);
832
+ const ctrl = new AbortController();
833
+ const t = setTimeout(() => ctrl.abort(), args.timeoutMs ?? this.options.timeoutMs);
834
+ try {
835
+ const res = await this.options.fetch(url, {
836
+ method: args.method,
837
+ headers: {
838
+ ...this.authHeaders(),
839
+ ...args.body !== void 0 ? { "Content-Type": "application/json" } : {},
840
+ Accept: "application/json"
841
+ },
842
+ ...args.body !== void 0 ? { body: JSON.stringify(args.body) } : {},
843
+ signal: ctrl.signal
844
+ }).catch((err) => {
845
+ if (ctrl.signal.aborted) {
846
+ throw new MantyxNetworkError(`Request timed out after ${args.timeoutMs ?? this.options.timeoutMs}ms`);
847
+ }
848
+ throw new MantyxNetworkError(`Network error: ${err.message}`, { cause: err });
849
+ });
850
+ if (!res.ok) {
851
+ throw await this.errorFromResponse(res);
852
+ }
853
+ const text = await res.text();
854
+ if (!text) return void 0;
855
+ try {
856
+ return JSON.parse(text);
857
+ } catch (err) {
858
+ throw new MantyxError(`Failed to parse JSON response: ${err.message}`);
859
+ }
860
+ } finally {
861
+ clearTimeout(t);
862
+ }
863
+ }
864
+ async errorFromResponse(res) {
865
+ let body = {};
866
+ try {
867
+ body = await res.json();
868
+ } catch {
869
+ }
870
+ if (res.status === 401) {
871
+ return new MantyxAuthError(body.error ?? "Invalid API key");
872
+ }
873
+ return new MantyxError(body.error ?? `HTTP ${res.status}`, {
874
+ code: body.code ?? `http_${res.status}`,
875
+ status: res.status,
876
+ ...body.hint ? { hint: body.hint } : {}
877
+ });
878
+ }
879
+ };
880
+ var AgentSession = class {
881
+ id;
882
+ client;
883
+ handlers;
884
+ tools;
885
+ constructor(client, id, handlers, tools) {
886
+ this.client = client;
887
+ this.id = id;
888
+ this.handlers = handlers;
889
+ this.tools = tools ?? [];
890
+ }
891
+ async send(prompt, opts = {}) {
892
+ const created = await this.client.request({
893
+ method: "POST",
894
+ path: `/agent-sessions/${encodeURIComponent(this.id)}/messages`,
895
+ body: this.buildSessionMessageBody(prompt, opts)
896
+ });
897
+ return this.client.driveRun(created.runId, this.handlers, {
898
+ ...opts.onAssistantDelta ? { onAssistantDelta: opts.onAssistantDelta } : {},
899
+ ...opts.signal ? { signal: opts.signal } : {}
900
+ });
901
+ }
902
+ async *stream(prompt, opts = {}) {
903
+ const created = await this.client.request({
904
+ method: "POST",
905
+ path: `/agent-sessions/${encodeURIComponent(this.id)}/messages`,
906
+ body: this.buildSessionMessageBody(prompt, opts)
907
+ });
908
+ yield* this.client.streamRunEvents(created.runId, this.handlers, opts.signal);
909
+ }
910
+ buildSessionMessageBody(prompt, opts) {
911
+ const body = { prompt };
912
+ if (this.tools.length > 0) body.tools = serializeToolRefs(this.tools);
913
+ if (opts.metadata && Object.keys(opts.metadata).length > 0) body.metadata = opts.metadata;
914
+ if (opts.reasoningLevel !== void 0) {
915
+ body.reasoningLevel = normalizeReasoningLevel(opts.reasoningLevel);
916
+ }
917
+ if (opts.outputSchema !== void 0) {
918
+ body.outputSchema = normalizeOutputSchema(opts.outputSchema);
919
+ }
920
+ return body;
921
+ }
922
+ async history() {
923
+ const info = await this.client.getSessionInfo(this.id);
924
+ return info.messages;
925
+ }
926
+ async info() {
927
+ return this.client.getSessionInfo(this.id);
928
+ }
929
+ async end() {
930
+ try {
931
+ await this.client.endSession(this.id);
932
+ } finally {
933
+ await closeMcpRefs(this.tools);
934
+ }
935
+ }
936
+ };
937
+ function serializeAgentSpec(spec, extra = {}) {
938
+ if (!spec.agentId && (typeof spec.systemPrompt !== "string" || spec.systemPrompt.length === 0)) {
939
+ throw new MantyxError("Either `agentId` or `systemPrompt` is required");
940
+ }
941
+ const body = {
942
+ tools: serializeToolRefs(spec.tools ?? [])
943
+ };
944
+ if (typeof spec.systemPrompt === "string") body.systemPrompt = spec.systemPrompt;
945
+ if (spec.agentId) body.agentId = spec.agentId;
946
+ if (spec.name) body.name = spec.name;
947
+ if (spec.modelId) body.modelId = spec.modelId;
948
+ if (spec.reasoningLevel !== void 0) {
949
+ body.reasoningLevel = normalizeReasoningLevel(spec.reasoningLevel);
950
+ }
951
+ if (spec.outputSchema !== void 0) {
952
+ body.outputSchema = normalizeOutputSchema(spec.outputSchema);
953
+ }
954
+ if (spec.budgets) body.budgets = spec.budgets;
955
+ if (spec.metadata && Object.keys(spec.metadata).length > 0) body.metadata = spec.metadata;
956
+ if (extra.prompt !== void 0) body.prompt = extra.prompt;
957
+ if (extra.messages !== void 0) body.messages = extra.messages;
958
+ return body;
959
+ }
960
+ function serializeToolRefs(tools) {
961
+ return tools.map((t) => {
962
+ switch (t.kind) {
963
+ case "mantyx":
964
+ return { kind: "mantyx", id: t.id };
965
+ case "mantyx_plugin":
966
+ return { kind: "mantyx_plugin", name: t.name };
967
+ case "local":
968
+ return {
969
+ kind: "local",
970
+ name: t.name,
971
+ description: t.description,
972
+ parameters: toToolParametersWire(t.parameters)
973
+ };
974
+ case "a2a":
975
+ return {
976
+ kind: "a2a",
977
+ name: t.name,
978
+ ...t.description !== void 0 ? { description: t.description } : {},
979
+ agentCardUrl: t.agentCardUrl,
980
+ ...t.headers ? { headers: { ...t.headers } } : {},
981
+ ...t.contextId ? { contextId: t.contextId } : {}
982
+ };
983
+ case "a2a_local": {
984
+ const card = t._resolvedCard;
985
+ if (!card) {
986
+ throw new MantyxError(
987
+ `defineLocalA2A(${JSON.stringify(t.name)}): agent card has not been resolved yet (was \`runAgent\` / \`createSession\` skipped?)`
988
+ );
989
+ }
990
+ return {
991
+ kind: "a2a_local",
992
+ name: t.name,
993
+ // The wire ships the resolved A2A Agent Card. Shallow-clone so
994
+ // consumers can mutate the input later without affecting the
995
+ // wire payload.
996
+ agentCard: { ...card }
997
+ };
998
+ }
999
+ case "mcp":
1000
+ return {
1001
+ kind: "mcp",
1002
+ name: t.name,
1003
+ url: t.url,
1004
+ ...t.headers ? { headers: { ...t.headers } } : {},
1005
+ ...t.toolFilter ? { toolFilter: [...t.toolFilter] } : {}
1006
+ };
1007
+ case "mcp_local": {
1008
+ const resolved = t._resolved;
1009
+ if (!resolved) {
1010
+ throw new MantyxError(
1011
+ `defineLocalMcp(${JSON.stringify(t.name)}): MCP server has not been initialised yet`
1012
+ );
1013
+ }
1014
+ const tools2 = resolved.tools.map((tool) => {
1015
+ const wire = {
1016
+ name: prefixedMcpToolName(t.name, tool.name),
1017
+ inputSchema: tool.inputSchema
1018
+ };
1019
+ if (typeof tool.description === "string") wire.description = tool.description;
1020
+ if (tool.annotations) wire.annotations = tool.annotations;
1021
+ return wire;
1022
+ });
1023
+ return {
1024
+ kind: "mcp_local",
1025
+ name: t.name,
1026
+ serverInfo: { ...resolved.serverInfo },
1027
+ tools: tools2
1028
+ };
1029
+ }
1030
+ }
1031
+ });
1032
+ }
1033
+ function collectLocalHandlers(tools) {
1034
+ const localTools = /* @__PURE__ */ new Map();
1035
+ const a2aTools = /* @__PURE__ */ new Map();
1036
+ const mcpServers = /* @__PURE__ */ new Map();
1037
+ for (const t of tools) {
1038
+ if (isLocalTool(t)) {
1039
+ localTools.set(t.name, t);
1040
+ } else if (isLocalA2ATool(t)) {
1041
+ a2aTools.set(t.name, t);
1042
+ } else if (isLocalMcpServer(t)) {
1043
+ mcpServers.set(t.name, t);
1044
+ }
1045
+ }
1046
+ return { localTools, a2aTools, mcpServers };
1047
+ }
1048
+ function describeHandlerName(ev) {
1049
+ if (ev.kind === "mcp_local" && ev.mcpServer && ev.mcpToolName) {
1050
+ return `${ev.mcpServer}/${ev.mcpToolName}`;
1051
+ }
1052
+ return ev.name;
1053
+ }
1054
+ function normalizeReasoningLevel(level) {
1055
+ if (typeof level === "number") {
1056
+ if (!Number.isFinite(level) || level < 0 || level > 100) {
1057
+ throw new MantyxError(
1058
+ `reasoningLevel must be a string anchor or an integer in 0..100, got ${level}`
1059
+ );
1060
+ }
1061
+ return Math.trunc(level);
1062
+ }
1063
+ if (level === "off" || level === "low" || level === "medium" || level === "high") {
1064
+ return level;
1065
+ }
1066
+ throw new MantyxError(
1067
+ `reasoningLevel must be one of "off" | "low" | "medium" | "high" or a number 0..100, got ${JSON.stringify(level)}`
1068
+ );
1069
+ }
1070
+ var OUTPUT_SCHEMA_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
1071
+ var OUTPUT_SCHEMA_MAX_BYTES = 32 * 1024;
1072
+ function normalizeOutputSchema(value) {
1073
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1074
+ throw new MantyxError(
1075
+ `outputSchema must be an object of shape { name?, schema }, got ${JSON.stringify(value)}`
1076
+ );
1077
+ }
1078
+ const out = {};
1079
+ if (value.name !== void 0) {
1080
+ if (typeof value.name !== "string" || !OUTPUT_SCHEMA_NAME_RE.test(value.name)) {
1081
+ throw new MantyxError(
1082
+ `outputSchema.name must match /^[a-zA-Z0-9_-]{1,64}$/, got ${JSON.stringify(value.name)}`
1083
+ );
1084
+ }
1085
+ out.name = value.name;
1086
+ }
1087
+ const schema = value.schema;
1088
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
1089
+ throw new MantyxError(
1090
+ `outputSchema.schema must be a non-null JSON object (the JSON Schema root)`
1091
+ );
1092
+ }
1093
+ out.schema = schema;
1094
+ let serialized;
1095
+ try {
1096
+ serialized = JSON.stringify(out);
1097
+ } catch (err) {
1098
+ throw new MantyxError(
1099
+ `outputSchema is not JSON-serialisable: ${err.message ?? String(err)}`
1100
+ );
1101
+ }
1102
+ if (serialized.length > OUTPUT_SCHEMA_MAX_BYTES) {
1103
+ throw new MantyxError(
1104
+ `outputSchema serialised JSON is ${serialized.length} bytes; the server enforces a 32 KB limit`
1105
+ );
1106
+ }
1107
+ return out;
1108
+ }
1109
+ function parseRunOutput(result, validator) {
1110
+ let parsed;
1111
+ try {
1112
+ parsed = JSON.parse(result.text);
1113
+ } catch (err) {
1114
+ throw new MantyxParseError(
1115
+ `Run ${result.runId} returned non-JSON text; cannot satisfy outputSchema`,
1116
+ result.text,
1117
+ { cause: err }
1118
+ );
1119
+ }
1120
+ if (validator) {
1121
+ try {
1122
+ return validator(parsed);
1123
+ } catch (err) {
1124
+ throw new MantyxParseError(
1125
+ `Run ${result.runId} output failed validation: ${err.message ?? String(err)}`,
1126
+ result.text,
1127
+ { cause: err }
1128
+ );
1129
+ }
1130
+ }
1131
+ return parsed;
1132
+ }
1133
+ function sleep(ms) {
1134
+ return new Promise((r) => setTimeout(r, ms));
1135
+ }
1136
+
1137
+ export {
1138
+ MantyxError,
1139
+ MantyxNetworkError,
1140
+ MantyxAuthError,
1141
+ MantyxToolError,
1142
+ MantyxRunError,
1143
+ MantyxParseError,
1144
+ defineLocalTool,
1145
+ mantyxTool,
1146
+ mantyxPluginTool,
1147
+ mantyxA2A,
1148
+ defineLocalA2A,
1149
+ mantyxMcp,
1150
+ defineLocalMcp,
1151
+ isLocalTool,
1152
+ isLocalA2ATool,
1153
+ isLocalMcpServer,
1154
+ readSseStream,
1155
+ zodToJsonSchema,
1156
+ toToolParametersWire,
1157
+ DEFAULT_BASE_URL,
1158
+ MantyxClient,
1159
+ AgentSession,
1160
+ parseRunOutput
1161
+ };
1162
+ //# sourceMappingURL=chunk-UVIVQD4O.js.map