@frumu/tandem-client 0.3.22

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,1239 @@
1
+ // src/normalize/index.ts
2
+ import { z } from "zod";
3
+ var TandemValidationError = class extends Error {
4
+ constructor(endpoint, status, issues, rawSnippet) {
5
+ super(`Tandem API Validation Error [${status}] at ${endpoint}: ${issues.length} issues found.`);
6
+ this.endpoint = endpoint;
7
+ this.status = status;
8
+ this.issues = issues;
9
+ this.rawSnippet = rawSnippet;
10
+ this.name = "TandemValidationError";
11
+ }
12
+ };
13
+ var idNormalizer = z.string().or(z.object({
14
+ id: z.string().optional(),
15
+ runID: z.string().optional(),
16
+ runId: z.string().optional(),
17
+ run_id: z.string().optional(),
18
+ sessionID: z.string().optional(),
19
+ sessionId: z.string().optional(),
20
+ session_id: z.string().optional(),
21
+ missionID: z.string().optional(),
22
+ missionId: z.string().optional(),
23
+ mission_id: z.string().optional(),
24
+ instanceID: z.string().optional(),
25
+ instanceId: z.string().optional(),
26
+ instance_id: z.string().optional()
27
+ })).transform((val) => {
28
+ if (typeof val === "string") return val;
29
+ return val.id || val.runID || val.runId || val.run_id || val.sessionID || val.sessionId || val.session_id || val.missionID || val.missionId || val.mission_id || val.instanceID || val.instanceId || val.instance_id;
30
+ });
31
+ var jsonFallback = z.any().transform((val) => val);
32
+ var jsonObjectFallback = z.record(z.string(), z.any()).transform((val) => val);
33
+ var SystemHealthSchema = z.object({
34
+ ready: z.boolean().optional(),
35
+ phase: z.string().optional()
36
+ }).passthrough();
37
+ var SessionRecordSchema = z.object({
38
+ id: z.string(),
39
+ title: z.string(),
40
+ created_at_ms: z.number().optional(),
41
+ createdAtMs: z.number().optional(),
42
+ directory: z.string().optional(),
43
+ workspace_root: z.string().optional(),
44
+ workspaceRoot: z.string().optional(),
45
+ archived: z.boolean().optional()
46
+ }).passthrough().transform((val) => ({
47
+ ...val,
48
+ createdAtMs: val.created_at_ms ?? val.createdAtMs ?? 0,
49
+ workspaceRoot: val.workspace_root ?? val.workspaceRoot
50
+ }));
51
+ var SessionListResponseSchema = z.object({
52
+ sessions: z.array(SessionRecordSchema).optional().default([]),
53
+ count: z.number().optional().default(0)
54
+ }).passthrough();
55
+ var SessionRunStateResponseSchema = z.object({
56
+ active: z.object({
57
+ runID: z.string().optional(),
58
+ runId: z.string().optional(),
59
+ run_id: z.string().optional(),
60
+ attachEventStream: z.string().optional()
61
+ }).passthrough().nullable().optional()
62
+ }).passthrough().transform((val) => {
63
+ if (!val.active) return { active: null };
64
+ return {
65
+ active: {
66
+ ...val.active,
67
+ runId: val.active.runId || val.active.runID || val.active.run_id
68
+ }
69
+ };
70
+ });
71
+ var RunNowResponseSchema = z.object({
72
+ ok: z.boolean().optional(),
73
+ runID: z.string().optional(),
74
+ runId: z.string().optional(),
75
+ run_id: z.string().optional(),
76
+ status: z.string().optional()
77
+ }).passthrough().transform((val) => ({
78
+ ...val,
79
+ runId: val.runId || val.runID || val.run_id
80
+ }));
81
+ var RunRecordSchema = z.object({
82
+ id: z.string().optional(),
83
+ runID: z.string().optional(),
84
+ runId: z.string().optional(),
85
+ run_id: z.string().optional(),
86
+ routine_id: z.string().optional(),
87
+ automation_id: z.string().optional(),
88
+ status: z.string().optional(),
89
+ started_at_ms: z.number().optional(),
90
+ finished_at_ms: z.number().optional()
91
+ }).passthrough().transform((val) => ({
92
+ ...val,
93
+ runId: val.runId || val.runID || val.run_id,
94
+ routineId: val.routine_id,
95
+ automationId: val.automation_id,
96
+ startedAtMs: val.started_at_ms,
97
+ finishedAtMs: val.finished_at_ms
98
+ }));
99
+ var ResourceWriteResponseSchema = z.object({
100
+ ok: z.boolean().default(true),
101
+ rev: z.number().optional()
102
+ }).passthrough();
103
+ var ResourceRecordSchema = z.object({
104
+ key: z.string(),
105
+ value: z.any(),
106
+ rev: z.number().optional(),
107
+ updated_at_ms: z.number().optional(),
108
+ updated_by: z.string().optional()
109
+ }).passthrough().transform((val) => ({
110
+ ...val,
111
+ updatedAtMs: val.updated_at_ms,
112
+ updatedBy: val.updated_by
113
+ }));
114
+ var ResourceListResponseSchema = z.object({
115
+ items: z.array(ResourceRecordSchema).optional().default([]),
116
+ count: z.number().optional().default(0)
117
+ }).passthrough();
118
+ var MemoryItemSchema = z.object({
119
+ id: z.string().optional(),
120
+ text: z.string(),
121
+ tags: z.array(z.string()).optional(),
122
+ source: z.string().optional(),
123
+ session_id: z.string().optional(),
124
+ sessionID: z.string().optional(),
125
+ run_id: z.string().optional(),
126
+ runID: z.string().optional()
127
+ }).passthrough().transform((val) => ({
128
+ ...val,
129
+ sessionId: val.session_id || val.sessionID,
130
+ runId: val.run_id || val.runID
131
+ }));
132
+ var MemoryListResponseSchema = z.object({
133
+ items: z.array(MemoryItemSchema).optional().default([]),
134
+ count: z.number().optional().default(0)
135
+ }).passthrough();
136
+ var MemorySearchResultSchema = z.object({
137
+ id: z.string(),
138
+ text: z.string(),
139
+ score: z.number().optional(),
140
+ tags: z.array(z.string()).optional()
141
+ }).passthrough();
142
+ var MemorySearchResponseSchema = z.object({
143
+ results: z.array(MemorySearchResultSchema).optional().default([]),
144
+ count: z.number().optional().default(0)
145
+ }).passthrough();
146
+ var EngineEventSchema = z.object({
147
+ type: z.string(),
148
+ properties: z.record(z.string(), z.any()).optional().default({}),
149
+ sessionID: z.string().optional(),
150
+ session_id: z.string().optional(),
151
+ sessionId: z.string().optional(),
152
+ runID: z.string().optional(),
153
+ run_id: z.string().optional(),
154
+ runId: z.string().optional(),
155
+ timestamp: z.string().optional()
156
+ }).passthrough().transform((val) => {
157
+ return {
158
+ ...val,
159
+ properties: val.properties,
160
+ sessionId: val.sessionId || val.sessionID || val.session_id,
161
+ runId: val.runId || val.runID || val.run_id
162
+ };
163
+ });
164
+ function parseResponse(schema, rawData, endpoint, status) {
165
+ const result = schema.safeParse(rawData);
166
+ if (!result.success) {
167
+ const snippet = JSON.stringify(rawData).substring(0, 200);
168
+ throw new TandemValidationError(endpoint, status, result.error.issues, snippet);
169
+ }
170
+ return result.data;
171
+ }
172
+
173
+ // src/stream.ts
174
+ function parseSseLine(data) {
175
+ const trimmed = data.trim();
176
+ if (!trimmed || trimmed === ": keep-alive" || trimmed.startsWith(":")) return null;
177
+ try {
178
+ const parsed = JSON.parse(trimmed);
179
+ const result = EngineEventSchema.safeParse(parsed);
180
+ if (result.success) return result.data;
181
+ return null;
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+ async function* filterByType(stream, type) {
187
+ for await (const event of stream) {
188
+ if (event.type === type) {
189
+ yield event;
190
+ }
191
+ }
192
+ }
193
+ async function on(stream, type, callback) {
194
+ for await (const event of stream) {
195
+ if (event.type === type) {
196
+ await callback(event);
197
+ }
198
+ }
199
+ }
200
+ async function* streamSse(url, token, options) {
201
+ const connectTimeoutMs = options?.connectTimeoutMs ?? 3e4;
202
+ const controller = new AbortController();
203
+ const connectTimer = setTimeout(() => controller.abort(), connectTimeoutMs);
204
+ const combinedSignal = options?.signal ? anySignal([controller.signal, options.signal]) : controller.signal;
205
+ let res;
206
+ try {
207
+ res = await fetch(url, {
208
+ headers: {
209
+ Accept: "text/event-stream",
210
+ Authorization: `Bearer ${token}`,
211
+ "Cache-Control": "no-cache"
212
+ },
213
+ signal: combinedSignal
214
+ });
215
+ } finally {
216
+ clearTimeout(connectTimer);
217
+ }
218
+ if (!res.ok) {
219
+ const body = await res.text().catch(() => "");
220
+ throw new Error(`SSE connect failed (${res.status} ${res.statusText}): ${body}`);
221
+ }
222
+ if (!res.body) throw new Error("SSE response has no body");
223
+ const decoder = new TextDecoder();
224
+ const reader = res.body.getReader();
225
+ let buffer = "";
226
+ try {
227
+ while (true) {
228
+ const { done, value } = await reader.read();
229
+ if (done) break;
230
+ buffer += decoder.decode(value, { stream: true });
231
+ const lines = buffer.split("\n");
232
+ buffer = lines.pop() ?? "";
233
+ let currentData = "";
234
+ for (const line of lines) {
235
+ if (line.startsWith("data:")) {
236
+ currentData += line.slice(5).trimStart();
237
+ } else if (line === "") {
238
+ if (currentData) {
239
+ const event = parseSseLine(currentData);
240
+ if (event) yield event;
241
+ currentData = "";
242
+ }
243
+ }
244
+ }
245
+ }
246
+ } finally {
247
+ reader.releaseLock();
248
+ }
249
+ }
250
+ function anySignal(signals) {
251
+ const controller = new AbortController();
252
+ for (const signal of signals) {
253
+ if (signal.aborted) {
254
+ controller.abort(signal.reason);
255
+ break;
256
+ }
257
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
258
+ }
259
+ return controller.signal;
260
+ }
261
+
262
+ // src/client.ts
263
+ var asString = (v) => typeof v === "string" && v.trim().length > 0 ? v : null;
264
+ var parseRunId = (payload) => {
265
+ try {
266
+ const id = idNormalizer.parse(payload);
267
+ if (id) return id;
268
+ } catch {
269
+ const nested = payload.run || null;
270
+ if (nested) {
271
+ try {
272
+ const id2 = idNormalizer.parse(nested);
273
+ if (id2) return id2;
274
+ } catch {
275
+ }
276
+ }
277
+ }
278
+ throw new Error("Run ID missing in engine response");
279
+ };
280
+ var TandemClient = class {
281
+ constructor(options) {
282
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
283
+ this.token = options.token;
284
+ this.timeoutMs = options.timeoutMs ?? 2e4;
285
+ const req = this._request.bind(this);
286
+ this.sessions = new Sessions(this.baseUrl, this.token, this.timeoutMs, req);
287
+ this.permissions = new Permissions(req);
288
+ this.questions = new Questions(req);
289
+ this.providers = new Providers(req);
290
+ this.channels = new Channels(req);
291
+ this.mcp = new Mcp(req);
292
+ this.routines = new Routines(req);
293
+ this.automations = new Automations(req);
294
+ this.memory = new Memory(req);
295
+ this.skills = new Skills(req);
296
+ this.resources = new Resources(req);
297
+ this.agentTeams = new AgentTeams(req);
298
+ this.missions = new Missions(req);
299
+ }
300
+ // ─── Health ───────────────────────────────────────────────────────────────
301
+ /** Check engine health. Returns `{ ready: true }` when the engine is ready. */
302
+ async health() {
303
+ const raw = await this._request("/global/health");
304
+ return parseResponse(SystemHealthSchema, raw, "/global/health", 200);
305
+ }
306
+ // ─── Tools ────────────────────────────────────────────────────────────────
307
+ /** List all tool IDs registered in the engine. */
308
+ async listToolIds() {
309
+ return this._request("/tool/ids");
310
+ }
311
+ /** List all tools with their schemas. */
312
+ async listTools() {
313
+ const raw = await this._request("/tool");
314
+ return Array.isArray(raw) ? raw : [];
315
+ }
316
+ /**
317
+ * Execute a built-in tool directly (without a session).
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * const result = await client.executeTool("workspace_list_files", { path: "." });
322
+ * console.log(result.output);
323
+ * ```
324
+ */
325
+ async executeTool(tool, args) {
326
+ return this._request("/tool/execute", {
327
+ method: "POST",
328
+ body: JSON.stringify({ tool, args: args ?? {} })
329
+ });
330
+ }
331
+ // ─── SSE streaming ────────────────────────────────────────────────────────
332
+ /**
333
+ * Stream events from an active run as an async generator.
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * for await (const event of client.stream(sessionId, runId)) {
338
+ * if (event.type === "session.response") {
339
+ * process.stdout.write(String(event.properties.delta ?? ""));
340
+ * }
341
+ * if (event.type === "run.complete" || event.type === "run.failed") break;
342
+ * }
343
+ * ```
344
+ */
345
+ stream(sessionId, runId, options) {
346
+ const params = new URLSearchParams({ sessionID: sessionId });
347
+ if (runId) params.set("runID", runId);
348
+ const url = `${this.baseUrl}/event?${params.toString()}`;
349
+ return streamSse(url, this.token, options);
350
+ }
351
+ /**
352
+ * Stream the global event feed (all sessions).
353
+ */
354
+ globalStream(options) {
355
+ const url = `${this.baseUrl}/global/event`;
356
+ return streamSse(url, this.token, options);
357
+ }
358
+ /**
359
+ * Pull stored events for a specific run (paginated, not SSE).
360
+ */
361
+ async runEvents(runId, options) {
362
+ const params = new URLSearchParams();
363
+ if (options?.sinceSeq !== void 0) params.set("since_seq", String(options.sinceSeq));
364
+ if (options?.tail !== void 0) params.set("tail", String(options.tail));
365
+ const qs = params.toString() ? `?${params.toString()}` : "";
366
+ const raw = await this._request(`/run/${encodeURIComponent(runId)}/events${qs}`);
367
+ return Array.isArray(raw) ? raw : [];
368
+ }
369
+ // ─── Internal HTTP ────────────────────────────────────────────────────────
370
+ async _request(path, init = {}) {
371
+ const controller = new AbortController();
372
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
373
+ let res;
374
+ try {
375
+ res = await fetch(`${this.baseUrl}${path}`, {
376
+ ...init,
377
+ headers: {
378
+ "Content-Type": "application/json",
379
+ Authorization: `Bearer ${this.token}`,
380
+ ...init.headers ?? {}
381
+ },
382
+ signal: controller.signal
383
+ });
384
+ } catch (err) {
385
+ if (err instanceof Error && err.name === "AbortError") {
386
+ throw new Error(`Request timed out after ${this.timeoutMs}ms: ${path}`);
387
+ }
388
+ throw err;
389
+ } finally {
390
+ clearTimeout(timer);
391
+ }
392
+ if (res.status === 204) return void 0;
393
+ if (!res.ok) {
394
+ const body = await res.text().catch(() => "");
395
+ throw new Error(`Request failed (${res.status} ${res.statusText}): ${body}`);
396
+ }
397
+ return res.json();
398
+ }
399
+ };
400
+ var Sessions = class {
401
+ constructor(baseUrl, token, timeoutMs, req) {
402
+ this.baseUrl = baseUrl;
403
+ this.token = token;
404
+ this.timeoutMs = timeoutMs;
405
+ this.req = req;
406
+ }
407
+ /** Create a new session. Returns the session ID. */
408
+ async create(options = {}) {
409
+ const payload = {
410
+ title: options.title ?? "Tandem SDK Session",
411
+ directory: options.directory ?? "."
412
+ };
413
+ if (options.permissions) payload.permission = options.permissions;
414
+ if (options.model && options.provider) {
415
+ payload.model = { providerID: options.provider, modelID: options.model };
416
+ payload.provider = options.provider;
417
+ }
418
+ const data = await this.req("/session", {
419
+ method: "POST",
420
+ body: JSON.stringify(payload)
421
+ });
422
+ return data.id;
423
+ }
424
+ /** List sessions with optional filtering. */
425
+ async list(options = {}) {
426
+ const params = new URLSearchParams();
427
+ if (options.q) params.set("q", options.q);
428
+ if (options.page !== void 0) params.set("page", String(options.page));
429
+ if (options.pageSize !== void 0) params.set("page_size", String(options.pageSize));
430
+ if (options.archived !== void 0) params.set("archived", String(options.archived));
431
+ if (options.scope) params.set("scope", options.scope);
432
+ if (options.workspace) params.set("workspace", options.workspace);
433
+ const qs = params.toString() ? `?${params.toString()}` : "";
434
+ const raw = await this.req(`/session${qs}`);
435
+ return parseResponse(SessionListResponseSchema, raw, "/session", 200);
436
+ }
437
+ /** Get a session by ID. */
438
+ async get(sessionId) {
439
+ const raw = await this.req(`/session/${encodeURIComponent(sessionId)}`);
440
+ return parseResponse(SessionRecordSchema, raw, `/session/${sessionId}`, 200);
441
+ }
442
+ /** Update session metadata (title, archive status). */
443
+ async update(sessionId, options) {
444
+ const raw = await this.req(`/session/${encodeURIComponent(sessionId)}`, {
445
+ method: "PATCH",
446
+ body: JSON.stringify(options)
447
+ });
448
+ return parseResponse(SessionRecordSchema, raw, `/session/${sessionId}`, 200);
449
+ }
450
+ /** Archive a session (shorthand for `update(id, { archived: true })`). */
451
+ async archive(sessionId) {
452
+ return this.update(sessionId, { archived: true });
453
+ }
454
+ /** Delete a session permanently. */
455
+ async delete(sessionId) {
456
+ await this.req(`/session/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
457
+ }
458
+ /** Get all messages in a session. */
459
+ async messages(sessionId) {
460
+ return this.req(`/session/${encodeURIComponent(sessionId)}/message`);
461
+ }
462
+ /** Get pending TODOs associated with a session. */
463
+ async todos(sessionId) {
464
+ const raw = await this.req(`/session/${encodeURIComponent(sessionId)}/todo`);
465
+ if (Array.isArray(raw)) return raw;
466
+ const wrapped = raw;
467
+ return wrapped.todos ?? [];
468
+ }
469
+ /** Get the currently active run for a session (if any). */
470
+ async activeRun(sessionId) {
471
+ const raw = await this.req(`/session/${encodeURIComponent(sessionId)}/run`);
472
+ return parseResponse(SessionRunStateResponseSchema, raw, `/session/${sessionId}/run`, 200);
473
+ }
474
+ /**
475
+ * Start an async run and return the run ID.
476
+ * Use `client.stream(sessionId, runId)` to receive events.
477
+ *
478
+ * Handles 409 SESSION_RUN_CONFLICT by returning the existing run ID.
479
+ */
480
+ async promptAsync(sessionId, prompt) {
481
+ const payload = { parts: [{ type: "text", text: prompt }] };
482
+ const path = `/session/${encodeURIComponent(sessionId)}/prompt_async?return=run`;
483
+ const controller = new AbortController();
484
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
485
+ let res;
486
+ try {
487
+ res = await fetch(`${this.baseUrl}${path}`, {
488
+ method: "POST",
489
+ headers: {
490
+ "Content-Type": "application/json",
491
+ Authorization: `Bearer ${this.token}`
492
+ },
493
+ body: JSON.stringify(payload),
494
+ signal: controller.signal
495
+ });
496
+ } finally {
497
+ clearTimeout(timer);
498
+ }
499
+ if (res.status === 409) {
500
+ const conflict = await res.json().catch(() => ({}));
501
+ const active = conflict.activeRun;
502
+ const conflictId = asString(active?.runID) || asString(active?.runId) || asString(active?.run_id);
503
+ if (conflictId) return { runId: conflictId };
504
+ }
505
+ if (!res.ok) {
506
+ const body = await res.text().catch(() => "");
507
+ throw new Error(`promptAsync failed (${res.status}): ${body}`);
508
+ }
509
+ const data = await res.json();
510
+ return { runId: parseRunId(data) };
511
+ }
512
+ /**
513
+ * Run a prompt synchronously and return the text reply (blocking).
514
+ * For long tasks prefer `promptAsync` + `stream()`.
515
+ */
516
+ async promptSync(sessionId, prompt) {
517
+ const payload = { parts: [{ type: "text", text: prompt }] };
518
+ const data = await this.req(
519
+ `/session/${encodeURIComponent(sessionId)}/prompt_sync`,
520
+ { method: "POST", body: JSON.stringify(payload) }
521
+ );
522
+ return asString(data.reply) || asString(data.text) || asString(data.output) || "";
523
+ }
524
+ /**
525
+ * Abort the active run for a session.
526
+ */
527
+ async abort(sessionId) {
528
+ return this.req(`/session/${encodeURIComponent(sessionId)}/abort`, {
529
+ method: "POST",
530
+ body: JSON.stringify({})
531
+ });
532
+ }
533
+ /**
534
+ * Cancel the session's active run (alias of abort on some engine versions).
535
+ */
536
+ async cancel(sessionId) {
537
+ return this.req(`/session/${encodeURIComponent(sessionId)}/cancel`, {
538
+ method: "POST",
539
+ body: JSON.stringify({})
540
+ });
541
+ }
542
+ /**
543
+ * Cancel a specific run within a session.
544
+ */
545
+ async cancelRun(sessionId, runId) {
546
+ return this.req(
547
+ `/session/${encodeURIComponent(sessionId)}/run/${encodeURIComponent(runId)}/cancel`,
548
+ { method: "POST", body: JSON.stringify({}) }
549
+ );
550
+ }
551
+ /**
552
+ * Fork a session into a child session (divergent conversation branch).
553
+ */
554
+ async fork(sessionId) {
555
+ const raw = await this.req(`/session/${encodeURIComponent(sessionId)}/fork`, {
556
+ method: "POST",
557
+ body: JSON.stringify({})
558
+ });
559
+ return parseResponse(SessionRecordSchema, raw, `/session/${sessionId}/fork`, 200);
560
+ }
561
+ /**
562
+ * Get the workspace diff produced by the session's last run.
563
+ */
564
+ async diff(sessionId) {
565
+ return this.req(`/session/${encodeURIComponent(sessionId)}/diff`);
566
+ }
567
+ /**
568
+ * Revert uncommitted workspace changes made by the session.
569
+ */
570
+ async revert(sessionId) {
571
+ return this.req(`/session/${encodeURIComponent(sessionId)}/revert`, {
572
+ method: "POST",
573
+ body: JSON.stringify({})
574
+ });
575
+ }
576
+ /**
577
+ * Undo a previous revert (restore session changes).
578
+ */
579
+ async unrevert(sessionId) {
580
+ return this.req(`/session/${encodeURIComponent(sessionId)}/unrevert`, {
581
+ method: "POST",
582
+ body: JSON.stringify({})
583
+ });
584
+ }
585
+ /**
586
+ * Get child sessions forked from this session.
587
+ */
588
+ async children(sessionId) {
589
+ const raw = await this.req(`/session/${encodeURIComponent(sessionId)}/children`);
590
+ const parsed = parseResponse(SessionListResponseSchema, raw, `/session/${sessionId}/children`, 200);
591
+ return parsed.sessions;
592
+ }
593
+ /**
594
+ * Trigger an engine-side summarization of the session's conversation history.
595
+ */
596
+ async summarize(sessionId) {
597
+ return this.req(
598
+ `/session/${encodeURIComponent(sessionId)}/summarize`,
599
+ { method: "POST", body: JSON.stringify({}) }
600
+ );
601
+ }
602
+ /**
603
+ * Attach a session to a different workspace directory.
604
+ */
605
+ async attach(sessionId, targetWorkspace) {
606
+ return this.req(`/session/${encodeURIComponent(sessionId)}/attach`, {
607
+ method: "POST",
608
+ body: JSON.stringify({ target_workspace: targetWorkspace })
609
+ });
610
+ }
611
+ };
612
+ var Permissions = class {
613
+ constructor(req) {
614
+ this.req = req;
615
+ }
616
+ /** List all pending permission requests and existing rules. */
617
+ async list() {
618
+ return this.req("/permission");
619
+ }
620
+ /** Reply to a permission request. Use "always" to auto-approve future requests. */
621
+ async reply(requestId, reply) {
622
+ const res = await this.req(
623
+ `/permission/${encodeURIComponent(requestId)}/reply`,
624
+ { method: "POST", body: JSON.stringify({ reply }) }
625
+ );
626
+ if (!res.ok) throw new Error(`Permission reply rejected: ${res.error ?? requestId}`);
627
+ return { ok: true };
628
+ }
629
+ };
630
+ var Questions = class {
631
+ constructor(req) {
632
+ this.req = req;
633
+ }
634
+ /** List pending AI-generated questions awaiting user confirmation. */
635
+ async list() {
636
+ const raw = await this.req("/question");
637
+ if (Array.isArray(raw)) return { questions: raw };
638
+ return raw;
639
+ }
640
+ /** Answer a pending question. */
641
+ async reply(questionId, answer) {
642
+ return this.req(`/question/${encodeURIComponent(questionId)}/reply`, {
643
+ method: "POST",
644
+ body: JSON.stringify({ answer })
645
+ });
646
+ }
647
+ /** Reject/dismiss a pending question. */
648
+ async reject(questionId) {
649
+ return this.req(`/question/${encodeURIComponent(questionId)}/reject`, {
650
+ method: "POST",
651
+ body: JSON.stringify({})
652
+ });
653
+ }
654
+ };
655
+ var Providers = class {
656
+ constructor(req) {
657
+ this.req = req;
658
+ }
659
+ /** List all available providers and their models. */
660
+ async catalog() {
661
+ return this.req("/provider");
662
+ }
663
+ /** Get the current provider/model configuration. */
664
+ async config() {
665
+ return this.req("/config/providers");
666
+ }
667
+ /** Set the default provider and model. */
668
+ async setDefaults(providerId, modelId) {
669
+ await this.req("/config", {
670
+ method: "PATCH",
671
+ body: JSON.stringify({
672
+ default_provider: providerId,
673
+ providers: { [providerId]: { default_model: modelId } }
674
+ })
675
+ });
676
+ }
677
+ /** Store an API key for a provider. */
678
+ async setApiKey(providerId, apiKey) {
679
+ await this.req(`/auth/${encodeURIComponent(providerId)}`, {
680
+ method: "PUT",
681
+ body: JSON.stringify({ apiKey })
682
+ });
683
+ }
684
+ /** Get authentication status for a provider. */
685
+ async authStatus() {
686
+ return this.req("/provider/auth");
687
+ }
688
+ };
689
+ var Channels = class {
690
+ constructor(req) {
691
+ this.req = req;
692
+ }
693
+ /** Get channel configuration (Telegram / Discord / Slack). */
694
+ async config() {
695
+ return this.req("/channels/config");
696
+ }
697
+ /** Get live channel connection status. */
698
+ async status() {
699
+ return this.req("/channels/status");
700
+ }
701
+ /** Configure a channel (bot token, allowed users, etc.). */
702
+ async put(channel, payload) {
703
+ return this.req(`/channels/${channel}`, {
704
+ method: "PUT",
705
+ body: JSON.stringify(payload)
706
+ });
707
+ }
708
+ /** Remove a channel configuration. */
709
+ async delete(channel) {
710
+ return this.req(`/channels/${channel}`, { method: "DELETE" });
711
+ }
712
+ };
713
+ var Mcp = class {
714
+ constructor(req) {
715
+ this.req = req;
716
+ }
717
+ /** List registered MCP servers. */
718
+ async list() {
719
+ return this.req("/mcp");
720
+ }
721
+ /** List all discovered MCP tools. */
722
+ async listTools() {
723
+ return this.req("/mcp/tools");
724
+ }
725
+ /** List all discovered MCP resources. */
726
+ async listResources() {
727
+ const raw = await this.req("/mcp/resources");
728
+ return Array.isArray(raw) ? raw : [];
729
+ }
730
+ /**
731
+ * Register a new MCP server.
732
+ *
733
+ * @example
734
+ * ```typescript
735
+ * await client.mcp.add({ name: "arcade", transport: "https://mcp.arcade.ai/mcp" });
736
+ * await client.mcp.connect("arcade");
737
+ * ```
738
+ */
739
+ async add(options) {
740
+ return this.req("/mcp", {
741
+ method: "POST",
742
+ body: JSON.stringify(options)
743
+ });
744
+ }
745
+ /** Connect to an MCP server and discover its tools. */
746
+ async connect(name) {
747
+ return this.req(`/mcp/${encodeURIComponent(name)}/connect`, {
748
+ method: "POST"
749
+ });
750
+ }
751
+ /** Disconnect from an MCP server. */
752
+ async disconnect(name) {
753
+ return this.req(`/mcp/${encodeURIComponent(name)}/disconnect`, {
754
+ method: "POST"
755
+ });
756
+ }
757
+ /** Re-discover tools from a connected MCP server. */
758
+ async refresh(name) {
759
+ return this.req(
760
+ `/mcp/${encodeURIComponent(name)}/refresh`,
761
+ { method: "POST" }
762
+ );
763
+ }
764
+ /** Enable or disable an MCP server. */
765
+ async setEnabled(name, enabled) {
766
+ return this.req(`/mcp/${encodeURIComponent(name)}`, {
767
+ method: "PATCH",
768
+ body: JSON.stringify({ enabled })
769
+ });
770
+ }
771
+ };
772
+ var Memory = class {
773
+ constructor(req) {
774
+ this.req = req;
775
+ }
776
+ /**
777
+ * Store a memory item.
778
+ *
779
+ * @example
780
+ * ```typescript
781
+ * await client.memory.put({
782
+ * text: "The team uses Rust for backend services.",
783
+ * tags: ["team", "architecture"],
784
+ * });
785
+ * ```
786
+ */
787
+ async put(options) {
788
+ return this.req("/memory/put", {
789
+ method: "POST",
790
+ body: JSON.stringify(options)
791
+ });
792
+ }
793
+ /**
794
+ * Semantic search over stored memories.
795
+ *
796
+ * @example
797
+ * ```typescript
798
+ * const { results } = await client.memory.search({
799
+ * query: "backend technology choices",
800
+ * limit: 5,
801
+ * });
802
+ * ```
803
+ */
804
+ async search(options) {
805
+ const raw = await this.req("/memory/search", {
806
+ method: "POST",
807
+ body: JSON.stringify(options)
808
+ });
809
+ return parseResponse(MemorySearchResponseSchema, raw, "/memory/search", 200);
810
+ }
811
+ /** List stored memory items with optional text filter. */
812
+ async list(options) {
813
+ const params = new URLSearchParams();
814
+ if (options?.q) params.set("q", options.q);
815
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
816
+ if (options?.offset !== void 0) params.set("offset", String(options.offset));
817
+ const qs = params.toString() ? `?${params.toString()}` : "";
818
+ const raw = await this.req(`/memory${qs}`);
819
+ return parseResponse(MemoryListResponseSchema, raw, "/memory", 200);
820
+ }
821
+ /** Delete a memory item by ID. */
822
+ async delete(memoryId) {
823
+ return this.req(`/memory/${encodeURIComponent(memoryId)}`, {
824
+ method: "DELETE"
825
+ });
826
+ }
827
+ /** Promote a transient memory item to persistent storage. */
828
+ async promote(options) {
829
+ return this.req("/memory/promote", {
830
+ method: "POST",
831
+ body: JSON.stringify(options)
832
+ });
833
+ }
834
+ /** Retrieve the memory audit log for a run. */
835
+ async audit(options) {
836
+ const params = new URLSearchParams();
837
+ if (options?.run_id) params.set("run_id", options.run_id);
838
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
839
+ const qs = params.toString() ? `?${params.toString()}` : "";
840
+ const raw = await this.req(`/memory/audit${qs}`);
841
+ if (Array.isArray(raw)) return { entries: raw, count: raw.length };
842
+ return raw;
843
+ }
844
+ };
845
+ var Skills = class {
846
+ constructor(req) {
847
+ this.req = req;
848
+ }
849
+ /** List installed agent skills. */
850
+ async list(location) {
851
+ const qs = location ? `?location=${encodeURIComponent(location)}` : "";
852
+ const raw = await this.req(`/skills${qs}`);
853
+ if (Array.isArray(raw)) return { skills: raw, count: raw.length };
854
+ return raw;
855
+ }
856
+ /** Get details of a specific skill by name. */
857
+ async get(name) {
858
+ return this.req(`/skills/${encodeURIComponent(name)}`);
859
+ }
860
+ /** Import a skill from YAML content or a file path. */
861
+ async import(options) {
862
+ return this.req("/skills/import", {
863
+ method: "POST",
864
+ body: JSON.stringify(options)
865
+ });
866
+ }
867
+ /** Preview a skill import (dry run). */
868
+ async preview(options) {
869
+ return this.req("/skills/import/preview", {
870
+ method: "POST",
871
+ body: JSON.stringify(options)
872
+ });
873
+ }
874
+ /** List available skill templates shipped with the engine. */
875
+ async templates() {
876
+ const raw = await this.req("/skills/templates");
877
+ if (Array.isArray(raw)) return { templates: raw, count: raw.length };
878
+ return raw;
879
+ }
880
+ };
881
+ var Resources = class {
882
+ constructor(req) {
883
+ this.req = req;
884
+ }
885
+ /** List stored resource records. */
886
+ async list(options) {
887
+ const params = new URLSearchParams();
888
+ if (options?.prefix) params.set("prefix", options.prefix);
889
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
890
+ const qs = params.toString() ? `?${params.toString()}` : "";
891
+ const raw = await this.req(`/resource${qs}`);
892
+ return parseResponse(ResourceListResponseSchema, raw, "/resource", 200);
893
+ }
894
+ /**
895
+ * Write a resource key-value entry.
896
+ *
897
+ * @example
898
+ * ```typescript
899
+ * await client.resources.write({
900
+ * key: "agent-config/alert-threshold",
901
+ * value: { threshold: 0.95 },
902
+ * });
903
+ * ```
904
+ */
905
+ async write(options) {
906
+ return this.req("/resource", {
907
+ method: "PUT",
908
+ body: JSON.stringify(options)
909
+ });
910
+ }
911
+ /** Delete a resource entry. */
912
+ async delete(key, options) {
913
+ return this.req("/resource", {
914
+ method: "DELETE",
915
+ body: JSON.stringify({ key, ...options })
916
+ });
917
+ }
918
+ };
919
+ var Routines = class {
920
+ constructor(req) {
921
+ this.req = req;
922
+ }
923
+ /** List all scheduled routines. */
924
+ async list() {
925
+ return this.req("/routines");
926
+ }
927
+ /**
928
+ * Create a scheduled routine.
929
+ *
930
+ * @example
931
+ * ```typescript
932
+ * await client.routines.create({
933
+ * name: "Daily digest",
934
+ * schedule: "0 8 * * *",
935
+ * entrypoint: "Summarize activity from the last 24 hours",
936
+ * });
937
+ * ```
938
+ */
939
+ async create(options) {
940
+ const payload = { ...options };
941
+ if ("prompt" in payload && !("entrypoint" in payload)) {
942
+ payload.entrypoint = payload.prompt;
943
+ }
944
+ return this.req("/routines", {
945
+ method: "POST",
946
+ body: JSON.stringify(payload)
947
+ });
948
+ }
949
+ /** Update a routine (partial patch). */
950
+ async update(id, patch) {
951
+ return this.req(`/routines/${encodeURIComponent(id)}`, {
952
+ method: "PATCH",
953
+ body: JSON.stringify(patch)
954
+ });
955
+ }
956
+ /** Delete a routine by ID. */
957
+ async delete(id) {
958
+ await this.req(`/routines/${encodeURIComponent(id)}`, { method: "DELETE" });
959
+ }
960
+ /** Trigger a routine immediately (run now). */
961
+ async runNow(id) {
962
+ const raw = await this.req(`/routines/${encodeURIComponent(id)}/run_now`, {
963
+ method: "POST",
964
+ body: JSON.stringify({})
965
+ });
966
+ return parseResponse(RunNowResponseSchema, raw, `/routines/${id}/run_now`, 200);
967
+ }
968
+ /** List recent runs across all routines. */
969
+ async listRuns(options) {
970
+ const params = new URLSearchParams();
971
+ if (options?.routine_id) params.set("routine_id", options.routine_id);
972
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
973
+ const qs = params.toString() ? `?${params.toString()}` : "";
974
+ return this.req(`/routines/runs${qs}`);
975
+ }
976
+ /** List runs for a specific routine. */
977
+ async getRunsForRoutine(id, limit = 25) {
978
+ return this.req(`/routines/${encodeURIComponent(id)}/runs?limit=${limit}`);
979
+ }
980
+ /** Get a specific run record. */
981
+ async getRun(runId) {
982
+ const raw = await this.req(`/routines/runs/${encodeURIComponent(runId)}`);
983
+ return parseResponse(RunRecordSchema, raw, `/routines/runs/${runId}`, 200);
984
+ }
985
+ /** List artifacts produced by a run. */
986
+ async listArtifacts(runId) {
987
+ return this.req(`/routines/runs/${encodeURIComponent(runId)}/artifacts`);
988
+ }
989
+ /** Approve a run that requires human approval. */
990
+ async approveRun(runId, reason) {
991
+ return this.req(`/routines/runs/${encodeURIComponent(runId)}/approve`, {
992
+ method: "POST",
993
+ body: JSON.stringify({ reason: reason ?? "" })
994
+ });
995
+ }
996
+ /** Deny a run that requires human approval. */
997
+ async denyRun(runId, reason) {
998
+ return this.req(`/routines/runs/${encodeURIComponent(runId)}/deny`, {
999
+ method: "POST",
1000
+ body: JSON.stringify({ reason: reason ?? "" })
1001
+ });
1002
+ }
1003
+ /** Pause an active run. */
1004
+ async pauseRun(runId, reason) {
1005
+ return this.req(`/routines/runs/${encodeURIComponent(runId)}/pause`, {
1006
+ method: "POST",
1007
+ body: JSON.stringify({ reason: reason ?? "" })
1008
+ });
1009
+ }
1010
+ /** Resume a paused run. */
1011
+ async resumeRun(runId, reason) {
1012
+ return this.req(`/routines/runs/${encodeURIComponent(runId)}/resume`, {
1013
+ method: "POST",
1014
+ body: JSON.stringify({ reason: reason ?? "" })
1015
+ });
1016
+ }
1017
+ /** Get execution history for a routine. */
1018
+ async history(id, limit) {
1019
+ const qs = limit !== void 0 ? `?limit=${limit}` : "";
1020
+ const raw = await this.req(`/routines/${encodeURIComponent(id)}/history${qs}`);
1021
+ if (Array.isArray(raw)) return { history: raw, count: raw.length };
1022
+ return raw;
1023
+ }
1024
+ };
1025
+ var Automations = class {
1026
+ constructor(req) {
1027
+ this.req = req;
1028
+ }
1029
+ /** List all automations. */
1030
+ async list() {
1031
+ return this.req("/automations");
1032
+ }
1033
+ /**
1034
+ * Create a mission-scoped automation.
1035
+ *
1036
+ * @example
1037
+ * ```typescript
1038
+ * await client.automations.create({
1039
+ * name: "Weekly security scan",
1040
+ * schedule: "0 9 * * 1", // every Monday 9am
1041
+ * mission: {
1042
+ * objective: "Run a security audit of the API surface",
1043
+ * success_criteria: ["No critical vulnerabilities", "Report written to reports/security.md"],
1044
+ * },
1045
+ * policy: {
1046
+ * tool: { external_integrations_allowed: false },
1047
+ * approval: { requires_approval: true },
1048
+ * },
1049
+ * });
1050
+ * ```
1051
+ */
1052
+ async create(options) {
1053
+ return this.req("/automations", {
1054
+ method: "POST",
1055
+ body: JSON.stringify(options)
1056
+ });
1057
+ }
1058
+ /** Update an automation (partial patch). */
1059
+ async update(id, patch) {
1060
+ return this.req(`/automations/${encodeURIComponent(id)}`, {
1061
+ method: "PATCH",
1062
+ body: JSON.stringify(patch)
1063
+ });
1064
+ }
1065
+ /** Delete an automation. */
1066
+ async delete(id) {
1067
+ await this.req(`/automations/${encodeURIComponent(id)}`, { method: "DELETE" });
1068
+ }
1069
+ /** Trigger an automation immediately. */
1070
+ async runNow(id) {
1071
+ const raw = await this.req(`/automations/${encodeURIComponent(id)}/run_now`, {
1072
+ method: "POST",
1073
+ body: JSON.stringify({})
1074
+ });
1075
+ return parseResponse(RunNowResponseSchema, raw, `/automations/${id}/run_now`, 200);
1076
+ }
1077
+ /** List recent runs across all automations. */
1078
+ async listRuns(options) {
1079
+ const params = new URLSearchParams();
1080
+ if (options?.automation_id) params.set("automation_id", options.automation_id);
1081
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
1082
+ const qs = params.toString() ? `?${params.toString()}` : "";
1083
+ return this.req(`/automations/runs${qs}`);
1084
+ }
1085
+ /** List runs for a specific automation. */
1086
+ async getRunsForAutomation(id, limit = 25) {
1087
+ return this.req(`/automations/${encodeURIComponent(id)}/runs?limit=${limit}`);
1088
+ }
1089
+ /** Get a specific automation run record. */
1090
+ async getRun(runId) {
1091
+ const raw = await this.req(`/automations/runs/${encodeURIComponent(runId)}`);
1092
+ return parseResponse(RunRecordSchema, raw, `/automations/runs/${runId}`, 200);
1093
+ }
1094
+ /** List artifacts from an automation run. */
1095
+ async listArtifacts(runId) {
1096
+ return this.req(`/automations/runs/${encodeURIComponent(runId)}/artifacts`);
1097
+ }
1098
+ /** Approve an automation run pending human review. */
1099
+ async approveRun(runId, reason) {
1100
+ return this.req(`/automations/runs/${encodeURIComponent(runId)}/approve`, {
1101
+ method: "POST",
1102
+ body: JSON.stringify({ reason: reason ?? "" })
1103
+ });
1104
+ }
1105
+ /** Deny an automation run pending human review. */
1106
+ async denyRun(runId, reason) {
1107
+ return this.req(`/automations/runs/${encodeURIComponent(runId)}/deny`, {
1108
+ method: "POST",
1109
+ body: JSON.stringify({ reason: reason ?? "" })
1110
+ });
1111
+ }
1112
+ /** Pause an active automation run. */
1113
+ async pauseRun(runId, reason) {
1114
+ return this.req(`/automations/runs/${encodeURIComponent(runId)}/pause`, {
1115
+ method: "POST",
1116
+ body: JSON.stringify({ reason: reason ?? "" })
1117
+ });
1118
+ }
1119
+ /** Resume a paused automation run. */
1120
+ async resumeRun(runId, reason) {
1121
+ return this.req(`/automations/runs/${encodeURIComponent(runId)}/resume`, {
1122
+ method: "POST",
1123
+ body: JSON.stringify({ reason: reason ?? "" })
1124
+ });
1125
+ }
1126
+ /** Get execution history for an automation. */
1127
+ async history(id, limit) {
1128
+ const qs = limit !== void 0 ? `?limit=${limit}` : "";
1129
+ const raw = await this.req(`/automations/${encodeURIComponent(id)}/history${qs}`);
1130
+ if (Array.isArray(raw)) return { history: raw, count: raw.length };
1131
+ return raw;
1132
+ }
1133
+ };
1134
+ var AgentTeams = class {
1135
+ constructor(req) {
1136
+ this.req = req;
1137
+ }
1138
+ /** List available agent team templates. */
1139
+ async listTemplates() {
1140
+ return this.req("/agent-team/templates");
1141
+ }
1142
+ /** List agent team instances. */
1143
+ async listInstances(options) {
1144
+ const params = new URLSearchParams();
1145
+ if (options?.missionID) params.set("missionID", options.missionID);
1146
+ if (options?.parentInstanceID) params.set("parentInstanceID", options.parentInstanceID);
1147
+ if (options?.status) params.set("status", options.status);
1148
+ const qs = params.toString() ? `?${params.toString()}` : "";
1149
+ return this.req(`/agent-team/instances${qs}`);
1150
+ }
1151
+ /** List missions managed by the agent team orchestrator. */
1152
+ async listMissions() {
1153
+ return this.req("/agent-team/missions");
1154
+ }
1155
+ /** List pending spawn and tool approvals. */
1156
+ async listApprovals() {
1157
+ return this.req("/agent-team/approvals");
1158
+ }
1159
+ /**
1160
+ * Spawn a new agent team instance.
1161
+ *
1162
+ * @example
1163
+ * ```typescript
1164
+ * const result = await client.agentTeams.spawn({
1165
+ * missionID: "mission-123",
1166
+ * role: "builder",
1167
+ * justification: "Implementing feature X as planned",
1168
+ * });
1169
+ * ```
1170
+ */
1171
+ async spawn(input) {
1172
+ return this.req("/agent-team/spawn", {
1173
+ method: "POST",
1174
+ body: JSON.stringify(input)
1175
+ });
1176
+ }
1177
+ /** Approve a pending agent team spawn request. */
1178
+ async approveSpawn(approvalId, reason) {
1179
+ return this.req(
1180
+ `/agent-team/approvals/spawn/${encodeURIComponent(approvalId)}/approve`,
1181
+ { method: "POST", body: JSON.stringify({ reason: reason ?? "" }) }
1182
+ );
1183
+ }
1184
+ /** Deny a pending agent team spawn request. */
1185
+ async denySpawn(approvalId, reason) {
1186
+ return this.req(
1187
+ `/agent-team/approvals/spawn/${encodeURIComponent(approvalId)}/deny`,
1188
+ { method: "POST", body: JSON.stringify({ reason: reason ?? "" }) }
1189
+ );
1190
+ }
1191
+ };
1192
+ var Missions = class {
1193
+ constructor(req) {
1194
+ this.req = req;
1195
+ }
1196
+ /** List all missions. */
1197
+ async list() {
1198
+ return this.req("/mission");
1199
+ }
1200
+ /**
1201
+ * Create a new mission with work items.
1202
+ *
1203
+ * @example
1204
+ * ```typescript
1205
+ * const { mission } = await client.missions.create({
1206
+ * title: "Q1 Security Hardening",
1207
+ * goal: "Audit and fix security issues in the API surface",
1208
+ * work_items: [
1209
+ * { title: "Audit auth middleware", assigned_agent: "security-auditor" },
1210
+ * { title: "Review input validation", assigned_agent: "security-auditor" },
1211
+ * ],
1212
+ * });
1213
+ * ```
1214
+ */
1215
+ async create(input) {
1216
+ return this.req("/mission", {
1217
+ method: "POST",
1218
+ body: JSON.stringify(input)
1219
+ });
1220
+ }
1221
+ /** Get a mission by ID. */
1222
+ async get(missionId) {
1223
+ return this.req(`/mission/${encodeURIComponent(missionId)}`);
1224
+ }
1225
+ /** Apply a state event to a mission. */
1226
+ async applyEvent(missionId, event) {
1227
+ return this.req(`/mission/${encodeURIComponent(missionId)}/event`, {
1228
+ method: "POST",
1229
+ body: JSON.stringify({ event })
1230
+ });
1231
+ }
1232
+ };
1233
+ export {
1234
+ TandemClient,
1235
+ TandemValidationError,
1236
+ filterByType,
1237
+ on,
1238
+ streamSse
1239
+ };