open-agents-ai 0.184.70 → 0.184.72

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 +255 -23
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -560,6 +560,10 @@ var init_normalizeUrl = __esm({
560
560
  "use strict";
561
561
  PROVIDERS = [
562
562
  // --- Cloud providers (specific domains) ---
563
+ {
564
+ match: (u) => /api\.anthropic\.com/i.test(u),
565
+ info: { id: "anthropic", label: "Anthropic (Claude)", local: false, authRequired: true, keyPrefix: "sk-ant-", modelsPath: "/v1/models" }
566
+ },
563
567
  {
564
568
  match: (u) => /api\.openai\.com/i.test(u),
565
569
  info: { id: "openai", label: "OpenAI", local: false, authRequired: true, keyPrefix: "sk-", modelsPath: "/v1/models" }
@@ -11949,7 +11953,7 @@ function formatTime(seconds) {
11949
11953
  const s = Math.floor(seconds % 60);
11950
11954
  return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
11951
11955
  }
11952
- var AUDIO_EXTS, VIDEO_EXTS, _tcModule, _tcChecked, TranscribeFileTool, TranscribeUrlTool;
11956
+ var AUDIO_EXTS, VIDEO_EXTS, _tcModule, _tcChecked, TranscribeFileTool, TranscribeUrlTool, YouTubeDownloadTool;
11953
11957
  var init_transcribe_tool = __esm({
11954
11958
  "packages/execution/dist/tools/transcribe-tool.js"() {
11955
11959
  "use strict";
@@ -12267,6 +12271,90 @@ ${result.output}`,
12267
12271
  }
12268
12272
  }
12269
12273
  };
12274
+ YouTubeDownloadTool = class {
12275
+ name = "youtube_download";
12276
+ description = "Download video or audio from YouTube. Saves mp4 (video) or mp3 (audio) to the working directory. Uses yt-dlp (auto-installed). Supports youtube.com/watch, youtu.be, shorts, live URLs.";
12277
+ parameters = {
12278
+ type: "object",
12279
+ properties: {
12280
+ url: {
12281
+ type: "string",
12282
+ description: "YouTube URL (youtube.com/watch?v=..., youtu.be/..., etc.)"
12283
+ },
12284
+ format: {
12285
+ type: "string",
12286
+ enum: ["mp3", "mp4"],
12287
+ description: "Output format: 'mp3' for audio only, 'mp4' for video (default: mp3)"
12288
+ },
12289
+ output_dir: {
12290
+ type: "string",
12291
+ description: "Directory to save the file (default: current working directory)"
12292
+ }
12293
+ },
12294
+ required: ["url"]
12295
+ };
12296
+ workingDir;
12297
+ constructor(workingDir) {
12298
+ this.workingDir = workingDir;
12299
+ }
12300
+ async execute(args) {
12301
+ const start = Date.now();
12302
+ const url = String(args.url ?? "").trim();
12303
+ const format = String(args.format ?? "mp3").toLowerCase();
12304
+ const outputDir = String(args.output_dir ?? this.workingDir);
12305
+ if (!url) {
12306
+ return { success: false, output: "", error: "URL is required", durationMs: Date.now() - start };
12307
+ }
12308
+ if (!isYouTubeUrl(url)) {
12309
+ return { success: false, output: "", error: "Not a recognized YouTube URL. Supported: youtube.com/watch, youtu.be, shorts, live, embed", durationMs: Date.now() - start };
12310
+ }
12311
+ if (!ensureYtDlp()) {
12312
+ return { success: false, output: "", error: "yt-dlp not available and auto-install failed. Install manually: pip install yt-dlp", durationMs: Date.now() - start };
12313
+ }
12314
+ mkdirSync7(outputDir, { recursive: true });
12315
+ try {
12316
+ let title = "download";
12317
+ try {
12318
+ title = execSync10(`yt-dlp --get-title "${url}"`, { timeout: 15e3, stdio: "pipe" }).toString().trim().replace(/[<>:"/\\|?*]/g, "_").slice(0, 100);
12319
+ } catch {
12320
+ }
12321
+ if (format === "mp4") {
12322
+ const outPath = join19(outputDir, `${title}.mp4`);
12323
+ const outTemplate = join19(outputDir, `${title}.%(ext)s`);
12324
+ execSync10(`yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" --merge-output-format mp4 -o "${outTemplate}" "${url}"`, { timeout: 6e5, stdio: "pipe", cwd: outputDir });
12325
+ const actualPath = existsSync16(outPath) ? outPath : outTemplate.replace("%(ext)s", "mp4");
12326
+ return {
12327
+ success: true,
12328
+ output: `Downloaded video: ${actualPath}
12329
+ Title: ${title}
12330
+ Format: mp4`,
12331
+ durationMs: Date.now() - start
12332
+ };
12333
+ } else {
12334
+ const outPath = join19(outputDir, `${title}.mp3`);
12335
+ const outTemplate = join19(outputDir, `${title}.%(ext)s`);
12336
+ execSync10(`yt-dlp -x --audio-format mp3 --audio-quality 0 -o "${outTemplate}" "${url}"`, { timeout: 6e5, stdio: "pipe", cwd: outputDir });
12337
+ const actualPath = existsSync16(outPath) ? outPath : outTemplate.replace("%(ext)s", "mp3");
12338
+ return {
12339
+ success: true,
12340
+ output: `Downloaded audio: ${actualPath}
12341
+ Title: ${title}
12342
+ Format: mp3`,
12343
+ durationMs: Date.now() - start
12344
+ };
12345
+ }
12346
+ } catch (err) {
12347
+ const msg = err instanceof Error ? err.message : String(err);
12348
+ const stderr = err?.stderr?.toString?.()?.slice(0, 500) ?? "";
12349
+ return {
12350
+ success: false,
12351
+ output: "",
12352
+ error: `YouTube download failed: ${msg}${stderr ? "\n" + stderr : ""}`,
12353
+ durationMs: Date.now() - start
12354
+ };
12355
+ }
12356
+ }
12357
+ };
12270
12358
  }
12271
12359
  });
