open-agents-ai 0.104.0 → 0.104.3

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 +161 -46
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30893,6 +30893,7 @@ ${activitySummary}
30893
30893
  });
30894
30894
 
30895
30895
  // packages/cli/dist/tui/model-picker.js
30896
+ import { totalmem as totalmem2 } from "node:os";
30896
30897
  async function fetchOllamaModels(baseUrl) {
30897
30898
  const url = `${normalizeBaseUrl(baseUrl)}/api/tags`;
30898
30899
  const resp = await fetch(url, {
@@ -30932,10 +30933,22 @@ async function fetchOllamaModels(baseUrl) {
30932
30933
  }
30933
30934
  }
30934
30935
  if (show.model_info) {
30935
- for (const [key, value] of Object.entries(show.model_info)) {
30936
- if (key.endsWith(".context_length") && typeof value === "number") {
30937
- result[i].contextLength = value;
30938
- break;
30936
+ const info = show.model_info;
30937
+ const arch = info["general.architecture"];
30938
+ const paramCount = info["general.parameter_count"];
30939
+ const fileSizeGB = result[i].sizeBytes > 0 ? result[i].sizeBytes / 1024 ** 3 : paramCount ? paramCount * 0.6 / 1024 ** 3 : 4;
30940
+ if (arch) {
30941
+ const archMax = info[`${arch}.context_length`];
30942
+ const nLayers = info[`${arch}.block_count`];
30943
+ const nKVHeads = info[`${arch}.attention.head_count_kv`] ?? info[`${arch}.attention.head_count`];
30944
+ const keyDim = info[`${arch}.attention.key_length`];
30945
+ const valDim = info[`${arch}.attention.value_length`] ?? keyDim;
30946
+ if (archMax && nLayers && nKVHeads && keyDim && valDim) {
30947
+ const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
30948
+ result[i].contextLength = estimateRealisticContext(kvBytesPerToken, archMax, fileSizeGB);
30949
+ } else if (archMax) {
30950
+ const kvEstimate = fileSizeGB <= 5 ? 524288 : fileSizeGB <= 20 ? 1048576 : 1572864;
30951
+ result[i].contextLength = estimateRealisticContext(kvEstimate, archMax, fileSizeGB);
30939
30952
  }
30940
30953
  }
30941
30954
  }
@@ -31231,9 +31244,23 @@ async function queryModelContextSize(baseUrl, modelName) {
31231
31244
  return parseInt(match[1], 10);
31232
31245
  }
31233
31246
  if (data.model_info) {
31234
- for (const [key, value] of Object.entries(data.model_info)) {
31235
- if (key.endsWith(".context_length") && typeof value === "number") {
31236
- return value;
31247
+ const info = data.model_info;
31248
+ const arch = info["general.architecture"];
31249
+ const paramCount = info["general.parameter_count"];
31250
+ const modelSizeGB2 = paramCount ? paramCount * 0.6 / 1024 ** 3 : 4;
31251
+ if (arch) {
31252
+ const archMax = info[`${arch}.context_length`];
31253
+ const nLayers = info[`${arch}.block_count`];
31254
+ const nKVHeads = info[`${arch}.attention.head_count_kv`] ?? info[`${arch}.attention.head_count`];
31255
+ const keyDim = info[`${arch}.attention.key_length`];
31256
+ const valDim = info[`${arch}.attention.value_length`] ?? keyDim;
31257
+ if (archMax && nLayers && nKVHeads && keyDim && valDim) {
31258
+ const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
31259
+ return estimateRealisticContext(kvBytesPerToken, archMax, modelSizeGB2);
31260
+ }
31261
+ if (archMax) {
31262
+ const kvEstimate = modelSizeGB2 <= 5 ? 524288 : modelSizeGB2 <= 20 ? 1048576 : 1572864;
31263
+ return estimateRealisticContext(kvEstimate, archMax, modelSizeGB2);
31237
31264
  }
31238
31265
  }
31239
31266
  }
@@ -31242,10 +31269,25 @@ async function queryModelContextSize(baseUrl, modelName) {
31242
31269
  return null;
31243
31270
  }
31244
31271
  }
31272
+ function estimateRealisticContext(kvBytesPerToken, archMax, modelSizeGB2) {
31273
+ const totalMemGB = totalmem2() / 1024 ** 3;
31274
+ const usableBytes = totalMemGB * 0.7 * 1024 ** 3;
31275
+ const maxTokens = Math.floor(usableBytes / kvBytesPerToken);
31276
+ let numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
31277
+ numCtx = Math.min(numCtx, 131072, archMax);
31278
+ if (modelSizeGB2 && modelSizeGB2 > 0) {
31279
+ const maxKVBytes = modelSizeGB2 * 4 * 1024 ** 3;
31280
+ const budgetCap = Math.max(2048, Math.floor(maxKVBytes / kvBytesPerToken / 1024) * 1024);
31281
+ numCtx = Math.min(numCtx, budgetCap);
31282
+ }
31283
+ return numCtx;
31284
+ }
31245
31285
  async function queryOpenAIContextSize(baseUrl, modelName, apiKey) {
31246
31286
  try {
31247
31287
  const models = await fetchOpenAIModels(baseUrl, apiKey);
31248
31288
  const model = models.find((m) => m.name === modelName);
31289
+ if (model?.contextLength)
31290
+ return model.contextLength;
31249
31291
  if (model?.size) {
31250
31292
  const match = model.size.match(/(\d+)K ctx/);
31251
31293
  if (match)
@@ -32155,20 +32197,30 @@ function recommendModel(specs) {
32155
32197
  }
32156
32198
  return QWEN_VARIANTS.find((v) => v.tag === "qwen3.5:cloud");
32157
32199
  }
32158
- function calculateContextWindow(specs, modelSizeGB2) {
32200
+ function calculateContextWindow(specs, modelSizeGB2, kvBytesPerToken, archMax) {
32159
32201
  const totalAvail = Math.max(specs.gpuVramGB, specs.totalRamGB);
32160
- const remaining = totalAvail - modelSizeGB2;
32161
- if (remaining >= 200)
32162
- return { numCtx: 131072, label: "128K" };
32163
- if (remaining >= 100)
32164
- return { numCtx: 65536, label: "64K" };
32165
- if (remaining >= 50)
32166
- return { numCtx: 32768, label: "32K" };
32167
- if (remaining >= 20)
32168
- return { numCtx: 16384, label: "16K" };
32169
- if (remaining >= 8)
32170
- return { numCtx: 8192, label: "8K" };
32171
- return { numCtx: 4096, label: "4K" };
32202
+ const remaining = Math.max(0, totalAvail - modelSizeGB2);
32203
+ const usableGB = remaining * 0.85;
32204
+ let numCtx;
32205
+ if (kvBytesPerToken && kvBytesPerToken > 0) {
32206
+ const maxTokens = Math.floor(usableGB * 1024 ** 3 / kvBytesPerToken);
32207
+ numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
32208
+ } else {
32209
+ const kvEstimate = modelSizeGB2 <= 5 ? 524288 : modelSizeGB2 <= 20 ? 1048576 : 1572864;
32210
+ const maxTokens = Math.floor(usableGB * 1024 ** 3 / kvEstimate);
32211
+ numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
32212
+ }
32213
+ numCtx = Math.min(numCtx, 131072);
32214
+ if (archMax && archMax > 0)
32215
+ numCtx = Math.min(numCtx, archMax);
32216
+ if (kvBytesPerToken && kvBytesPerToken > 0 && modelSizeGB2 > 0) {
32217
+ const maxKVBytes = modelSizeGB2 * 4 * 1024 ** 3;
32218
+ const maxTokensFromBudget = Math.floor(maxKVBytes / kvBytesPerToken);
32219
+ const budgetCap = Math.max(2048, Math.floor(maxTokensFromBudget / 1024) * 1024);
32220
+ numCtx = Math.min(numCtx, budgetCap);
32221
+ }
32222
+ const label = numCtx >= 1024 ? `${Math.floor(numCtx / 1024)}K` : String(numCtx);
32223
+ return { numCtx, label };
32172
32224
  }
32173
32225
  function modelSupportsToolCalling(modelName) {
32174
32226
  const lower = modelName.toLowerCase();
@@ -33512,9 +33564,40 @@ function modelSizeGB(models, modelName) {
33512
33564
  const known = QWEN_VARIANTS.find((v) => modelName.includes(v.tag.split(":")[1] ?? ""));
33513
33565
  return known?.sizeGB ?? 4;
33514
33566
  }
33515
- async function createExpandedVariantAsync(baseModel, specs, sizeGB) {
33567
+ async function queryModelKVInfo(backendUrl, modelName) {
33568
+ try {
33569
+ const normalized = backendUrl.replace(/\/+$/, "");
33570
+ const res = await fetch(`${normalized}/api/show`, {
33571
+ method: "POST",
33572
+ headers: { "Content-Type": "application/json" },
33573
+ body: JSON.stringify({ name: modelName }),
33574
+ signal: AbortSignal.timeout(5e3)
33575
+ });
33576
+ if (!res.ok)
33577
+ return null;
33578
+ const data = await res.json();
33579
+ if (!data.model_info)
33580
+ return null;
33581
+ const info = data.model_info;
33582
+ const arch = info["general.architecture"];
33583
+ if (!arch)
33584
+ return null;
33585
+ const nLayers = info[`${arch}.block_count`];
33586
+ const nKVHeads = info[`${arch}.attention.head_count_kv`] ?? info[`${arch}.attention.head_count`];
33587
+ const keyDim = info[`${arch}.attention.key_length`];
33588
+ const valDim = info[`${arch}.attention.value_length`] ?? keyDim;
33589
+ const archMax = info[`${arch}.context_length`];
33590
+ if (!nLayers || !nKVHeads || !keyDim || !valDim || !archMax)
33591
+ return null;
33592
+ const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
33593
+ return { kvBytesPerToken, archMax };
33594
+ } catch {
33595
+ return null;
33596
+ }
33597
+ }
33598
+ async function createExpandedVariantAsync(baseModel, specs, sizeGB, kvBytesPerToken, archMax) {
33516
33599
  const customName = expandedModelName(baseModel);
33517
- const ctx = calculateContextWindow(specs, sizeGB);
33600
+ const ctx = calculateContextWindow(specs, sizeGB, kvBytesPerToken, archMax);
33518
33601
  try {
33519
33602
  const numPredict = Math.min(16384, Math.max(2048, Math.floor(ctx.numCtx * 0.25)));
33520
33603
  const modelfileContent = [
@@ -33537,37 +33620,51 @@ async function createExpandedVariantAsync(baseModel, specs, sizeGB) {
33537
33620
  }
33538
33621
  }
33539
33622
  async function ensureExpandedContext(modelName, backendUrl) {
33540
- if (modelName.startsWith("open-agents-")) {
33541
- const specs2 = await detectSystemSpecsAsync();
33542
- const ctx2 = calculateContextWindow(specs2, 4);
33543
- return { model: modelName, created: false, contextLabel: ctx2.label, numCtx: ctx2.numCtx };
33544
- }
33545
33623
  if (modelName.includes("cloud") || modelName.includes(":cloud")) {
33546
33624
  return { model: modelName, created: false, contextLabel: "remote", numCtx: 0 };
33547
33625
  }
33548
- const existing = await checkExpandedVariant(modelName, backendUrl);
33549
- if (existing === null) {
33550
- return { model: modelName, created: false, contextLabel: "", numCtx: 0 };
33551
- }
33552
33626
  const specs = await detectSystemSpecsAsync();
33553
- if (typeof existing === "string") {
33554
- let sizeGB2 = 4;
33555
- try {
33556
- const models = await fetchOllamaModels(backendUrl);
33557
- sizeGB2 = modelSizeGB(models, modelName);
33558
- } catch {
33559
- }
33560
- const ctx2 = calculateContextWindow(specs, sizeGB2);
33561
- return { model: existing, created: false, contextLabel: ctx2.label, numCtx: ctx2.numCtx };
33562
- }
33627
+ const kvInfo = await queryModelKVInfo(backendUrl, modelName);
33563
33628
  let sizeGB = 4;
33564
33629
  try {
33565
33630
  const models = await fetchOllamaModels(backendUrl);
33566
33631
  sizeGB = modelSizeGB(models, modelName);
33567
33632
  } catch {
33568
33633
  }
33569
- const ctx = calculateContextWindow(specs, sizeGB);
33570
- const created = await createExpandedVariantAsync(modelName, specs, sizeGB);
33634
+ const ctx = calculateContextWindow(specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
33635
+ if (modelName.startsWith("open-agents-")) {
33636
+ try {
33637
+ const normalized = backendUrl.replace(/\/+$/, "");
33638
+ const showRes = await fetch(`${normalized}/api/show`, {
33639
+ method: "POST",
33640
+ headers: { "Content-Type": "application/json" },
33641
+ body: JSON.stringify({ name: modelName }),
33642
+ signal: AbortSignal.timeout(5e3)
33643
+ });
33644
+ if (showRes.ok) {
33645
+ const showData = await showRes.json();
33646
+ const numCtxMatch = showData.parameters?.match(/num_ctx\s+(\d+)/);
33647
+ const currentNumCtx = numCtxMatch ? parseInt(numCtxMatch[1], 10) : 0;
33648
+ if (currentNumCtx !== ctx.numCtx) {
33649
+ const fromMatch = showData.modelfile?.match(/^FROM\s+(.+)$/m);
33650
+ const baseModel = fromMatch?.[1]?.trim();
33651
+ if (baseModel && !baseModel.startsWith("open-agents-")) {
33652
+ await createExpandedVariantAsync(baseModel, specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
33653
+ }
33654
+ }
33655
+ }
33656
+ } catch {
33657
+ }
33658
+ return { model: modelName, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
33659
+ }
33660
+ const existing = await checkExpandedVariant(modelName, backendUrl);
33661
+ if (existing === null) {
33662
+ return { model: modelName, created: false, contextLabel: "", numCtx: 0 };
33663
+ }
33664
+ if (typeof existing === "string") {
33665
+ return { model: existing, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
33666
+ }
33667
+ const created = await createExpandedVariantAsync(modelName, specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
33571
33668
  if (created) {
33572
33669
  return { model: created, created: true, contextLabel: ctx.label, numCtx: ctx.numCtx };
33573
33670
  }
@@ -33720,7 +33817,7 @@ function tuiSelect(opts) {
33720
33817
  cursor = first >= 0 ? first : 0;
33721
33818
  }
33722
33819
  const selectChrome = 3;
33723
- const contentArea = process.stdout.rows ?? 24;
33820
+ const contentArea = opts.availableRows ?? process.stdout.rows ?? 24;
33724
33821
  const maxVisible = opts.maxVisible ?? Math.max(3, contentArea - selectChrome);
33725
33822
  let scrollOffset = 0;
33726
33823
  let lastRenderedLines = 0;
@@ -38775,6 +38872,24 @@ async function handleEndpoint(arg, ctx, local = false) {
38775
38872
 
38776
38873
  `);
38777
38874
  }
38875
+ if (ctx.setContextWindowSize) {
38876
+ const activeModel = ctx.config.model;
38877
+ const modelEntry = findModel(newModels, activeModel);
38878
+ if (modelEntry?.contextLength) {
38879
+ ctx.setContextWindowSize(modelEntry.contextLength);
38880
+ } else {
38881
+ const ctxSize = await queryContextSize(normalizedUrl, activeModel, apiKey);
38882
+ if (ctxSize) {
38883
+ ctx.setContextWindowSize(ctxSize);
38884
+ } else {
38885
+ ctx.setContextWindowSize(0);
38886
+ }
38887
+ }
38888
+ }
38889
+ if (ctx.setCapabilities) {
38890
+ const caps = await queryModelCapabilities(normalizedUrl, ctx.config.model);
38891
+ ctx.setCapabilities(caps);
38892
+ }
38778
38893
  }
38779
38894
  } catch {
38780
38895
  renderWarning(`Could not verify model availability on ${provider.label}. If inference fails, use /model to switch.`);
@@ -46337,7 +46452,7 @@ var init_braille_spinner = __esm({
46337
46452
  });
46338
46453
 
46339
46454
  // packages/cli/dist/tui/system-metrics.js
46340
- import { loadavg as loadavg2, cpus as cpus2, totalmem as totalmem2, freemem as freemem2, platform as platform4 } from "node:os";
46455
+ import { loadavg as loadavg2, cpus as cpus2, totalmem as totalmem3, freemem as freemem2, platform as platform4 } from "node:os";
46341
46456
  import { exec as exec3 } from "node:child_process";
46342
46457
  import { readFile as readFile16 } from "node:fs/promises";
46343
46458
  function formatRate(bytesPerSec) {
@@ -46471,7 +46586,7 @@ function collectCpuRam() {
46471
46586
  const [l1] = loadavg2();
46472
46587
  const cores = cpus2().length;
46473
46588
  const cpuModel = cpus2()[0]?.model ?? "";
46474
- const totalMem = totalmem2();
46589
+ const totalMem = totalmem3();
46475
46590
  const usedMem = totalMem - freemem2();
46476
46591
  return {
46477
46592
  cpuUtil: Math.min(100, Math.round(l1 / cores * 100)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.104.0",
3
+ "version": "0.104.3",
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",