open-agents-ai 0.187.550 → 0.187.552

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 CHANGED
@@ -6853,6 +6853,13 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, readdir
6853
6853
  import { join } from 'node:path';
6854
6854
  import { homedir, hostname } from 'node:os';
6855
6855
  import { createHash, createHmac } from 'node:crypto';
6856
+ import { createRequire as _createRequire } from 'node:module';
6857
+
6858
+ // CommonJS require() shim — the daemon body uses require('node:child_process'),
6859
+ // require('node:path'), require('node:fs'), etc. which are NOT available in
6860
+ // .mjs files by default. This shim restores it without converting every
6861
+ // individual call site to dynamic import().
6862
+ const require = _createRequire(import.meta.url);
6856
6863
 
6857
6864
  // NKN utilities — imported dynamically to avoid crash on older open-agents-nexus versions.
6858
6865
  // Falls back to inline implementations if the package doesn't export them yet.
@@ -587528,12 +587535,12 @@ function resolveLocalPeerId() {
587528
587535
  }
587529
587536
  function locateTorScript(filename) {
587530
587537
  const candidates = [
587531
- // npm-installed layout: scripts get copied to dist/scripts/tor/ by
587532
- // scripts/build-publish.mjs. __dirname at runtime points at dist/api.
587538
+ // npm-installed layout: build-publish.mjs copies scripts to
587539
+ // publish/dist/scripts/tor/ which lands at <install>/dist/scripts/tor/.
587540
+ join119(__dirname, "scripts", "tor", filename),
587533
587541
  join119(__dirname, "..", "scripts", "tor", filename),
587534
- // Workspace dev: cli package's source tree.
587535
587542
  join119(__dirname, "..", "..", "scripts", "tor", filename),
587536
- // Workspace dev (from cwd at repo root):
587543
+ // Workspace dev: cli package's source tree.
587537
587544
  join119(process.cwd(), "packages", "cli", "scripts", "tor", filename),
587538
587545
  join119(process.cwd(), "scripts", "tor", filename)
587539
587546
  ];
@@ -591342,13 +591349,60 @@ async function loadModels() {
591342
591349
  fetch('/v1/config', { headers: headers() }).then(r => r.json()).catch(() => ({})),
591343
591350
  ]);
591344
591351
  modelSelect.innerHTML = '';