12272
12360
 
@@ -22603,6 +22691,7 @@ __export(dist_exports, {
22603
22691
  WebFetchTool: () => WebFetchTool,
22604
22692
  WebSearchTool: () => WebSearchTool,
22605
22693
  WorkingNotesTool: () => WorkingNotesTool,
22694
+ YouTubeDownloadTool: () => YouTubeDownloadTool,
22606
22695
  applyPatch: () => applyPatch,
22607
22696
  buildCompactDiff: () => buildCompactDiff,
22608
22697
  buildCustomTools: () => buildCustomTools,
@@ -28402,11 +28491,14 @@ ${transcript}`
28402
28491
  /** Multi-key pool — round-robin rotation per request for load distribution */
28403
28492
  _keyPool = [];
28404
28493
  _keyIndex = 0;
28494
+ /** Whether this backend targets Anthropic's Messages API */
28495
+ _isAnthropic = false;
28405
28496
  constructor(baseUrl, model, apiKey, thinking) {
28406
28497
  this.baseUrl = normalizeBaseUrl(baseUrl);
28407
28498
  this.model = model;
28408
28499
  this.apiKey = apiKey ?? "";
28409
28500
  this.thinking = thinking ?? true;
28501
+ this._isAnthropic = /api\.anthropic\.com/i.test(baseUrl);
28410
28502
  }
28411
28503
  /** Set multiple API keys for round-robin rotation per request */
28412
28504
  setKeyPool(keys) {
@@ -28417,7 +28509,7 @@ ${transcript}`
28417
28509
  setAbortSignal(signal) {
28418
28510
  this._abortSignal = signal;
28419
28511
  }
28420
- /** Build auth headers — all providers use standard Bearer token auth.
28512
+ /** Build auth headers — adapts to provider (Bearer for most, x-api-key for Anthropic).
28421
28513
  * When a key pool is set, round-robins through keys per request. */
28422
28514
  authHeaders() {
28423
28515
  const headers = { "Content-Type": "application/json" };
@@ -28427,11 +28519,19 @@ ${transcript}`
28427
28519
  this._keyIndex++;
28428
28520
  }
28429
28521
  if (key) {
28430
- headers["Authorization"] = `Bearer ${key}`;
28522
+ if (this._isAnthropic) {
28523
+ headers["x-api-key"] = key;
28524
+ headers["anthropic-version"] = "2023-06-01";
28525
+ } else {
28526
+ headers["Authorization"] = `Bearer ${key}`;
28527
+ }
28431
28528
  }
28432
28529
  return headers;
28433
28530
  }
28434
28531
  async chatCompletion(request) {
28532
+ if (this._isAnthropic) {
28533
+ return this._anthropicChatCompletion(request);
28534
+ }
28435
28535
  const body = {
28436
28536
  model: this.model,
28437
28537
  messages: request.messages,
@@ -28489,6 +28589,106 @@ ${transcript}`
28489
28589
  } : void 0
28490
28590
  };
28491
28591
  }
