open-agents-ai 0.103.37 → 0.103.39

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 +113 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12607,30 +12607,78 @@ async function handleCmd(cmd) {
12607
12607
  dlog('expose: auth key configured (' + exposeAuthKey.length + ' chars)');
12608
12608
  }
12609
12609
 
12610
- // Query Ollama for available models
12611
- const ollamaUrl = args.ollama_url || process.env.OLLAMA_URL || 'http://localhost:11434';
12610
+ // Passthrough mode: forward from a remote /endpoint (Chutes, Groq, etc.)
12611
+ var isPassthrough = args.passthrough === 'true';
12612
+ var endpointAuth = args.endpoint_auth || '';
12613
+ if (isPassthrough) {
12614
+ dlog('expose: PASSTHROUGH mode \u2014 forwarding from upstream endpoint');
12615
+ }
12616
+
12617
+ // Query models from the backend
12618
+ var rawUrl = args.ollama_url || process.env.OLLAMA_URL || 'http://localhost:11434';
12619
+ // For passthrough: normalize URL \u2014 strip /v1/chat/completions, /v1, etc. so we can append our own paths
12620
+ const ollamaUrl = isPassthrough
12621
+ ? rawUrl.replace(/\\/+$/, '').replace(/\\/chat\\/completions$/, '').replace(/\\/completions$/, '').replace(/\\/models(\\/.*)?$/, '').replace(/\\/v1$/, '').replace(/\\/+$/, '')
12622
+ : rawUrl;
12612
12623
  let models = [];
