open-agents-ai 0.103.6 → 0.103.7

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 +184 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28448,7 +28448,77 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
28448
28448
  }
28449
28449
  return result;
28450
28450
  }
28451
- var OA_DIR, SUBDIRS, CONTEXT_FILES, PENDING_TASK_FILE, CONTEXT_SAVE_FILE, MAX_CONTEXT_ENTRIES, SKIP_DIRS;
28451
+ function loadUsageFile(filePath) {
28452
+ try {
28453
+ if (existsSync28(filePath)) {
28454
+ return JSON.parse(readFileSync20(filePath, "utf-8"));
28455
+ }
28456
+ } catch {
28457
+ }
28458
+ return { records: [] };
28459
+ }
28460
+ function saveUsageFile(filePath, data) {
28461
+ const dir = join38(filePath, "..");
28462
+ mkdirSync9(dir, { recursive: true });
28463
+ writeFileSync9(filePath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
28464
+ }
28465
+ function recordUsage(kind, value, opts) {
28466
+ const now = (/* @__PURE__ */ new Date()).toISOString();
28467
+ const update = (filePath) => {
28468
+ const data = loadUsageFile(filePath);
28469
+ const existing = data.records.find((r) => r.kind === kind && r.value === value);
28470
+ if (existing) {
28471
+ existing.useCount++;
28472
+ existing.lastUsed = now;
28473
+ if (opts?.meta)
28474
+ existing.meta = { ...existing.meta, ...opts.meta };
28475
+ } else {
28476
+ data.records.push({
28477
+ value,
28478
+ kind,
28479
+ useCount: 1,
28480
+ firstUsed: now,
28481
+ lastUsed: now,
28482
+ meta: opts?.meta
28483
+ });
28484
+ }
28485
+ if (data.records.length > MAX_HISTORY_RECORDS) {
28486
+ data.records.sort((a, b) => new Date(b.lastUsed).getTime() - new Date(a.lastUsed).getTime());
28487
+ data.records = data.records.slice(0, MAX_HISTORY_RECORDS);
28488
+ }
28489
+ saveUsageFile(filePath, data);
28490
+ };
28491
+ update(join38(homedir9(), ".open-agents", USAGE_HISTORY_FILE));
28492
+ if (opts?.repoRoot) {
28493
+ update(join38(opts.repoRoot, OA_DIR, USAGE_HISTORY_FILE));
28494
+ }
28495
+ }
28496
+ function loadUsageHistory(kind, repoRoot) {
28497
+ const globalPath = join38(homedir9(), ".open-agents", USAGE_HISTORY_FILE);
28498
+ const globalData = loadUsageFile(globalPath);
28499
+ const localData = repoRoot ? loadUsageFile(join38(repoRoot, OA_DIR, USAGE_HISTORY_FILE)) : { records: [] };
28500
+ const map = /* @__PURE__ */ new Map();
28501
+ for (const r of globalData.records) {
28502
+ if (r.kind !== kind)
28503
+ continue;
28504
+ map.set(r.value, { ...r, localUses: 0 });
28505
+ }
28506
+ for (const r of localData.records) {
28507
+ if (r.kind !== kind)
28508
+ continue;
28509
+ const existing = map.get(r.value);
28510
+ if (existing) {
28511
+ existing.localUses = r.useCount;
28512
+ if (new Date(r.lastUsed) > new Date(existing.lastUsed)) {
28513
+ existing.lastUsed = r.lastUsed;
28514
+ }
28515
+ } else {
28516
+ map.set(r.value, { ...r, localUses: r.useCount });
28517
+ }
28518
+ }
28519
+ return Array.from(map.values()).sort((a, b) => new Date(b.lastUsed).getTime() - new Date(a.lastUsed).getTime());
28520
+ }
28521
+ var OA_DIR, SUBDIRS, CONTEXT_FILES, PENDING_TASK_FILE, CONTEXT_SAVE_FILE, MAX_CONTEXT_ENTRIES, SKIP_DIRS, USAGE_HISTORY_FILE, MAX_HISTORY_RECORDS;
28452
28522
  var init_oa_directory = __esm({
28453
28523
  "packages/cli/dist/tui/oa-directory.js"() {
28454
28524
  "use strict";
@@ -28486,6 +28556,8 @@ var init_oa_directory = __esm({
28486
28556
  ".idea",
28487
28557
  ".vscode"
28488
28558
  ]);
28559
+ USAGE_HISTORY_FILE = "usage-history.json";
28560
+ MAX_HISTORY_RECORDS = 50;
28489
28561
  }
28490
28562
  });
28491
28563
 
@@ -30024,12 +30096,22 @@ function tuiSelect(opts) {
30024
30096
  const { items, title, rl } = opts;
30025
30097
  const renderRow = opts.renderRow ?? defaultRenderRow;
30026
30098
  const activeKey = opts.activeKey ?? null;
30099
+ const skipSet = new Set(opts.skipKeys ?? []);
30027
30100
  if (items.length === 0) {
30028
30101
  return Promise.resolve({ confirmed: false, key: null, index: -1 });
30029
30102
  }
30103
+ const isSkippable = (idx) => skipSet.has(items[idx].key);
30104
+ const findSelectable = (from, dir) => {
30105
+ let idx = from;
30106
+ while (idx >= 0 && idx < items.length && isSkippable(idx)) {
30107
+ idx += dir;
30108
+ }
30109
+ return idx >= 0 && idx < items.length ? idx : from;
30110
+ };
30030
30111
  let cursor = activeKey ? items.findIndex((i) => i.key === activeKey) : 0;
30031
30112
  if (cursor < 0)
30032
30113
  cursor = 0;
30114
+ cursor = findSelectable(cursor, 1);
30033
30115
  const termRows = process.stdout.rows ?? 24;
30034
30116
  const maxVisible = opts.maxVisible ?? Math.max(5, termRows - 6);
30035
30117
  let scrollOffset = Math.max(0, cursor - Math.floor(maxVisible / 2));
@@ -30037,8 +30119,16 @@ function tuiSelect(opts) {
30037
30119
  return new Promise((resolve31) => {
30038
30120
  const stdin = process.stdin;
30039
30121
  const hadRawMode = stdin.isRaw;
30122
+ const savedRlListeners = [];
30040
30123
  if (rl) {
30041
30124
  rl.pause();
30125
+ for (const event of ["keypress", "data"]) {
30126
+ const listeners = stdin.listeners(event);
30127
+ for (const fn of listeners) {
30128
+ savedRlListeners.push({ event, fn });
30129
+ stdin.removeListener(event, fn);
30130
+ }
30131
+ }
30042
30132
  }
30043
30133
  if (typeof stdin.setRawMode === "function") {
30044
30134
  stdin.setRawMode(true);
@@ -30076,6 +30166,10 @@ function tuiSelect(opts) {
30076
30166
  const visibleEnd = Math.min(items.length, scrollOffset + maxVisible);
30077
30167
  for (let i = scrollOffset; i < visibleEnd; i++) {
30078
30168
  const item = items[i];
30169
+ if (isSkippable(i)) {
30170
+ lines.push(` ${item.label}`);
30171
+ continue;
30172
+ }
30079
30173
  const focused = i === cursor;
30080
30174
  const isActive = item.key === activeKey;
30081
30175
  lines.push(renderRow(item, focused, isActive));
@@ -30104,6 +30198,9 @@ function tuiSelect(opts) {
30104
30198
  if (typeof stdin.setRawMode === "function") {
30105
30199
  stdin.setRawMode(hadRawMode ?? false);
30106
30200
  }
30201
+ for (const { event, fn } of savedRlListeners) {
30202
+ stdin.on(event, fn);
30203
+ }
30107
30204
  if (rl) {
30108
30205
  rl.resume();
30109
30206
  rl.prompt(false);
@@ -30112,20 +30209,28 @@ function tuiSelect(opts) {
30112
30209
  function onData(chunk) {
30113
30210
  const seq = chunk.toString("utf8");
30114
30211
  if (seq === "\x1B[A" || seq === "k") {
30115
- cursor = Math.max(0, cursor - 1);
30116
- render();
30212
+ const next = cursor - 1;
30213
+ if (next >= 0) {
30214
+ cursor = findSelectable(next, -1);
30215
+ render();
30216
+ }
30117
30217
  } else if (seq === "\x1B[B" || seq === "j") {
30118
- cursor = Math.min(items.length - 1, cursor + 1);
30119
- render();
30218
+ const next = cursor + 1;
30219
+ if (next < items.length) {
30220
+ cursor = findSelectable(next, 1);
30221
+ render();
30222
+ }
30120
30223
  } else if (seq === "\x1B[H" || seq === "g") {
30121
- cursor = 0;
30224
+ cursor = findSelectable(0, 1);
30122
30225
  render();
30123
30226
  } else if (seq === "\x1B[F" || seq === "G") {
30124
- cursor = items.length - 1;
30227
+ cursor = findSelectable(items.length - 1, -1);
30125
30228
  render();
30126
30229
  } else if (seq === "\r" || seq === "\n") {
30127
- cleanup();
30128
- resolve31({ confirmed: true, key: items[cursor].key, index: cursor });
30230
+ if (!isSkippable(cursor)) {
30231
+ cleanup();
30232
+ resolve31({ confirmed: true, key: items[cursor].key, index: cursor });
30233
+ }
30129
30234
  } else if (seq === "\x1B" || seq === "\x1B\x1B" || seq === "q") {
30130
30235
  cleanup();
30131
30236
  resolve31({ confirmed: false, key: null, index: cursor });
@@ -31274,16 +31379,39 @@ async function showModelPicker(ctx, local = false) {
31274
31379
  renderWarning("No models found.");
31275
31380
  return;
31276
31381
  }
31277
- const items = models.map((m) => ({
31278
- key: m.name,
31279
- label: m.name,
31280
- detail: [m.parameterSize, m.size, m.modified].filter(Boolean).join(" ")
31281
- }));
31382
+ const items = [];
31383
+ const history = loadUsageHistory("model", ctx.repoRoot);
31384
+ const liveModelNames = new Set(models.map((m) => m.name));
31385
+ if (history.length > 0) {
31386
+ items.push({ key: "__header_recent__", label: c2.dim("\u2500\u2500 Recent \u2500\u2500"), detail: "" });
31387
+ for (const h of history.slice(0, 8)) {
31388
+ const uses = h.localUses > 0 ? `${h.useCount} uses (${h.localUses} local)` : `${h.useCount} uses`;
31389
+ const available = liveModelNames.has(h.value) ? "" : c2.yellow(" [offline]");
31390
+ items.push({
31391
+ key: h.value,
31392
+ label: h.value,
31393
+ detail: `${uses}${available}`
31394
+ });
31395
+ }
31396
+ items.push({ key: "__header_available__", label: c2.dim("\u2500\u2500 Available \u2500\u2500"), detail: "" });
31397
+ }
31398
+ const historyKeys = new Set(history.map((h) => h.value));
31399
+ for (const m of models) {
31400
+ if (history.length > 0 && historyKeys.has(m.name))
31401
+ continue;
31402
+ items.push({
31403
+ key: m.name,
31404
+ label: m.name,
31405
+ detail: [m.parameterSize, m.size, m.modified].filter(Boolean).join(" ")
31406
+ });
31407
+ }
31282
31408
  const result = await tuiSelect({
31283
31409
  items,
31284
31410
  activeKey: ctx.config.model,
31285
31411
  title: "Select Model",
31286
- rl: ctx.rl
31412
+ rl: ctx.rl,
31413
+ // Skip header rows
31414
+ skipKeys: ["__header_recent__", "__header_available__"]
31287
31415
  });
31288
31416
  if (!result.confirmed || !result.key) {
31289
31417
  renderInfo("Model selection cancelled.");
@@ -31296,6 +31424,30 @@ async function showModelPicker(ctx, local = false) {
31296
31424
  }
31297
31425
  async function handleEndpoint(arg, ctx, local = false) {
31298
31426
  if (!arg) {
31427
+ const history = loadUsageHistory("endpoint", ctx.repoRoot);
31428
+ if (history.length > 0) {
31429
+ const items = history.map((h) => {
31430
+ const provider2 = h.meta?.provider ?? detectProvider(h.value).label;
31431
+ const uses = h.localUses > 0 ? `${h.useCount} uses (${h.localUses} local)` : `${h.useCount} uses`;
31432
+ return {
31433
+ key: h.value,
31434
+ label: h.value,
31435
+ detail: `${provider2} ${uses}`
31436
+ };
31437
+ });
31438
+ const result = await tuiSelect({
31439
+ items,
31440
+ activeKey: ctx.config.backendUrl,
31441
+ title: "Select Endpoint",
31442
+ rl: ctx.rl
31443
+ });
31444
+ if (result.confirmed && result.key) {
31445
+ await handleEndpoint(result.key, ctx, local);
31446
+ return;
31447
+ }
31448
+ renderInfo("Endpoint selection cancelled.");
31449
+ return;
31450
+ }
31299
31451
  const currentProvider = detectProvider(ctx.config.backendUrl);
31300
31452
  process.stdout.write(`
31301
31453
  ${c2.bold("Current endpoint:")}
@@ -31399,6 +31551,15 @@ async function handleEndpoint(arg, ctx, local = false) {
31399
31551
  }
31400
31552
  ctx.saveSettings(endpointSettings);
31401
31553
  }
31554
+ recordUsage("endpoint", normalizedUrl, {
31555
+ repoRoot: local ? ctx.repoRoot : void 0,
31556
+ local,
31557
+ meta: {
31558
+ provider: provider.label,
31559
+ backendType,
31560
+ ...apiKey ? { authHint: apiKey.slice(0, 4) + "..." } : {}
31561
+ }
31562
+ });
31402
31563
  process.stdout.write(`
31403
31564
  ${c2.green("\u2714")} Endpoint updated and saved${local ? " (project-local)" : ""}:
31404
31565
  `);
@@ -31715,6 +31876,14 @@ async function switchModel(query, ctx, local = false) {
31715
31876
  if (local) {
31716
31877
  renderInfo("Saved as project-local override.");
31717
31878
  }
31879
+ recordUsage("model", finalModel, {
31880
+ repoRoot: local ? ctx.repoRoot : void 0,
31881
+ local,
31882
+ meta: {
31883
+ endpoint: ctx.config.backendUrl,
31884
+ parameterSize: match.parameterSize ?? ""
31885
+ }
31886
+ });
31718
31887
  if (ctx.setContextWindowSize) {
31719
31888
  const ctxSize = await queryContextSize(ctx.config.backendUrl, finalModel, ctx.config.apiKey);
31720
31889
  if (ctxSize) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.6",
3
+ "version": "0.103.7",
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",