28592
+ /** Anthropic Messages API translation — converts our standard format to/from Anthropic's. */
28593
+ async _anthropicChatCompletion(request) {
28594
+ const systemMsgs = request.messages.filter((m) => m.role === "system");
28595
+ const nonSystemMsgs = request.messages.filter((m) => m.role !== "system");
28596
+ const systemText = systemMsgs.map((m) => typeof m.content === "string" ? m.content : "").join("\n\n");
28597
+ const anthropicMessages = [];
28598
+ for (const msg of nonSystemMsgs) {
28599
+ if (msg.role === "tool") {
28600
+ anthropicMessages.push({
28601
+ role: "user",
28602
+ content: [{
28603
+ type: "tool_result",
28604
+ tool_use_id: msg.tool_call_id ?? "",
28605
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
28606
+ }]
28607
+ });
28608
+ } else if (msg.role === "assistant" && msg.tool_calls?.length > 0) {
28609
+ const blocks = [];
28610
+ if (typeof msg.content === "string" && msg.content) {
28611
+ blocks.push({ type: "text", text: msg.content });
28612
+ }
28613
+ for (const tc of msg.tool_calls) {
28614
+ blocks.push({
28615
+ type: "tool_use",
28616
+ id: tc.id,
28617
+ name: tc.function?.name ?? tc.name,
28618
+ input: typeof tc.function?.arguments === "string" ? (() => {
28619
+ try {
28620
+ return JSON.parse(tc.function.arguments);
28621
+ } catch {
28622
+ return {};
28623
+ }
28624
+ })() : tc.function?.arguments ?? {}
28625
+ });
28626
+ }
28627
+ anthropicMessages.push({ role: "assistant", content: blocks });
28628
+ } else {
28629
+ anthropicMessages.push({
28630
+ role: msg.role === "user" ? "user" : "assistant",
28631
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content ?? "")
28632
+ });
28633
+ }
28634
+ }
28635
+ const anthropicTools = (request.tools ?? []).filter((t) => t.type === "function" && t.function).map((t) => ({
28636
+ name: t.function.name,
28637
+ description: t.function.description,
28638
+ input_schema: t.function.parameters
28639
+ }));
28640
+ const body = {
28641
+ model: this.model,
28642
+ messages: anthropicMessages,
28643
+ max_tokens: request.maxTokens ?? 8192,
28644
+ temperature: request.temperature
28645
+ };
28646
+ if (systemText)
28647
+ body.system = systemText;
28648
+ if (anthropicTools.length > 0)
28649
+ body.tools = anthropicTools;
28650
+ const fetchOpts = {
28651
+ method: "POST",
28652
+ headers: this.authHeaders(),
28653
+ body: JSON.stringify(body)
28654
+ };
28655
+ if (this._abortSignal)
28656
+ fetchOpts.signal = this._abortSignal;
28657
+ const resp = await fetch(`${this.baseUrl}/v1/messages`, fetchOpts);
28658
+ if (!resp.ok) {
28659
+ const text = await resp.text().catch(() => "");
28660
+ throw new Error(`Anthropic HTTP ${resp.status}: ${text.slice(0, 300)}`);
28661
+ }
28662
+ const data = await resp.json();
28663
+ const contentBlocks = data.content ?? [];
28664
+ const usage = data.usage;
28665
+ let textContent = "";
28666
+ const toolCalls = [];
28667
+ for (const block of contentBlocks) {
28668
+ if (block.type === "text") {
28669
+ textContent += block.text ?? "";
28670
+ } else if (block.type === "tool_use") {
28671
+ toolCalls.push({
28672
+ id: block.id || crypto.randomUUID(),
28673
+ name: block.name,
28674
+ arguments: block.input ?? {}
28675
+ });
28676
+ }
28677
+ }
28678
+ return {
28679
+ choices: [{
28680
+ message: {
28681
+ content: textContent || null,
28682
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0
28683
+ }
28684
+ }],
28685
+ usage: usage ? {
28686
+ totalTokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
28687
+ promptTokens: usage.input_tokens,
28688
+ completionTokens: usage.output_tokens
28689
+ } : void 0
28690
+ };
28691
+ }
28492
28692
  /**
28493
28693
  * SSE streaming variant — yields StreamChunks as tokens arrive.
28494
28694
  * Uses `stream: true` and the current thinking setting.
@@ -38528,9 +38728,15 @@ async function fetchOllamaModels(baseUrl) {
38528
38728
  async function fetchOpenAIModels(baseUrl, apiKey) {
38529
38729
  const normalized = normalizeBaseUrl(baseUrl);
38530
38730
  const url = `${normalized}/v1/models`;
38731
+ const isAnthropic = /api\.anthropic\.com/i.test(baseUrl);
38531
38732
  const headers = {};
38532
38733
  if (apiKey) {
38533
- headers["Authorization"] = `Bearer ${apiKey}`;
38734
+ if (isAnthropic) {
38735
+ headers["x-api-key"] = apiKey;
38736
+ headers["anthropic-version"] = "2023-06-01";
38737
+ } else {
38738
+ headers["Authorization"] = `Bearer ${apiKey}`;
38739
+ }
38534
38740
  }
38535
38741
  const resp = await fetch(url, {
38536
38742
  headers,
@@ -45959,26 +46165,45 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
45959
46165
  const isArm = process.arch === "arm64" || process.arch === "arm";
45960
46166
  if (isArm) {
45961
46167
  renderInfo(" ARM device detected \u2014 installing build prerequisites then deps individually.");
46168
+ const isMac = process.platform === "darwin";
45962
46169
  try {
45963
46170
  const { spawnSync } = await import("node:child_process");
45964
- renderInfo(" System build tools needed (llvm, gcc). Requesting sudo access...");
45965
- const sudoCheck = spawnSync("sudo", ["-v"], { stdio: "inherit", timeout: 6e4 });
45966
- if (sudoCheck.status === 0) {
45967
- renderInfo(" Installing system build dependencies (this may take a minute)...");
45968
- spawnSync("sudo", [
45969
- "apt-get",
45970
- "install",
45971
- "-y",
45972
- "--no-install-recommends",
45973
- "llvm-dev",
45974
- "gcc",
45975
- "g++",
45976
- "gfortran",
45977
- "libopenblas-dev",
45978
- "libsndfile1-dev"
45979
- ], { stdio: "inherit", timeout: 12e4 });
46171
+ if (isMac) {
46172
+ renderInfo(" macOS ARM detected \u2014 installing build deps via Homebrew...");
46173
+ const brewCheck = spawnSync("which", ["brew"], { stdio: "pipe", timeout: 5e3 });
46174
+ if (brewCheck.status === 0) {
46175
+ spawnSync("brew", ["install", "llvm", "gcc", "openblas", "libsndfile"], {
46176
+ stdio: "inherit",
46177
+ timeout: 3e5
46178
+ });
46179
+ const llvmPrefix = spawnSync("brew", ["--prefix", "llvm"], { stdio: "pipe", timeout: 5e3 });
46180
+ if (llvmPrefix.stdout) {
46181
+ process.env.LLVM_CONFIG = `${llvmPrefix.stdout.toString().trim()}/bin/llvm-config`;
46182
+ }
46183
+ } else {
46184
+ renderWarning(" Homebrew not found. Install it: https://brew.sh");
46185
+ renderWarning(" Then run: brew install llvm gcc openblas libsndfile");
46186
+ }
45980
46187
  } else {
45981
- renderWarning(" sudo not available \u2014 skipping system build deps. librosa/lhotse may fail to compile.");
46188
+ renderInfo(" System build tools needed (llvm, gcc). Requesting sudo access...");
46189
+ const sudoCheck = spawnSync("sudo", ["-v"], { stdio: "inherit", timeout: 6e4 });
46190
+ if (sudoCheck.status === 0) {
46191
+ renderInfo(" Installing system build dependencies (this may take a minute)...");
46192
+ spawnSync("sudo", [
46193
+ "apt-get",
46194
+ "install",
46195
+ "-y",
46196
+ "--no-install-recommends",
46197
+ "llvm-dev",
46198
+ "gcc",
46199
+ "g++",
46200
+ "gfortran",
46201
+ "libopenblas-dev",
46202
+ "libsndfile1-dev"
46203
+ ], { stdio: "inherit", timeout: 12e4 });
46204
+ } else {
46205
+ renderWarning(" sudo not available \u2014 skipping system build deps. librosa/lhotse may fail to compile.");
46206
+ }
45982
46207
  }
45983
46208
  } catch (err) {
45984
46209
  renderWarning(` Could not install system build deps: ${err instanceof Error ? err.message : String(err)}`);
@@ -50098,7 +50323,12 @@ async function handleEndpoint(arg, ctx, local = false) {
50098
50323
  const healthUrl = `${normalizedUrl}${provider.modelsPath}`;
50099
50324
  const headers = {};
50100
50325
  if (apiKey) {
50101
- headers["Authorization"] = `Bearer ${apiKey}`;
50326
+ if (provider.id === "anthropic") {
50327
+ headers["x-api-key"] = apiKey;
50328
+ headers["anthropic-version"] = "2023-06-01";
50329
+ } else {
50330
+ headers["Authorization"] = `Bearer ${apiKey}`;
50331
+ }
50102
50332
  }
50103
50333
  const resp = await fetch(healthUrl, {
50104
50334
  headers,
@@ -63749,9 +63979,10 @@ function buildTools(repoRoot, config, contextWindowSize) {
63749
63979
  // Skill system (AIWG skills — discovery and execution)
63750
63980
  new SkillListTool(repoRoot),
63751
63981
  new SkillExecuteTool(repoRoot),
63752
- // Transcription tools (transcribe-cli / faster-whisper)
63982
+ // Transcription + media download tools (transcribe-cli / faster-whisper / yt-dlp)
63753
63983
  new TranscribeFileTool(repoRoot),
63754
63984
  new TranscribeUrlTool(repoRoot),
63985
+ new YouTubeDownloadTool(repoRoot),
63755
63986
  // Structured file generation (CSV, JSON, Markdown, Excel-compatible)
63756
63987
  new StructuredFileTool(repoRoot),
63757
63988
  // Code sandbox (isolated code execution)
@@ -64994,6 +65225,7 @@ async function startInteractive(config, repoPath) {
64994
65225
  initOaDirectory(repoRoot);
64995
65226
  const savedSettings = resolveSettings(repoRoot);
64996
65227
  if (process.stdout.isTTY) {
65228
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
64997
65229
  process.stdout.write("\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1015l\x1B[?1049h\x1B[48;5;233m\x1B[2J\x1B[3J\x1B[H\x1B[?25l");
64998
65230
  const restoreScreen = () => {
64999
65231
  process.stdout.write("\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1015l\x1B[?25h\x1B[?1049l");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.184.70",
3
+ "version": "0.184.72",
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",