12613
- try {
12614
- const tagsResp = await fetch(ollamaUrl + '/api/tags');
12615
- if (tagsResp.ok) {
12616
- const tagsData = await tagsResp.json();
12617
- models = (tagsData.models || []).map(m => ({
12618
- name: m.name,
12619
- size: m.size || 0,
12620
- parameterSize: m.details?.parameter_size || '',
12621
- family: m.details?.family || '',
12622
- quantization: m.details?.quantization_level || '',
12623
- }));
12624
+
12625
+ if (isPassthrough) {
12626
+ // Passthrough: query /v1/models from the upstream endpoint (OpenAI-compatible)
12627
+ try {
12628
+ var modelsHeaders = { 'Content-Type': 'application/json' };
12629
+ if (endpointAuth) modelsHeaders['Authorization'] = 'Bearer ' + endpointAuth;
12630
+ var modelsUrl = ollamaUrl.replace(/\\/+$/, '') + '/v1/models';
12631
+ dlog('expose: passthrough querying models from ' + modelsUrl);
12632
+ const modelsResp = await fetch(modelsUrl, { headers: modelsHeaders });
12633
+ if (modelsResp.ok) {
12634
+ const modelsData = await modelsResp.json();
12635
+ var modelList = modelsData.data || modelsData.models || [];
12636
+ models = modelList.map(function(m) {
12637
+ return {
12638
+ name: m.id || m.name || 'unknown',
12639
+ size: 0,
12640
+ parameterSize: '',
12641
+ family: m.owned_by || '',
12642
+ quantization: '',
12643
+ };
12644
+ });
12645
+ } else {
12646
+ var errText = '';
12647
+ try { errText = await modelsResp.text(); } catch {}
12648
+ writeResp(id, { ok: false, output: 'Upstream /v1/models returned ' + modelsResp.status + ': ' + errText.slice(0, 200) });
12649
+ return;
12650
+ }
12651
+ } catch (e) {
12652
+ writeResp(id, { ok: false, output: 'Cannot reach upstream endpoint at ' + ollamaUrl + ': ' + e.message });
12653
+ return;
12654
+ }
12655
+ } else {
12656
+ // Local Ollama: query /api/tags
12657
+ try {
12658
+ const tagsResp = await fetch(ollamaUrl + '/api/tags');
12659
+ if (tagsResp.ok) {
12660
+ const tagsData = await tagsResp.json();
12661
+ models = (tagsData.models || []).map(function(m) {
12662
+ return {
12663
+ name: m.name,
12664
+ size: m.size || 0,
12665
+ parameterSize: m.details ? m.details.parameter_size || '' : '',
12666
+ family: m.details ? m.details.family || '' : '',
12667
+ quantization: m.details ? m.details.quantization_level || '' : '',
12668
+ };
12669
+ });
12670
+ }
12671
+ } catch (e) {
12672
+ writeResp(id, { ok: false, output: 'Cannot reach Ollama at ' + ollamaUrl + ': ' + e.message });
12673
+ return;
12624
12674
  }
12625
- } catch (e) {
12626
- writeResp(id, { ok: false, output: 'Cannot reach Ollama at ' + ollamaUrl + ': ' + e.message });
12627
- return;
12628
12675
  }
12629
12676
 
12630
12677
  if (models.length === 0) {
12631
- writeResp(id, { ok: false, output: 'No models found on Ollama. Pull a model first.' });
12678
+ writeResp(id, { ok: false, output: isPassthrough ? 'No models found on upstream endpoint.' : 'No models found on Ollama. Pull a model first.' });
12632
12679
  return;
12633
12680
  }
12681
+ dlog('expose: found ' + models.length + ' models' + (isPassthrough ? ' (passthrough)' : ''));
12634
12682
 
12635
12683
  // Fetch market rates from OpenRouter (free, no auth)
12636
12684
  let marketRates = {};
@@ -12782,10 +12830,12 @@ async function handleCmd(cmd) {
12782
12830
  if (parsedReq.temperature !== undefined) chatBody.temperature = parsedReq.temperature;
12783
12831
  if (parsedReq.max_tokens) chatBody.max_tokens = parsedReq.max_tokens;
12784
12832
 
12785
- dlog('expose: calling /v1/chat/completions with ' + chatBody.messages.length + ' messages, tools=' + (chatBody.tools ? chatBody.tools.length : 0));
12833
+ var chatHeaders = { 'Content-Type': 'application/json' };
12834
+ if (isPassthrough && endpointAuth) chatHeaders['Authorization'] = 'Bearer ' + endpointAuth;
12835
+ dlog('expose: calling /v1/chat/completions with ' + chatBody.messages.length + ' messages, tools=' + (chatBody.tools ? chatBody.tools.length : 0) + (isPassthrough ? ' (passthrough)' : ''));
12786
12836
  genResp = await fetch(ollamaUrl + '/v1/chat/completions', {
12787
12837
  method: 'POST',
12788
- headers: { 'Content-Type': 'application/json' },
12838
+ headers: chatHeaders,
12789
12839
  body: JSON.stringify(chatBody),
12790
12840
  });
12791
12841
  genData = await genResp.json();
@@ -12816,8 +12866,36 @@ async function handleCmd(cmd) {
12816
12866
  usage: { input_tokens: inputTokens, output_tokens: outputTokens },
12817
12867
  pricing: entry.pricing,
12818
12868
  };
12869
+ } else if (isPassthrough) {
12870
+ // Passthrough flat prompt \u2014 convert to /v1/chat/completions (upstream has no /api/generate)
12871
+ var flatPrompt = parsedReq && parsedReq.prompt ? parsedReq.prompt : (prompt || 'Hello');
12872
+ var ptHeaders = { 'Content-Type': 'application/json' };
12873
+ if (endpointAuth) ptHeaders['Authorization'] = 'Bearer ' + endpointAuth;
12874
+ genResp = await fetch(ollamaUrl + '/v1/chat/completions', {
12875
+ method: 'POST',
12876
+ headers: ptHeaders,
12877
+ body: JSON.stringify({
12878
+ model: model.name,
12879
+ messages: [{ role: 'user', content: flatPrompt }],
12880
+ stream: false,
12881
+ }),
12882
+ });
12883
+ genData = await genResp.json();
12884
+ var ptChoices = genData.choices || [];
12885
+ var ptMsg = (ptChoices[0] || {}).message || {};
12886
+ output = (ptMsg.content || '').replace(/<think>[\\s\\S]*?<\\/think>/g, '').trim();
12887
+ inputTokens = (genData.usage || {}).prompt_tokens || 0;
12888
+ outputTokens = (genData.usage || {}).completion_tokens || 0;
12889
+
12890
+ responsePayload = {
12891
+ model: model.name,
12892
+ response: output,
12893
+ choices: ptChoices,
12894
+ usage: { input_tokens: inputTokens, output_tokens: outputTokens },
12895
+ pricing: entry.pricing,
12896
+ };
12819
12897
  } else {
12820
- // Flat prompt \u2014 use /api/generate (original behavior)
12898
+ // Flat prompt \u2014 use /api/generate (local Ollama)
12821
12899
  var flatPrompt = parsedReq && parsedReq.prompt ? parsedReq.prompt : (prompt || 'Hello');
12822
12900
  genResp = await fetch(ollamaUrl + '/api/generate', {
12823
12901
  method: 'POST',
@@ -28358,6 +28436,7 @@ ${this.formatConnectionInfo()}`);
28358
28436
  _margin;
28359
28437
  _passthrough = false;
28360
28438
  _loadbalance = false;
28439
+ _endpointAuth;
28361
28440
  _pollTimer = null;
28362
28441
  _stats = {
28363
28442
  status: "standby",
@@ -28402,6 +28481,7 @@ ${this.formatConnectionInfo()}`);
28402
28481
  this._margin = options.margin ?? 0.5;
28403
28482
  this._passthrough = options.passthrough ?? false;
28404
28483
  this._loadbalance = options.loadbalance ?? false;
28484
+ this._endpointAuth = options.endpointAuth;
28405
28485
  this._onInfo = options.onInfo;
28406
28486
  this._onError = options.onError;
28407
28487
  if (options.authKey === void 0 || options.authKey === "") {
@@ -28421,21 +28501,23 @@ ${this.formatConnectionInfo()}`);
28421
28501
  throw new Error(`Nexus daemon connect failed: ${connectResult.error}`);
28422
28502
  }
28423
28503
  await new Promise((r) => setTimeout(r, 500));
28424
- this._onInfo?.("Registering inference capabilities...");
28425
- let exposeResult = await this._nexusTool.execute({
28504
+ this._onInfo?.(this._passthrough ? `Registering passthrough capabilities (\u2192 ${this._targetUrl})...` : "Registering inference capabilities...");
28505
+ const exposeArgs = {
28426
28506
  action: "expose",
28427
28507
  ollama_url: this._targetUrl,
28428
28508
  margin: String(this._margin),
28429
28509
  auth_key: this._authKey
28430
- });
28510
+ };
28511
+ if (this._passthrough) {
28512
+ exposeArgs.passthrough = "true";
28513
+ if (this._endpointAuth) {
28514
+ exposeArgs.endpoint_auth = this._endpointAuth;
28515
+ }
28516
+ }
28517
+ let exposeResult = await this._nexusTool.execute(exposeArgs);
28431
28518
  if (!exposeResult.success && exposeResult.error?.includes("not running")) {
28432
28519
  await new Promise((r) => setTimeout(r, 1500));
28433
- exposeResult = await this._nexusTool.execute({
28434
- action: "expose",
28435
- ollama_url: this._targetUrl,
28436
- margin: String(this._margin),
28437
- auth_key: this._authKey
28438
- });
28520
+ exposeResult = await this._nexusTool.execute(exposeArgs);
28439
28521
  }
28440
28522
  if (!exposeResult.success) {
28441
28523
  throw new Error(`Expose failed: ${exposeResult.error}`);
@@ -46888,7 +46970,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
46888
46970
  let targetUrl;
46889
46971
  if (kindOrUrl === "passthrough" || passthrough) {
46890
46972
  kind = "custom";
46891
- targetUrl = config.backendUrl;
46973
+ targetUrl = normalizeBaseUrl(config.backendUrl);
46892
46974
  passthrough = true;
46893
46975
  } else if (knownKinds.includes(kindOrUrl)) {
46894
46976
  kind = kindOrUrl;
@@ -46917,6 +46999,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
46917
46999
  stateDir: join50(repoRoot, ".oa"),
46918
47000
  passthrough: passthrough ?? false,
46919
47001
  loadbalance: loadbalance ?? false,
47002
+ endpointAuth: passthrough ? config.apiKey : void 0,
46920
47003
  onInfo: (msg) => {
46921
47004
  exposeSpinnerMsg = msg;
46922
47005
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.37",
3
+ "version": "0.103.39",
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",