osborn 0.9.198 → 0.9.199
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-llm.js +23 -5
- package/dist/index.js +10 -3
- package/dist/voice-io.js +2 -2
- package/package.json +1 -1
package/dist/claude-llm.js
CHANGED
|
@@ -31,6 +31,12 @@ const TOOL_CALL_BUDGET = 3;
|
|
|
31
31
|
* Strip markdown formatting for TTS (text-to-speech)
|
|
32
32
|
* Removes **bold**, ##headers, ```code```, etc. so TTS doesn't read them literally
|
|
33
33
|
*/
|
|
34
|
+
function extractSpeedMarker(text) {
|
|
35
|
+
const m = text.match(/^\[SPEED:(0\.[5-9]\d*|1\.[0-3]\d*)\]/);
|
|
36
|
+
if (!m)
|
|
37
|
+
return { text };
|
|
38
|
+
return { text: text.slice(m[0].length), speed: parseFloat(m[1]) };
|
|
39
|
+
}
|
|
34
40
|
function stripMarkdownForTTS(text) {
|
|
35
41
|
return text
|
|
36
42
|
// Remove code blocks (``` ... ```)
|
|
@@ -677,6 +683,7 @@ export class ClaudeLLM extends llm.LLM {
|
|
|
677
683
|
#currentTurnMessageId = null;
|
|
678
684
|
#currentTurnChunkIndex = 0;
|
|
679
685
|
#currentTurnChunks = [];
|
|
686
|
+
#currentTurnSpeed;
|
|
680
687
|
#backgroundConsumerRunning = false;
|
|
681
688
|
// Active queries — multiple can be running (SDK queues them internally).
|
|
682
689
|
// We keep ALL references so interrupt() can stop whatever is currently executing.
|
|
@@ -1249,18 +1256,22 @@ export class ClaudeLLM extends llm.LLM {
|
|
|
1249
1256
|
this.#currentTurnMessageId = crypto.randomUUID();
|
|
1250
1257
|
this.#currentTurnChunkIndex = 0;
|
|
1251
1258
|
this.#currentTurnChunks = [];
|
|
1259
|
+
this.#currentTurnSpeed = undefined;
|
|
1252
1260
|
}
|
|
1253
1261
|
const turnMessageId = this.#currentTurnMessageId;
|
|
1254
1262
|
for (const block of msg.message.content) {
|
|
1255
1263
|
if (block.type === 'text' && block.text) {
|
|
1256
1264
|
const chunkIndex = this.#currentTurnChunkIndex;
|
|
1257
|
-
|
|
1258
|
-
|
|
1265
|
+
const { text: cleanText, speed } = extractSpeedMarker(block.text);
|
|
1266
|
+
if (speed !== undefined)
|
|
1267
|
+
this.#currentTurnSpeed = speed;
|
|
1268
|
+
callbacks.eventEmitter.emit('assistant_text', { text: cleanText, messageId: turnMessageId, chunkIndex });
|
|
1269
|
+
const ttsChunk = stripMarkdownForTTS(cleanText);
|
|
1259
1270
|
if (ttsChunk.trim()) {
|
|
1260
1271
|
this.#currentTurnChunks.push(ttsChunk);
|
|
1261
1272
|
this.#currentTurnChunkIndex++;
|
|
1262
1273
|
console.log(`🔊 TTS say (${ttsChunk.length} chars): "${ttsChunk}"`);
|
|
1263
|
-
callbacks.eventEmitter.emit('tts_say', { text: ttsChunk, messageId: turnMessageId, chunkIndex });
|
|
1274
|
+
callbacks.eventEmitter.emit('tts_say', { text: ttsChunk, messageId: turnMessageId, chunkIndex, speed: this.#currentTurnSpeed });
|
|
1264
1275
|
}
|
|
1265
1276
|
}
|
|
1266
1277
|
}
|
|
@@ -1279,6 +1290,7 @@ export class ClaudeLLM extends llm.LLM {
|
|
|
1279
1290
|
this.#currentTurnMessageId = null;
|
|
1280
1291
|
this.#currentTurnChunkIndex = 0;
|
|
1281
1292
|
this.#currentTurnChunks = [];
|
|
1293
|
+
this.#currentTurnSpeed = undefined;
|
|
1282
1294
|
console.log('✅ Claude turn complete (persistent session stays alive)');
|
|
1283
1295
|
}
|
|
1284
1296
|
}
|
|
@@ -2136,6 +2148,7 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
2136
2148
|
let streamTurnMessageId = null;
|
|
2137
2149
|
let streamTurnChunkIndex = 0;
|
|
2138
2150
|
let streamTurnChunks = [];
|
|
2151
|
+
let streamTurnSpeed;
|
|
2139
2152
|
// DIRECT MODE OPTIMIZATION: When skipTTSQueue is true, we run the Claude query
|
|
2140
2153
|
// in the background and return from run() immediately. This is critical because:
|
|
2141
2154
|
//
|
|
@@ -2227,11 +2240,16 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
2227
2240
|
streamTurnMessageId = crypto.randomUUID();
|
|
2228
2241
|
streamTurnChunkIndex = 0;
|
|
2229
2242
|
streamTurnChunks = [];
|
|
2243
|
+
streamTurnSpeed = undefined;
|
|
2230
2244
|
}
|
|
2231
2245
|
for (const block of message.message.content) {
|
|
2232
2246
|
if (block.type === 'text' && block.text) {
|
|
2233
2247
|
hasOutput = true;
|
|
2234
|
-
|
|
2248
|
+
// Strip [SPEED:X.X] marker before emitting to frontend or TTS
|
|
2249
|
+
const { text: cleanText, speed } = extractSpeedMarker(block.text);
|
|
2250
|
+
if (speed !== undefined)
|
|
2251
|
+
streamTurnSpeed = speed;
|
|
2252
|
+
const rawText = cleanText;
|
|
2235
2253
|
const chunkIndex = streamTurnChunkIndex;
|
|
2236
2254
|
// Emit RAW text to frontend (for chat bubbles with full formatting)
|
|
2237
2255
|
this.#eventEmitter.emit('assistant_text', { text: rawText, messageId: streamTurnMessageId, chunkIndex });
|
|
@@ -2244,7 +2262,7 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
2244
2262
|
// Direct mode: emit event for session.say() — bypasses LiveKit's
|
|
2245
2263
|
// BufferedTokenStream which causes stuck/delayed/out-of-order audio
|
|
2246
2264
|
console.log(`🔊 TTS say (${ttsChunk.length} chars): "${ttsChunk}"`);
|
|
2247
|
-
this.#eventEmitter.emit('tts_say', { text: ttsChunk, messageId: streamTurnMessageId, chunkIndex });
|
|
2265
|
+
this.#eventEmitter.emit('tts_say', { text: ttsChunk, messageId: streamTurnMessageId, chunkIndex, speed: streamTurnSpeed });
|
|
2248
2266
|
}
|
|
2249
2267
|
else {
|
|
2250
2268
|
// Realtime mode: use LLM stream queue (framework handles TTS)
|
package/dist/index.js
CHANGED
|
@@ -3441,6 +3441,13 @@ async function main() {
|
|
|
3441
3441
|
}
|
|
3442
3442
|
const sayId = Date.now(); // simple ID to correlate start/end logs
|
|
3443
3443
|
console.log(`🗣️ [${sayId}] session.say START (${data.text.length} chars): "${data.text}"`);
|
|
3444
|
+
// Apply agent-requested speed (e.g. [SPEED:0.85] stripped from response text)
|
|
3445
|
+
if (data.speed !== undefined) {
|
|
3446
|
+
try {
|
|
3447
|
+
tts.updateOptions?.({ speed: data.speed });
|
|
3448
|
+
}
|
|
3449
|
+
catch { }
|
|
3450
|
+
}
|
|
3444
3451
|
try {
|
|
3445
3452
|
const handle = currentSession.say(data.text);
|
|
3446
3453
|
if (handle && typeof handle.addDoneCallback === 'function') {
|
|
@@ -3647,9 +3654,9 @@ async function main() {
|
|
|
3647
3654
|
// a full 3s window to keep talking before deciding it was false and
|
|
3648
3655
|
// resuming. Other two knobs left at SDK defaults.
|
|
3649
3656
|
interruption: {
|
|
3650
|
-
minDuration:
|
|
3651
|
-
minWords:
|
|
3652
|
-
falseInterruptionTimeout: 3500, // default 2000 — 3.5s false-interrupt window
|
|
3657
|
+
minDuration: 800, // default 500 — require 800ms sustained speech (tightened from 1500; Soniox semantic STT reduces false positives)
|
|
3658
|
+
minWords: 1, // default 0 — require ≥1 word ("stop", "wait", "no" now count)
|
|
3659
|
+
falseInterruptionTimeout: 3500, // default 2000 — 3.5s false-interrupt window
|
|
3653
3660
|
// resumeFalseInterruption: true, // default true (unchanged)
|
|
3654
3661
|
// discardAudioIfUninterruptible: true,// default true (unchanged)
|
|
3655
3662
|
},
|
package/dist/voice-io.js
CHANGED
|
@@ -23,7 +23,7 @@ export function createSTT(config) {
|
|
|
23
23
|
model: (config.model || 'stt-rt-v4'),
|
|
24
24
|
languageHints: config.language ? [config.language] : ['en'],
|
|
25
25
|
maxEndpointDelayMs: 1200, // give model room to decide on mid-thought pauses
|
|
26
|
-
endpointLatencyAdjustmentLevel:
|
|
26
|
+
endpointLatencyAdjustmentLevel: 3, // max aggression — semantic model handles accuracy
|
|
27
27
|
context: {
|
|
28
28
|
terms: ['Claude', 'TypeScript', 'LiveKit', 'Deepgram', 'npm', 'Railway', 'Fly.io'],
|
|
29
29
|
},
|
|
@@ -76,7 +76,7 @@ export function createTTS(config) {
|
|
|
76
76
|
tts = new soniox.TTS({
|
|
77
77
|
model: (config.model || 'tts-rt-v1'),
|
|
78
78
|
voice: config.voice || 'Maya',
|
|
79
|
-
speed:
|
|
79
|
+
speed: 0.9,
|
|
80
80
|
});
|
|
81
81
|
break;
|
|
82
82
|
case 'openai':
|