open-agents-ai 0.163.0 → 0.165.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.
Files changed (2) hide show
  1. package/dist/index.js +121 -24
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11674,6 +11674,25 @@ async function loadTranscribeCli() {
11674
11674
  }
11675
11675
  return null;
11676
11676
  }
11677
+ function isYouTubeUrl(url) {
11678
+ return /(?:youtube\.com\/(?:watch|shorts|live|embed|v\/)|youtu\.be\/)/i.test(url);
11679
+ }
11680
+ function ensureYtDlp() {
11681
+ try {
11682
+ execSync10("yt-dlp --version", { timeout: 5e3, stdio: "pipe" });
11683
+ return true;
11684
+ } catch {
11685
+ try {
11686
+ execSync10("pip3 install --user yt-dlp 2>/dev/null || pip install --user yt-dlp 2>/dev/null", {
11687
+ timeout: 6e4,
11688
+ stdio: "pipe"
11689
+ });
11690
+ return true;
11691
+ } catch {
11692
+ return false;
11693
+ }
11694
+ }
11695
+ }
11677
11696
  function formatTime(seconds) {
11678
11697
  const m = Math.floor(seconds / 60);
11679
11698
  const s = Math.floor(seconds % 60);
@@ -11762,23 +11781,69 @@ var init_transcribe_tool = __esm({
11762
11781
  model,
11763
11782
  format: "json",
11764
11783
  diarize,
11765
- wordTimestamps: false
11784
+ wordTimestamps: true
11785
+ // Always get timestamps for structured output
11766
11786
  });
11767
11787
  const transcriptDir = join19(this.workingDir, ".oa", "transcripts");
11768
11788
  mkdirSync7(transcriptDir, { recursive: true });
11769
- const outFile = join19(transcriptDir, `${basename4(filePath)}.txt`);
11770
- writeFileSync7(outFile, result.text, "utf-8");
11789
+ const fileBase = basename4(filePath).replace(/\.[^.]+$/, "");
11790
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
11791
+ const txtFile = join19(transcriptDir, `${fileBase}-${timestamp}.txt`);
11792
+ writeFileSync7(txtFile, result.text, "utf-8");
11793
+ const jsonFile = join19(transcriptDir, `${fileBase}-${timestamp}.json`);
11794
+ const structured = {
11795
+ source: filePath,
11796
+ model,
11797
+ language: result.language,
11798
+ duration: result.duration,
11799
+ wordCount: result.wordCount,
11800
+ speakers: result.speakers,
11801
+ transcribedAt: (/* @__PURE__ */ new Date()).toISOString(),
11802
+ segments: result.segments.map((seg) => ({
11803
+ start: seg.start,
11804
+ end: seg.end,
11805
+ text: seg.text,
11806
+ speaker: seg.speaker || void 0
11807
+ })),
11808
+ fullText: result.text
11809
+ };
11810
+ writeFileSync7(jsonFile, JSON.stringify(structured, null, 2), "utf-8");
11811
+ try {
11812
+ const memDir = join19(this.workingDir, ".oa", "memory");
11813
+ mkdirSync7(memDir, { recursive: true });
11814
+ const memFile = join19(memDir, "transcripts.json");
11815
+ let memEntries = [];
11816
+ try {
11817
+ memEntries = JSON.parse(readFileSync13(memFile, "utf-8"));
11818
+ } catch {
11819
+ }
11820
+ memEntries.push({
11821
+ source: basename4(filePath),
11822
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11823
+ duration: result.duration,
11824
+ wordCount: result.wordCount,
11825
+ language: result.language,
11826
+ summary: result.text.slice(0, 200),
11827
+ jsonPath: jsonFile,
11828
+ txtPath: txtFile
11829
+ });
11830
+ if (memEntries.length > 50)
11831
+ memEntries = memEntries.slice(-50);
11832
+ writeFileSync7(memFile, JSON.stringify(memEntries, null, 2), "utf-8");
11833
+ } catch {
11834
+ }
11771
11835
  const lines = [
11772
11836
  `Transcription of: ${basename4(filePath)}`,
11773
11837
  `Model: ${model} | Language: ${result.language} | Duration: ${result.duration ? `${result.duration.toFixed(1)}s` : "unknown"}`,
11774
- `Words: ${result.wordCount} | Saved to: ${outFile}`,
11838
+ `Words: ${result.wordCount} | Segments: ${result.segments.length}`,
11839
+ `Saved: ${txtFile} (text) + ${jsonFile} (structured with timestamps)`,
11775
11840
  ""
11776
11841
  ];
11777
11842
  if (result.speakers.length > 1) {
11778
11843
  lines.push(`Speakers: ${result.speakers.join(", ")}`);
11779
11844
  lines.push("");
11780
11845
  }
11781
- if (diarize && result.segments.length > 0) {
11846
+ if (result.segments.length > 0) {
11782
11847
  for (const seg of result.segments) {
11783
11848
  const ts = `[${formatTime(seg.start)} \u2192 ${formatTime(seg.end)}]`;
11784
11849
  const speaker = seg.speaker ? `${seg.speaker}: ` : "";
@@ -11832,13 +11897,13 @@ var init_transcribe_tool = __esm({
11832
11897
  };
11833
11898
  TranscribeUrlTool = class {
11834
11899
  name = "transcribe_url";
11835
- description = "Download an audio or video file from a URL and transcribe it to text. Supports direct links to MP3, WAV, MP4, and other media files. The file is downloaded to a temp location, transcribed locally, then cleaned up.";
11900
+ description = "Download and transcribe audio/video from a URL. Supports YouTube links (youtube.com/watch?v=..., youtu.be/...) and direct media URLs (MP3, WAV, MP4, etc.). YouTube audio is extracted via yt-dlp (auto-installed). Transcription is local via faster-whisper (no cloud API).";
11836
11901
  parameters = {
11837
11902
  type: "object",
11838
11903
  properties: {
11839
11904
  url: {
11840
11905
  type: "string",
11841
- description: "URL of the audio or video file to download and transcribe"
11906
+ description: "URL to transcribe \u2014 YouTube links (youtube.com/watch?v=..., youtu.be/...) or direct media files (MP3, WAV, MP4)"
11842
11907
  },
11843
11908
  model: {
11844
11909
  type: "string",
@@ -11866,25 +11931,57 @@ var init_transcribe_tool = __esm({
11866
11931
  }
11867
11932
  const tmpDir = join19(this.workingDir, ".oa", "tmp");
11868
11933
  mkdirSync7(tmpDir, { recursive: true });
11869
- const urlPath = new URL(url).pathname;
11870
- let ext = extname3(urlPath).toLowerCase();
11871
- if (!ext || !AUDIO_EXTS.has(ext) && !VIDEO_EXTS.has(ext)) {
11872
- ext = ".mp3";
11873
- }
11874
- const tmpFile = join19(tmpDir, `download-${Date.now()}${ext}`);
11934
+ const tmpBase = join19(tmpDir, `download-${Date.now()}`);
11935
+ let tmpFile = "";
11875
11936
  try {
11876
- try {
11877
- execSync10(`curl -sL -o "${tmpFile}" "${url}"`, {
11878
- timeout: 12e4,
11879
- stdio: ["pipe", "pipe", "pipe"]
11880
- });
11881
- } catch {
11882
- execSync10(`wget -q -O "${tmpFile}" "${url}"`, {
11883
- timeout: 12e4,
11884
- stdio: ["pipe", "pipe", "pipe"]
11885
- });
11937
+ if (isYouTubeUrl(url)) {
11938
+ if (!ensureYtDlp()) {
11939
+ return {
11940
+ success: false,
11941
+ output: "",
11942
+ error: "yt-dlp not found and auto-install failed. Install manually: pip3 install yt-dlp",
11943
+ durationMs: performance.now() - start
11944
+ };
11945
+ }
11946
+ tmpFile = `${tmpBase}.mp3`;
11947
+ try {
11948
+ execSync10(`yt-dlp -x --audio-format mp3 --audio-quality 5 -o "${tmpBase}.%(ext)s" "${url}" 2>&1`, { timeout: 3e5, stdio: ["pipe", "pipe", "pipe"] });
11949
+ if (!existsSync16(tmpFile)) {
11950
+ const { readdirSync: rd } = __require("node:fs");
11951
+ const files = rd(tmpDir).filter((f) => f.startsWith(`download-`) && f !== ".gitkeep");
11952
+ const match = files.find((f) => f.includes(basename4(tmpBase)));
11953
+ if (match)
11954
+ tmpFile = join19(tmpDir, match);
11955
+ }
11956
+ } catch (dlErr) {
11957
+ const errMsg = dlErr instanceof Error ? dlErr.message : String(dlErr);
11958
+ return {
11959
+ success: false,
11960
+ output: "",
11961
+ error: `yt-dlp failed: ${errMsg.slice(0, 200)}. Is the video available and not age-restricted?`,
11962
+ durationMs: performance.now() - start
11963
+ };
11964
+ }
11965
+ } else {
11966
+ const urlPath = new URL(url).pathname;
11967
+ let ext = extname3(urlPath).toLowerCase();
11968
+ if (!ext || !AUDIO_EXTS.has(ext) && !VIDEO_EXTS.has(ext)) {
11969
+ ext = ".mp3";
11970
+ }
11971
+ tmpFile = `${tmpBase}${ext}`;
11972
+ try {
11973
+ execSync10(`curl -sL -o "${tmpFile}" "${url}"`, {
11974
+ timeout: 12e4,
11975
+ stdio: ["pipe", "pipe", "pipe"]
11976
+ });
11977
+ } catch {
11978
+ execSync10(`wget -q -O "${tmpFile}" "${url}"`, {
11979
+ timeout: 12e4,
11980
+ stdio: ["pipe", "pipe", "pipe"]
11981
+ });
11982
+ }
11886
11983
  }
11887
- if (!existsSync16(tmpFile)) {
11984
+ if (!tmpFile || !existsSync16(tmpFile)) {
11888
11985
  return {
11889
11986
  success: false,
11890
11987
  output: "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.163.0",
3
+ "version": "0.165.0",
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",