@teamlearners/clawops 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.
@@ -0,0 +1,2613 @@
1
+ import { DEFAULT_BASE_URL, AgentError, AgentConnectionError } from '../chunk-UKAT24I6.js';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ // src/agent/audio.ts
6
+ var _BIAS = 132;
7
+ var _CLIP = 32635;
8
+ var DECODE_TABLE = [
9
+ -32124,
10
+ -31100,
11
+ -30076,
12
+ -29052,
13
+ -28028,
14
+ -27004,
15
+ -25980,
16
+ -24956,
17
+ -23932,
18
+ -22908,
19
+ -21884,
20
+ -20860,
21
+ -19836,
22
+ -18812,
23
+ -17788,
24
+ -16764,
25
+ -15996,
26
+ -15484,
27
+ -14972,
28
+ -14460,
29
+ -13948,
30
+ -13436,
31
+ -12924,
32
+ -12412,
33
+ -11900,
34
+ -11388,
35
+ -10876,
36
+ -10364,
37
+ -9852,
38
+ -9340,
39
+ -8828,
40
+ -8316,
41
+ -7932,
42
+ -7676,
43
+ -7420,
44
+ -7164,
45
+ -6908,
46
+ -6652,
47
+ -6396,
48
+ -6140,
49
+ -5884,
50
+ -5628,
51
+ -5372,
52
+ -5116,
53
+ -4860,
54
+ -4604,
55
+ -4348,
56
+ -4092,
57
+ -3900,
58
+ -3772,
59
+ -3644,
60
+ -3516,
61
+ -3388,
62
+ -3260,
63
+ -3132,
64
+ -3004,
65
+ -2876,
66
+ -2748,
67
+ -2620,
68
+ -2492,
69
+ -2364,
70
+ -2236,
71
+ -2108,
72
+ -1980,
73
+ -1884,
74
+ -1820,
75
+ -1756,
76
+ -1692,
77
+ -1628,
78
+ -1564,
79
+ -1500,
80
+ -1436,
81
+ -1372,
82
+ -1308,
83
+ -1244,
84
+ -1180,
85
+ -1116,
86
+ -1052,
87
+ -988,
88
+ -924,
89
+ -876,
90
+ -844,
91
+ -812,
92
+ -780,
93
+ -748,
94
+ -716,
95
+ -684,
96
+ -652,
97
+ -620,
98
+ -588,
99
+ -556,
100
+ -524,
101
+ -492,
102
+ -460,
103
+ -428,
104
+ -396,
105
+ -372,
106
+ -356,
107
+ -340,
108
+ -324,
109
+ -308,
110
+ -292,
111
+ -276,
112
+ -260,
113
+ -244,
114
+ -228,
115
+ -212,
116
+ -196,
117
+ -180,
118
+ -164,
119
+ -148,
120
+ -132,
121
+ -120,
122
+ -112,
123
+ -104,
124
+ -96,
125
+ -88,
126
+ -80,
127
+ -72,
128
+ -64,
129
+ -56,
130
+ -48,
131
+ -40,
132
+ -32,
133
+ -24,
134
+ -16,
135
+ -8,
136
+ 0,
137
+ 32124,
138
+ 31100,
139
+ 30076,
140
+ 29052,
141
+ 28028,
142
+ 27004,
143
+ 25980,
144
+ 24956,
145
+ 23932,
146
+ 22908,
147
+ 21884,
148
+ 20860,
149
+ 19836,
150
+ 18812,
151
+ 17788,
152
+ 16764,
153
+ 15996,
154
+ 15484,
155
+ 14972,
156
+ 14460,
157
+ 13948,
158
+ 13436,
159
+ 12924,
160
+ 12412,
161
+ 11900,
162
+ 11388,
163
+ 10876,
164
+ 10364,
165
+ 9852,
166
+ 9340,
167
+ 8828,
168
+ 8316,
169
+ 7932,
170
+ 7676,
171
+ 7420,
172
+ 7164,
173
+ 6908,
174
+ 6652,
175
+ 6396,
176
+ 6140,
177
+ 5884,
178
+ 5628,
179
+ 5372,
180
+ 5116,
181
+ 4860,
182
+ 4604,
183
+ 4348,
184
+ 4092,
185
+ 3900,
186
+ 3772,
187
+ 3644,
188
+ 3516,
189
+ 3388,
190
+ 3260,
191
+ 3132,
192
+ 3004,
193
+ 2876,
194
+ 2748,
195
+ 2620,
196
+ 2492,
197
+ 2364,
198
+ 2236,
199
+ 2108,
200
+ 1980,
201
+ 1884,
202
+ 1820,
203
+ 1756,
204
+ 1692,
205
+ 1628,
206
+ 1564,
207
+ 1500,
208
+ 1436,
209
+ 1372,
210
+ 1308,
211
+ 1244,
212
+ 1180,
213
+ 1116,
214
+ 1052,
215
+ 988,
216
+ 924,
217
+ 876,
218
+ 844,
219
+ 812,
220
+ 780,
221
+ 748,
222
+ 716,
223
+ 684,
224
+ 652,
225
+ 620,
226
+ 588,
227
+ 556,
228
+ 524,
229
+ 492,
230
+ 460,
231
+ 428,
232
+ 396,
233
+ 372,
234
+ 356,
235
+ 340,
236
+ 324,
237
+ 308,
238
+ 292,
239
+ 276,
240
+ 260,
241
+ 244,
242
+ 228,
243
+ 212,
244
+ 196,
245
+ 180,
246
+ 164,
247
+ 148,
248
+ 132,
249
+ 120,
250
+ 112,
251
+ 104,
252
+ 96,
253
+ 88,
254
+ 80,
255
+ 72,
256
+ 64,
257
+ 56,
258
+ 48,
259
+ 40,
260
+ 32,
261
+ 24,
262
+ 16,
263
+ 8,
264
+ 0
265
+ ];
266
+ function encodeUlawSample(sample) {
267
+ let sign = 0;
268
+ if (sample < 0) {
269
+ sign = 128;
270
+ sample = -sample;
271
+ }
272
+ if (sample > _CLIP) {
273
+ sample = _CLIP;
274
+ }
275
+ sample += _BIAS;
276
+ let exponent = 7;
277
+ const expValues = [16384, 8192, 4096, 2048, 1024, 512, 256];
278
+ for (const expVal of expValues) {
279
+ if (sample >= expVal) {
280
+ break;
281
+ }
282
+ exponent -= 1;
283
+ }
284
+ const mantissa = sample >> exponent + 3 & 15;
285
+ return ~(sign | exponent << 4 | mantissa) & 255;
286
+ }
287
+ function pcm16ToUlaw(pcm) {
288
+ if (!pcm.length) return Buffer.alloc(0);
289
+ const nSamples = pcm.length >> 1;
290
+ const out = Buffer.alloc(nSamples);
291
+ for (let i = 0; i < nSamples; i++) {
292
+ const sample = pcm.readInt16LE(i * 2);
293
+ out[i] = encodeUlawSample(sample);
294
+ }
295
+ return out;
296
+ }
297
+ function ulawToPcm16(ulaw) {
298
+ if (!ulaw.length) return Buffer.alloc(0);
299
+ const out = Buffer.alloc(ulaw.length * 2);
300
+ for (let i = 0; i < ulaw.length; i++) {
301
+ out.writeInt16LE(DECODE_TABLE[ulaw[i]], i * 2);
302
+ }
303
+ return out;
304
+ }
305
+ function resamplePcm16(pcm, fromRate, toRate) {
306
+ if (fromRate === toRate || !pcm.length) return pcm;
307
+ const nSamples = pcm.length >> 1;
308
+ const ratio = fromRate / toRate;
309
+ const outLen = Math.floor(nSamples / ratio);
310
+ const out = Buffer.alloc(outLen * 2);
311
+ for (let i = 0; i < outLen; i++) {
312
+ const srcPos = i * ratio;
313
+ const idx = Math.floor(srcPos);
314
+ const frac = srcPos - idx;
315
+ let val;
316
+ if (idx + 1 < nSamples) {
317
+ val = pcm.readInt16LE(idx * 2) * (1 - frac) + pcm.readInt16LE((idx + 1) * 2) * frac;
318
+ } else {
319
+ val = pcm.readInt16LE(idx * 2);
320
+ }
321
+ out.writeInt16LE(Math.round(val), i * 2);
322
+ }
323
+ return out;
324
+ }
325
+
326
+ // src/agent/control-ws.ts
327
+ var DEFAULT_PATH = "/v1/agent/control";
328
+ var INITIAL_RECONNECT_DELAY = 1e3;
329
+ var MAX_RECONNECT_DELAY = 3e4;
330
+ function buildControlWsUrl(options) {
331
+ const base = options.baseUrl.replace(/^http/, "ws").replace(/\/$/, "");
332
+ const path2 = options.path ?? DEFAULT_PATH;
333
+ return `${base}${path2}?api_key=${encodeURIComponent(options.apiKey)}&agent_id=${encodeURIComponent(options.agentId)}`;
334
+ }
335
+ var ControlWebSocket = class {
336
+ constructor(_options) {
337
+ this._options = _options;
338
+ this._url = buildControlWsUrl(_options);
339
+ this._connectedPromise = new Promise((resolve) => {
340
+ this._connectedResolve = resolve;
341
+ });
342
+ }
343
+ _url;
344
+ _ws = null;
345
+ _handlers = /* @__PURE__ */ new Map();
346
+ _reconnectDelay = INITIAL_RECONNECT_DELAY;
347
+ _closed = false;
348
+ _connectedResolve = null;
349
+ _connectedPromise;
350
+ /** Register an event handler for a specific event type. */
351
+ on(event, handler) {
352
+ let list = this._handlers.get(event);
353
+ if (!list) {
354
+ list = [];
355
+ this._handlers.set(event, list);
356
+ }
357
+ list.push(handler);
358
+ }
359
+ /** Connect to the control WebSocket. */
360
+ async connect() {
361
+ this._closed = false;
362
+ await this._doConnect();
363
+ }
364
+ /** Wait until the WebSocket is connected. */
365
+ async waitConnected() {
366
+ return this._connectedPromise;
367
+ }
368
+ /** Send a JSON message over the control WebSocket. */
369
+ send(message) {
370
+ if (this._ws && this._ws.readyState === 1) {
371
+ this._ws.send(JSON.stringify(message));
372
+ }
373
+ }
374
+ /** Close the WebSocket and stop reconnecting. */
375
+ close() {
376
+ this._closed = true;
377
+ if (this._ws) {
378
+ this._ws.close();
379
+ this._ws = null;
380
+ }
381
+ }
382
+ async _doConnect() {
383
+ const { WebSocket } = await import('ws');
384
+ const ws = new WebSocket(this._url);
385
+ this._ws = ws;
386
+ ws.on("open", () => {
387
+ this._reconnectDelay = INITIAL_RECONNECT_DELAY;
388
+ if (this._connectedResolve) {
389
+ this._connectedResolve();
390
+ this._connectedResolve = null;
391
+ }
392
+ });
393
+ ws.on("message", (data) => {
394
+ try {
395
+ const msg = JSON.parse(data.toString());
396
+ this._dispatchEvent(msg);
397
+ } catch {
398
+ console.error("[ControlWebSocket] Failed to parse message");
399
+ }
400
+ });
401
+ ws.on("close", () => {
402
+ if (!this._closed) {
403
+ this._scheduleReconnect();
404
+ }
405
+ });
406
+ ws.on("error", (err) => {
407
+ console.error("[ControlWebSocket] Error:", err.message);
408
+ });
409
+ }
410
+ _dispatchEvent(event) {
411
+ const handlers = this._handlers.get(event.event);
412
+ if (handlers) {
413
+ for (const handler of handlers) {
414
+ try {
415
+ const result = handler(event);
416
+ if (result && typeof result.catch === "function") {
417
+ result.catch((err) => {
418
+ console.error(`[ControlWebSocket] Error in handler for ${event.event}:`, err);
419
+ });
420
+ }
421
+ } catch (err) {
422
+ console.error(`[ControlWebSocket] Error in handler for ${event.event}:`, err);
423
+ }
424
+ }
425
+ }
426
+ }
427
+ _scheduleReconnect() {
428
+ const delay = this._reconnectDelay;
429
+ this._reconnectDelay = Math.min(this._reconnectDelay * 2, MAX_RECONNECT_DELAY);
430
+ setTimeout(() => {
431
+ if (!this._closed) {
432
+ this._doConnect().catch((err) => {
433
+ console.error("[ControlWebSocket] Reconnect failed:", err);
434
+ this._scheduleReconnect();
435
+ });
436
+ }
437
+ }, delay);
438
+ }
439
+ };
440
+
441
+ // src/agent/mcp/client.ts
442
+ var MCPClient = class {
443
+ _servers = /* @__PURE__ */ new Map();
444
+ _clients = /* @__PURE__ */ new Map();
445
+ /** Add an MCP server configuration. */
446
+ addServer(name, config) {
447
+ this._servers.set(name, config);
448
+ }
449
+ /** Connect to all configured MCP servers and discover tools. */
450
+ async connect() {
451
+ const allTools = [];
452
+ for (const [name, config] of this._servers) {
453
+ try {
454
+ const tools = await this._connectServer(name, config);
455
+ allTools.push(...tools);
456
+ } catch (err) {
457
+ console.error(`[MCPClient] Failed to connect to server '${name}':`, err);
458
+ }
459
+ }
460
+ return allTools;
461
+ }
462
+ /** Disconnect from all MCP servers. */
463
+ async disconnect() {
464
+ for (const [name, client] of this._clients) {
465
+ try {
466
+ const c = client;
467
+ if (c.close) {
468
+ await c.close();
469
+ }
470
+ } catch (err) {
471
+ console.error(`[MCPClient] Error disconnecting from '${name}':`, err);
472
+ }
473
+ }
474
+ this._clients.clear();
475
+ }
476
+ async _connectServer(name, config) {
477
+ const sdk = await import('@modelcontextprotocol/sdk/client/index.js');
478
+ const { Client } = sdk;
479
+ const client = new Client({ name: `clawops-${name}`, version: "1.0.0" });
480
+ let transport;
481
+ if (config.type === "stdio") {
482
+ const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js');
483
+ transport = new StdioClientTransport({
484
+ command: config["command"],
485
+ args: config["args"] ?? [],
486
+ env: config["env"]
487
+ });
488
+ } else if (config.type === "http") {
489
+ const { StreamableHTTPClientTransport } = await import('@modelcontextprotocol/sdk/client/streamableHttp.js');
490
+ transport = new StreamableHTTPClientTransport(
491
+ new URL(config["url"])
492
+ );
493
+ } else {
494
+ throw new Error(`Unsupported MCP server type: ${config.type}`);
495
+ }
496
+ await client.connect(transport);
497
+ this._clients.set(name, client);
498
+ const toolsResult = await client.listTools();
499
+ const tools = [];
500
+ for (const toolDef of toolsResult.tools) {
501
+ const inputSchema = toolDef.inputSchema;
502
+ tools.push({
503
+ name: toolDef.name,
504
+ description: toolDef.description ?? "",
505
+ parameters: inputSchema.properties ?? {},
506
+ required: inputSchema.required ?? [],
507
+ handler: async (args) => {
508
+ const result = await client.callTool({
509
+ name: toolDef.name,
510
+ arguments: args
511
+ });
512
+ const content = result.content;
513
+ if (content && content.length > 0) {
514
+ return content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
515
+ }
516
+ return JSON.stringify(result);
517
+ }
518
+ });
519
+ }
520
+ return tools;
521
+ }
522
+ };
523
+
524
+ // src/agent/media-ws.ts
525
+ function parseStartEvent(data) {
526
+ const start = data["start"];
527
+ return {
528
+ streamSid: start["streamSid"] ?? data["streamSid"] ?? "",
529
+ callSid: start["callSid"] ?? "",
530
+ accountSid: start["accountSid"] ?? "",
531
+ tracks: start["tracks"] ?? [],
532
+ customParameters: start["customParameters"] ?? {},
533
+ mediaFormat: start["mediaFormat"] ?? {
534
+ encoding: "audio/x-mulaw",
535
+ sampleRate: 8e3,
536
+ channels: 1
537
+ }
538
+ };
539
+ }
540
+ function parseMediaEvent(data) {
541
+ const media = data["media"];
542
+ return {
543
+ track: media["track"] ?? "inbound",
544
+ chunk: media["chunk"] ?? media["payload"] ?? "",
545
+ timestamp: media["timestamp"] ?? ""
546
+ };
547
+ }
548
+ function buildMediaResponse(streamSid, payload) {
549
+ return JSON.stringify({
550
+ event: "media",
551
+ streamSid,
552
+ media: {
553
+ payload
554
+ }
555
+ });
556
+ }
557
+ var MediaWebSocket = class {
558
+ _ws = null;
559
+ _streamSid = null;
560
+ _audioQueue = [];
561
+ _sendLoopRunning = false;
562
+ _closed = false;
563
+ _onAudio = null;
564
+ _onStart = null;
565
+ _onClose = null;
566
+ _markSeq = 0;
567
+ _markResolves = /* @__PURE__ */ new Map();
568
+ /** Set the handler for inbound audio data. */
569
+ onAudio(handler) {
570
+ this._onAudio = handler;
571
+ }
572
+ /** Set the handler for the start event. */
573
+ onStart(handler) {
574
+ this._onStart = handler;
575
+ }
576
+ /** Set the handler for connection close. */
577
+ onClose(handler) {
578
+ this._onClose = handler;
579
+ }
580
+ /** Connect to a media WebSocket URL. */
581
+ async connect(url) {
582
+ const { WebSocket } = await import('ws');
583
+ return new Promise((resolve, reject) => {
584
+ const ws = new WebSocket(url);
585
+ this._ws = ws;
586
+ this._closed = false;
587
+ ws.on("open", () => {
588
+ this._startSendLoop();
589
+ resolve();
590
+ });
591
+ ws.on("message", (data) => {
592
+ try {
593
+ const msg = JSON.parse(data.toString());
594
+ this._handleMessage(msg);
595
+ } catch {
596
+ }
597
+ });
598
+ ws.on("close", () => {
599
+ this._closed = true;
600
+ if (this._onClose) {
601
+ this._onClose();
602
+ }
603
+ });
604
+ ws.on("error", (err) => {
605
+ if (!this._ws) {
606
+ reject(err);
607
+ }
608
+ console.error("[MediaWebSocket] Error:", err.message);
609
+ });
610
+ });
611
+ }
612
+ /** Queue base64-encoded audio for sending. */
613
+ sendAudio(audioBase64) {
614
+ this._audioQueue.push(audioBase64);
615
+ }
616
+ /** Clear all queued outbound audio. */
617
+ sendClear() {
618
+ this._audioQueue.length = 0;
619
+ if (this._ws && this._streamSid && this._ws.readyState === 1) {
620
+ this._ws.send(
621
+ JSON.stringify({
622
+ event: "clear",
623
+ streamSid: this._streamSid
624
+ })
625
+ );
626
+ }
627
+ }
628
+ /** Send a mark event and return a promise that resolves when the mark is acknowledged. */
629
+ sendMark() {
630
+ const label = `mark_${++this._markSeq}`;
631
+ return new Promise((resolve) => {
632
+ this._markResolves.set(label, resolve);
633
+ if (this._ws && this._streamSid && this._ws.readyState === 1) {
634
+ this._ws.send(
635
+ JSON.stringify({
636
+ event: "mark",
637
+ streamSid: this._streamSid,
638
+ mark: { name: label }
639
+ })
640
+ );
641
+ }
642
+ });
643
+ }
644
+ /** Close the media WebSocket. */
645
+ close() {
646
+ this._closed = true;
647
+ if (this._ws) {
648
+ this._ws.close();
649
+ this._ws = null;
650
+ }
651
+ for (const resolve of this._markResolves.values()) {
652
+ resolve();
653
+ }
654
+ this._markResolves.clear();
655
+ }
656
+ _handleMessage(msg) {
657
+ const event = msg["event"];
658
+ switch (event) {
659
+ case "start": {
660
+ const startEvt = parseStartEvent(msg);
661
+ this._streamSid = startEvt.streamSid;
662
+ if (this._onStart) {
663
+ this._onStart(startEvt);
664
+ }
665
+ break;
666
+ }
667
+ case "media": {
668
+ const mediaEvt = parseMediaEvent(msg);
669
+ if (mediaEvt.track === "inbound" && this._onAudio) {
670
+ const audioBuf = Buffer.from(mediaEvt.chunk, "base64");
671
+ this._onAudio(audioBuf);
672
+ }
673
+ break;
674
+ }
675
+ case "mark": {
676
+ const mark = msg["mark"];
677
+ const name = mark?.["name"];
678
+ if (name && this._markResolves.has(name)) {
679
+ this._markResolves.get(name)();
680
+ this._markResolves.delete(name);
681
+ }
682
+ break;
683
+ }
684
+ case "stop": {
685
+ this.close();
686
+ break;
687
+ }
688
+ }
689
+ }
690
+ _startSendLoop() {
691
+ if (this._sendLoopRunning) return;
692
+ this._sendLoopRunning = true;
693
+ const loop = () => {
694
+ if (this._closed) {
695
+ this._sendLoopRunning = false;
696
+ return;
697
+ }
698
+ while (this._audioQueue.length > 0 && this._ws && this._ws.readyState === 1) {
699
+ const payload = this._audioQueue.shift();
700
+ if (this._streamSid) {
701
+ this._ws.send(buildMediaResponse(this._streamSid, payload));
702
+ }
703
+ }
704
+ setTimeout(loop, 20);
705
+ };
706
+ loop();
707
+ }
708
+ };
709
+ var WAV_SAMPLE_RATE = 8e3;
710
+ var WAV_CHANNELS = 1;
711
+ var WAV_BITS_PER_SAMPLE = 16;
712
+ function makeWavHeader(dataSize) {
713
+ const header = Buffer.alloc(44);
714
+ const byteRate = WAV_SAMPLE_RATE * WAV_CHANNELS * (WAV_BITS_PER_SAMPLE / 8);
715
+ const blockAlign = WAV_CHANNELS * (WAV_BITS_PER_SAMPLE / 8);
716
+ header.write("RIFF", 0);
717
+ header.writeUInt32LE(36 + dataSize, 4);
718
+ header.write("WAVE", 8);
719
+ header.write("fmt ", 12);
720
+ header.writeUInt32LE(16, 16);
721
+ header.writeUInt16LE(1, 20);
722
+ header.writeUInt16LE(WAV_CHANNELS, 22);
723
+ header.writeUInt32LE(WAV_SAMPLE_RATE, 24);
724
+ header.writeUInt32LE(byteRate, 28);
725
+ header.writeUInt16LE(blockAlign, 32);
726
+ header.writeUInt16LE(WAV_BITS_PER_SAMPLE, 34);
727
+ header.write("data", 36);
728
+ header.writeUInt32LE(dataSize, 40);
729
+ return header;
730
+ }
731
+ var AudioRecorder = class {
732
+ _outputDir;
733
+ _sampleRate;
734
+ _inboundChunks = [];
735
+ _outboundChunks = [];
736
+ _callId = null;
737
+ _started = false;
738
+ constructor(options) {
739
+ this._outputDir = options.outputDir;
740
+ this._sampleRate = options.sampleRate ?? WAV_SAMPLE_RATE;
741
+ }
742
+ /** Start recording for a call. */
743
+ start(callId) {
744
+ this._callId = callId;
745
+ this._inboundChunks = [];
746
+ this._outboundChunks = [];
747
+ this._started = true;
748
+ if (!fs.existsSync(this._outputDir)) {
749
+ fs.mkdirSync(this._outputDir, { recursive: true });
750
+ }
751
+ }
752
+ /** Write inbound (caller) PCM16 audio. */
753
+ writeInbound(pcm) {
754
+ if (this._started) {
755
+ this._inboundChunks.push(Buffer.from(pcm));
756
+ }
757
+ }
758
+ /** Write raw outbound (agent) PCM16 audio. */
759
+ writeRawOutbound(pcm) {
760
+ if (this._started) {
761
+ this._outboundChunks.push(Buffer.from(pcm));
762
+ }
763
+ }
764
+ /** Stop recording and write WAV files to disk. Returns file paths. */
765
+ stop() {
766
+ if (!this._started || !this._callId) {
767
+ return {};
768
+ }
769
+ this._started = false;
770
+ const result = {};
771
+ if (this._inboundChunks.length > 0) {
772
+ const inboundPath = path.join(this._outputDir, `${this._callId}_inbound.wav`);
773
+ this._writeWav(inboundPath, this._inboundChunks);
774
+ result.inbound = inboundPath;
775
+ }
776
+ if (this._outboundChunks.length > 0) {
777
+ const outboundPath = path.join(this._outputDir, `${this._callId}_outbound.wav`);
778
+ this._writeWav(outboundPath, this._outboundChunks);
779
+ result.outbound = outboundPath;
780
+ }
781
+ this._inboundChunks = [];
782
+ this._outboundChunks = [];
783
+ return result;
784
+ }
785
+ _writeWav(filePath, chunks) {
786
+ const pcmData = Buffer.concat(chunks);
787
+ const header = makeWavHeader(pcmData.length);
788
+ const fd = fs.openSync(filePath, "w");
789
+ fs.writeSync(fd, header);
790
+ fs.writeSync(fd, pcmData);
791
+ fs.closeSync(fd);
792
+ }
793
+ };
794
+
795
+ // src/agent/session.ts
796
+ var CallSession = class {
797
+ callId;
798
+ fromNumber;
799
+ toNumber;
800
+ accountId;
801
+ direction;
802
+ startTime;
803
+ metadata;
804
+ _status;
805
+ _sendAudioFn = null;
806
+ _clearAudioFn = null;
807
+ _hangupFn = null;
808
+ _handlers = /* @__PURE__ */ new Map();
809
+ _endedPromise;
810
+ _resolveEnded;
811
+ constructor(options) {
812
+ this.callId = options.callId;
813
+ this.fromNumber = options.fromNumber;
814
+ this.toNumber = options.toNumber;
815
+ this.accountId = options.accountId;
816
+ this.direction = options.direction;
817
+ this.metadata = options.metadata ?? {};
818
+ this._status = "ringing";
819
+ this.startTime = /* @__PURE__ */ new Date();
820
+ this._endedPromise = new Promise((resolve) => {
821
+ this._resolveEnded = resolve;
822
+ });
823
+ }
824
+ get status() {
825
+ return this._status;
826
+ }
827
+ get duration() {
828
+ return (Date.now() - this.startTime.getTime()) / 1e3;
829
+ }
830
+ /** Bind transport functions (called internally by the agent). */
831
+ _bindTransport(send, clear, hangup) {
832
+ this._sendAudioFn = send;
833
+ this._clearAudioFn = clear;
834
+ this._hangupFn = hangup;
835
+ this._status = "active";
836
+ }
837
+ /** Send PCM16 or ulaw audio to the caller. */
838
+ sendAudio(audio) {
839
+ if (this._sendAudioFn) {
840
+ this._sendAudioFn(audio);
841
+ }
842
+ }
843
+ /** Clear any queued outbound audio. */
844
+ clearAudio() {
845
+ if (this._clearAudioFn) {
846
+ this._clearAudioFn();
847
+ }
848
+ }
849
+ /** Hang up the call. */
850
+ hangup() {
851
+ if (this._hangupFn) {
852
+ this._hangupFn();
853
+ }
854
+ }
855
+ /** Register an event handler. */
856
+ on(event, handler) {
857
+ let list = this._handlers.get(event);
858
+ if (!list) {
859
+ list = [];
860
+ this._handlers.set(event, list);
861
+ }
862
+ list.push(handler);
863
+ }
864
+ /** Wait for the call to end. */
865
+ async wait() {
866
+ return this._endedPromise;
867
+ }
868
+ /** Mark the session as ended (called internally). */
869
+ _markEnded() {
870
+ this._status = "ended";
871
+ this._emit({ type: "ended" });
872
+ this._resolveEnded();
873
+ }
874
+ /** Emit an event to registered handlers. */
875
+ _emit(event) {
876
+ const handlers = this._handlers.get(event.type);
877
+ if (handlers) {
878
+ for (const handler of handlers) {
879
+ try {
880
+ const result = handler(event);
881
+ if (result && typeof result.catch === "function") {
882
+ result.catch((err) => {
883
+ console.error(`[CallSession] Error in ${event.type} handler:`, err);
884
+ });
885
+ }
886
+ } catch (err) {
887
+ console.error(`[CallSession] Error in ${event.type} handler:`, err);
888
+ }
889
+ }
890
+ }
891
+ }
892
+ };
893
+
894
+ // src/agent/tool.ts
895
+ function functionTool(fn) {
896
+ return fn;
897
+ }
898
+ var ToolRegistry = class _ToolRegistry {
899
+ _tools = /* @__PURE__ */ new Map();
900
+ _mcpTools = /* @__PURE__ */ new Map();
901
+ /** Register a function tool. */
902
+ register(tool) {
903
+ this._tools.set(tool.name, tool);
904
+ }
905
+ /** Register tools discovered from MCP servers. */
906
+ registerMcpTools(tools) {
907
+ for (const tool of tools) {
908
+ this._mcpTools.set(tool.name, tool);
909
+ }
910
+ }
911
+ /** Clear all MCP-registered tools. */
912
+ clearMcpTools() {
913
+ this._mcpTools.clear();
914
+ }
915
+ /**
916
+ * Create a fork (shallow copy) of this registry.
917
+ * Useful for per-session tool isolation.
918
+ */
919
+ fork() {
920
+ const copy = new _ToolRegistry();
921
+ for (const [name, tool] of this._tools) {
922
+ copy._tools.set(name, tool);
923
+ }
924
+ for (const [name, tool] of this._mcpTools) {
925
+ copy._mcpTools.set(name, tool);
926
+ }
927
+ return copy;
928
+ }
929
+ /** Check if a tool is registered. */
930
+ has(name) {
931
+ return this._tools.has(name) || this._mcpTools.has(name);
932
+ }
933
+ /** Get a tool by name. */
934
+ get(name) {
935
+ return this._tools.get(name) ?? this._mcpTools.get(name);
936
+ }
937
+ /** Convert all registered tools to OpenAI function-calling format. */
938
+ toOpenAITools() {
939
+ const result = [];
940
+ const allTools = [...this._tools.values(), ...this._mcpTools.values()];
941
+ for (const tool of allTools) {
942
+ result.push({
943
+ type: "function",
944
+ function: {
945
+ name: tool.name,
946
+ description: tool.description,
947
+ parameters: {
948
+ type: "object",
949
+ properties: tool.parameters,
950
+ required: tool.required
951
+ }
952
+ }
953
+ });
954
+ }
955
+ return result;
956
+ }
957
+ /** Call a tool by name with the given arguments. */
958
+ async call(name, args) {
959
+ const tool = this.get(name);
960
+ if (!tool) {
961
+ throw new Error(`Tool '${name}' not found in registry`);
962
+ }
963
+ return tool.handler(args);
964
+ }
965
+ /** Get the number of registered tools (including MCP). */
966
+ get size() {
967
+ return this._tools.size + this._mcpTools.size;
968
+ }
969
+ };
970
+ function zodToToolParams(schema) {
971
+ const shape = schema.shape;
972
+ const parameters = {};
973
+ const required = [];
974
+ for (const [key, field] of Object.entries(shape)) {
975
+ const zodField = field;
976
+ const desc = zodField.description;
977
+ let isOptional = false;
978
+ let innerType = zodField;
979
+ if ("unwrap" in zodField && zodField._def?.typeName === "ZodOptional") {
980
+ isOptional = true;
981
+ innerType = zodField.unwrap();
982
+ }
983
+ const jsonSchema = {};
984
+ const typeName = innerType._def?.typeName;
985
+ switch (typeName) {
986
+ case "ZodString":
987
+ jsonSchema.type = "string";
988
+ break;
989
+ case "ZodNumber":
990
+ jsonSchema.type = "number";
991
+ break;
992
+ case "ZodBoolean":
993
+ jsonSchema.type = "boolean";
994
+ break;
995
+ case "ZodArray":
996
+ jsonSchema.type = "array";
997
+ break;
998
+ case "ZodEnum":
999
+ jsonSchema.type = "string";
1000
+ jsonSchema.enum = innerType._def?.values;
1001
+ break;
1002
+ default:
1003
+ jsonSchema.type = "string";
1004
+ }
1005
+ if (desc) {
1006
+ jsonSchema.description = desc;
1007
+ }
1008
+ parameters[key] = jsonSchema;
1009
+ if (!isOptional) {
1010
+ required.push(key);
1011
+ }
1012
+ }
1013
+ return { parameters, required };
1014
+ }
1015
+
1016
+ // src/agent/tracing/config.ts
1017
+ var DEFAULT_CONFIG = {
1018
+ enabled: false,
1019
+ serviceName: "clawops-agent"
1020
+ };
1021
+ var _currentConfig = { ...DEFAULT_CONFIG };
1022
+ function getTracingConfig() {
1023
+ return _currentConfig;
1024
+ }
1025
+ function setTracingConfig(config) {
1026
+ _currentConfig = { ..._currentConfig, ...config };
1027
+ }
1028
+ function resetTracingConfig() {
1029
+ _currentConfig = { ...DEFAULT_CONFIG };
1030
+ }
1031
+
1032
+ // src/agent/tracing/spans.ts
1033
+ var _tracer = null;
1034
+ async function getTracer() {
1035
+ const config = getTracingConfig();
1036
+ if (!config.enabled) return null;
1037
+ if (_tracer) return _tracer;
1038
+ try {
1039
+ const otel = await import('@opentelemetry/api');
1040
+ const provider = config.tracerProvider ?? otel.trace.getTracerProvider();
1041
+ _tracer = provider.getTracer(
1042
+ config.serviceName ?? "clawops-agent",
1043
+ "1.0.0"
1044
+ );
1045
+ return _tracer;
1046
+ } catch {
1047
+ return null;
1048
+ }
1049
+ }
1050
+ async function withSpan(name, attributes, fn) {
1051
+ const tracer = await getTracer();
1052
+ if (!tracer) {
1053
+ return fn(createNoOpSpan());
1054
+ }
1055
+ const span = tracer.startSpan(name, { attributes });
1056
+ try {
1057
+ const result = await fn(span);
1058
+ span.setStatus({ code: 1 });
1059
+ return result;
1060
+ } catch (err) {
1061
+ span.setStatus({ code: 2, message: String(err) });
1062
+ if (err instanceof Error) {
1063
+ span.recordException(err);
1064
+ }
1065
+ throw err;
1066
+ } finally {
1067
+ span.end();
1068
+ }
1069
+ }
1070
+ function createNoOpSpan() {
1071
+ return {
1072
+ setAttribute() {
1073
+ },
1074
+ setStatus() {
1075
+ },
1076
+ recordException() {
1077
+ },
1078
+ end() {
1079
+ }
1080
+ };
1081
+ }
1082
+
1083
+ // src/agent/tracing/attributes.ts
1084
+ var ATTR_CALL_ID = "clawops.call.id";
1085
+ var ATTR_CALL_DIRECTION = "clawops.call.direction";
1086
+ var ATTR_AGENT_ID = "clawops.agent.id";
1087
+
1088
+ // src/agent/agent.ts
1089
+ var ClawOpsAgent = class {
1090
+ _apiKey;
1091
+ _agentId;
1092
+ _baseUrl;
1093
+ _sessionFactory = null;
1094
+ _tools = new ToolRegistry();
1095
+ _handlers = /* @__PURE__ */ new Map();
1096
+ _controlWs = null;
1097
+ _mcpClient = null;
1098
+ _recordingDir;
1099
+ _activeSessions = /* @__PURE__ */ new Map();
1100
+ constructor(options = {}) {
1101
+ this._apiKey = options.apiKey ?? process.env["CLAWOPS_API_KEY"] ?? "";
1102
+ this._agentId = options.agentId ?? process.env["CLAWOPS_AGENT_ID"] ?? "";
1103
+ this._baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
1104
+ this._recordingDir = options.recordingDir ?? null;
1105
+ if (options.session) {
1106
+ if (typeof options.session === "function") {
1107
+ this._sessionFactory = options.session;
1108
+ } else {
1109
+ const sessionInstance = options.session;
1110
+ this._sessionFactory = () => sessionInstance;
1111
+ }
1112
+ }
1113
+ if (options.mcpServers) {
1114
+ this._mcpClient = new MCPClient();
1115
+ for (const [name, config] of Object.entries(options.mcpServers)) {
1116
+ this._mcpClient.addServer(name, config);
1117
+ }
1118
+ }
1119
+ if (options.tracing) {
1120
+ setTracingConfig(options.tracing);
1121
+ }
1122
+ }
1123
+ /** Register a function tool. */
1124
+ tool(tool) {
1125
+ this._tools.register(tool);
1126
+ return this;
1127
+ }
1128
+ /** Register an event handler. */
1129
+ on(event, handler) {
1130
+ let list = this._handlers.get(event);
1131
+ if (!list) {
1132
+ list = [];
1133
+ this._handlers.set(event, list);
1134
+ }
1135
+ list.push(handler);
1136
+ return this;
1137
+ }
1138
+ /** Connect to the ClawOps platform and start listening for calls. */
1139
+ async connect() {
1140
+ if (!this._apiKey) {
1141
+ throw new AgentError("API key is required. Set CLAWOPS_API_KEY or pass apiKey option.");
1142
+ }
1143
+ if (!this._agentId) {
1144
+ throw new AgentError("Agent ID is required. Set CLAWOPS_AGENT_ID or pass agentId option.");
1145
+ }
1146
+ if (this._mcpClient) {
1147
+ try {
1148
+ const mcpTools = await this._mcpClient.connect();
1149
+ this._tools.registerMcpTools(mcpTools);
1150
+ } catch (err) {
1151
+ console.error("[ClawOpsAgent] MCP connection error:", err);
1152
+ }
1153
+ }
1154
+ this._controlWs = new ControlWebSocket({
1155
+ baseUrl: this._baseUrl,
1156
+ apiKey: this._apiKey,
1157
+ agentId: this._agentId
1158
+ });
1159
+ this._controlWs.on("call.incoming", (event) => this._handleIncoming(event));
1160
+ this._controlWs.on("call.ended", (event) => this._handleEnded(event));
1161
+ this._controlWs.on("call.outbound_ready", (event) => this._handleOutboundReady(event));
1162
+ this._controlWs.on("call.ringing", (event) => this._handleRinging(event));
1163
+ this._controlWs.on("call.failed", (event) => this._handleFailed(event));
1164
+ try {
1165
+ await this._controlWs.connect();
1166
+ await this._controlWs.waitConnected();
1167
+ } catch (err) {
1168
+ throw new AgentConnectionError(
1169
+ `Failed to connect to ClawOps: ${err instanceof Error ? err.message : String(err)}`
1170
+ );
1171
+ }
1172
+ }
1173
+ /**
1174
+ * Connect and block until disconnected.
1175
+ * Convenience method for simple agent scripts.
1176
+ */
1177
+ async serve() {
1178
+ await this.connect();
1179
+ return new Promise((resolve) => {
1180
+ const shutdown = () => {
1181
+ this.disconnect().then(resolve).catch(() => resolve());
1182
+ };
1183
+ process.on("SIGINT", shutdown);
1184
+ process.on("SIGTERM", shutdown);
1185
+ });
1186
+ }
1187
+ /** Disconnect from the platform. */
1188
+ async disconnect() {
1189
+ if (this._controlWs) {
1190
+ this._controlWs.close();
1191
+ this._controlWs = null;
1192
+ }
1193
+ for (const session of this._activeSessions.values()) {
1194
+ session._markEnded();
1195
+ }
1196
+ this._activeSessions.clear();
1197
+ if (this._mcpClient) {
1198
+ await this._mcpClient.disconnect();
1199
+ this._tools.clearMcpTools();
1200
+ }
1201
+ }
1202
+ /**
1203
+ * Initiate an outbound call.
1204
+ */
1205
+ call(options) {
1206
+ if (!this._controlWs) {
1207
+ throw new AgentError("Agent is not connected. Call connect() first.");
1208
+ }
1209
+ this._controlWs.send({
1210
+ action: "call.create",
1211
+ data: {
1212
+ to_number: options.to,
1213
+ from_number: options.from,
1214
+ metadata: options.metadata
1215
+ }
1216
+ });
1217
+ }
1218
+ _handleIncoming(event) {
1219
+ const data = event.data;
1220
+ const session = new CallSession({
1221
+ callId: data.call_id,
1222
+ fromNumber: data.from_number ?? "",
1223
+ toNumber: data.to_number ?? "",
1224
+ accountId: data.account_id ?? "",
1225
+ direction: data.direction ?? "inbound",
1226
+ metadata: data.metadata
1227
+ });
1228
+ this._activeSessions.set(data.call_id, session);
1229
+ this._emitEvent("call.incoming", session);
1230
+ if (data.media_ws_url) {
1231
+ this._startCallSession(session, data.media_ws_url).catch((err) => {
1232
+ console.error(`[ClawOpsAgent] Error in call session ${data.call_id}:`, err);
1233
+ });
1234
+ }
1235
+ }
1236
+ _handleEnded(event) {
1237
+ const session = this._activeSessions.get(event.data.call_id);
1238
+ if (session) {
1239
+ session._markEnded();
1240
+ this._activeSessions.delete(event.data.call_id);
1241
+ this._emitEvent("call.ended", session);
1242
+ }
1243
+ }
1244
+ _handleOutboundReady(event) {
1245
+ const data = event.data;
1246
+ const session = new CallSession({
1247
+ callId: data.call_id,
1248
+ fromNumber: data.from_number ?? "",
1249
+ toNumber: data.to_number ?? "",
1250
+ accountId: data.account_id ?? "",
1251
+ direction: "outbound",
1252
+ metadata: data.metadata
1253
+ });
1254
+ this._activeSessions.set(data.call_id, session);
1255
+ this._emitEvent("call.outbound_ready", session);
1256
+ if (data.media_ws_url) {
1257
+ this._startCallSession(session, data.media_ws_url).catch((err) => {
1258
+ console.error(`[ClawOpsAgent] Error in call session ${data.call_id}:`, err);
1259
+ });
1260
+ }
1261
+ }
1262
+ _handleRinging(event) {
1263
+ const session = this._activeSessions.get(event.data.call_id);
1264
+ if (session) {
1265
+ this._emitEvent("call.ringing", session);
1266
+ }
1267
+ }
1268
+ _handleFailed(event) {
1269
+ const session = this._activeSessions.get(event.data.call_id);
1270
+ if (session) {
1271
+ session._markEnded();
1272
+ this._activeSessions.delete(event.data.call_id);
1273
+ this._emitEvent("call.failed", session);
1274
+ }
1275
+ }
1276
+ async _startCallSession(session, mediaWsUrl) {
1277
+ await withSpan(
1278
+ "clawops.call_session",
1279
+ {
1280
+ [ATTR_CALL_ID]: session.callId,
1281
+ [ATTR_CALL_DIRECTION]: session.direction,
1282
+ [ATTR_AGENT_ID]: this._agentId
1283
+ },
1284
+ async () => {
1285
+ const sessionTools = this._tools.fork();
1286
+ let recorder = null;
1287
+ if (this._recordingDir) {
1288
+ recorder = new AudioRecorder({ outputDir: this._recordingDir });
1289
+ recorder.start(session.callId);
1290
+ }
1291
+ const mediaWs = new MediaWebSocket();
1292
+ session._bindTransport(
1293
+ (audio) => {
1294
+ const ulaw = pcm16ToUlaw(audio);
1295
+ mediaWs.sendAudio(ulaw.toString("base64"));
1296
+ if (recorder) {
1297
+ recorder.writeRawOutbound(audio);
1298
+ }
1299
+ },
1300
+ () => {
1301
+ mediaWs.sendClear();
1302
+ },
1303
+ () => {
1304
+ mediaWs.close();
1305
+ }
1306
+ );
1307
+ const sessionHandler = this._sessionFactory ? this._sessionFactory() : null;
1308
+ mediaWs.onAudio((ulawAudio) => {
1309
+ const pcm = ulawToPcm16(ulawAudio);
1310
+ if (recorder) {
1311
+ recorder.writeInbound(pcm);
1312
+ }
1313
+ if (sessionHandler) {
1314
+ const resampled = resamplePcm16(pcm, 8e3, 16e3);
1315
+ sessionHandler.feedAudio(resampled);
1316
+ }
1317
+ });
1318
+ mediaWs.onClose(() => {
1319
+ if (recorder) {
1320
+ recorder.stop();
1321
+ }
1322
+ session._markEnded();
1323
+ });
1324
+ try {
1325
+ await mediaWs.connect(mediaWsUrl);
1326
+ if (sessionHandler) {
1327
+ await sessionHandler.start(session, sessionTools);
1328
+ }
1329
+ await session.wait();
1330
+ if (sessionHandler) {
1331
+ await sessionHandler.stop();
1332
+ }
1333
+ } catch (err) {
1334
+ console.error(`[ClawOpsAgent] Call session error:`, err);
1335
+ } finally {
1336
+ mediaWs.close();
1337
+ if (recorder) {
1338
+ recorder.stop();
1339
+ }
1340
+ }
1341
+ }
1342
+ );
1343
+ }
1344
+ _emitEvent(event, session) {
1345
+ const handlers = this._handlers.get(event);
1346
+ if (handlers) {
1347
+ for (const handler of handlers) {
1348
+ try {
1349
+ const result = handler(session);
1350
+ if (result && typeof result.catch === "function") {
1351
+ result.catch((err) => {
1352
+ console.error(`[ClawOpsAgent] Error in ${event} handler:`, err);
1353
+ });
1354
+ }
1355
+ } catch (err) {
1356
+ console.error(`[ClawOpsAgent] Error in ${event} handler:`, err);
1357
+ }
1358
+ }
1359
+ }
1360
+ }
1361
+ };
1362
+
1363
+ // src/agent/pipeline/openai-realtime.ts
1364
+ var OpenAIRealtime = class {
1365
+ _options;
1366
+ _ws = null;
1367
+ _callSession = null;
1368
+ _tools = null;
1369
+ _closed = false;
1370
+ constructor(options = {}) {
1371
+ this._options = {
1372
+ model: "gpt-4o-realtime-preview",
1373
+ voice: "alloy",
1374
+ inputAudioFormat: "pcm16",
1375
+ outputAudioFormat: "pcm16",
1376
+ ...options
1377
+ };
1378
+ }
1379
+ async start(callSession, tools) {
1380
+ this._callSession = callSession;
1381
+ this._tools = tools ?? null;
1382
+ this._closed = false;
1383
+ const apiKey = this._options.apiKey ?? process.env["OPENAI_API_KEY"];
1384
+ if (!apiKey) {
1385
+ throw new Error("OpenAI API key is required");
1386
+ }
1387
+ const { WebSocket } = await import('ws');
1388
+ const url = `wss://api.openai.com/v1/realtime?model=${this._options.model}`;
1389
+ this._ws = new WebSocket(url, {
1390
+ headers: {
1391
+ Authorization: `Bearer ${apiKey}`,
1392
+ "OpenAI-Beta": "realtime=v1"
1393
+ }
1394
+ });
1395
+ return new Promise((resolve, reject) => {
1396
+ const ws = this._ws;
1397
+ ws.on("open", () => {
1398
+ this._sendSessionUpdate();
1399
+ resolve();
1400
+ });
1401
+ ws.on("message", (data) => {
1402
+ try {
1403
+ const msg = JSON.parse(data.toString());
1404
+ this._handleMessage(msg);
1405
+ } catch {
1406
+ }
1407
+ });
1408
+ ws.on("close", () => {
1409
+ this._closed = true;
1410
+ });
1411
+ ws.on("error", (err) => {
1412
+ if (!this._ws) {
1413
+ reject(err);
1414
+ }
1415
+ console.error("[OpenAIRealtime] WebSocket error:", err.message);
1416
+ });
1417
+ });
1418
+ }
1419
+ feedAudio(audio) {
1420
+ if (this._ws && this._ws.readyState === 1 && !this._closed) {
1421
+ this._ws.send(
1422
+ JSON.stringify({
1423
+ type: "input_audio_buffer.append",
1424
+ audio: audio.toString("base64")
1425
+ })
1426
+ );
1427
+ }
1428
+ }
1429
+ async stop() {
1430
+ this._closed = true;
1431
+ if (this._ws) {
1432
+ this._ws.close();
1433
+ this._ws = null;
1434
+ }
1435
+ }
1436
+ _sendSessionUpdate() {
1437
+ if (!this._ws || this._ws.readyState !== 1) return;
1438
+ const sessionConfig = {
1439
+ modalities: ["text", "audio"],
1440
+ voice: this._options.voice,
1441
+ input_audio_format: this._options.inputAudioFormat,
1442
+ output_audio_format: this._options.outputAudioFormat
1443
+ };
1444
+ if (this._options.instructions) {
1445
+ sessionConfig["instructions"] = this._options.instructions;
1446
+ }
1447
+ if (this._options.temperature !== void 0) {
1448
+ sessionConfig["temperature"] = this._options.temperature;
1449
+ }
1450
+ if (this._options.turnDetection !== void 0) {
1451
+ sessionConfig["turn_detection"] = this._options.turnDetection;
1452
+ }
1453
+ if (this._tools && this._tools.size > 0) {
1454
+ sessionConfig["tools"] = this._tools.toOpenAITools().map((t) => t.function);
1455
+ }
1456
+ this._ws.send(
1457
+ JSON.stringify({
1458
+ type: "session.update",
1459
+ session: sessionConfig
1460
+ })
1461
+ );
1462
+ }
1463
+ _handleMessage(msg) {
1464
+ const type = msg["type"];
1465
+ switch (type) {
1466
+ case "response.audio.delta": {
1467
+ const delta = msg["delta"];
1468
+ if (delta && this._callSession) {
1469
+ const audio = Buffer.from(delta, "base64");
1470
+ this._callSession.sendAudio(audio);
1471
+ }
1472
+ break;
1473
+ }
1474
+ case "response.audio.done": {
1475
+ break;
1476
+ }
1477
+ case "response.function_call_arguments.done": {
1478
+ this._handleFunctionCall(msg);
1479
+ break;
1480
+ }
1481
+ case "input_audio_buffer.speech_started": {
1482
+ if (this._callSession) {
1483
+ this._callSession.clearAudio();
1484
+ }
1485
+ break;
1486
+ }
1487
+ case "error": {
1488
+ console.error("[OpenAIRealtime] API error:", msg["error"]);
1489
+ break;
1490
+ }
1491
+ }
1492
+ }
1493
+ async _handleFunctionCall(msg) {
1494
+ const name = msg["name"];
1495
+ const callId = msg["call_id"];
1496
+ const argsStr = msg["arguments"];
1497
+ if (!this._tools || !this._tools.has(name)) {
1498
+ console.error(`[OpenAIRealtime] Unknown tool: ${name}`);
1499
+ return;
1500
+ }
1501
+ try {
1502
+ const args = JSON.parse(argsStr);
1503
+ const result = await this._tools.call(name, args);
1504
+ if (this._ws && this._ws.readyState === 1) {
1505
+ this._ws.send(
1506
+ JSON.stringify({
1507
+ type: "conversation.item.create",
1508
+ item: {
1509
+ type: "function_call_output",
1510
+ call_id: callId,
1511
+ output: typeof result === "string" ? result : JSON.stringify(result)
1512
+ }
1513
+ })
1514
+ );
1515
+ this._ws.send(JSON.stringify({ type: "response.create" }));
1516
+ }
1517
+ } catch (err) {
1518
+ console.error(`[OpenAIRealtime] Tool call error for ${name}:`, err);
1519
+ }
1520
+ }
1521
+ };
1522
+
1523
+ // src/agent/pipeline/gemini-realtime.ts
1524
+ var GeminiRealtime = class {
1525
+ _options;
1526
+ _ws = null;
1527
+ _callSession = null;
1528
+ _tools = null;
1529
+ _closed = false;
1530
+ constructor(options = {}) {
1531
+ this._options = {
1532
+ model: "gemini-2.0-flash-exp",
1533
+ ...options
1534
+ };
1535
+ }
1536
+ async start(callSession, tools) {
1537
+ this._callSession = callSession;
1538
+ this._tools = tools ?? null;
1539
+ this._closed = false;
1540
+ const apiKey = this._options.apiKey ?? process.env["GOOGLE_API_KEY"];
1541
+ if (!apiKey) {
1542
+ throw new Error("Google API key is required");
1543
+ }
1544
+ const { WebSocket } = await import('ws');
1545
+ this._options.model;
1546
+ const url = `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent?key=${apiKey}`;
1547
+ this._ws = new WebSocket(url);
1548
+ return new Promise((resolve, reject) => {
1549
+ const ws = this._ws;
1550
+ ws.on("open", () => {
1551
+ this._sendSetup();
1552
+ resolve();
1553
+ });
1554
+ ws.on("message", (data) => {
1555
+ try {
1556
+ const msg = JSON.parse(data.toString());
1557
+ this._handleMessage(msg);
1558
+ } catch {
1559
+ }
1560
+ });
1561
+ ws.on("close", () => {
1562
+ this._closed = true;
1563
+ });
1564
+ ws.on("error", (err) => {
1565
+ if (!this._ws) {
1566
+ reject(err);
1567
+ }
1568
+ console.error("[GeminiRealtime] WebSocket error:", err.message);
1569
+ });
1570
+ });
1571
+ }
1572
+ feedAudio(audio) {
1573
+ if (this._ws && this._ws.readyState === 1 && !this._closed) {
1574
+ this._ws.send(
1575
+ JSON.stringify({
1576
+ realtimeInput: {
1577
+ mediaChunks: [
1578
+ {
1579
+ mimeType: "audio/pcm;rate=16000",
1580
+ data: audio.toString("base64")
1581
+ }
1582
+ ]
1583
+ }
1584
+ })
1585
+ );
1586
+ }
1587
+ }
1588
+ async stop() {
1589
+ this._closed = true;
1590
+ if (this._ws) {
1591
+ this._ws.close();
1592
+ this._ws = null;
1593
+ }
1594
+ }
1595
+ _sendSetup() {
1596
+ if (!this._ws || this._ws.readyState !== 1) return;
1597
+ const setupConfig = {
1598
+ model: `models/${this._options.model}`,
1599
+ generationConfig: {
1600
+ responseModalities: ["AUDIO"],
1601
+ speechConfig: {
1602
+ voiceConfig: {
1603
+ prebuiltVoiceConfig: {
1604
+ voiceName: this._options.voice ?? "Aoede"
1605
+ }
1606
+ }
1607
+ },
1608
+ ...this._options.generationConfig
1609
+ }
1610
+ };
1611
+ if (this._options.systemInstruction) {
1612
+ setupConfig["systemInstruction"] = {
1613
+ parts: [{ text: this._options.systemInstruction }]
1614
+ };
1615
+ }
1616
+ if (this._tools && this._tools.size > 0) {
1617
+ const toolDefs = this._tools.toOpenAITools().map((t) => ({
1618
+ name: t.function.name,
1619
+ description: t.function.description,
1620
+ parameters: t.function.parameters
1621
+ }));
1622
+ setupConfig["tools"] = [{ functionDeclarations: toolDefs }];
1623
+ }
1624
+ this._ws.send(JSON.stringify({ setup: setupConfig }));
1625
+ }
1626
+ _handleMessage(msg) {
1627
+ const serverContent = msg["serverContent"];
1628
+ if (serverContent) {
1629
+ const modelTurn = serverContent["modelTurn"];
1630
+ if (modelTurn) {
1631
+ const parts = modelTurn["parts"];
1632
+ if (parts) {
1633
+ for (const part of parts) {
1634
+ const inlineData = part["inlineData"];
1635
+ if (inlineData && inlineData["data"] && this._callSession) {
1636
+ const audio = Buffer.from(inlineData["data"], "base64");
1637
+ this._callSession.sendAudio(audio);
1638
+ }
1639
+ }
1640
+ }
1641
+ }
1642
+ }
1643
+ const toolCall = msg["toolCall"];
1644
+ if (toolCall) {
1645
+ this._handleToolCall(toolCall);
1646
+ }
1647
+ }
1648
+ async _handleToolCall(toolCall) {
1649
+ const functionCalls = toolCall["functionCalls"];
1650
+ if (!functionCalls || !this._tools) return;
1651
+ const responses = [];
1652
+ for (const fc of functionCalls) {
1653
+ const name = fc["name"];
1654
+ const args = fc["args"] ?? {};
1655
+ try {
1656
+ const result = await this._tools.call(name, args);
1657
+ responses.push({
1658
+ name,
1659
+ response: { result: typeof result === "string" ? result : JSON.stringify(result) }
1660
+ });
1661
+ } catch (err) {
1662
+ console.error(`[GeminiRealtime] Tool call error for ${name}:`, err);
1663
+ responses.push({
1664
+ name,
1665
+ response: { error: String(err) }
1666
+ });
1667
+ }
1668
+ }
1669
+ if (this._ws && this._ws.readyState === 1) {
1670
+ this._ws.send(
1671
+ JSON.stringify({
1672
+ toolResponse: {
1673
+ functionResponses: responses
1674
+ }
1675
+ })
1676
+ );
1677
+ }
1678
+ }
1679
+ };
1680
+
1681
+ // src/agent/pipeline/pipeline-session.ts
1682
+ var PipelineSession = class {
1683
+ _stt;
1684
+ _llm;
1685
+ _tts;
1686
+ _systemPrompt;
1687
+ _temperature;
1688
+ _maxTokens;
1689
+ _sampleRate;
1690
+ _interruptOnSpeech;
1691
+ _callSession = null;
1692
+ _tools = null;
1693
+ _conversation = [];
1694
+ _audioBuffer = [];
1695
+ _running = false;
1696
+ _speaking = false;
1697
+ constructor(options) {
1698
+ this._stt = options.stt;
1699
+ this._llm = options.llm;
1700
+ this._tts = options.tts;
1701
+ this._systemPrompt = options.systemPrompt;
1702
+ this._temperature = options.temperature;
1703
+ this._maxTokens = options.maxTokens;
1704
+ this._sampleRate = options.sampleRate ?? 8e3;
1705
+ this._interruptOnSpeech = options.interruptOnSpeech ?? true;
1706
+ }
1707
+ async start(callSession, tools) {
1708
+ this._callSession = callSession;
1709
+ this._tools = tools ?? null;
1710
+ this._running = true;
1711
+ this._conversation = [];
1712
+ if (this._systemPrompt) {
1713
+ this._conversation.push({
1714
+ role: "system",
1715
+ content: this._systemPrompt
1716
+ });
1717
+ }
1718
+ this._runSttLoop().catch((err) => {
1719
+ console.error("[PipelineSession] STT loop error:", err);
1720
+ });
1721
+ }
1722
+ feedAudio(audio) {
1723
+ if (this._running) {
1724
+ this._audioBuffer.push(audio);
1725
+ }
1726
+ }
1727
+ async stop() {
1728
+ this._running = false;
1729
+ this._audioBuffer = [];
1730
+ }
1731
+ async _runSttLoop() {
1732
+ const audioStream = this._createAudioStream();
1733
+ for await (const event of this._stt.transcribe(audioStream, {
1734
+ sampleRate: this._sampleRate
1735
+ })) {
1736
+ if (!this._running) break;
1737
+ if (event.type === "interim" && this._speaking && this._interruptOnSpeech) {
1738
+ this._speaking = false;
1739
+ if (this._callSession) {
1740
+ this._callSession.clearAudio();
1741
+ }
1742
+ }
1743
+ if (event.type === "final" && event.transcript.trim()) {
1744
+ await this._handleUserSpeech(event.transcript);
1745
+ }
1746
+ }
1747
+ }
1748
+ async *_createAudioStream() {
1749
+ while (this._running) {
1750
+ if (this._audioBuffer.length > 0) {
1751
+ yield this._audioBuffer.shift();
1752
+ } else {
1753
+ await new Promise((resolve) => setTimeout(resolve, 20));
1754
+ }
1755
+ }
1756
+ }
1757
+ async _handleUserSpeech(transcript) {
1758
+ this._conversation.push({ role: "user", content: transcript });
1759
+ let fullResponse = "";
1760
+ const textChunks = [];
1761
+ const llmStream = this._llm.generate(this._conversation, {
1762
+ tools: this._tools ?? void 0,
1763
+ temperature: this._temperature,
1764
+ maxTokens: this._maxTokens
1765
+ });
1766
+ for await (const chunk of llmStream) {
1767
+ if (!this._running) break;
1768
+ if (chunk.type === "text" && chunk.text) {
1769
+ textChunks.push(chunk.text);
1770
+ fullResponse += chunk.text;
1771
+ } else if (chunk.type === "tool_call" && chunk.toolCall) {
1772
+ await this._handleToolCall(chunk);
1773
+ }
1774
+ }
1775
+ if (fullResponse.trim()) {
1776
+ this._conversation.push({ role: "assistant", content: fullResponse });
1777
+ await this._synthesizeAndSend(fullResponse);
1778
+ }
1779
+ }
1780
+ async _handleToolCall(chunk) {
1781
+ if (!chunk.toolCall || !this._tools) return;
1782
+ const { id, name, arguments: argsStr } = chunk.toolCall;
1783
+ try {
1784
+ const args = JSON.parse(argsStr);
1785
+ const result = await this._tools.call(name, args);
1786
+ this._conversation.push({
1787
+ role: "assistant",
1788
+ content: ""
1789
+ // Tool call info stored in the message flow
1790
+ });
1791
+ this._conversation.push({
1792
+ role: "tool",
1793
+ content: typeof result === "string" ? result : JSON.stringify(result),
1794
+ tool_call_id: id,
1795
+ name
1796
+ });
1797
+ let followUpText = "";
1798
+ const followUpStream = this._llm.generate(this._conversation, {
1799
+ tools: this._tools ?? void 0,
1800
+ temperature: this._temperature,
1801
+ maxTokens: this._maxTokens
1802
+ });
1803
+ for await (const followChunk of followUpStream) {
1804
+ if (!this._running) break;
1805
+ if (followChunk.type === "text" && followChunk.text) {
1806
+ followUpText += followChunk.text;
1807
+ }
1808
+ }
1809
+ if (followUpText.trim()) {
1810
+ this._conversation.push({ role: "assistant", content: followUpText });
1811
+ await this._synthesizeAndSend(followUpText);
1812
+ }
1813
+ } catch (err) {
1814
+ console.error(`[PipelineSession] Tool call error for ${name}:`, err);
1815
+ }
1816
+ }
1817
+ async _synthesizeAndSend(text) {
1818
+ if (!this._callSession || !this._running) return;
1819
+ this._speaking = true;
1820
+ try {
1821
+ for await (const audioChunk of this._tts.synthesize(text, {
1822
+ sampleRate: this._sampleRate
1823
+ })) {
1824
+ if (!this._running || !this._speaking) break;
1825
+ this._callSession.sendAudio(audioChunk);
1826
+ }
1827
+ } catch (err) {
1828
+ console.error("[PipelineSession] TTS error:", err);
1829
+ } finally {
1830
+ this._speaking = false;
1831
+ }
1832
+ }
1833
+ };
1834
+
1835
+ // src/agent/pipeline/deepgram-stt.ts
1836
+ var DeepgramSTT = class {
1837
+ _options;
1838
+ constructor(options = {}) {
1839
+ this._options = {
1840
+ model: "nova-2",
1841
+ language: "ko",
1842
+ interimResults: true,
1843
+ punctuate: true,
1844
+ smartFormat: true,
1845
+ encoding: "linear16",
1846
+ sampleRate: 8e3,
1847
+ channels: 1,
1848
+ ...options
1849
+ };
1850
+ }
1851
+ async *transcribe(audioStream, options) {
1852
+ const apiKey = this._options.apiKey ?? process.env["DEEPGRAM_API_KEY"];
1853
+ if (!apiKey) {
1854
+ throw new Error("Deepgram API key is required");
1855
+ }
1856
+ const language = options?.language ?? this._options.language;
1857
+ const sampleRate = options?.sampleRate ?? this._options.sampleRate;
1858
+ const { WebSocket } = await import('ws');
1859
+ const params = new URLSearchParams({
1860
+ model: this._options.model,
1861
+ language,
1862
+ punctuate: String(this._options.punctuate),
1863
+ interim_results: String(this._options.interimResults),
1864
+ smart_format: String(this._options.smartFormat),
1865
+ encoding: this._options.encoding,
1866
+ sample_rate: String(sampleRate),
1867
+ channels: String(this._options.channels)
1868
+ });
1869
+ const url = `wss://api.deepgram.com/v1/listen?${params.toString()}`;
1870
+ const ws = new WebSocket(url, {
1871
+ headers: { Authorization: `Token ${apiKey}` }
1872
+ });
1873
+ const eventQueue = [];
1874
+ let resolveWait = null;
1875
+ let done = false;
1876
+ ws.on("message", (data) => {
1877
+ try {
1878
+ const msg = JSON.parse(data.toString());
1879
+ const channel = msg["channel"];
1880
+ if (!channel) return;
1881
+ const alternatives = channel["alternatives"];
1882
+ if (!alternatives || alternatives.length === 0) return;
1883
+ const transcript = alternatives[0]["transcript"] ?? "";
1884
+ if (!transcript) return;
1885
+ const isFinal = msg["is_final"];
1886
+ const speechFinal = msg["speech_final"];
1887
+ const event = {
1888
+ type: isFinal || speechFinal ? "final" : "interim",
1889
+ transcript
1890
+ };
1891
+ eventQueue.push(event);
1892
+ if (resolveWait) {
1893
+ resolveWait();
1894
+ resolveWait = null;
1895
+ }
1896
+ } catch {
1897
+ }
1898
+ });
1899
+ ws.on("close", () => {
1900
+ done = true;
1901
+ if (resolveWait) {
1902
+ resolveWait();
1903
+ resolveWait = null;
1904
+ }
1905
+ });
1906
+ ws.on("error", (err) => {
1907
+ console.error("[DeepgramSTT] WebSocket error:", err.message);
1908
+ done = true;
1909
+ if (resolveWait) {
1910
+ resolveWait();
1911
+ resolveWait = null;
1912
+ }
1913
+ });
1914
+ await new Promise((resolve, reject) => {
1915
+ ws.on("open", resolve);
1916
+ ws.on("error", reject);
1917
+ });
1918
+ const feedPromise = (async () => {
1919
+ try {
1920
+ for await (const chunk of audioStream) {
1921
+ if (done) break;
1922
+ if (ws.readyState === 1) {
1923
+ ws.send(chunk);
1924
+ }
1925
+ }
1926
+ } finally {
1927
+ if (ws.readyState === 1) {
1928
+ ws.send(JSON.stringify({ type: "CloseStream" }));
1929
+ }
1930
+ }
1931
+ })();
1932
+ try {
1933
+ while (!done || eventQueue.length > 0) {
1934
+ if (eventQueue.length > 0) {
1935
+ yield eventQueue.shift();
1936
+ } else if (!done) {
1937
+ await new Promise((resolve) => {
1938
+ resolveWait = resolve;
1939
+ });
1940
+ }
1941
+ }
1942
+ } finally {
1943
+ done = true;
1944
+ ws.close();
1945
+ await feedPromise.catch(() => {
1946
+ });
1947
+ }
1948
+ }
1949
+ };
1950
+
1951
+ // src/agent/pipeline/elevenlabs-tts.ts
1952
+ var ElevenLabsTTS = class {
1953
+ _options;
1954
+ constructor(options = {}) {
1955
+ this._options = {
1956
+ voiceId: "21m00Tcm4TlvDq8ikWAM",
1957
+ modelId: "eleven_multilingual_v2",
1958
+ outputFormat: "pcm_16000",
1959
+ stability: 0.5,
1960
+ similarityBoost: 0.75,
1961
+ style: 0,
1962
+ useSpeakerBoost: true,
1963
+ ...options
1964
+ };
1965
+ }
1966
+ async *synthesize(text, options) {
1967
+ const apiKey = this._options.apiKey ?? process.env["ELEVENLABS_API_KEY"];
1968
+ if (!apiKey) {
1969
+ throw new Error("ElevenLabs API key is required");
1970
+ }
1971
+ const voiceId = options?.voice ?? this._options.voiceId;
1972
+ if (typeof text === "string") {
1973
+ yield* this._synthesizeSingle(apiKey, voiceId, text);
1974
+ } else {
1975
+ yield* this._synthesizeStreaming(apiKey, voiceId, text);
1976
+ }
1977
+ }
1978
+ async *_synthesizeSingle(apiKey, voiceId, text) {
1979
+ const url = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}?output_format=${this._options.outputFormat}`;
1980
+ const response = await fetch(url, {
1981
+ method: "POST",
1982
+ headers: {
1983
+ "xi-api-key": apiKey,
1984
+ "Content-Type": "application/json"
1985
+ },
1986
+ body: JSON.stringify({
1987
+ text,
1988
+ model_id: this._options.modelId,
1989
+ voice_settings: {
1990
+ stability: this._options.stability,
1991
+ similarity_boost: this._options.similarityBoost,
1992
+ style: this._options.style,
1993
+ use_speaker_boost: this._options.useSpeakerBoost
1994
+ }
1995
+ })
1996
+ });
1997
+ if (!response.ok) {
1998
+ throw new Error(`ElevenLabs API error: ${response.status} ${response.statusText}`);
1999
+ }
2000
+ const body = response.body;
2001
+ if (!body) {
2002
+ throw new Error("ElevenLabs returned empty response");
2003
+ }
2004
+ const reader = body.getReader();
2005
+ try {
2006
+ while (true) {
2007
+ const { done, value } = await reader.read();
2008
+ if (done) break;
2009
+ yield Buffer.from(value);
2010
+ }
2011
+ } finally {
2012
+ reader.releaseLock();
2013
+ }
2014
+ }
2015
+ async *_synthesizeStreaming(apiKey, voiceId, textStream) {
2016
+ const { WebSocket } = await import('ws');
2017
+ const url = `wss://api.elevenlabs.io/v1/text-to-speech/${voiceId}/stream-input?model_id=${this._options.modelId}&output_format=${this._options.outputFormat}`;
2018
+ const ws = new WebSocket(url);
2019
+ const audioQueue = [];
2020
+ let resolveWait = null;
2021
+ let done = false;
2022
+ ws.on("open", () => {
2023
+ ws.send(
2024
+ JSON.stringify({
2025
+ text: " ",
2026
+ voice_settings: {
2027
+ stability: this._options.stability,
2028
+ similarity_boost: this._options.similarityBoost,
2029
+ style: this._options.style,
2030
+ use_speaker_boost: this._options.useSpeakerBoost
2031
+ },
2032
+ xi_api_key: apiKey
2033
+ })
2034
+ );
2035
+ });
2036
+ ws.on("message", (data) => {
2037
+ try {
2038
+ const msg = JSON.parse(data.toString());
2039
+ const audio = msg["audio"];
2040
+ if (audio) {
2041
+ audioQueue.push(Buffer.from(audio, "base64"));
2042
+ if (resolveWait) {
2043
+ resolveWait();
2044
+ resolveWait = null;
2045
+ }
2046
+ }
2047
+ } catch {
2048
+ }
2049
+ });
2050
+ ws.on("close", () => {
2051
+ done = true;
2052
+ if (resolveWait) {
2053
+ resolveWait();
2054
+ resolveWait = null;
2055
+ }
2056
+ });
2057
+ ws.on("error", (err) => {
2058
+ console.error("[ElevenLabsTTS] WebSocket error:", err.message);
2059
+ done = true;
2060
+ if (resolveWait) {
2061
+ resolveWait();
2062
+ resolveWait = null;
2063
+ }
2064
+ });
2065
+ await new Promise((resolve, reject) => {
2066
+ ws.on("open", resolve);
2067
+ ws.on("error", reject);
2068
+ });
2069
+ const feedPromise = (async () => {
2070
+ try {
2071
+ for await (const chunk of textStream) {
2072
+ if (done) break;
2073
+ if (ws.readyState === 1) {
2074
+ ws.send(JSON.stringify({ text: chunk }));
2075
+ }
2076
+ }
2077
+ } finally {
2078
+ if (ws.readyState === 1) {
2079
+ ws.send(JSON.stringify({ text: "" }));
2080
+ }
2081
+ }
2082
+ })();
2083
+ try {
2084
+ while (!done || audioQueue.length > 0) {
2085
+ if (audioQueue.length > 0) {
2086
+ yield audioQueue.shift();
2087
+ } else if (!done) {
2088
+ await new Promise((resolve) => {
2089
+ resolveWait = resolve;
2090
+ });
2091
+ }
2092
+ }
2093
+ } finally {
2094
+ done = true;
2095
+ ws.close();
2096
+ await feedPromise.catch(() => {
2097
+ });
2098
+ }
2099
+ }
2100
+ };
2101
+
2102
+ // src/agent/pipeline/openai-llm.ts
2103
+ var OpenAILLM = class {
2104
+ _options;
2105
+ constructor(options = {}) {
2106
+ this._options = {
2107
+ model: "gpt-4o",
2108
+ ...options
2109
+ };
2110
+ }
2111
+ async *generate(messages, options) {
2112
+ const OpenAI = (await import('openai')).default;
2113
+ const client = new OpenAI({
2114
+ apiKey: this._options.apiKey ?? process.env["OPENAI_API_KEY"]
2115
+ });
2116
+ const requestParams = {
2117
+ model: this._options.model,
2118
+ messages: messages.map((m) => {
2119
+ const msg = { role: m.role, content: m.content };
2120
+ if (m.name) msg["name"] = m.name;
2121
+ if (m.tool_call_id) msg["tool_call_id"] = m.tool_call_id;
2122
+ return msg;
2123
+ }),
2124
+ stream: true,
2125
+ temperature: options?.temperature ?? this._options.temperature,
2126
+ max_tokens: options?.maxTokens ?? this._options.maxTokens
2127
+ };
2128
+ if (options?.tools && options.tools.size > 0) {
2129
+ requestParams["tools"] = options.tools.toOpenAITools();
2130
+ }
2131
+ const stream = await client.chat.completions.create(
2132
+ requestParams
2133
+ );
2134
+ let currentToolCall = null;
2135
+ for await (const chunk of stream) {
2136
+ const choices = chunk["choices"];
2137
+ if (!choices || choices.length === 0) continue;
2138
+ const delta = choices[0]["delta"];
2139
+ if (!delta) continue;
2140
+ const content = delta["content"];
2141
+ if (content) {
2142
+ yield { type: "text", text: content };
2143
+ }
2144
+ const toolCalls = delta["tool_calls"];
2145
+ if (toolCalls) {
2146
+ for (const tc of toolCalls) {
2147
+ const fn = tc["function"];
2148
+ if (fn) {
2149
+ if (fn["name"]) {
2150
+ if (currentToolCall) {
2151
+ yield {
2152
+ type: "tool_call",
2153
+ toolCall: {
2154
+ id: currentToolCall.id,
2155
+ name: currentToolCall.name,
2156
+ arguments: currentToolCall.arguments
2157
+ }
2158
+ };
2159
+ }
2160
+ currentToolCall = {
2161
+ id: tc["id"] ?? "",
2162
+ name: fn["name"],
2163
+ arguments: fn["arguments"] ?? ""
2164
+ };
2165
+ } else if (currentToolCall && fn["arguments"]) {
2166
+ currentToolCall.arguments += fn["arguments"];
2167
+ }
2168
+ }
2169
+ }
2170
+ }
2171
+ }
2172
+ if (currentToolCall) {
2173
+ yield {
2174
+ type: "tool_call",
2175
+ toolCall: {
2176
+ id: currentToolCall.id,
2177
+ name: currentToolCall.name,
2178
+ arguments: currentToolCall.arguments
2179
+ }
2180
+ };
2181
+ }
2182
+ yield { type: "done" };
2183
+ }
2184
+ };
2185
+
2186
+ // src/agent/pipeline/anthropic-llm.ts
2187
+ var AnthropicLLM = class {
2188
+ _options;
2189
+ constructor(options = {}) {
2190
+ this._options = {
2191
+ model: "claude-sonnet-4-20250514",
2192
+ maxTokens: 1024,
2193
+ ...options
2194
+ };
2195
+ }
2196
+ async *generate(messages, options) {
2197
+ const AnthropicModule = await import('@anthropic-ai/sdk');
2198
+ const Anthropic = AnthropicModule.default;
2199
+ const client = new Anthropic({
2200
+ apiKey: this._options.apiKey ?? process.env["ANTHROPIC_API_KEY"]
2201
+ });
2202
+ let systemPrompt;
2203
+ const conversationMessages = [];
2204
+ for (const msg of messages) {
2205
+ if (msg.role === "system") {
2206
+ systemPrompt = msg.content;
2207
+ } else {
2208
+ conversationMessages.push({
2209
+ role: msg.role === "tool" ? "user" : msg.role,
2210
+ content: msg.role === "tool" ? [
2211
+ {
2212
+ type: "tool_result",
2213
+ tool_use_id: msg.tool_call_id,
2214
+ content: msg.content
2215
+ }
2216
+ ] : msg.content
2217
+ });
2218
+ }
2219
+ }
2220
+ const requestParams = {
2221
+ model: this._options.model,
2222
+ messages: conversationMessages,
2223
+ max_tokens: options?.maxTokens ?? this._options.maxTokens,
2224
+ stream: true
2225
+ };
2226
+ if (systemPrompt) {
2227
+ requestParams["system"] = systemPrompt;
2228
+ }
2229
+ if (options?.temperature ?? this._options.temperature) {
2230
+ requestParams["temperature"] = options?.temperature ?? this._options.temperature;
2231
+ }
2232
+ if (options?.tools && options.tools.size > 0) {
2233
+ const toolDefs = options.tools.toOpenAITools().map((t) => ({
2234
+ name: t.function.name,
2235
+ description: t.function.description,
2236
+ input_schema: t.function.parameters
2237
+ }));
2238
+ requestParams["tools"] = toolDefs;
2239
+ }
2240
+ const stream = client.messages.stream(
2241
+ requestParams
2242
+ );
2243
+ let currentToolCall = null;
2244
+ for await (const event of stream) {
2245
+ const eventType = event["type"];
2246
+ switch (eventType) {
2247
+ case "content_block_start": {
2248
+ const contentBlock = event["content_block"];
2249
+ if (contentBlock?.["type"] === "tool_use") {
2250
+ currentToolCall = {
2251
+ id: contentBlock["id"],
2252
+ name: contentBlock["name"],
2253
+ arguments: ""
2254
+ };
2255
+ }
2256
+ break;
2257
+ }
2258
+ case "content_block_delta": {
2259
+ const delta = event["delta"];
2260
+ if (delta?.["type"] === "text_delta") {
2261
+ yield { type: "text", text: delta["text"] };
2262
+ } else if (delta?.["type"] === "input_json_delta" && currentToolCall) {
2263
+ currentToolCall.arguments += delta["partial_json"] ?? "";
2264
+ }
2265
+ break;
2266
+ }
2267
+ case "content_block_stop": {
2268
+ if (currentToolCall) {
2269
+ yield {
2270
+ type: "tool_call",
2271
+ toolCall: {
2272
+ id: currentToolCall.id,
2273
+ name: currentToolCall.name,
2274
+ arguments: currentToolCall.arguments
2275
+ }
2276
+ };
2277
+ currentToolCall = null;
2278
+ }
2279
+ break;
2280
+ }
2281
+ case "message_stop": {
2282
+ yield { type: "done" };
2283
+ break;
2284
+ }
2285
+ }
2286
+ }
2287
+ }
2288
+ };
2289
+
2290
+ // src/agent/pipeline/gemini-llm.ts
2291
+ var GeminiLLM = class {
2292
+ _options;
2293
+ constructor(options = {}) {
2294
+ this._options = {
2295
+ model: "gemini-2.0-flash",
2296
+ ...options
2297
+ };
2298
+ }
2299
+ async *generate(messages, options) {
2300
+ const { GoogleGenAI } = await import('@google/genai');
2301
+ const client = new GoogleGenAI({
2302
+ apiKey: this._options.apiKey ?? process.env["GOOGLE_API_KEY"]
2303
+ });
2304
+ let systemInstruction;
2305
+ const contents = [];
2306
+ for (const msg of messages) {
2307
+ if (msg.role === "system") {
2308
+ systemInstruction = msg.content;
2309
+ continue;
2310
+ }
2311
+ const role = msg.role === "assistant" ? "model" : "user";
2312
+ contents.push({
2313
+ role,
2314
+ parts: [{ text: msg.content }]
2315
+ });
2316
+ }
2317
+ const requestConfig = {
2318
+ contents
2319
+ };
2320
+ if (systemInstruction) {
2321
+ requestConfig["systemInstruction"] = { parts: [{ text: systemInstruction }] };
2322
+ }
2323
+ const generationConfig = {};
2324
+ if (options?.temperature ?? this._options.temperature) {
2325
+ generationConfig["temperature"] = options?.temperature ?? this._options.temperature;
2326
+ }
2327
+ if (options?.maxTokens ?? this._options.maxTokens) {
2328
+ generationConfig["maxOutputTokens"] = options?.maxTokens ?? this._options.maxTokens;
2329
+ }
2330
+ if (Object.keys(generationConfig).length > 0) {
2331
+ requestConfig["generationConfig"] = generationConfig;
2332
+ }
2333
+ if (options?.tools && options.tools.size > 0) {
2334
+ const toolDefs = options.tools.toOpenAITools().map((t) => ({
2335
+ name: t.function.name,
2336
+ description: t.function.description,
2337
+ parameters: t.function.parameters
2338
+ }));
2339
+ requestConfig["tools"] = [{ functionDeclarations: toolDefs }];
2340
+ }
2341
+ const response = await client.models.generateContentStream({
2342
+ model: this._options.model,
2343
+ ...requestConfig
2344
+ });
2345
+ for await (const chunk of response) {
2346
+ const candidates = chunk["candidates"];
2347
+ if (!candidates || candidates.length === 0) continue;
2348
+ const content = candidates[0]?.["content"];
2349
+ const parts = content?.["parts"];
2350
+ if (!parts) continue;
2351
+ for (const part of parts) {
2352
+ if ("text" in part && part["text"]) {
2353
+ yield { type: "text", text: part["text"] };
2354
+ }
2355
+ if ("functionCall" in part && part["functionCall"]) {
2356
+ const fc = part["functionCall"];
2357
+ yield {
2358
+ type: "tool_call",
2359
+ toolCall: {
2360
+ id: `gemini_${Date.now()}`,
2361
+ name: fc.name,
2362
+ arguments: JSON.stringify(fc.args ?? {})
2363
+ }
2364
+ };
2365
+ }
2366
+ }
2367
+ }
2368
+ yield { type: "done" };
2369
+ }
2370
+ };
2371
+
2372
+ // src/agent/pipeline/openai-compat-llm.ts
2373
+ var OpenAICompatLLM = class {
2374
+ _options;
2375
+ constructor(options) {
2376
+ this._options = options;
2377
+ }
2378
+ async *generate(messages, options) {
2379
+ const OpenAI = (await import('openai')).default;
2380
+ const client = new OpenAI({
2381
+ apiKey: this._options.apiKey ?? "not-needed",
2382
+ baseURL: this._options.baseUrl,
2383
+ defaultHeaders: this._options.defaultHeaders
2384
+ });
2385
+ const requestParams = {
2386
+ model: this._options.model,
2387
+ messages: messages.map((m) => {
2388
+ const msg = { role: m.role, content: m.content };
2389
+ if (m.name) msg["name"] = m.name;
2390
+ if (m.tool_call_id) msg["tool_call_id"] = m.tool_call_id;
2391
+ return msg;
2392
+ }),
2393
+ stream: true,
2394
+ temperature: options?.temperature ?? this._options.temperature,
2395
+ max_tokens: options?.maxTokens ?? this._options.maxTokens
2396
+ };
2397
+ if (options?.tools && options.tools.size > 0) {
2398
+ requestParams["tools"] = options.tools.toOpenAITools();
2399
+ }
2400
+ const stream = await client.chat.completions.create(
2401
+ requestParams
2402
+ );
2403
+ let currentToolCall = null;
2404
+ for await (const chunk of stream) {
2405
+ const choices = chunk["choices"];
2406
+ if (!choices || choices.length === 0) continue;
2407
+ const delta = choices[0]["delta"];
2408
+ if (!delta) continue;
2409
+ const content = delta["content"];
2410
+ if (content) {
2411
+ yield { type: "text", text: content };
2412
+ }
2413
+ const toolCalls = delta["tool_calls"];
2414
+ if (toolCalls) {
2415
+ for (const tc of toolCalls) {
2416
+ const fn = tc["function"];
2417
+ if (fn) {
2418
+ if (fn["name"]) {
2419
+ if (currentToolCall) {
2420
+ yield {
2421
+ type: "tool_call",
2422
+ toolCall: {
2423
+ id: currentToolCall.id,
2424
+ name: currentToolCall.name,
2425
+ arguments: currentToolCall.arguments
2426
+ }
2427
+ };
2428
+ }
2429
+ currentToolCall = {
2430
+ id: tc["id"] ?? "",
2431
+ name: fn["name"],
2432
+ arguments: fn["arguments"] ?? ""
2433
+ };
2434
+ } else if (currentToolCall && fn["arguments"]) {
2435
+ currentToolCall.arguments += fn["arguments"];
2436
+ }
2437
+ }
2438
+ }
2439
+ }
2440
+ }
2441
+ if (currentToolCall) {
2442
+ yield {
2443
+ type: "tool_call",
2444
+ toolCall: {
2445
+ id: currentToolCall.id,
2446
+ name: currentToolCall.name,
2447
+ arguments: currentToolCall.arguments
2448
+ }
2449
+ };
2450
+ }
2451
+ yield { type: "done" };
2452
+ }
2453
+ };
2454
+
2455
+ // src/agent/pipeline/ollama-llm.ts
2456
+ var OllamaLLM = class {
2457
+ _inner;
2458
+ constructor(options = {}) {
2459
+ const baseUrl = (options.baseUrl ?? "http://localhost:11434").replace(/\/$/, "");
2460
+ this._inner = new OpenAICompatLLM({
2461
+ baseUrl: `${baseUrl}/v1`,
2462
+ model: options.model ?? "llama3.1",
2463
+ apiKey: "ollama",
2464
+ temperature: options.temperature,
2465
+ maxTokens: options.maxTokens
2466
+ });
2467
+ }
2468
+ async *generate(messages, options) {
2469
+ yield* this._inner.generate(messages, options);
2470
+ }
2471
+ };
2472
+
2473
+ // src/agent/pipeline/mistral-llm.ts
2474
+ var MistralLLM = class {
2475
+ _inner;
2476
+ constructor(options = {}) {
2477
+ this._inner = new OpenAICompatLLM({
2478
+ apiKey: options.apiKey ?? process.env["MISTRAL_API_KEY"],
2479
+ baseUrl: "https://api.mistral.ai/v1",
2480
+ model: options.model ?? "mistral-small-latest",
2481
+ temperature: options.temperature,
2482
+ maxTokens: options.maxTokens
2483
+ });
2484
+ }
2485
+ async *generate(messages, options) {
2486
+ yield* this._inner.generate(messages, options);
2487
+ }
2488
+ };
2489
+
2490
+ // src/agent/pipeline/groq-llm.ts
2491
+ var GroqLLM = class {
2492
+ _inner;
2493
+ constructor(options = {}) {
2494
+ this._inner = new OpenAICompatLLM({
2495
+ apiKey: options.apiKey ?? process.env["GROQ_API_KEY"],
2496
+ baseUrl: "https://api.groq.com/openai/v1",
2497
+ model: options.model ?? "meta-llama/llama-4-scout-17b-16e-instruct",
2498
+ temperature: options.temperature,
2499
+ maxTokens: options.maxTokens
2500
+ });
2501
+ }
2502
+ async *generate(messages, options) {
2503
+ yield* this._inner.generate(messages, options);
2504
+ }
2505
+ };
2506
+
2507
+ // src/agent/pipeline/perplexity-llm.ts
2508
+ var PerplexityLLM = class {
2509
+ _inner;
2510
+ constructor(options = {}) {
2511
+ this._inner = new OpenAICompatLLM({
2512
+ apiKey: options.apiKey ?? process.env["PERPLEXITY_API_KEY"],
2513
+ baseUrl: "https://api.perplexity.ai",
2514
+ model: options.model ?? "sonar",
2515
+ temperature: options.temperature,
2516
+ maxTokens: options.maxTokens
2517
+ });
2518
+ }
2519
+ async *generate(messages, options) {
2520
+ yield* this._inner.generate(messages, options);
2521
+ }
2522
+ };
2523
+
2524
+ // src/agent/pipeline/together-llm.ts
2525
+ var TogetherLLM = class {
2526
+ _inner;
2527
+ constructor(options = {}) {
2528
+ this._inner = new OpenAICompatLLM({
2529
+ apiKey: options.apiKey ?? process.env["TOGETHER_API_KEY"],
2530
+ baseUrl: "https://api.together.xyz/v1",
2531
+ model: options.model ?? "meta-llama/Llama-4-Scout-17B-16E-Instruct",
2532
+ temperature: options.temperature,
2533
+ maxTokens: options.maxTokens
2534
+ });
2535
+ }
2536
+ async *generate(messages, options) {
2537
+ yield* this._inner.generate(messages, options);
2538
+ }
2539
+ };
2540
+
2541
+ // src/agent/pipeline/fireworks-llm.ts
2542
+ var FireworksLLM = class {
2543
+ _inner;
2544
+ constructor(options = {}) {
2545
+ this._inner = new OpenAICompatLLM({
2546
+ apiKey: options.apiKey ?? process.env["FIREWORKS_API_KEY"],
2547
+ baseUrl: "https://api.fireworks.ai/inference/v1",
2548
+ model: options.model ?? "accounts/fireworks/models/llama4-scout-instruct-basic",
2549
+ temperature: options.temperature,
2550
+ maxTokens: options.maxTokens
2551
+ });
2552
+ }
2553
+ async *generate(messages, options) {
2554
+ yield* this._inner.generate(messages, options);
2555
+ }
2556
+ };
2557
+
2558
+ // src/agent/pipeline/deepseek-llm.ts
2559
+ var DeepSeekLLM = class {
2560
+ _inner;
2561
+ constructor(options = {}) {
2562
+ this._inner = new OpenAICompatLLM({
2563
+ apiKey: options.apiKey ?? process.env["DEEPSEEK_API_KEY"],
2564
+ baseUrl: "https://api.deepseek.com",
2565
+ model: options.model ?? "deepseek-chat",
2566
+ temperature: options.temperature,
2567
+ maxTokens: options.maxTokens
2568
+ });
2569
+ }
2570
+ async *generate(messages, options) {
2571
+ yield* this._inner.generate(messages, options);
2572
+ }
2573
+ };
2574
+
2575
+ // src/agent/pipeline/xai-llm.ts
2576
+ var XaiLLM = class {
2577
+ _inner;
2578
+ constructor(options = {}) {
2579
+ this._inner = new OpenAICompatLLM({
2580
+ apiKey: options.apiKey ?? process.env["XAI_API_KEY"],
2581
+ baseUrl: "https://api.x.ai/v1",
2582
+ model: options.model ?? "grok-4-1-fast",
2583
+ temperature: options.temperature,
2584
+ maxTokens: options.maxTokens
2585
+ });
2586
+ }
2587
+ async *generate(messages, options) {
2588
+ yield* this._inner.generate(messages, options);
2589
+ }
2590
+ };
2591
+
2592
+ // src/agent/mcp/stdio.ts
2593
+ function mcpServerStdio(options) {
2594
+ return {
2595
+ type: "stdio",
2596
+ command: options.command,
2597
+ args: options.args,
2598
+ env: options.env
2599
+ };
2600
+ }
2601
+
2602
+ // src/agent/mcp/http.ts
2603
+ function mcpServerHTTP(options) {
2604
+ return {
2605
+ type: "http",
2606
+ url: options.url,
2607
+ headers: options.headers
2608
+ };
2609
+ }
2610
+
2611
+ export { AnthropicLLM, AudioRecorder, CallSession, ClawOpsAgent, DECODE_TABLE, DeepSeekLLM, DeepgramSTT, ElevenLabsTTS, FireworksLLM, GeminiLLM, GeminiRealtime, GroqLLM, MCPClient, MistralLLM, OllamaLLM, OpenAICompatLLM, OpenAILLM, OpenAIRealtime, PerplexityLLM, PipelineSession, TogetherLLM, ToolRegistry, XaiLLM, functionTool, getTracingConfig, mcpServerHTTP, mcpServerStdio, pcm16ToUlaw, resamplePcm16, resetTracingConfig, setTracingConfig, ulawToPcm16, zodToToolParams };
2612
+ //# sourceMappingURL=index.js.map
2613
+ //# sourceMappingURL=index.js.map