@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.
- package/LICENSE +21 -0
- package/README.md +313 -0
- package/dist/agent/index.cjs +2668 -0
- package/dist/agent/index.cjs.map +1 -0
- package/dist/agent/index.d.cts +821 -0
- package/dist/agent/index.d.ts +821 -0
- package/dist/agent/index.js +2613 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/chunk-JNSHMKDI.cjs +175 -0
- package/dist/chunk-JNSHMKDI.cjs.map +1 -0
- package/dist/chunk-UKAT24I6.js +152 -0
- package/dist/chunk-UKAT24I6.js.map +1 -0
- package/dist/index.cjs +601 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +549 -0
- package/dist/index.d.ts +549 -0
- package/dist/index.js +523 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
|
@@ -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
|