open-agents-ai 0.187.551 → 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
@@ -591351,25 +591351,42 @@ async function loadModels() {
591351
591351
  modelSelect.innerHTML = '';
591352
591352
  const list = Array.isArray(modelsResp.data) ? modelsResp.data : [];
591353
591353
  if (list.length === 0) {
591354
- // Empty backend most common cause is ollama not running on this
591355
- // host, OR /v1/models 401'd due to auth. Surface a clickable
591356
- // diagnostic in the dropdown so the user knows what to do.
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
591357
  const opt = document.createElement('option');
591358
591358
  opt.value = '';
591359
591359
  opt.disabled = true;
591360
591360
  opt.selected = true;
591361
+ let firstFail = null;
591362
+ for (const d of diags) { if (!d.ok) { firstFail = d; break; } }
591361
591363
  const backendUrl = (cfgResp && cfgResp.config && cfgResp.config.backendUrl) || '';
591362
- opt.textContent = backendUrl
591363
- ? '(no models backend at ' + backendUrl + ' not reachable; check ollama/vllm)'
591364
- : '(no models — set endpoint via /endpoint or check backend)';
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
+ }
591365
591372
  modelSelect.appendChild(opt);
591366
- // Visible status hint near the picker if there's a slot for it.
591367
591373
  try {
591368
591374
  const hint = document.getElementById('model-empty-hint');
591369
591375
  if (hint) {
591370
591376
  hint.style.display = 'block';
591371
591377
  hint.style.color = 'var(--color-warning)';
591372
- hint.textContent = 'No models found. Start ollama (run "ollama serve") and pull a model (run "ollama pull qwen3.6:latest"), or change the backend endpoint.';
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>';
591373
591390
  }
591374
591391
  } catch {}
591375
591392
  return;
@@ -596463,6 +596480,30 @@ async function loadSettingsConnections() {
596463
596480
  window.__oaEndpointHistory = history;
596464
596481
  host.innerHTML =
596465
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>' +
596466
596507
  '<div>' +
596467
596508
  '<label style="display:block;font-size:0.78rem;font-weight:500;margin-bottom:6px">Endpoint history</label>' +
596468
596509
  histHTML +
@@ -596499,6 +596540,88 @@ async function loadSettingsConnections() {
596499
596540
  }
596500
596541
  }
596501
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
+
596502
596625
  // Click-to-load from history: copy the selected endpoint into the editor
596503
596626
  // fields below so the user can save / test / modify before applying.
596504
596627
  function selectEndpointFromHistory(i) {
@@ -598783,15 +598906,32 @@ async function refreshEndpointRegistry() {
598783
598906
  } catch {
598784
598907
  }
598785
598908
  }
598909
+ function getLastEndpointDiagnostics() {
598910
+ return _lastEndpointDiagnostics;
598911
+ }
598786
598912
  async function fetchAggregatedModels() {
598787
598913
  const allModels = [];
598788
598914
  modelRouteMap.clear();
598915
+ const diagnostics = [];
598789
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;
598790
598925
  try {
598791
598926
  const isOllama = ep.type === "ollama";
598792
598927
  const modelsPath = isOllama ? "/api/tags" : "/v1/models";
598793
598928
  const result = await ollamaRequest(ep.url, modelsPath, "GET");
598794
- 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
+ }
598795
598935
  const body = JSON.parse(result.body);
598796
598936
  if (isOllama) {
598797
598937
  const models = body.models ?? [];
@@ -598805,6 +598945,7 @@ async function fetchAggregatedModels() {
598805
598945
  owned_by: ep.label
598806
598946
  });
598807
598947
  }
598948
+ diag.modelCount = models.length;
598808
598949
  } else {
598809
598950
  const models = body.data ?? [];
598810
598951
  for (const m2 of models) {
@@ -598818,11 +598959,16 @@ async function fetchAggregatedModels() {
598818
598959
  owned_by: ep.label
598819
598960
  });
598820
598961
  }
598962
+ diag.modelCount = models.length;
598821
598963
  }
598822
- } catch {
598964
+ diag.ok = diag.modelCount > 0;
598965
+ } catch (err) {
598966
+ diag.error = err instanceof Error ? err.message : String(err);
598823
598967
  }