591345
- for (const m of (modelsResp.data || [])) {
591352
+ const list = Array.isArray(modelsResp.data) ? modelsResp.data : [];
591353
+ if (list.length === 0) {
591354
+ // Use the daemon's per-endpoint diagnostics (added in 0.187.552+)
591355
+ // to surface the ACTUAL reason rather than a guess.
591356
+ const diags = (modelsResp && modelsResp._endpoint_status) || [];
591357
+ const opt = document.createElement('option');
591358
+ opt.value = '';
591359
+ opt.disabled = true;
591360
+ opt.selected = true;
591361
+ let firstFail = null;
591362
+ for (const d of diags) { if (!d.ok) { firstFail = d; break; } }
591363
+ const backendUrl = (cfgResp && cfgResp.config && cfgResp.config.backendUrl) || '';
591364
+ if (firstFail) {
591365
+ const reason = firstFail.error || ('HTTP ' + (firstFail.status || '?'));
591366
+ opt.textContent = '(no models — ' + firstFail.url + ' → ' + reason + ')';
591367
+ } else {
591368
+ opt.textContent = backendUrl
591369
+ ? '(no models — backend at ' + backendUrl + ' not reachable; check ollama/vllm)'
591370
+ : '(no models — set endpoint via /endpoint or check backend)';
591371
+ }
591372
+ modelSelect.appendChild(opt);
591373
+ try {
591374
+ const hint = document.getElementById('model-empty-hint');
591375
+ if (hint) {
591376
+ hint.style.display = 'block';
591377
+ hint.style.color = 'var(--color-warning)';
591378
+ // If the diagnostic flagged the IPv6 trap (URL contains localhost),
591379
+ // offer a one-click fix that switches to 127.0.0.1.
591380
+ const ipv6Trap = firstFail && firstFail.ipv6Trap;
591381
+ let txt = 'No models found.';
591382
+ if (ipv6Trap) {
591383
+ txt += ' The backend URL uses "localhost" — on dual-stack hosts Node fetch tries IPv6 first which often fails when ollama binds to 127.0.0.1 only.';
591384
+ } else {
591385
+ txt += ' Check that ollama is running (run "ollama serve") and that the model is pulled (run "ollama pull qwen3.6:latest").';
591386
+ }
591387
+ hint.innerHTML = txt + ' ' +
591388
+ (ipv6Trap ? '<button onclick="autoFixLocalhostBackend()" style="margin-left:6px;font-size:0.72rem;background:var(--color-accent);color:#fff;border:none;padding:3px 10px;border-radius:3px;cursor:pointer">switch to 127.0.0.1</button>' : '') +
591389
+ '<button onclick="loadModels()" style="margin-left:6px;font-size:0.72rem;background:var(--color-bg-input);color:var(--color-fg);border:1px solid var(--color-border);padding:3px 10px;border-radius:3px;cursor:pointer">retry</button>';
591390
+ }
591391
+ } catch {}
591392
+ return;
591393
+ }
591394
+ for (const m of list) {
591346
591395
  const opt = document.createElement('option');
591347
591396
  // Strip "local/" prefix for cleaner display
591348
591397
  opt.value = m.id.replace(/^local\\//, '');
591349
591398
  opt.textContent = m.id.replace(/^local\\//, '');
591350
591399
  modelSelect.appendChild(opt);
591351
591400
  }
591401
+ // Hide the empty-state hint if we got models.
591402
+ try {
591403
+ const hint = document.getElementById('model-empty-hint');
591404
+ if (hint) hint.style.display = 'none';
591405
+ } catch {}
591352
591406
  // Resolve the recalled model in priority order
591353
591407
  const daemonModel = (cfgResp && cfgResp.config && cfgResp.config.model) || '';
591354
591408
  let cachedModel = '';
@@ -596426,6 +596480,30 @@ async function loadSettingsConnections() {
596426
596480
  window.__oaEndpointHistory = history;
596427
596481
  host.innerHTML =
596428
596482
  '<div style="display:flex;flex-direction:column;gap:14px">' +
596483
+ // Quick presets — one-click apply for the common defaults so the
596484
+ // user doesn't have to type or even pick a backend type. Selecting
596485
+ // a preset fills the URL + backend type fields below; clicking
596486
+ // "Apply preset" saves directly without further input.
596487
+ '<div>' +
596488
+ '<label style="display:block;font-size:0.78rem;font-weight:500;margin-bottom:6px">Quick preset</label>' +
596489
+ '<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">' +
596490
+ '<select id="settings-conn-preset" style="flex:1;min-width:240px;background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:6px 10px;border-radius:var(--radius-sm);font-size:0.78rem">' +
596491
+ '<option value="">— select preset —</option>' +
596492
+ '<option value="ollama">Ollama (http://127.0.0.1:11434)</option>' +
596493
+ '<option value="ollama-lan">Ollama on LAN (http://0.0.0.0:11434)</option>' +
596494
+ '<option value="vllm">vLLM (http://127.0.0.1:8000/v1)</option>' +
596495
+ '<option value="lmstudio">LM Studio (http://127.0.0.1:1234/v1)</option>' +
596496
+ '<option value="openai">OpenAI (https://api.openai.com/v1)</option>' +
596497
+ '<option value="anthropic">Anthropic (https://api.anthropic.com/v1)</option>' +
596498
+ '<option value="chutes">Chutes (https://llm.chutes.ai/v1)</option>' +
596499
+ '<option value="groq">Groq (https://api.groq.com/openai/v1)</option>' +
596500
+ '<option value="deepinfra">DeepInfra (https://api.deepinfra.com/v1/openai)</option>' +
596501
+ '</select>' +
596502
+ '<button class="oa-btn-secondary" type="button" onclick="applyEndpointPreset(true)" style="background:var(--color-accent);color:#fff;border-color:var(--color-accent)">apply preset</button>' +
596503
+ '<button class="oa-btn-secondary" type="button" onclick="applyEndpointPreset(false)" title="fill the fields below without saving">load to form</button>' +
596504
+ '</div>' +
596505
+ '<p style="font-size:0.7rem;color:var(--color-fg-muted);margin:4px 0 0">Pick a preset and click <b>apply preset</b> to switch backends instantly. <b>load to form</b> just populates the fields below for further editing.</p>' +
596506
+ '</div>' +
596429
596507
  '<div>' +
596430
596508
  '<label style="display:block;font-size:0.78rem;font-weight:500;margin-bottom:6px">Endpoint history</label>' +
596431
596509
  histHTML +
@@ -596462,6 +596540,88 @@ async function loadSettingsConnections() {
596462
596540
  }
596463
596541
  }
596464
596542
 
596543
+ // ─── Quick presets ──────────────────────────────────────────────────────
596544
+ // Map preset key → { url, backendType, requiresAuth, label }. Used by
596545
+ // applyEndpointPreset() below to populate the URL + type fields with one
596546
+ // click, optionally saving immediately.
596547
+ const _OA_ENDPOINT_PRESETS = {
596548
+ 'ollama': { url: 'http://127.0.0.1:11434', backendType: 'ollama' },
596549
+ 'ollama-lan': { url: 'http://0.0.0.0:11434', backendType: 'ollama' },
596550
+ 'vllm': { url: 'http://127.0.0.1:8000/v1', backendType: 'vllm' },
596551
+ 'lmstudio': { url: 'http://127.0.0.1:1234/v1', backendType: 'openai' },
596552
+ 'openai': { url: 'https://api.openai.com/v1', backendType: 'openai', requiresAuth: true },
596553
+ 'anthropic': { url: 'https://api.anthropic.com/v1', backendType: 'openai', requiresAuth: true },
596554
+ 'chutes': { url: 'https://llm.chutes.ai/v1', backendType: 'vllm', requiresAuth: true },
596555
+ 'groq': { url: 'https://api.groq.com/openai/v1', backendType: 'openai', requiresAuth: true },
596556
+ 'deepinfra': { url: 'https://api.deepinfra.com/v1/openai', backendType: 'openai', requiresAuth: true },
596557
+ };
596558
+
596559
+ // One-click endpoint switcher. apply=true saves directly (no typing
596560
+ // needed). apply=false just fills the URL/backend-type fields so the
596561
+ // user can edit before saving.
596562
+ async function applyEndpointPreset(apply) {
596563
+ const sel = document.getElementById('settings-conn-preset');
596564
+ const inp = document.getElementById('settings-conn-endpoint');
596565
+ const bt = document.getElementById('settings-conn-backend-type');
596566
+ const auth = document.getElementById('settings-conn-auth');
596567
+ const status = document.getElementById('settings-conn-status');
596568
+ if (!sel || !inp || !bt || !status) return;
596569
+ const key = sel.value;
596570
+ const preset = _OA_ENDPOINT_PRESETS[key];
596571
+ if (!preset) {
596572
+ status.textContent = 'pick a preset first';
596573
+ status.style.color = 'var(--color-warning)';
596574
+ return;
596575
+ }
596576
+ inp.value = preset.url;
596577
+ bt.value = preset.backendType;
596578
+ if (!apply) {
596579
+ status.textContent = 'preset loaded — edit fields and click save';
596580
+ status.style.color = 'var(--color-fg-muted)';
596581
+ return;
596582
+ }
596583
+ // Save path — abort if the preset requires auth and none is set yet
596584
+ // AND none was previously set on the daemon (authSet flag from last load).
596585
+ if (preset.requiresAuth && (!auth || !auth.value) && !window.__oaAuthAlreadySet) {
596586
+ status.textContent = '✗ this preset requires an auth key — fill the auth field below first';
596587
+ status.style.color = 'var(--color-warning)';
596588
+ return;
596589
+ }
596590
+ await saveSettingsConnections();
596591
+ }
596592
+ window.applyEndpointPreset = applyEndpointPreset;
596593
+
596594
+ // One-click fix for the IPv6 trap: replace localhost in the current
596595
+ // backend URL with 127.0.0.1 and save. Used by the empty-models hint
596596
+ // when the daemon flagged ipv6Trap on the active endpoint.
596597
+ async function autoFixLocalhostBackend() {
596598
+ const hint = document.getElementById('model-empty-hint');
596599
+ try {
596600
+ const cfg = await fetch('/v1/config', { headers: headers() }).then(r => r.json()).catch(() => ({}));
596601
+ const cur = (cfg && cfg.config && cfg.config.backendUrl) || '';
596602
+ if (!cur || !/localhost/i.test(cur)) {
596603
+ if (hint) { hint.textContent = 'Backend URL does not use localhost — nothing to fix.'; hint.style.color = 'var(--color-warning)'; }
596604
+ return;
596605
+ }
596606
+ const fixed = cur.replace(/localhost/i, '127.0.0.1');
596607
+ const r = await fetch('/v1/config/endpoint', {
596608
+ method: 'PUT',
596609
+ headers: { ...headers(), 'Content-Type': 'application/json' },
596610
+ body: JSON.stringify({ url: fixed }),
596611
+ });
596612
+ if (r.ok) {
596613
+ if (hint) { hint.textContent = '✓ switched to ' + fixed + ' — refreshing models…'; hint.style.color = 'var(--color-success)'; }
596614
+ setTimeout(() => loadModels(), 800);
596615
+ } else {
596616
+ const err = await r.json().catch(() => ({}));
596617
+ if (hint) { hint.textContent = '✗ failed: HTTP ' + r.status + (err.error ? ' — ' + err.error : ''); hint.style.color = 'var(--color-error)'; }
596618
+ }
596619
+ } catch (e) {
596620
+ if (hint) { hint.textContent = '✗ ' + (e && e.message ? e.message : String(e)); hint.style.color = 'var(--color-error)'; }
596621
+ }
596622
+ }
596623
+ window.autoFixLocalhostBackend = autoFixLocalhostBackend;
596624
+
596465
596625
  // Click-to-load from history: copy the selected endpoint into the editor
596466
596626
  // fields below so the user can save / test / modify before applying.
596467
596627
  function selectEndpointFromHistory(i) {
@@ -598746,15 +598906,32 @@ async function refreshEndpointRegistry() {
598746
598906
  } catch {
598747
598907
  }
598748
598908
  }
598909
+ function getLastEndpointDiagnostics() {
598910
+ return _lastEndpointDiagnostics;
598911
+ }
598749
598912
  async function fetchAggregatedModels() {
598750
598913
  const allModels = [];
598751
598914
  modelRouteMap.clear();
598915
+ const diagnostics = [];
598752
598916
  const fetches = endpointRegistry.map(async (ep) => {
598917
+ const diag = {
598918
+ label: ep.label,
598919
+ url: ep.url,
598920
+ type: ep.type,
598921
+ ok: false,
598922
+ modelCount: 0
598923
+ };
598924
+ if (/^https?:\/\/localhost(:|\/|$)/i.test(ep.url)) diag.ipv6Trap = true;
598753
598925
  try {
598754
598926
  const isOllama = ep.type === "ollama";
598755
598927
  const modelsPath = isOllama ? "/api/tags" : "/v1/models";
598756
598928
  const result = await ollamaRequest(ep.url, modelsPath, "GET");
598757
- if (result.status !== 200) return;
598929
+ diag.status = result.status;
598930
+ if (result.status !== 200) {
598931
+ diag.error = `HTTP ${result.status}`;
598932
+ diagnostics.push(diag);
598933
+ return;
598934
+ }
598758
598935
  const body = JSON.parse(result.body);
598759
598936
  if (isOllama) {
598760
598937
  const models = body.models ?? [];
@@ -598768,6 +598945,7 @@ async function fetchAggregatedModels() {
598768
598945
  owned_by: ep.label
598769
598946
  });
598770
598947
  }
598948
+ diag.modelCount = models.length;
598771
598949
  } else {
598772
598950
  const models = body.data ?? [];
598773
598951
  for (const m2 of models) {
@@ -598781,11 +598959,16 @@ async function fetchAggregatedModels() {
598781
598959
  owned_by: ep.label
598782
598960
  });
598783
598961
  }
598962
+ diag.modelCount = models.length;
598784
598963
  }
598785
- } catch {
598964
+ diag.ok = diag.modelCount > 0;
598965
+ } catch (err) {
598966
+ diag.error = err instanceof Error ? err.message : String(err);
598786
598967
  }
598968
+ diagnostics.push(diag);
598787
598969
  });
598788
598970
  await Promise.allSettled(fetches);
598971
+ _lastEndpointDiagnostics = diagnostics;
598789
598972
  return allModels.sort((a2, b) => a2.id.localeCompare(b.id));
598790
598973
  }
