open-agents-ai 0.184.69 → 0.184.71
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/index.js +235 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11949,7 +11949,7 @@ function formatTime(seconds) {
|
|
|
11949
11949
|
const s = Math.floor(seconds % 60);
|
|
11950
11950
|
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
11951
11951
|
}
|
|
11952
|
-
var AUDIO_EXTS, VIDEO_EXTS, _tcModule, _tcChecked, TranscribeFileTool, TranscribeUrlTool;
|
|
11952
|
+
var AUDIO_EXTS, VIDEO_EXTS, _tcModule, _tcChecked, TranscribeFileTool, TranscribeUrlTool, YouTubeDownloadTool;
|
|
11953
11953
|
var init_transcribe_tool = __esm({
|
|
11954
11954
|
"packages/execution/dist/tools/transcribe-tool.js"() {
|
|
11955
11955
|
"use strict";
|
|
@@ -12267,6 +12267,90 @@ ${result.output}`,
|
|
|
12267
12267
|
}
|
|
12268
12268
|
}
|
|
12269
12269
|
};
|
|
12270
|
+
YouTubeDownloadTool = class {
|
|
12271
|
+
name = "youtube_download";
|
|
12272
|
+
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.";
|
|
12273
|
+
parameters = {
|
|
12274
|
+
type: "object",
|
|
12275
|
+
properties: {
|
|
12276
|
+
url: {
|
|
12277
|
+
type: "string",
|
|
12278
|
+
description: "YouTube URL (youtube.com/watch?v=..., youtu.be/..., etc.)"
|
|
12279
|
+
},
|
|
12280
|
+
format: {
|
|
12281
|
+
type: "string",
|
|
12282
|
+
enum: ["mp3", "mp4"],
|
|
12283
|
+
description: "Output format: 'mp3' for audio only, 'mp4' for video (default: mp3)"
|
|
12284
|
+
},
|
|
12285
|
+
output_dir: {
|
|
12286
|
+
type: "string",
|
|
12287
|
+
description: "Directory to save the file (default: current working directory)"
|
|
12288
|
+
}
|
|
12289
|
+
},
|
|
12290
|
+
required: ["url"]
|
|
12291
|
+
};
|
|
12292
|
+
workingDir;
|
|
12293
|
+
constructor(workingDir) {
|
|
12294
|
+
this.workingDir = workingDir;
|
|
12295
|
+
}
|
|
12296
|
+
async execute(args) {
|
|
12297
|
+
const start = Date.now();
|
|
12298
|
+
const url = String(args.url ?? "").trim();
|
|
12299
|
+
const format = String(args.format ?? "mp3").toLowerCase();
|
|
12300
|
+
const outputDir = String(args.output_dir ?? this.workingDir);
|
|
12301
|
+
if (!url) {
|
|
12302
|
+
return { success: false, output: "", error: "URL is required", durationMs: Date.now() - start };
|
|
12303
|
+
}
|
|
12304
|
+
if (!isYouTubeUrl(url)) {
|
|
12305
|
+
return { success: false, output: "", error: "Not a recognized YouTube URL. Supported: youtube.com/watch, youtu.be, shorts, live, embed", durationMs: Date.now() - start };
|
|
12306
|
+
}
|
|
12307
|
+
if (!ensureYtDlp()) {
|
|
12308
|
+
return { success: false, output: "", error: "yt-dlp not available and auto-install failed. Install manually: pip install yt-dlp", durationMs: Date.now() - start };
|
|
12309
|
+
}
|
|
12310
|
+
mkdirSync7(outputDir, { recursive: true });
|
|
12311
|
+
try {
|
|
12312
|
+
let title = "download";
|
|
12313
|
+
try {
|
|
12314
|
+
title = execSync10(`yt-dlp --get-title "${url}"`, { timeout: 15e3, stdio: "pipe" }).toString().trim().replace(/[<>:"/\\|?*]/g, "_").slice(0, 100);
|
|
12315
|
+
} catch {
|
|
12316
|
+
}
|
|
12317
|
+
if (format === "mp4") {
|
|
12318
|
+
const outPath = join19(outputDir, `${title}.mp4`);
|
|
12319
|
+
const outTemplate = join19(outputDir, `${title}.%(ext)s`);
|
|
12320
|
+
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 });
|
|
12321
|
+
const actualPath = existsSync16(outPath) ? outPath : outTemplate.replace("%(ext)s", "mp4");
|
|
12322
|
+
return {
|
|
12323
|
+
success: true,
|
|
12324
|
+
output: `Downloaded video: ${actualPath}
|
|
12325
|
+
Title: ${title}
|
|
12326
|
+
Format: mp4`,
|
|
12327
|
+
durationMs: Date.now() - start
|
|
12328
|
+
};
|
|
12329
|
+
} else {
|
|
12330
|
+
const outPath = join19(outputDir, `${title}.mp3`);
|
|
12331
|
+
const outTemplate = join19(outputDir, `${title}.%(ext)s`);
|
|
12332
|
+
execSync10(`yt-dlp -x --audio-format mp3 --audio-quality 0 -o "${outTemplate}" "${url}"`, { timeout: 6e5, stdio: "pipe", cwd: outputDir });
|
|
12333
|
+
const actualPath = existsSync16(outPath) ? outPath : outTemplate.replace("%(ext)s", "mp3");
|
|
12334
|
+
return {
|
|
12335
|
+
success: true,
|
|
12336
|
+
output: `Downloaded audio: ${actualPath}
|
|
12337
|
+
Title: ${title}
|
|
12338
|
+
Format: mp3`,
|
|
12339
|
+
durationMs: Date.now() - start
|
|
12340
|
+
};
|
|
12341
|
+
}
|
|
12342
|
+
} catch (err) {
|
|
12343
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12344
|
+
const stderr = err?.stderr?.toString?.()?.slice(0, 500) ?? "";
|
|
12345
|
+
return {
|
|
12346
|
+
success: false,
|
|
12347
|
+
output: "",
|
|
12348
|
+
error: `YouTube download failed: ${msg}${stderr ? "\n" + stderr : ""}`,
|
|
12349
|
+
durationMs: Date.now() - start
|
|
12350
|
+
};
|
|
12351
|
+
}
|
|
12352
|
+
}
|
|
12353
|
+
};
|
|
12270
12354
|
}
|
|
12271
12355
|
});
|
|
12272
12356
|
|
|
@@ -22603,6 +22687,7 @@ __export(dist_exports, {
|
|
|
22603
22687
|
WebFetchTool: () => WebFetchTool,
|
|
22604
22688
|
WebSearchTool: () => WebSearchTool,
|
|
22605
22689
|
WorkingNotesTool: () => WorkingNotesTool,
|
|
22690
|
+
YouTubeDownloadTool: () => YouTubeDownloadTool,
|
|
22606
22691
|
applyPatch: () => applyPatch,
|
|
22607
22692
|
buildCompactDiff: () => buildCompactDiff,
|
|
22608
22693
|
buildCustomTools: () => buildCustomTools,
|
|
@@ -28399,21 +28484,35 @@ ${transcript}`
|
|
|
28399
28484
|
thinking;
|
|
28400
28485
|
/** Abort signal — set by the runner so /stop can cancel in-flight requests */
|
|
28401
28486
|
_abortSignal = null;
|
|
28487
|
+
/** Multi-key pool — round-robin rotation per request for load distribution */
|
|
28488
|
+
_keyPool = [];
|
|
28489
|
+
_keyIndex = 0;
|
|
28402
28490
|
constructor(baseUrl, model, apiKey, thinking) {
|
|
28403
28491
|
this.baseUrl = normalizeBaseUrl(baseUrl);
|
|
28404
28492
|
this.model = model;
|
|
28405
28493
|
this.apiKey = apiKey ?? "";
|
|
28406
28494
|
this.thinking = thinking ?? true;
|
|
28407
28495
|
}
|
|
28496
|
+
/** Set multiple API keys for round-robin rotation per request */
|
|
28497
|
+
setKeyPool(keys) {
|
|
28498
|
+
this._keyPool = keys.filter((k) => k.length > 0);
|
|
28499
|
+
this._keyIndex = 0;
|
|
28500
|
+
}
|
|
28408
28501
|
/** Set the abort signal from the runner (called at run start) */
|
|
28409
28502
|
setAbortSignal(signal) {
|
|
28410
28503
|
this._abortSignal = signal;
|
|
28411
28504
|
}
|
|
28412
|
-
/** Build auth headers — all providers use standard Bearer token auth.
|
|
28505
|
+
/** Build auth headers — all providers use standard Bearer token auth.
|
|
28506
|
+
* When a key pool is set, round-robins through keys per request. */
|
|
28413
28507
|
authHeaders() {
|
|
28414
28508
|
const headers = { "Content-Type": "application/json" };
|
|
28415
|
-
|
|
28416
|
-
|
|
28509
|
+
let key = this.apiKey;
|
|
28510
|
+
if (this._keyPool.length > 0) {
|
|
28511
|
+
key = this._keyPool[this._keyIndex % this._keyPool.length];
|
|
28512
|
+
this._keyIndex++;
|
|
28513
|
+
}
|
|
28514
|
+
if (key) {
|
|
28515
|
+
headers["Authorization"] = `Bearer ${key}`;
|
|
28417
28516
|
}
|
|
28418
28517
|
return headers;
|
|
28419
28518
|
}
|
|
@@ -45945,26 +46044,45 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
|
45945
46044
|
const isArm = process.arch === "arm64" || process.arch === "arm";
|
|
45946
46045
|
if (isArm) {
|
|
45947
46046
|
renderInfo(" ARM device detected \u2014 installing build prerequisites then deps individually.");
|
|
46047
|
+
const isMac = process.platform === "darwin";
|
|
45948
46048
|
try {
|
|
45949
46049
|
const { spawnSync } = await import("node:child_process");
|
|
45950
|
-
|
|
45951
|
-
|
|
45952
|
-
|
|
45953
|
-
|
|
45954
|
-
|
|
45955
|
-
|
|
45956
|
-
|
|
45957
|
-
|
|
45958
|
-
"--
|
|
45959
|
-
|
|
45960
|
-
|
|
45961
|
-
|
|
45962
|
-
|
|
45963
|
-
"
|
|
45964
|
-
"
|
|
45965
|
-
|
|
46050
|
+
if (isMac) {
|
|
46051
|
+
renderInfo(" macOS ARM detected \u2014 installing build deps via Homebrew...");
|
|
46052
|
+
const brewCheck = spawnSync("which", ["brew"], { stdio: "pipe", timeout: 5e3 });
|
|
46053
|
+
if (brewCheck.status === 0) {
|
|
46054
|
+
spawnSync("brew", ["install", "llvm", "gcc", "openblas", "libsndfile"], {
|
|
46055
|
+
stdio: "inherit",
|
|
46056
|
+
timeout: 3e5
|
|
46057
|
+
});
|
|
46058
|
+
const llvmPrefix = spawnSync("brew", ["--prefix", "llvm"], { stdio: "pipe", timeout: 5e3 });
|
|
46059
|
+
if (llvmPrefix.stdout) {
|
|
46060
|
+
process.env.LLVM_CONFIG = `${llvmPrefix.stdout.toString().trim()}/bin/llvm-config`;
|
|
46061
|
+
}
|
|
46062
|
+
} else {
|
|
46063
|
+
renderWarning(" Homebrew not found. Install it: https://brew.sh");
|
|
46064
|
+
renderWarning(" Then run: brew install llvm gcc openblas libsndfile");
|
|
46065
|
+
}
|
|
45966
46066
|
} else {
|
|
45967
|
-
|
|
46067
|
+
renderInfo(" System build tools needed (llvm, gcc). Requesting sudo access...");
|
|
46068
|
+
const sudoCheck = spawnSync("sudo", ["-v"], { stdio: "inherit", timeout: 6e4 });
|
|
46069
|
+
if (sudoCheck.status === 0) {
|
|
46070
|
+
renderInfo(" Installing system build dependencies (this may take a minute)...");
|
|
46071
|
+
spawnSync("sudo", [
|
|
46072
|
+
"apt-get",
|
|
46073
|
+
"install",
|
|
46074
|
+
"-y",
|
|
46075
|
+
"--no-install-recommends",
|
|
46076
|
+
"llvm-dev",
|
|
46077
|
+
"gcc",
|
|
46078
|
+
"g++",
|
|
46079
|
+
"gfortran",
|
|
46080
|
+
"libopenblas-dev",
|
|
46081
|
+
"libsndfile1-dev"
|
|
46082
|
+
], { stdio: "inherit", timeout: 12e4 });
|
|
46083
|
+
} else {
|
|
46084
|
+
renderWarning(" sudo not available \u2014 skipping system build deps. librosa/lhotse may fail to compile.");
|
|
46085
|
+
}
|
|
45968
46086
|
}
|
|
45969
46087
|
} catch (err) {
|
|
45970
46088
|
renderWarning(` Could not install system build deps: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -49969,6 +50087,85 @@ async function handleEndpoint(arg, ctx, local = false) {
|
|
|
49969
50087
|
await handleSponsoredEndpoint(ctx, local);
|
|
49970
50088
|
return;
|
|
49971
50089
|
}
|
|
50090
|
+
if (arg.startsWith("add ")) {
|
|
50091
|
+
const addArg = arg.slice(4).replace(/\u2014/g, "--").replace(/\u2013/g, "--");
|
|
50092
|
+
const addParts = addArg.split(/\s+/);
|
|
50093
|
+
const addUrl = addParts[0];
|
|
50094
|
+
const addAuthIdx = addParts.indexOf("--auth") !== -1 ? addParts.indexOf("--auth") : addParts.indexOf("-auth");
|
|
50095
|
+
const addKey = addAuthIdx !== -1 && addParts[addAuthIdx + 1] ? addParts[addAuthIdx + 1] : "";
|
|
50096
|
+
if (!addKey) {
|
|
50097
|
+
renderError("Usage: /endpoint add <url> --auth <key>");
|
|
50098
|
+
return;
|
|
50099
|
+
}
|
|
50100
|
+
const normalizedAddUrl = normalizeBaseUrl(addUrl);
|
|
50101
|
+
const suffix = addKey.slice(-4);
|
|
50102
|
+
const provider2 = detectProvider(addUrl);
|
|
50103
|
+
const settings = resolveSettings(ctx.repoRoot);
|
|
50104
|
+
const pool = settings.endpointKeys ?? [];
|
|
50105
|
+
if (pool.some((k) => k.suffix === suffix)) {
|
|
50106
|
+
renderWarning(`Key ending ...${suffix} is already in the pool.`);
|
|
50107
|
+
return;
|
|
50108
|
+
}
|
|
50109
|
+
pool.push({ key: addKey, suffix });
|
|
50110
|
+
const backendType2 = provider2.id === "ollama" ? "ollama" : "vllm";
|
|
50111
|
+
ctx.setEndpoint(normalizedAddUrl, backendType2, addKey);
|
|
50112
|
+
setConfigValue("backendUrl", normalizedAddUrl);
|
|
50113
|
+
setConfigValue("backendType", backendType2);
|
|
50114
|
+
setConfigValue("apiKey", addKey);
|
|
50115
|
+
ctx.saveSettings({ backendUrl: normalizedAddUrl, backendType: backendType2, apiKey: addKey, endpointKeys: pool });
|
|
50116
|
+
saveGlobalSettings({ endpointKeys: pool });
|
|
50117
|
+
if (ctx.setKeyPool)
|
|
50118
|
+
ctx.setKeyPool(pool.map((k) => k.key));
|
|
50119
|
+
recordUsage("endpoint", normalizedAddUrl, {
|
|
50120
|
+
meta: { provider: provider2.label, backendType: backendType2, authHint: addKey.slice(0, 4) + "...", authKey: addKey }
|
|
50121
|
+
});
|
|
50122
|
+
renderInfo(`Added key ...${suffix} to pool (${pool.length} key${pool.length > 1 ? "s" : ""} total)`);
|
|
50123
|
+
for (const k of pool) {
|
|
50124
|
+
process.stdout.write(` ${c2.dim("\u25CF")} ${provider2.label}-${k.suffix} ${c2.dim(k.key.slice(0, 4) + "..." + k.key.slice(-4))}
|
|
50125
|
+
`);
|
|
50126
|
+
}
|
|
50127
|
+
process.stdout.write("\n");
|
|
50128
|
+
return;
|
|
50129
|
+
}
|
|
50130
|
+
if (arg === "keys" || arg === "pool") {
|
|
50131
|
+
const settings = resolveSettings(ctx.repoRoot);
|
|
50132
|
+
const pool = settings.endpointKeys ?? [];
|
|
50133
|
+
if (pool.length === 0) {
|
|
50134
|
+
renderInfo("No key pool configured. Use /endpoint add <url> --auth <key> to add keys.");
|
|
50135
|
+
return;
|
|
50136
|
+
}
|
|
50137
|
+
const provider2 = detectProvider(ctx.config.backendUrl);
|
|
50138
|
+
renderInfo(`Key pool for ${c2.bold(ctx.config.backendUrl)} (${pool.length} key${pool.length > 1 ? "s" : ""}):`);
|
|
50139
|
+
for (const k of pool) {
|
|
50140
|
+
process.stdout.write(` ${c2.green("\u25CF")} ${provider2.label}-${k.suffix} ${c2.dim(k.key.slice(0, 4) + "..." + k.key.slice(-4))}
|
|
50141
|
+
`);
|
|
50142
|
+
}
|
|
50143
|
+
process.stdout.write(`
|
|
50144
|
+
${c2.dim("Remove: /endpoint remove <suffix>")}
|
|
50145
|
+
|
|
50146
|
+
`);
|
|
50147
|
+
return;
|
|
50148
|
+
}
|
|
50149
|
+
if (arg.startsWith("remove ") || arg.startsWith("rm ")) {
|
|
50150
|
+
const suffix = arg.split(/\s+/)[1];
|
|
50151
|
+
const settings = resolveSettings(ctx.repoRoot);
|
|
50152
|
+
const pool = settings.endpointKeys ?? [];
|
|
50153
|
+
const idx = pool.findIndex((k) => k.suffix === suffix);
|
|
50154
|
+
if (idx === -1) {
|
|
50155
|
+
renderError(`No key with suffix "${suffix}" in the pool.`);
|
|
50156
|
+
return;
|
|
50157
|
+
}
|
|
50158
|
+
pool.splice(idx, 1);
|
|
50159
|
+
ctx.saveSettings({ endpointKeys: pool });
|
|
50160
|
+
saveGlobalSettings({ endpointKeys: pool });
|
|
50161
|
+
if (ctx.setKeyPool)
|
|
50162
|
+
ctx.setKeyPool(pool.map((k) => k.key));
|
|
50163
|
+
renderInfo(`Removed key ...${suffix} from pool (${pool.length} remaining).`);
|
|
50164
|
+
if (pool.length === 0) {
|
|
50165
|
+
renderInfo("Pool empty \u2014 using single API key from config.");
|
|
50166
|
+
}
|
|
50167
|
+
return;
|
|
50168
|
+
}
|
|
49972
50169
|
const normalizedArg = arg.replace(/\u2014/g, "--").replace(/\u2013/g, "--");
|
|
49973
50170
|
const parts = normalizedArg.split(/\s+/);
|
|
49974
50171
|
const url = parts[0];
|
|
@@ -63656,9 +63853,10 @@ function buildTools(repoRoot, config, contextWindowSize) {
|
|
|
63656
63853
|
// Skill system (AIWG skills — discovery and execution)
|
|
63657
63854
|
new SkillListTool(repoRoot),
|
|
63658
63855
|
new SkillExecuteTool(repoRoot),
|
|
63659
|
-
// Transcription tools (transcribe-cli / faster-whisper)
|
|
63856
|
+
// Transcription + media download tools (transcribe-cli / faster-whisper / yt-dlp)
|
|
63660
63857
|
new TranscribeFileTool(repoRoot),
|
|
63661
63858
|
new TranscribeUrlTool(repoRoot),
|
|
63859
|
+
new YouTubeDownloadTool(repoRoot),
|
|
63662
63860
|
// Structured file generation (CSV, JSON, Markdown, Excel-compatible)
|
|
63663
63861
|
new StructuredFileTool(repoRoot),
|
|
63664
63862
|
// Code sandbox (isolated code execution)
|
|
@@ -64049,6 +64247,10 @@ ${lines.join("\n")}
|
|
|
64049
64247
|
backend = new NexusAgenticBackend(sendFn, config.model, targetPeer, config.apiKey, thinkingEnabled);
|
|
64050
64248
|
} else {
|
|
64051
64249
|
backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey, thinkingEnabled);
|
|
64250
|
+
const poolKeys = config._keyPool;
|
|
64251
|
+
if (poolKeys && poolKeys.length > 0 && backend instanceof OllamaAgenticBackend) {
|
|
64252
|
+
backend.setKeyPool(poolKeys);
|
|
64253
|
+
}
|
|
64052
64254
|
}
|
|
64053
64255
|
try {
|
|
64054
64256
|
const endpointHistory = loadUsageHistory("endpoint", repoRoot);
|
|
@@ -64934,6 +65136,9 @@ async function startInteractive(config, repoPath) {
|
|
|
64934
65136
|
}
|
|
64935
65137
|
if (savedSettings.apiKey)
|
|
64936
65138
|
config = { ...config, apiKey: savedSettings.apiKey };
|
|
65139
|
+
if (savedSettings.endpointKeys && savedSettings.endpointKeys.length > 0) {
|
|
65140
|
+
config._keyPool = savedSettings.endpointKeys.map((k) => k.key);
|
|
65141
|
+
}
|
|
64937
65142
|
if (savedSettings.verbose !== void 0)
|
|
64938
65143
|
config = { ...config, verbose: savedSettings.verbose };
|
|
64939
65144
|
if (savedSettings.maxRetries !== void 0)
|
|
@@ -65982,6 +66187,14 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
65982
66187
|
statusBar.stopRemoteMetricsPolling();
|
|
65983
66188
|
}
|
|
65984
66189
|
},
|
|
66190
|
+
setKeyPool(keys) {
|
|
66191
|
+
if (activeTask) {
|
|
66192
|
+
const backend = activeTask.runner._backend;
|
|
66193
|
+
if (backend?.setKeyPool)
|
|
66194
|
+
backend.setKeyPool(keys);
|
|
66195
|
+
}
|
|
66196
|
+
currentConfig._keyPool = keys;
|
|
66197
|
+
},
|
|
65985
66198
|
clearScreen() {
|
|
65986
66199
|
if (isNeovimActive()) {
|
|
65987
66200
|
writeToNeovimOutput("[screen cleared]\n");
|
package/package.json
CHANGED