598968
+ diagnostics.push(diag);
598824
598969
  });
598825
598970
  await Promise.allSettled(fetches);
598971
+ _lastEndpointDiagnostics = diagnostics;
598826
598972
  return allModels.sort((a2, b) => a2.id.localeCompare(b.id));
598827
598973
  }
598828
598974
  function resolveModelEndpoint(modelId) {
@@ -600061,12 +600207,15 @@ function renderHelpMarkdown(help) {
600061
600207
  return out;
600062
600208
  }
600063
600209
  async function handleV1Models(res, ollamaUrl) {
600210
+ void ollamaUrl;
600064
600211
  try {
600065
600212
  const models = await fetchAggregatedModels();
600213
+ const diagnostics = getLastEndpointDiagnostics();
600066
600214
  jsonResponse(res, 200, {
600067
600215
  object: "list",
600068
600216
  data: models,
600069
- _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
600070
600219
  });
600071
600220
  } catch (err) {
600072
600221
  jsonResponse(res, 502, {
@@ -601989,11 +602138,12 @@ async function handlePutConfigEndpoint(req2, res) {
601989
602138
  return;
601990
602139
  }
601991
602140
  const requestBody = body;
601992
- const url = requestBody["url"];
601993
- if (typeof url !== "string" || !url) {
602141
+ const rawUrl = requestBody["url"];
602142
+ if (typeof rawUrl !== "string" || !rawUrl) {
601994
602143
  jsonResponse(res, 400, { error: "Missing required field: url" });
601995
602144
  return;
601996
602145
  }
602146
+ const url = process.env["OA_KEEP_LOCALHOST"] === "1" ? rawUrl : rawUrl.replace(/^(https?:\/\/)localhost(:|\/|$)/i, "$1127.0.0.1$2");
601997
602147
  const settings = { backendUrl: url };
601998
602148
  if (typeof requestBody["auth"] === "string") settings.apiKey = requestBody["auth"];
601999
602149
  if (typeof requestBody["backendType"] === "string") settings.backendType = requestBody["backendType"];
@@ -602008,7 +602158,11 @@ async function handlePutConfigEndpoint(req2, res) {
602008
602158
  recordUsage("endpoint", url, { meta });
602009
602159
  } catch {
602010
602160
  }
602011
- jsonResponse(res, 200, { url, status: "updated" });
602161
+ try {
602162
+ await refreshEndpointRegistry();
602163
+ } catch {
602164
+ }
602165
+ jsonResponse(res, 200, { url, status: "updated", reloaded: true });
602012
602166
  }
602013
602167
  function handleGetCommands(res) {
602014
602168
  const commands = SLASH_COMMANDS.map(([cmd, desc]) => ({
@@ -602061,6 +602215,11 @@ async function handlePostCommand(res, cmd) {
602061
602215
  }
602062
602216
  }
602063
602217
  async function handleRequest(req2, res, ollamaUrl, verbose) {
602218
+ try {
602219
+ const _liveCfg = loadConfig();
602220
+ if (_liveCfg.backendUrl) ollamaUrl = _liveCfg.backendUrl;
602221
+ } catch {
602222
+ }
602064
602223
  const method = req2.method ?? "GET";
602065
602224
  const urlObj = new URL(req2.url ?? "/", `http://${req2.headers.host ?? "localhost"}`);
602066
602225
  const pathname = urlObj.pathname;
@@ -605920,7 +606079,7 @@ function setTimerEnabled(name10, enabled2) {
605920
606079
  return false;
605921
606080
  }
605922
606081
  }
605923
- 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;
605924
606083
  var init_serve = __esm({
605925
606084
  "packages/cli/src/api/serve.ts"() {
605926
606085
  "use strict";
@@ -605953,6 +606112,7 @@ var init_serve = __esm({
605953
606112
  endpointRegistry = [];
605954
606113
  modelRouteMap = /* @__PURE__ */ new Map();
605955
606114
  endpointUsage = /* @__PURE__ */ new Map();
606115
+ _lastEndpointDiagnostics = [];
605956
606116
  BACKEND_TIMEOUT_DEFAULT_MS = 12e4;
605957
606117
  BACKEND_TIMEOUT_MAX_MS = 36e5;
605958
606118
  metrics = {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.551",
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.551",
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.551",
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",