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