@mantyx/sdk 0.1.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.js ADDED
@@ -0,0 +1,608 @@
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
+
50
+ // src/sse.ts
51
+ async function* readSseStream(body, opts = {}) {
52
+ if (!body) return;
53
+ const reader = body.getReader();
54
+ const decoder = new TextDecoder("utf-8");
55
+ let buffer = "";
56
+ let cancelled = false;
57
+ const onAbort = () => {
58
+ cancelled = true;
59
+ try {
60
+ void reader.cancel();
61
+ } catch {
62
+ }
63
+ };
64
+ if (opts.signal) {
65
+ if (opts.signal.aborted) {
66
+ onAbort();
67
+ } else {
68
+ opts.signal.addEventListener("abort", onAbort, { once: true });
69
+ }
70
+ }
71
+ try {
72
+ while (!cancelled) {
73
+ const { done, value } = await reader.read();
74
+ if (done) break;
75
+ buffer += decoder.decode(value, { stream: true });
76
+ let sepIdx;
77
+ while ((sepIdx = findSeparator(buffer)) !== -1) {
78
+ const raw = buffer.slice(0, sepIdx);
79
+ buffer = buffer.slice(sepIdx + (buffer.startsWith("\r", sepIdx) ? 4 : 2));
80
+ const ev = parseEventBlock(raw);
81
+ if (ev) yield ev;
82
+ }
83
+ }
84
+ } finally {
85
+ if (opts.signal) opts.signal.removeEventListener("abort", onAbort);
86
+ try {
87
+ reader.releaseLock();
88
+ } catch {
89
+ }
90
+ }
91
+ }
92
+ function findSeparator(s) {
93
+ const lf = s.indexOf("\n\n");
94
+ const crlf = s.indexOf("\r\n\r\n");
95
+ if (lf === -1) return crlf;
96
+ if (crlf === -1) return lf;
97
+ return Math.min(lf, crlf);
98
+ }
99
+ function parseEventBlock(block) {
100
+ const lines = block.split(/\r?\n/);
101
+ let id;
102
+ let event;
103
+ const dataLines = [];
104
+ for (const line of lines) {
105
+ if (line.length === 0) continue;
106
+ if (line.startsWith(":")) continue;
107
+ const colonIdx = line.indexOf(":");
108
+ const field = colonIdx === -1 ? line : line.slice(0, colonIdx);
109
+ let value = colonIdx === -1 ? "" : line.slice(colonIdx + 1);
110
+ if (value.startsWith(" ")) value = value.slice(1);
111
+ if (field === "id") id = value;
112
+ else if (field === "event") event = value;
113
+ else if (field === "data") dataLines.push(value);
114
+ }
115
+ if (dataLines.length === 0 && id === void 0 && event === void 0) {
116
+ return null;
117
+ }
118
+ return {
119
+ ...id !== void 0 ? { id } : {},
120
+ ...event !== void 0 ? { event } : {},
121
+ data: dataLines.join("\n")
122
+ };
123
+ }
124
+
125
+ // src/tools.ts
126
+ function defineLocalTool(opts) {
127
+ if (!/^[a-zA-Z0-9_]{1,64}$/.test(opts.name)) {
128
+ throw new Error(
129
+ `Invalid local tool name ${JSON.stringify(opts.name)}: must match /^[a-zA-Z0-9_]{1,64}$/`
130
+ );
131
+ }
132
+ return {
133
+ kind: "local",
134
+ name: opts.name,
135
+ description: opts.description ?? "",
136
+ parameters: opts.parameters,
137
+ execute: opts.execute
138
+ };
139
+ }
140
+ function mantyxTool(id) {
141
+ if (typeof id !== "string" || id.length === 0) {
142
+ throw new Error("mantyxTool(id): id must be a non-empty string");
143
+ }
144
+ return { kind: "mantyx", id };
145
+ }
146
+ function mantyxPluginTool(name) {
147
+ if (typeof name !== "string" || !name.startsWith("@") || !name.includes("/")) {
148
+ throw new Error(
149
+ `mantyxPluginTool(name): expected "@plugin-slug/tool-name", got ${JSON.stringify(name)}`
150
+ );
151
+ }
152
+ return { kind: "mantyx_plugin", name };
153
+ }
154
+ function isLocalTool(t) {
155
+ return t.kind === "local";
156
+ }
157
+
158
+ // src/zod-to-json-schema.ts
159
+ import { z } from "zod";
160
+ function zodToJsonSchema(schema) {
161
+ const builtIn = z.toJSONSchema;
162
+ if (typeof builtIn === "function") {
163
+ try {
164
+ const out = builtIn.call(z, schema);
165
+ if (out && typeof out === "object") return out;
166
+ } catch {
167
+ }
168
+ }
169
+ return convertNode(schema);
170
+ }
171
+ function convertNode(schema) {
172
+ const def = schema._def;
173
+ const typeName = def?.typeName;
174
+ switch (typeName) {
175
+ case "ZodString":
176
+ return { type: "string" };
177
+ case "ZodNumber":
178
+ return { type: "number" };
179
+ case "ZodBoolean":
180
+ return { type: "boolean" };
181
+ case "ZodNull":
182
+ return { type: "null" };
183
+ case "ZodLiteral": {
184
+ const value = def.value;
185
+ return { const: value, type: typeof value };
186
+ }
187
+ case "ZodEnum": {
188
+ const values = def.values ?? [];
189
+ return { type: "string", enum: [...values] };
190
+ }
191
+ case "ZodArray": {
192
+ const inner = def.type;
193
+ return {
194
+ type: "array",
195
+ items: inner ? convertNode(inner) : {}
196
+ };
197
+ }
198
+ case "ZodOptional":
199
+ case "ZodNullable": {
200
+ const inner = def.innerType;
201
+ return inner ? convertNode(inner) : {};
202
+ }
203
+ case "ZodDefault": {
204
+ const inner = def.innerType;
205
+ return inner ? convertNode(inner) : {};
206
+ }
207
+ case "ZodObject": {
208
+ const shape = def.shape;
209
+ const fields = typeof shape === "function" ? shape() : shape;
210
+ const properties = {};
211
+ const required = [];
212
+ if (fields) {
213
+ for (const [key, value] of Object.entries(fields)) {
214
+ properties[key] = convertNode(value);
215
+ const innerDef = value._def;
216
+ const innerTypeName = innerDef?.typeName;
217
+ if (innerTypeName !== "ZodOptional" && innerTypeName !== "ZodDefault") {
218
+ required.push(key);
219
+ }
220
+ }
221
+ }
222
+ const out = { type: "object", properties };
223
+ if (required.length > 0) out.required = required;
224
+ return out;
225
+ }
226
+ default:
227
+ return {};
228
+ }
229
+ }
230
+ function toToolParametersWire(parameters) {
231
+ if (!parameters) return { type: "object", properties: {} };
232
+ if (typeof parameters._def !== "undefined") {
233
+ return zodToJsonSchema(parameters);
234
+ }
235
+ return parameters;
236
+ }
237
+
238
+ // src/client.ts
239
+ var DEFAULT_BASE_URL = "https://api.mantyx.com";
240
+ var MantyxClient = class {
241
+ options;
242
+ constructor(opts) {
243
+ if (!opts.apiKey || typeof opts.apiKey !== "string") {
244
+ throw new MantyxError("apiKey is required");
245
+ }
246
+ if (!opts.workspaceSlug || typeof opts.workspaceSlug !== "string") {
247
+ throw new MantyxError("workspaceSlug is required");
248
+ }
249
+ const f = opts.fetch ?? globalThis.fetch;
250
+ if (typeof f !== "function") {
251
+ throw new MantyxError(
252
+ "Global fetch is not available; pass a custom `fetch` implementation in MantyxClientOptions."
253
+ );
254
+ }
255
+ this.options = {
256
+ apiKey: opts.apiKey,
257
+ workspaceSlug: opts.workspaceSlug,
258
+ baseUrl: (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
259
+ fetch: f,
260
+ timeoutMs: opts.timeoutMs ?? 6e4
261
+ };
262
+ }
263
+ // -------------------------------------------------------------- Models
264
+ async listModels() {
265
+ return this.request({
266
+ method: "GET",
267
+ path: "/models"
268
+ });
269
+ }
270
+ // ------------------------------------------------------------- One-shot
271
+ async runAgent(spec) {
272
+ const handlers = collectLocalHandlers(spec.tools ?? []);
273
+ const created = await this.request({
274
+ method: "POST",
275
+ path: "/agent-runs",
276
+ body: serializeAgentSpec(spec, {
277
+ prompt: spec.prompt,
278
+ messages: spec.messages
279
+ })
280
+ });
281
+ return this.driveRun(created.runId, handlers, {
282
+ ...spec.onAssistantDelta ? { onAssistantDelta: spec.onAssistantDelta } : {},
283
+ ...spec.onEvent ? { onEvent: spec.onEvent } : {},
284
+ ...spec.signal ? { signal: spec.signal } : {}
285
+ });
286
+ }
287
+ async *streamAgent(spec) {
288
+ const handlers = collectLocalHandlers(spec.tools ?? []);
289
+ const created = await this.request({
290
+ method: "POST",
291
+ path: "/agent-runs",
292
+ body: serializeAgentSpec(spec, {
293
+ prompt: spec.prompt,
294
+ messages: spec.messages
295
+ })
296
+ });
297
+ yield* this.streamRunEvents(created.runId, handlers, spec.signal);
298
+ }
299
+ // ------------------------------------------------------------- Sessions
300
+ async createSession(spec) {
301
+ const handlers = collectLocalHandlers(spec.tools ?? []);
302
+ const created = await this.request({
303
+ method: "POST",
304
+ path: "/agent-sessions",
305
+ body: serializeAgentSpec(spec)
306
+ });
307
+ return new AgentSession(this, created.sessionId, handlers);
308
+ }
309
+ async resumeSession(sessionId, opts = {}) {
310
+ await this.getSessionInfo(sessionId);
311
+ const handlers = collectLocalHandlers(opts.tools ?? []);
312
+ return new AgentSession(this, sessionId, handlers, opts.tools);
313
+ }
314
+ async endSession(sessionId) {
315
+ await this.request({
316
+ method: "DELETE",
317
+ path: `/agent-sessions/${encodeURIComponent(sessionId)}`
318
+ });
319
+ }
320
+ async getSessionInfo(sessionId) {
321
+ return this.request({
322
+ method: "GET",
323
+ path: `/agent-sessions/${encodeURIComponent(sessionId)}`
324
+ });
325
+ }
326
+ // ----------------------------------------------------------- Internals
327
+ /** Drive an existing run to completion (collect events, dispatch local tools). */
328
+ async driveRun(runId, handlers, opts = {}) {
329
+ const collected = [];
330
+ let finalText = "";
331
+ for await (const ev of this.streamRunEvents(runId, handlers, opts.signal)) {
332
+ collected.push(ev);
333
+ if (opts.onEvent) opts.onEvent(ev);
334
+ if (ev.type === "assistant_delta" && opts.onAssistantDelta) {
335
+ opts.onAssistantDelta(ev.text);
336
+ }
337
+ if (ev.type === "result") {
338
+ const r = ev;
339
+ if (r.subtype === "success") {
340
+ finalText = typeof r.text === "string" ? r.text : "";
341
+ } else {
342
+ throw new MantyxRunError(runId, r.subtype, r.error ?? r.subtype);
343
+ }
344
+ } else if (ev.type === "error") {
345
+ const e = ev;
346
+ throw new MantyxRunError(runId, e.code ?? "error", e.error);
347
+ } else if (ev.type === "cancelled") {
348
+ throw new MantyxRunError(runId, "cancelled", "Run was cancelled");
349
+ }
350
+ }
351
+ return { runId, text: finalText, events: collected };
352
+ }
353
+ async *streamRunEvents(runId, handlers, signal) {
354
+ const url = this.absoluteUrl(`/agent-runs/${encodeURIComponent(runId)}/stream`);
355
+ let lastSeq = 0;
356
+ while (true) {
357
+ const reqUrl = lastSeq > 0 ? `${url}?lastSeq=${lastSeq}` : url;
358
+ const res = await this.options.fetch(reqUrl, {
359
+ method: "GET",
360
+ headers: {
361
+ ...this.authHeaders(),
362
+ Accept: "text/event-stream",
363
+ ...lastSeq > 0 ? { "Last-Event-ID": String(lastSeq) } : {}
364
+ },
365
+ ...signal ? { signal } : {}
366
+ }).catch((err) => {
367
+ throw new MantyxNetworkError(`Failed to open SSE stream: ${err.message}`, {
368
+ cause: err
369
+ });
370
+ });
371
+ if (!res.ok) {
372
+ throw await this.errorFromResponse(res);
373
+ }
374
+ let terminal = false;
375
+ try {
376
+ for await (const sseEvent of readSseStream(res.body, { ...signal ? { signal } : {} })) {
377
+ let data = {};
378
+ try {
379
+ data = JSON.parse(sseEvent.data || "{}");
380
+ } catch {
381
+ data = {};
382
+ }
383
+ const evType = sseEvent.event ?? data.type ?? "message";
384
+ const seq = typeof data.seq === "number" ? data.seq : lastSeq;
385
+ if (typeof seq === "number" && seq > lastSeq) lastSeq = seq;
386
+ const ev = { seq, type: evType, ...data };
387
+ yield ev;
388
+ if (evType === "local_tool_call") {
389
+ const localEv = ev;
390
+ void this.dispatchLocalTool(runId, localEv, handlers).catch((err) => {
391
+ console.error("[mantyx-sdk] local tool dispatch failed:", err);
392
+ });
393
+ }
394
+ if (evType === "result" || evType === "error" || evType === "cancelled") {
395
+ terminal = true;
396
+ return;
397
+ }
398
+ }
399
+ } catch (err) {
400
+ if (signal?.aborted) {
401
+ throw new MantyxRunError(runId, "cancelled", "Run was cancelled by the client");
402
+ }
403
+ await sleep(500);
404
+ continue;
405
+ }
406
+ if (terminal) return;
407
+ }
408
+ }
409
+ async dispatchLocalTool(runId, ev, handlers) {
410
+ const handler = handlers.get(ev.name);
411
+ if (!handler) {
412
+ await this.postToolResult(runId, ev.toolUseId, {
413
+ error: `No local handler registered for tool ${JSON.stringify(ev.name)}`
414
+ });
415
+ return;
416
+ }
417
+ try {
418
+ const args = handler.parameters ? handler.parameters.parse?.(ev.args) ?? ev.args : ev.args;
419
+ const out = await handler.execute(args);
420
+ const resultText = typeof out === "string" ? out : JSON.stringify(out);
421
+ await this.postToolResult(runId, ev.toolUseId, { result: resultText });
422
+ } catch (err) {
423
+ const message = err instanceof Error ? err.message : String(err);
424
+ await this.postToolResult(runId, ev.toolUseId, {
425
+ error: new MantyxToolError(handler.name, message).message
426
+ });
427
+ }
428
+ }
429
+ async postToolResult(runId, toolUseId, payload) {
430
+ await this.request({
431
+ method: "POST",
432
+ path: `/agent-runs/${encodeURIComponent(runId)}/tool-results`,
433
+ body: { toolUseId, ...payload }
434
+ });
435
+ }
436
+ async cancelRun(runId) {
437
+ await this.request({
438
+ method: "POST",
439
+ path: `/agent-runs/${encodeURIComponent(runId)}/cancel`
440
+ });
441
+ }
442
+ // -------------------------------------------------------------- HTTP
443
+ absoluteUrl(path) {
444
+ return `${this.options.baseUrl}/api/v1/workspaces/${encodeURIComponent(this.options.workspaceSlug)}${path}`;
445
+ }
446
+ authHeaders() {
447
+ return { Authorization: `Bearer ${this.options.apiKey}` };
448
+ }
449
+ async request(args) {
450
+ const url = this.absoluteUrl(args.path);
451
+ const ctrl = new AbortController();
452
+ const t = setTimeout(() => ctrl.abort(), args.timeoutMs ?? this.options.timeoutMs);
453
+ try {
454
+ const res = await this.options.fetch(url, {
455
+ method: args.method,
456
+ headers: {
457
+ ...this.authHeaders(),
458
+ ...args.body !== void 0 ? { "Content-Type": "application/json" } : {},
459
+ Accept: "application/json"
460
+ },
461
+ ...args.body !== void 0 ? { body: JSON.stringify(args.body) } : {},
462
+ signal: ctrl.signal
463
+ }).catch((err) => {
464
+ if (ctrl.signal.aborted) {
465
+ throw new MantyxNetworkError(`Request timed out after ${args.timeoutMs ?? this.options.timeoutMs}ms`);
466
+ }
467
+ throw new MantyxNetworkError(`Network error: ${err.message}`, { cause: err });
468
+ });
469
+ if (!res.ok) {
470
+ throw await this.errorFromResponse(res);
471
+ }
472
+ const text = await res.text();
473
+ if (!text) return void 0;
474
+ try {
475
+ return JSON.parse(text);
476
+ } catch (err) {
477
+ throw new MantyxError(`Failed to parse JSON response: ${err.message}`);
478
+ }
479
+ } finally {
480
+ clearTimeout(t);
481
+ }
482
+ }
483
+ async errorFromResponse(res) {
484
+ let body = {};
485
+ try {
486
+ body = await res.json();
487
+ } catch {
488
+ }
489
+ if (res.status === 401) {
490
+ return new MantyxAuthError(body.error ?? "Invalid API key");
491
+ }
492
+ return new MantyxError(body.error ?? `HTTP ${res.status}`, {
493
+ code: body.code ?? `http_${res.status}`,
494
+ status: res.status,
495
+ ...body.hint ? { hint: body.hint } : {}
496
+ });
497
+ }
498
+ };
499
+ var AgentSession = class {
500
+ id;
501
+ client;
502
+ handlers;
503
+ toolsForResume;
504
+ constructor(client, id, handlers, toolsForResume) {
505
+ this.client = client;
506
+ this.id = id;
507
+ this.handlers = handlers;
508
+ this.toolsForResume = toolsForResume;
509
+ }
510
+ async send(prompt, opts = {}) {
511
+ const created = await this.client.request({
512
+ method: "POST",
513
+ path: `/agent-sessions/${encodeURIComponent(this.id)}/messages`,
514
+ body: {
515
+ prompt,
516
+ ...this.toolsForResume ? { tools: serializeToolRefs(this.toolsForResume) } : {},
517
+ ...opts.metadata && Object.keys(opts.metadata).length > 0 ? { metadata: opts.metadata } : {}
518
+ }
519
+ });
520
+ return this.client.driveRun(created.runId, this.handlers, {
521
+ ...opts.onAssistantDelta ? { onAssistantDelta: opts.onAssistantDelta } : {},
522
+ ...opts.signal ? { signal: opts.signal } : {}
523
+ });
524
+ }
525
+ async *stream(prompt, opts = {}) {
526
+ const created = await this.client.request({
527
+ method: "POST",
528
+ path: `/agent-sessions/${encodeURIComponent(this.id)}/messages`,
529
+ body: {
530
+ prompt,
531
+ ...this.toolsForResume ? { tools: serializeToolRefs(this.toolsForResume) } : {},
532
+ ...opts.metadata && Object.keys(opts.metadata).length > 0 ? { metadata: opts.metadata } : {}
533
+ }
534
+ });
535
+ yield* this.client.streamRunEvents(created.runId, this.handlers, opts.signal);
536
+ }
537
+ async history() {
538
+ const info = await this.client.getSessionInfo(this.id);
539
+ return info.messages;
540
+ }
541
+ async info() {
542
+ return this.client.getSessionInfo(this.id);
543
+ }
544
+ async end() {
545
+ await this.client.endSession(this.id);
546
+ }
547
+ };
548
+ function serializeAgentSpec(spec, extra = {}) {
549
+ if (!spec.agentId && (typeof spec.systemPrompt !== "string" || spec.systemPrompt.length === 0)) {
550
+ throw new MantyxError("Either `agentId` or `systemPrompt` is required");
551
+ }
552
+ const body = {
553
+ tools: serializeToolRefs(spec.tools ?? [])
554
+ };
555
+ if (typeof spec.systemPrompt === "string") body.systemPrompt = spec.systemPrompt;
556
+ if (spec.agentId) body.agentId = spec.agentId;
557
+ if (spec.name) body.name = spec.name;
558
+ if (spec.modelId) body.modelId = spec.modelId;
559
+ if (spec.budgets) body.budgets = spec.budgets;
560
+ if (spec.metadata && Object.keys(spec.metadata).length > 0) body.metadata = spec.metadata;
561
+ if (extra.prompt !== void 0) body.prompt = extra.prompt;
562
+ if (extra.messages !== void 0) body.messages = extra.messages;
563
+ return body;
564
+ }
565
+ function serializeToolRefs(tools) {
566
+ return tools.map((t) => {
567
+ if (t.kind === "mantyx") return { kind: "mantyx", id: t.id };
568
+ if (t.kind === "mantyx_plugin") return { kind: "mantyx_plugin", name: t.name };
569
+ return {
570
+ kind: "local",
571
+ name: t.name,
572
+ description: t.description,
573
+ parameters: toToolParametersWire(t.parameters)
574
+ };
575
+ });
576
+ }
577
+ function collectLocalHandlers(tools) {
578
+ const map = /* @__PURE__ */ new Map();
579
+ for (const t of tools) {
580
+ if (isLocalTool(t)) map.set(t.name, t);
581
+ }
582
+ return map;
583
+ }
584
+ function sleep(ms) {
585
+ return new Promise((r) => setTimeout(r, ms));
586
+ }
587
+
588
+ // src/version.ts
589
+ var SDK_VERSION = "0.1.0";
590
+ export {
591
+ AgentSession,
592
+ DEFAULT_BASE_URL,
593
+ MantyxAuthError,
594
+ MantyxClient,
595
+ MantyxError,
596
+ MantyxNetworkError,
597
+ MantyxRunError,
598
+ MantyxToolError,
599
+ SDK_VERSION,
600
+ defineLocalTool,
601
+ isLocalTool,
602
+ mantyxPluginTool,
603
+ mantyxTool,
604
+ readSseStream,
605
+ toToolParametersWire,
606
+ zodToJsonSchema
607
+ };
608
+ //# sourceMappingURL=index.js.map