open-agents-ai 0.184.69 → 0.184.70

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 +111 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28399,21 +28399,35 @@ ${transcript}`
28399
28399
  thinking;
28400
28400
  /** Abort signal — set by the runner so /stop can cancel in-flight requests */
28401
28401
  _abortSignal = null;
28402
+ /** Multi-key pool — round-robin rotation per request for load distribution */
28403
+ _keyPool = [];
28404
+ _keyIndex = 0;
28402
28405
  constructor(baseUrl, model, apiKey, thinking) {
28403
28406
  this.baseUrl = normalizeBaseUrl(baseUrl);
28404
28407
  this.model = model;
28405
28408
  this.apiKey = apiKey ?? "";
28406
28409
  this.thinking = thinking ?? true;
28407
28410
  }
28411
+ /** Set multiple API keys for round-robin rotation per request */
28412
+ setKeyPool(keys) {
28413
+ this._keyPool = keys.filter((k) => k.length > 0);
28414
+ this._keyIndex = 0;
28415
+ }
28408
28416
  /** Set the abort signal from the runner (called at run start) */
28409
28417
  setAbortSignal(signal) {
28410
28418
  this._abortSignal = signal;
28411
28419
  }
28412
- /** Build auth headers — all providers use standard Bearer token auth. */
28420
+ /** Build auth headers — all providers use standard Bearer token auth.
28421
+ * When a key pool is set, round-robins through keys per request. */
28413
28422
  authHeaders() {
28414
28423
  const headers = { "Content-Type": "application/json" };
28415
- if (this.apiKey) {
28416
- headers["Authorization"] = `Bearer ${this.apiKey}`;
28424
+ let key = this.apiKey;
28425
+ if (this._keyPool.length > 0) {
28426
+ key = this._keyPool[this._keyIndex % this._keyPool.length];
28427
+ this._keyIndex++;
28428
+ }
28429
+ if (key) {
28430
+ headers["Authorization"] = `Bearer ${key}`;
28417
28431
  }
28418
28432
  return headers;
28419
28433
  }
@@ -49969,6 +49983,85 @@ async function handleEndpoint(arg, ctx, local = false) {
49969
49983
  await handleSponsoredEndpoint(ctx, local);
49970
49984
  return;
49971
49985
  }
49986
+ if (arg.startsWith("add ")) {
49987
+ const addArg = arg.slice(4).replace(/\u2014/g, "--").replace(/\u2013/g, "--");
49988
+ const addParts = addArg.split(/\s+/);
49989
+ const addUrl = addParts[0];
49990
+ const addAuthIdx = addParts.indexOf("--auth") !== -1 ? addParts.indexOf("--auth") : addParts.indexOf("-auth");
49991
+ const addKey = addAuthIdx !== -1 && addParts[addAuthIdx + 1] ? addParts[addAuthIdx + 1] : "";
49992
+ if (!addKey) {
49993
+ renderError("Usage: /endpoint add <url> --auth <key>");
49994
+ return;
49995
+ }
49996
+ const normalizedAddUrl = normalizeBaseUrl(addUrl);
49997
+ const suffix = addKey.slice(-4);
49998
+ const provider2 = detectProvider(addUrl);
49999
+ const settings = resolveSettings(ctx.repoRoot);
50000
+ const pool = settings.endpointKeys ?? [];
50001
+ if (pool.some((k) => k.suffix === suffix)) {
50002
+ renderWarning(`Key ending ...${suffix} is already in the pool.`);
50003
+ return;
50004
+ }
50005
+ pool.push({ key: addKey, suffix });
50006
+ const backendType2 = provider2.id === "ollama" ? "ollama" : "vllm";
50007
+ ctx.setEndpoint(normalizedAddUrl, backendType2, addKey);
50008
+ setConfigValue("backendUrl", normalizedAddUrl);
50009
+ setConfigValue("backendType", backendType2);
50010
+ setConfigValue("apiKey", addKey);
50011
+ ctx.saveSettings({ backendUrl: normalizedAddUrl, backendType: backendType2, apiKey: addKey, endpointKeys: pool });
50012
+ saveGlobalSettings({ endpointKeys: pool });
50013
+ if (ctx.setKeyPool)
50014
+ ctx.setKeyPool(pool.map((k) => k.key));
50015
+ recordUsage("endpoint", normalizedAddUrl, {
50016
+ meta: { provider: provider2.label, backendType: backendType2, authHint: addKey.slice(0, 4) + "...", authKey: addKey }
50017
+ });
50018
+ renderInfo(`Added key ...${suffix} to pool (${pool.length} key${pool.length > 1 ? "s" : ""} total)`);
50019
+ for (const k of pool) {
50020
+ process.stdout.write(` ${c2.dim("\u25CF")} ${provider2.label}-${k.suffix} ${c2.dim(k.key.slice(0, 4) + "..." + k.key.slice(-4))}
50021
+ `);
50022
+ }
50023
+ process.stdout.write("\n");
50024
+ return;
50025
+ }
50026
+ if (arg === "keys" || arg === "pool") {
50027
+ const settings = resolveSettings(ctx.repoRoot);
50028
+ const pool = settings.endpointKeys ?? [];
50029
+ if (pool.length === 0) {
50030
+ renderInfo("No key pool configured. Use /endpoint add <url> --auth <key> to add keys.");
50031
+ return;
50032
+ }
50033
+ const provider2 = detectProvider(ctx.config.backendUrl);
50034
+ renderInfo(`Key pool for ${c2.bold(ctx.config.backendUrl)} (${pool.length} key${pool.length > 1 ? "s" : ""}):`);
50035
+ for (const k of pool) {
50036
+ process.stdout.write(` ${c2.green("\u25CF")} ${provider2.label}-${k.suffix} ${c2.dim(k.key.slice(0, 4) + "..." + k.key.slice(-4))}
50037
+ `);
50038
+ }
50039
+ process.stdout.write(`
50040
+ ${c2.dim("Remove: /endpoint remove <suffix>")}
50041
+
50042
+ `);
50043
+ return;
50044
+ }
50045
+ if (arg.startsWith("remove ") || arg.startsWith("rm ")) {
50046
+ const suffix = arg.split(/\s+/)[1];
50047
+ const settings = resolveSettings(ctx.repoRoot);
50048
+ const pool = settings.endpointKeys ?? [];
50049
+ const idx = pool.findIndex((k) => k.suffix === suffix);
50050
+ if (idx === -1) {
50051
+ renderError(`No key with suffix "${suffix}" in the pool.`);
50052
+ return;
50053
+ }
50054
+ pool.splice(idx, 1);
50055
+ ctx.saveSettings({ endpointKeys: pool });
50056
+ saveGlobalSettings({ endpointKeys: pool });
50057
+ if (ctx.setKeyPool)
50058
+ ctx.setKeyPool(pool.map((k) => k.key));
50059
+ renderInfo(`Removed key ...${suffix} from pool (${pool.length} remaining).`);
50060
+ if (pool.length === 0) {
50061
+ renderInfo("Pool empty \u2014 using single API key from config.");
50062
+ }
50063
+ return;
50064
+ }
49972
50065
  const normalizedArg = arg.replace(/\u2014/g, "--").replace(/\u2013/g, "--");
49973
50066
  const parts = normalizedArg.split(/\s+/);
49974
50067
  const url = parts[0];
@@ -64049,6 +64142,10 @@ ${lines.join("\n")}
64049
64142
  backend = new NexusAgenticBackend(sendFn, config.model, targetPeer, config.apiKey, thinkingEnabled);
64050
64143
  } else {
64051
64144
  backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey, thinkingEnabled);
64145
+ const poolKeys = config._keyPool;
64146
+ if (poolKeys && poolKeys.length > 0 && backend instanceof OllamaAgenticBackend) {
64147
+ backend.setKeyPool(poolKeys);
64148
+ }
64052
64149
  }
64053
64150
  try {
64054
64151
  const endpointHistory = loadUsageHistory("endpoint", repoRoot);
@@ -64934,6 +65031,9 @@ async function startInteractive(config, repoPath) {
64934
65031
  }
64935
65032
  if (savedSettings.apiKey)
64936
65033
  config = { ...config, apiKey: savedSettings.apiKey };
65034
+ if (savedSettings.endpointKeys && savedSettings.endpointKeys.length > 0) {
65035
+ config._keyPool = savedSettings.endpointKeys.map((k) => k.key);
65036
+ }
64937
65037
  if (savedSettings.verbose !== void 0)
64938
65038
  config = { ...config, verbose: savedSettings.verbose };
64939
65039
  if (savedSettings.maxRetries !== void 0)
@@ -65982,6 +66082,14 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
65982
66082
  statusBar.stopRemoteMetricsPolling();
65983
66083
  }
65984
66084
  },
66085
+ setKeyPool(keys) {
66086
+ if (activeTask) {
66087
+ const backend = activeTask.runner._backend;
66088
+ if (backend?.setKeyPool)
66089
+ backend.setKeyPool(keys);
66090
+ }
66091
+ currentConfig._keyPool = keys;
66092
+ },
65985
66093
  clearScreen() {
65986
66094
  if (isNeovimActive()) {
65987
66095
  writeToNeovimOutput("[screen cleared]\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.184.69",
3
+ "version": "0.184.70",
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",