598791
598974
  function resolveModelEndpoint(modelId) {
@@ -600024,12 +600207,15 @@ function renderHelpMarkdown(help) {
600024
600207
  return out;
600025
600208
  }
600026
600209
  async function handleV1Models(res, ollamaUrl) {
600210
+ void ollamaUrl;
600027
600211
  try {
600028
600212
  const models = await fetchAggregatedModels();
600213
+ const diagnostics = getLastEndpointDiagnostics();
600029
600214
  jsonResponse(res, 200, {
600030
600215
  object: "list",
600031
600216
  data: models,
600032
- _endpoints: endpointRegistry.map((e2) => ({ label: e2.label, url: e2.url, type: e2.type }))
600217
+ _endpoints: endpointRegistry.map((e2) => ({ label: e2.label, url: e2.url, type: e2.type })),
600218
+ _endpoint_status: diagnostics
600033
600219
  });
600034
600220
  } catch (err) {
600035
600221
  jsonResponse(res, 502, {
@@ -601952,11 +602138,12 @@ async function handlePutConfigEndpoint(req2, res) {
601952
602138
  return;
601953
602139
  }
601954
602140
  const requestBody = body;
601955
- const url = requestBody["url"];
601956
- if (typeof url !== "string" || !url) {
602141
+ const rawUrl = requestBody["url"];
602142
+ if (typeof rawUrl !== "string" || !rawUrl) {
601957
602143
  jsonResponse(res, 400, { error: "Missing required field: url" });
601958
602144
  return;
601959
602145
  }
602146
+ const url = process.env["OA_KEEP_LOCALHOST"] === "1" ? rawUrl : rawUrl.replace(/^(https?:\/\/)localhost(:|\/|$)/i, "$1127.0.0.1$2");
601960
602147
  const settings = { backendUrl: url };
601961
602148
  if (typeof requestBody["auth"] === "string") settings.apiKey = requestBody["auth"];
601962
602149
  if (typeof requestBody["backendType"] === "string") settings.backendType = requestBody["backendType"];
@@ -601971,7 +602158,11 @@ async function handlePutConfigEndpoint(req2, res) {
601971
602158
  recordUsage("endpoint", url, { meta });
601972
602159
  } catch {
601973
602160
  }
601974
- jsonResponse(res, 200, { url, status: "updated" });
602161
+ try {
602162
+ await refreshEndpointRegistry();
602163
+ } catch {
602164
+ }
602165
+ jsonResponse(res, 200, { url, status: "updated", reloaded: true });
601975
602166
  }
601976
602167
  function handleGetCommands(res) {
601977
602168
  const commands = SLASH_COMMANDS.map(([cmd, desc]) => ({
@@ -602024,6 +602215,11 @@ async function handlePostCommand(res, cmd) {
602024
602215
  }
602025
602216
  }
602026
602217
  async function handleRequest(req2, res, ollamaUrl, verbose) {
602218
+ try {
602219
+ const _liveCfg = loadConfig();
602220
+ if (_liveCfg.backendUrl) ollamaUrl = _liveCfg.backendUrl;
602221
+ } catch {
602222
+ }
602027
602223
  const method = req2.method ?? "GET";
602028
602224
  const urlObj = new URL(req2.url ?? "/", `http://${req2.headers.host ?? "localhost"}`);
602029
602225
  const pathname = urlObj.pathname;
@@ -605883,7 +606079,7 @@ function setTimerEnabled(name10, enabled2) {
605883
606079
  return false;
605884
606080
  }
605885
606081
  }
605886
- var require3, endpointRegistry, modelRouteMap, endpointUsage, BACKEND_TIMEOUT_DEFAULT_MS, BACKEND_TIMEOUT_MAX_MS, metrics, startedAt, runningProcesses, perKeyUsage, CRON_MARKER2;
606082
+ var require3, endpointRegistry, modelRouteMap, endpointUsage, _lastEndpointDiagnostics, BACKEND_TIMEOUT_DEFAULT_MS, BACKEND_TIMEOUT_MAX_MS, metrics, startedAt, runningProcesses, perKeyUsage, CRON_MARKER2;
605887
606083
  var init_serve = __esm({
605888
606084
  "packages/cli/src/api/serve.ts"() {
605889
606085
  "use strict";
@@ -605916,6 +606112,7 @@ var init_serve = __esm({
605916
606112
  endpointRegistry = [];
605917
606113
  modelRouteMap = /* @__PURE__ */ new Map();
605918
606114
  endpointUsage = /* @__PURE__ */ new Map();
606115
+ _lastEndpointDiagnostics = [];
605919
606116
  BACKEND_TIMEOUT_DEFAULT_MS = 12e4;
605920
606117
  BACKEND_TIMEOUT_MAX_MS = 36e5;
605921
606118
  metrics = {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.550",
3
+ "version": "0.187.552",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.550",
9
+ "version": "0.187.552",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
@@ -468,9 +468,9 @@
468
468
  }
469
469
  },
470
470
  "node_modules/@ipld/dag-pb": {
471
- "version": "4.1.5",
472
- "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.5.tgz",
473
- "integrity": "sha512-w4PZ2yPqvNmlAir7/2hsCRMqny1EY5jj26iZcSgxREJexmbAc2FI21jp26MqiNdfgAxvkCnf2N/TJI18GaDNwA==",
471
+ "version": "4.1.6",
472
+ "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.1.6.tgz",
473
+ "integrity": "sha512-n0NuhOuU7zfCBWLJdnR0mCvgMbeHjKzP1XGi3E8w+i2oj+sZ9LZup+sSmgqYwzEvmOpWL5hUG5G+oqR1WQxE1Q==",
474
474
  "license": "Apache-2.0 OR MIT",
475
475
  "dependencies": {
476
476
  "multiformats": "^13.1.0"
@@ -1324,9 +1324,9 @@
1324
1324
  }
1325
1325
  },
1326
1326
  "node_modules/@multiformats/murmur3": {
1327
- "version": "2.2.2",
1328
- "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.2.2.tgz",
1329
- "integrity": "sha512-B37HsL4uqZpKzOPRNmpr9hR1lbywZfJR053sYgkQGnnbfNzM1Nw4SlL9I8hMAZRk1TOqr8q+PeaFeuNmKgi2YA==",
1327
+ "version": "2.2.3",
1328
+ "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.2.3.tgz",
1329
+ "integrity": "sha512-ofAL2QfuMm1DxBWAt7goM13Xoo2edCb1Og/1FqXzpV4a/ZCge0UK6OJOcQ6rdu4gBMm3Sv9YGbx/wXmLKOLbUQ==",
1330
1330
  "license": "Apache-2.0 OR MIT",
1331
1331
  "dependencies": {
1332
1332
  "multiformats": "^13.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.550",
3
+ "version": "0.187.552",
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",