open-agents-ai 0.137.0 → 0.138.1

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.
Files changed (2) hide show
  1. package/dist/index.js +140 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21583,8 +21583,55 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
21583
21583
  break;
21584
21584
  }
21585
21585
  if (/does not support tools|HTTP 400.*tools/i.test(errMsg)) {
21586
- this.emit({ type: "error", content: `Model does not support tool calling. Use /model to select a model with tool support (e.g., qwen3.5, nemotron).`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
21587
- break;
21586
+ this.emit({
21587
+ type: "status",
21588
+ content: `Model lacks native tool support \u2014 switching to prompt-injected tool mode`,
21589
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
21590
+ });
21591
+ const toolDescriptions = Array.from(this.tools.values()).map((t) => `- ${t.name}: ${t.description}`).join("\n");
21592
+ const toolInjectMsg = [
21593
+ "\n\n[TOOL MODE \u2014 PROMPT INJECTION]",
21594
+ "This model does not have native tool-calling. To use tools, output a JSON block:",
21595
+ "```json",
21596
+ '{"tool": "tool_name", "args": {"param": "value"}}',
21597
+ "```",
21598
+ "\nAvailable tools:",
21599
+ toolDescriptions,
21600
+ "\nOutput EXACTLY ONE tool call per response in the JSON format above.",
21601
+ "After seeing the tool result, continue or call another tool.",
21602
+ 'When done, output: {"tool": "task_complete", "args": {"summary": "what you did"}}'
21603
+ ].join("\n");
21604
+ messages.push({ role: "system", content: toolInjectMsg });
21605
+ chatRequest.tools = [];
21606
+ try {
21607
+ response = this.options.streamEnabled && this.hasStreamingSupport() ? await this.streamingRequest(chatRequest, turn) : await this.backend.chatCompletion(chatRequest);
21608
+ const content = response.choices?.[0]?.message?.content ?? "";
21609
+ const jsonMatch = content.match(/```json\s*\n?([\s\S]*?)```/);
21610
+ if (jsonMatch) {
21611
+ try {
21612
+ const parsed = JSON.parse(jsonMatch[1]);
21613
+ if (parsed.tool && this.tools.has(parsed.tool)) {
21614
+ const tool = this.tools.get(parsed.tool);
21615
+ const result = await tool.execute(parsed.args ?? {});
21616
+ messages.push({ role: "assistant", content });
21617
+ messages.push({ role: "user", content: `Tool result (${parsed.tool}): ${result.output.slice(0, 2e3)}` });
21618
+ if (parsed.tool === "task_complete") {
21619
+ completed = true;
21620
+ summary = String(parsed.args?.summary ?? content);
21621
+ }
21622
+ toolCallCount++;
21623
+ continue;
21624
+ }
21625
+ } catch {
21626
+ }
21627
+ }
21628
+ messages.push({ role: "assistant", content });
21629
+ continue;
21630
+ } catch (retryErr2) {
21631
+ const msg2 = retryErr2 instanceof Error ? retryErr2.message : String(retryErr2);
21632
+ this.emit({ type: "error", content: `Prompt-injected tool mode also failed: ${msg2}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
21633
+ break;
21634
+ }
21588
21635
  }
21589
21636
  messages.push({ role: "user", content: "[System: backend request failed, retrying on next turn. The previous request was lost.]" });
21590
21637
  continue;
@@ -38718,20 +38765,21 @@ var init_voice = __esm({
38718
38765
  speak(text, emotion) {
38719
38766
  const pitchBias = emotion ? emotionToPitchBias(emotion, this.starkMode, this.autistMode) : 0;
38720
38767
  const speedFactor = emotion ? emotionToSpeedFactor(emotion, this.starkMode, this.autistMode) : 1;
38721
- this.enqueueSpeech(text, 1, 1 + pitchBias, speedFactor);
38768
+ this.enqueueSpeech(text, 1, 1 + pitchBias, speedFactor, 0.6);
38722
38769
  }
38723
38770
  /**
38724
- * Speak text at reduced volume with pitch shift to indicate
38771
+ * Speak text at reduced volume with narrow stereo placement to indicate
38725
38772
  * subordinate/secondary output (sub-agent activity, tool narration, etc.).
38726
- * Volume: 55% of primary. Base pitch: 0.92x (lower).
38727
- * Emotion further modulates pitch and speed on top of the subordinate base.
38773
+ * Volume: 55% of primary. Stereo delay: 0.15ms (narrow — "recessed/behind").
38774
+ * No pitch shift depth is conveyed by stereo narrowness + lower volume.
38775
+ * Emotion still modulates pitch and speed on top.
38728
38776
  */
38729
38777
  speakSubordinate(text, emotion) {
38730
38778
  const pitchBias = emotion ? emotionToPitchBias(emotion, this.starkMode, this.autistMode) : 0;
38731
38779
  const speedFactor = emotion ? emotionToSpeedFactor(emotion, this.starkMode, this.autistMode) : 1;
38732
- this.enqueueSpeech(text, 0.55, 0.92 + pitchBias, speedFactor);
38780
+ this.enqueueSpeech(text, 0.55, 1 + pitchBias, speedFactor, 0.15);
38733
38781
  }
38734
- enqueueSpeech(text, volume, pitchFactor, speedFactor = 1) {
38782
+ enqueueSpeech(text, volume, pitchFactor, speedFactor = 1, stereoDelayMs = 0.6) {
38735
38783
  if (!this.enabled || !this.ready)
38736
38784
  return;
38737
38785
  const chunks = this.chunkText(text);
@@ -38739,7 +38787,7 @@ var init_voice = __esm({
38739
38787
  this.speakQueue.length = 0;
38740
38788
  }
38741
38789
  for (const chunk of chunks) {
38742
- this.speakQueue.push({ text: chunk, volume, pitchFactor, speedFactor });
38790
+ this.speakQueue.push({ text: chunk, volume, pitchFactor, speedFactor, stereoDelayMs });
38743
38791
  }
38744
38792
  if (!this.speaking) {
38745
38793
  this.drainQueue().catch(() => {
@@ -38919,7 +38967,7 @@ var init_voice = __esm({
38919
38967
  }
38920
38968
  isFirst = false;
38921
38969
  try {
38922
- await this.synthesizeAndPlay(item.text, item.volume, item.pitchFactor, item.speedFactor);
38970
+ await this.synthesizeAndPlay(item.text, item.volume, item.pitchFactor, item.speedFactor, item.stereoDelayMs);
38923
38971
  } catch {
38924
38972
  }
38925
38973
  }
@@ -38931,9 +38979,9 @@ var init_voice = __esm({
38931
38979
  // -------------------------------------------------------------------------
38932
38980
  // Synthesis pipeline
38933
38981
  // -------------------------------------------------------------------------
38934
- async synthesizeAndPlay(text, volume = 1, pitchFactor = 1, speedFactor = 1) {
38982
+ async synthesizeAndPlay(text, volume = 1, pitchFactor = 1, speedFactor = 1, stereoDelayMs = 0.6) {
38935
38983
  if (this.luxttsActive) {
38936
- await this.synthesizeWithLuxtts(text, volume, pitchFactor, speedFactor);
38984
+ await this.synthesizeWithLuxtts(text, volume, pitchFactor, speedFactor, stereoDelayMs);
38937
38985
  return;
38938
38986
  }
38939
38987
  if (this.mlxActive) {
@@ -38979,6 +39027,7 @@ var init_voice = __esm({
38979
39027
  if (pitchFactor !== 1) {
38980
39028
  audioData = this.resamplePitch(audioData, pitchFactor);
38981
39029
  }
39030
+ const stereo = this.applyStereoDelay(audioData, sampleRate, stereoDelayMs);
38982
39031
  if (this.onPCMOutput) {
38983
39032
  const int16 = new Int16Array(audioData.length);
38984
39033
  for (let i = 0; i < audioData.length; i++) {
@@ -38988,7 +39037,7 @@ var init_voice = __esm({
38988
39037
  this.onPCMOutput(Buffer.from(int16.buffer), sampleRate);
38989
39038
  }
38990
39039
  const wavPath = join50(tmpdir9(), `oa-voice-${Date.now()}.wav`);
38991
- this.writeWav(audioData, sampleRate, wavPath);
39040
+ this.writeStereoWav(stereo.left, stereo.right, sampleRate, wavPath);
38992
39041
  await this.playWav(wavPath);
38993
39042
  try {
38994
39043
  unlinkSync8(wavPath);
@@ -39049,6 +39098,63 @@ var init_voice = __esm({
39049
39098
  }
39050
39099
  return out;
39051
39100
  }
39101
+ /**
39102
+ * Apply interaural time delay (ITD) to create stereo depth from mono audio.
39103
+ *
39104
+ * The right channel is delayed by `delayMs` milliseconds relative to the left.
39105
+ * This creates a perceived spatial position — wider delay = more "present/forward",
39106
+ * narrower delay = more "recessed/behind". Based on Blauert's duplex theory:
39107
+ * ITDs below ~1.5ms produce lateralization without audible echo.
39108
+ *
39109
+ * Typical values:
39110
+ * 0.6ms — primary voice, wide stereo presence ("speaking to you")
39111
+ * 0.15ms — secondary voice, narrow/centered ("background, recessed")
39112
+ */
39113
+ applyStereoDelay(mono, sampleRate, delayMs) {
39114
+ const delaySamples = Math.round(delayMs / 1e3 * sampleRate);
39115
+ const outLen = mono.length + delaySamples;
39116
+ const left = new Float32Array(outLen);
39117
+ const right = new Float32Array(outLen);
39118
+ left.set(mono, 0);
39119
+ right.set(mono, delaySamples);
39120
+ return { left, right };
39121
+ }
39122
+ /**
39123
+ * Write a stereo (2-channel interleaved) WAV file from left/right Float32 channels.
39124
+ * PCM 16-bit, same sample rate as the model output.
39125
+ */
39126
+ writeStereoWav(left, right, sampleRate, path) {
39127
+ const numChannels = 2;
39128
+ const bitsPerSample = 16;
39129
+ const numSamples = Math.min(left.length, right.length);
39130
+ const byteRate = sampleRate * numChannels * (bitsPerSample / 8);
39131
+ const blockAlign = numChannels * (bitsPerSample / 8);
39132
+ const dataSize = numSamples * numChannels * (bitsPerSample / 8);
39133
+ const buffer = Buffer.alloc(44 + dataSize);
39134
+ buffer.write("RIFF", 0);
39135
+ buffer.writeUInt32LE(36 + dataSize, 4);
39136
+ buffer.write("WAVE", 8);
39137
+ buffer.write("fmt ", 12);
39138
+ buffer.writeUInt32LE(16, 16);
39139
+ buffer.writeUInt16LE(1, 20);
39140
+ buffer.writeUInt16LE(numChannels, 22);
39141
+ buffer.writeUInt32LE(sampleRate, 24);
39142
+ buffer.writeUInt32LE(byteRate, 28);
39143
+ buffer.writeUInt16LE(blockAlign, 32);
39144
+ buffer.writeUInt16LE(bitsPerSample, 34);
39145
+ buffer.write("data", 36);
39146
+ buffer.writeUInt32LE(dataSize, 40);
39147
+ let pos = 44;
39148
+ for (let i = 0; i < numSamples; i++) {
39149
+ const lSample = Math.max(-1, Math.min(1, left[i]));
39150
+ const rSample = Math.max(-1, Math.min(1, right[i]));
39151
+ buffer.writeInt16LE(lSample < 0 ? lSample * 32768 : lSample * 32767, pos);
39152
+ pos += 2;
39153
+ buffer.writeInt16LE(rSample < 0 ? rSample * 32768 : rSample * 32767, pos);
39154
+ pos += 2;
39155
+ }
39156
+ writeFileSync14(path, buffer);
39157
+ }
39052
39158
  // -------------------------------------------------------------------------
39053
39159
  // Phonemization
39054
39160
  // -------------------------------------------------------------------------
@@ -39743,9 +39849,9 @@ if __name__ == '__main__':
39743
39849
  /**
39744
39850
  * Synthesize and play audio using LuxTTS voice cloning.
39745
39851
  * Speed is passed natively to LuxTTS. Pitch is post-processed via resampling.
39746
- * Volume is applied via WAV sample scaling.
39852
+ * Volume is applied via WAV sample scaling. Stereo ITD applied for depth.
39747
39853
  */
39748
- async synthesizeWithLuxtts(text, volume = 1, pitchFactor = 1, speedFactor = 1) {
39854
+ async synthesizeWithLuxtts(text, volume = 1, pitchFactor = 1, speedFactor = 1, stereoDelayMs = 0.6) {
39749
39855
  if (!this.luxttsCloneRef || !existsSync35(this.luxttsCloneRef)) {
39750
39856
  return;
39751
39857
  }
@@ -39810,6 +39916,25 @@ if __name__ == '__main__':
39810
39916
  } catch {
39811
39917
  }
39812
39918
  }
39919
+ if (stereoDelayMs > 0) {
39920
+ try {
39921
+ const wavData = readFileSync24(wavPath);
39922
+ if (wavData.length > 44) {
39923
+ const sampleRate = wavData.readUInt32LE(24);
39924
+ const numChannels = wavData.readUInt16LE(22);
39925
+ if (numChannels === 1) {
39926
+ const int16 = new Int16Array(wavData.buffer, wavData.byteOffset + 44, (wavData.length - 44) / 2);
39927
+ const float32 = new Float32Array(int16.length);
39928
+ for (let i = 0; i < int16.length; i++) {
39929
+ float32[i] = int16[i] / 32768;
39930
+ }
39931
+ const stereo = this.applyStereoDelay(float32, sampleRate, stereoDelayMs);
39932
+ this.writeStereoWav(stereo.left, stereo.right, sampleRate, wavPath);
39933
+ }
39934
+ }
39935
+ } catch {
39936
+ }
39937
+ }
39813
39938
  await this.playWav(wavPath);
39814
39939
  try {
39815
39940
  unlinkSync8(wavPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.137.0",
3
+ "version": "0.138.1",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",