open-agents-ai 0.103.6 → 0.103.8

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 +349 -48
  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
 
@@ -30014,46 +30086,102 @@ function ansi3(code, text) {
30014
30086
  function fg2562(code, text) {
30015
30087
  return isTTY3 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
30016
30088
  }
30089
+ function stripAnsi(s) {
30090
+ return s.replace(/\x1B\[[0-9;]*m/g, "");
30091
+ }
30017
30092
  function defaultRenderRow(item, focused, isActive) {
30018
30093
  const marker = isActive ? selectColors.green("\u25CF") : focused ? selectColors.orange("\u25CF") : selectColors.dim("\u25CB");
30019
30094
  const label = focused ? selectColors.orange(selectColors.bold(item.label)) : isActive ? selectColors.green(item.label) : item.label;
30020
30095
  const detail = item.detail ? ` ${selectColors.dim(item.detail)}` : "";
30021
30096
  return ` ${marker} ${label}${detail}`;
30022
30097
  }
30098
+ function matchRow(item, focused, isActive) {
30099
+ if (focused || isActive) {
30100
+ return defaultRenderRow(item, focused, isActive);
30101
+ }
30102
+ const marker = selectColors.matchLight("\u25CB");
30103
+ const label = selectColors.matchLight(stripAnsi(item.label));
30104
+ const detail = item.detail ? ` ${selectColors.dim(stripAnsi(item.detail))}` : "";
30105
+ return ` ${marker} ${label}${detail}`;
30106
+ }
30023
30107
  function tuiSelect(opts) {
30024
30108
  const { items, title, rl } = opts;
30025
30109
  const renderRow = opts.renderRow ?? defaultRenderRow;
30026
30110
  const activeKey = opts.activeKey ?? null;
30111
+ const skipSet = new Set(opts.skipKeys ?? []);
30027
30112
  if (items.length === 0) {
30028
30113
  return Promise.resolve({ confirmed: false, key: null, index: -1 });
30029
30114
  }
30030
- let cursor = activeKey ? items.findIndex((i) => i.key === activeKey) : 0;
30031
- if (cursor < 0)
30032
- cursor = 0;
30115
+ const isSkippable = (idx) => skipSet.has(items[idx].key);
30116
+ let filter = "";
30117
+ let matchSet = /* @__PURE__ */ new Set();
30118
+ function updateFilter() {
30119
+ if (!filter) {
30120
+ matchSet = new Set(items.map((_, i) => i));
30121
+ } else {
30122
+ const lower = filter.toLowerCase();
30123
+ matchSet = /* @__PURE__ */ new Set();
30124
+ for (let i = 0; i < items.length; i++) {
30125
+ if (isSkippable(i))
30126
+ continue;
30127
+ const plain = stripAnsi(items[i].label).toLowerCase();
30128
+ const detailPlain = items[i].detail ? stripAnsi(items[i].detail).toLowerCase() : "";
30129
+ if (plain.includes(lower) || detailPlain.includes(lower)) {
30130
+ matchSet.add(i);
30131
+ }
30132
+ }
30133
+ }
30134
+ }
30135
+ updateFilter();
30136
+ const findSelectable = (from, dir) => {
30137
+ let idx = from;
30138
+ while (idx >= 0 && idx < items.length) {
30139
+ if (!isSkippable(idx) && matchSet.has(idx))
30140
+ return idx;
30141
+ idx += dir;
30142
+ }
30143
+ return -1;
30144
+ };
30145
+ let cursor = activeKey ? items.findIndex((i) => i.key === activeKey) : -1;
30146
+ if (cursor < 0 || isSkippable(cursor)) {
30147
+ const first = findSelectable(0, 1);
30148
+ cursor = first >= 0 ? first : 0;
30149
+ }
30033
30150
  const termRows = process.stdout.rows ?? 24;
30034
- const maxVisible = opts.maxVisible ?? Math.max(5, termRows - 6);
30035
- let scrollOffset = Math.max(0, cursor - Math.floor(maxVisible / 2));
30151
+ const overhead = 6;
30152
+ const maxVisible = opts.maxVisible ?? Math.max(3, termRows - overhead);
30153
+ let scrollOffset = 0;
30036
30154
  let lastRenderedLines = 0;
30037
30155
  return new Promise((resolve31) => {
30038
30156
  const stdin = process.stdin;
30039
30157
  const hadRawMode = stdin.isRaw;
30158
+ const savedRlListeners = [];
30040
30159
  if (rl) {
30041
30160
  rl.pause();
30161
+ for (const event of ["keypress", "data"]) {
30162
+ const listeners = stdin.listeners(event);
30163
+ for (const fn of listeners) {
30164
+ savedRlListeners.push({ event, fn });
30165
+ stdin.removeListener(event, fn);
30166
+ }
30167
+ }
30042
30168
  }
30043
30169
  if (typeof stdin.setRawMode === "function") {
30044
30170
  stdin.setRawMode(true);
30045
30171
  }
30046
30172
  stdin.resume();
30047
30173
  process.stdout.write("\x1B[?25l");
30048
- function clampScroll() {
30049
- if (cursor < scrollOffset) {
30050
- scrollOffset = cursor;
30051
- } else if (cursor >= scrollOffset + maxVisible) {
30052
- scrollOffset = cursor - maxVisible + 1;
30053
- }
30054
- scrollOffset = Math.max(0, Math.min(items.length - maxVisible, scrollOffset));
30055
- if (scrollOffset < 0)
30056
- scrollOffset = 0;
30174
+ function clampScroll(displayList) {
30175
+ const cursorPos = displayList.indexOf(cursor);
30176
+ if (cursorPos < 0)
30177
+ return;
30178
+ if (cursorPos < scrollOffset) {
30179
+ scrollOffset = cursorPos;
30180
+ } else if (cursorPos >= scrollOffset + maxVisible) {
30181
+ scrollOffset = cursorPos - maxVisible + 1;
30182
+ }
30183
+ const maxOffset = Math.max(0, displayList.length - maxVisible);
30184
+ scrollOffset = Math.max(0, Math.min(maxOffset, scrollOffset));
30057
30185
  }
30058
30186
  function render() {
30059
30187
  if (lastRenderedLines > 0) {
@@ -30063,29 +30191,66 @@ function tuiSelect(opts) {
30063
30191
  }
30064
30192
  process.stdout.write(`\x1B[${lastRenderedLines}A`);
30065
30193
  }
30066
- clampScroll();
30067
30194
  const lines = [];
30068
30195
  if (title) {
30069
30196
  lines.push(`
30070
30197
  ${selectColors.bold(title)}`);
30071
- lines.push("");
30072
30198
  }
30073
- if (scrollOffset > 0) {
30074
- lines.push(` ${selectColors.dim(` \u25B2 ${scrollOffset} more`)}`);
30199
+ if (filter) {
30200
+ const count = matchSet.size;
30201
+ lines.push(` ${selectColors.cyan("/")} ${selectColors.bold(filter)} ${selectColors.dim(`(${count} match${count !== 1 ? "es" : ""})`)}`);
30202
+ } else {
30203
+ lines.push(` ${selectColors.dim("Type to filter...")}`);
30075
30204
  }
30076
- const visibleEnd = Math.min(items.length, scrollOffset + maxVisible);
30077
- for (let i = scrollOffset; i < visibleEnd; i++) {
30078
- const item = items[i];
30079
- const focused = i === cursor;
30205
+ lines.push("");
30206
+ let displayList;
30207
+ if (filter) {
30208
+ displayList = [];
30209
+ for (let i = 0; i < items.length; i++) {
30210
+ if (matchSet.has(i) || isSkippable(i)) {
30211
+ displayList.push(i);
30212
+ }
30213
+ }
30214
+ displayList = displayList.filter((idx, pos) => {
30215
+ if (!isSkippable(idx))
30216
+ return true;
30217
+ for (let j = pos + 1; j < displayList.length; j++) {
30218
+ if (!isSkippable(displayList[j]))
30219
+ return true;
30220
+ break;
30221
+ }
30222
+ return false;
30223
+ });
30224
+ } else {
30225
+ displayList = items.map((_, i) => i);
30226
+ }
30227
+ clampScroll(displayList);
30228
+ const visibleStart = scrollOffset;
30229
+ const visibleEnd = Math.min(displayList.length, scrollOffset + maxVisible);
30230
+ if (visibleStart > 0) {
30231
+ lines.push(` ${selectColors.dim(` \u25B2 ${visibleStart} more`)}`);
30232
+ }
30233
+ for (let vi = visibleStart; vi < visibleEnd; vi++) {
30234
+ const idx = displayList[vi];
30235
+ const item = items[idx];
30236
+ if (isSkippable(idx)) {
30237
+ lines.push(` ${item.label}`);
30238
+ continue;
30239
+ }
30240
+ const focused = idx === cursor;
30080
30241
  const isActive = item.key === activeKey;
30081
- lines.push(renderRow(item, focused, isActive));
30242
+ if (filter) {
30243
+ lines.push(matchRow(item, focused, isActive));
30244
+ } else {
30245
+ lines.push(renderRow(item, focused, isActive));
30246
+ }
30082
30247
  }
30083
- const remaining = items.length - visibleEnd;
30248
+ const remaining = displayList.length - visibleEnd;
30084
30249
  if (remaining > 0) {
30085
30250
  lines.push(` ${selectColors.dim(` \u25BC ${remaining} more`)}`);
30086
30251
  }
30087
30252
  lines.push("");
30088
- lines.push(` ${selectColors.dim("\u2191/\u2193 navigate Enter select Esc cancel")}`);
30253
+ lines.push(` ${selectColors.dim("\u2191/\u2193 navigate Enter select Esc " + (filter ? "clear filter" : "cancel") + " Type to filter")}`);
30089
30254
  lines.push("");
30090
30255
  const output = lines.join("\n");
30091
30256
  process.stdout.write(output);
@@ -30093,6 +30258,7 @@ function tuiSelect(opts) {
30093
30258
  }
30094
30259
  function cleanup() {
30095
30260
  stdin.removeListener("data", onData);
30261
+ process.stdout.removeListener("resize", onResize);
30096
30262
  process.stdout.write("\x1B[?25h");
30097
30263
  if (lastRenderedLines > 0) {
30098
30264
  process.stdout.write(`\x1B[${lastRenderedLines}A`);
@@ -30104,6 +30270,9 @@ function tuiSelect(opts) {
30104
30270
  if (typeof stdin.setRawMode === "function") {
30105
30271
  stdin.setRawMode(hadRawMode ?? false);
30106
30272
  }
30273
+ for (const { event, fn } of savedRlListeners) {
30274
+ stdin.on(event, fn);
30275
+ }
30107
30276
  if (rl) {
30108
30277
  rl.resume();
30109
30278
  rl.prompt(false);
@@ -30111,29 +30280,93 @@ function tuiSelect(opts) {
30111
30280
  }
30112
30281
  function onData(chunk) {
30113
30282
  const seq = chunk.toString("utf8");
30114
- if (seq === "\x1B[A" || seq === "k") {
30115
- cursor = Math.max(0, cursor - 1);
30116
- render();
30117
- } else if (seq === "\x1B[B" || seq === "j") {
30118
- cursor = Math.min(items.length - 1, cursor + 1);
30119
- render();
30120
- } else if (seq === "\x1B[H" || seq === "g") {
30121
- cursor = 0;
30283
+ if (seq === "\x1B[A") {
30284
+ const next = findSelectable(cursor - 1, -1);
30285
+ if (next >= 0 && next !== cursor) {
30286
+ cursor = next;
30287
+ render();
30288
+ }
30289
+ } else if (seq === "\x1B[B") {
30290
+ const next = findSelectable(cursor + 1, 1);
30291
+ if (next >= 0 && next !== cursor) {
30292
+ cursor = next;
30293
+ render();
30294
+ }
30295
+ } else if (seq === "\x1B[5~") {
30296
+ for (let i = 0; i < maxVisible; i++) {
30297
+ const next = findSelectable(cursor - 1, -1);
30298
+ if (next < 0 || next === cursor)
30299
+ break;
30300
+ cursor = next;
30301
+ }
30122
30302
  render();
30123
- } else if (seq === "\x1B[F" || seq === "G") {
30124
- cursor = items.length - 1;
30303
+ } else if (seq === "\x1B[6~") {
30304
+ for (let i = 0; i < maxVisible; i++) {
30305
+ const next = findSelectable(cursor + 1, 1);
30306
+ if (next < 0 || next === cursor)
30307
+ break;
30308
+ cursor = next;
30309
+ }
30125
30310
  render();
30311
+ } else if (seq === "\x1B[H") {
30312
+ const first = findSelectable(0, 1);
30313
+ if (first >= 0) {
30314
+ cursor = first;
30315
+ render();
30316
+ }
30317
+ } else if (seq === "\x1B[F") {
30318
+ const last = findSelectable(items.length - 1, -1);
30319
+ if (last >= 0) {
30320
+ cursor = last;
30321
+ render();
30322
+ }
30126
30323
  } else if (seq === "\r" || seq === "\n") {
30127
- cleanup();
30128
- resolve31({ confirmed: true, key: items[cursor].key, index: cursor });
30129
- } else if (seq === "\x1B" || seq === "\x1B\x1B" || seq === "q") {
30130
- cleanup();
30131
- resolve31({ confirmed: false, key: null, index: cursor });
30324
+ if (!isSkippable(cursor) && matchSet.has(cursor)) {
30325
+ cleanup();
30326
+ resolve31({ confirmed: true, key: items[cursor].key, index: cursor });
30327
+ }
30328
+ } else if (seq === "\x1B" || seq === "\x1B\x1B") {
30329
+ if (filter) {
30330
+ filter = "";
30331
+ updateFilter();
30332
+ const valid = findSelectable(cursor, 1);
30333
+ if (valid >= 0)
30334
+ cursor = valid;
30335
+ scrollOffset = 0;
30336
+ render();
30337
+ } else {
30338
+ cleanup();
30339
+ resolve31({ confirmed: false, key: null, index: cursor });
30340
+ }
30132
30341
  } else if (seq === "") {
30133
30342
  cleanup();
30134
30343
  resolve31({ confirmed: false, key: null, index: cursor });
30344
+ } else if (seq === "\x7F" || seq === "\b") {
30345
+ if (filter.length > 0) {
30346
+ filter = filter.slice(0, -1);
30347
+ updateFilter();
30348
+ if (!matchSet.has(cursor) || isSkippable(cursor)) {
30349
+ const next = findSelectable(0, 1);
30350
+ if (next >= 0)
30351
+ cursor = next;
30352
+ }
30353
+ scrollOffset = 0;
30354
+ render();
30355
+ }
30356
+ } else if (seq.length === 1 && seq.charCodeAt(0) >= 32 && seq.charCodeAt(0) < 127) {
30357
+ filter += seq;
30358
+ updateFilter();
30359
+ if (matchSet.size > 0) {
30360
+ const first = findSelectable(0, 1);
30361
+ if (first >= 0)
30362
+ cursor = first;
30363
+ }
30364
+ scrollOffset = 0;
30365
+ render();
30135
30366
  }
30136
30367
  }
30368
+ const onResize = () => render();
30369
+ process.stdout.on("resize", onResize);
30137
30370
  stdin.on("data", onData);
30138
30371
  render();
30139
30372
  });
@@ -30148,7 +30381,11 @@ var init_tui_select = __esm({
30148
30381
  green: (t) => ansi3("32", t),
30149
30382
  dim: (t) => ansi3("2", t),
30150
30383
  bold: (t) => ansi3("1", t),
30151
- cyan: (t) => ansi3("36", t)
30384
+ cyan: (t) => ansi3("36", t),
30385
+ /** Lighter grey for filter matches (252 = near-white) */
30386
+ matchLight: (t) => fg2562(252, t),
30387
+ /** Dark grey for non-matching items (240 = dark grey) */
30388
+ matchDark: (t) => fg2562(240, t)
30152
30389
  };
30153
30390
  }
30154
30391
  });
@@ -31274,16 +31511,39 @@ async function showModelPicker(ctx, local = false) {
31274
31511
  renderWarning("No models found.");
31275
31512
  return;
31276
31513
  }
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
- }));
31514
+ const items = [];
31515
+ const history = loadUsageHistory("model", ctx.repoRoot);
31516
+ const liveModelNames = new Set(models.map((m) => m.name));
31517
+ if (history.length > 0) {
31518
+ items.push({ key: "__header_recent__", label: c2.dim("\u2500\u2500 Recent \u2500\u2500"), detail: "" });
31519
+ for (const h of history.slice(0, 8)) {
31520
+ const uses = h.localUses > 0 ? `${h.useCount} uses (${h.localUses} local)` : `${h.useCount} uses`;
31521
+ const available = liveModelNames.has(h.value) ? "" : c2.yellow(" [offline]");
31522
+ items.push({
31523
+ key: h.value,
31524
+ label: h.value,
31525
+ detail: `${uses}${available}`
31526
+ });
31527
+ }
31528
+ items.push({ key: "__header_available__", label: c2.dim("\u2500\u2500 Available \u2500\u2500"), detail: "" });
31529
+ }
31530
+ const historyKeys = new Set(history.map((h) => h.value));
31531
+ for (const m of models) {
31532
+ if (history.length > 0 && historyKeys.has(m.name))
31533
+ continue;
31534
+ items.push({
31535
+ key: m.name,
31536
+ label: m.name,
31537
+ detail: [m.parameterSize, m.size, m.modified].filter(Boolean).join(" ")
31538
+ });
31539
+ }
31282
31540
  const result = await tuiSelect({
31283
31541
  items,
31284
31542
  activeKey: ctx.config.model,
31285
31543
  title: "Select Model",
31286
- rl: ctx.rl
31544
+ rl: ctx.rl,
31545
+ // Skip header rows
31546
+ skipKeys: ["__header_recent__", "__header_available__"]
31287
31547
  });
31288
31548
  if (!result.confirmed || !result.key) {
31289
31549
  renderInfo("Model selection cancelled.");
@@ -31296,6 +31556,30 @@ async function showModelPicker(ctx, local = false) {
31296
31556
  }
31297
31557
  async function handleEndpoint(arg, ctx, local = false) {
31298
31558
  if (!arg) {
31559
+ const history = loadUsageHistory("endpoint", ctx.repoRoot);
31560
+ if (history.length > 0) {
31561
+ const items = history.map((h) => {
31562
+ const provider2 = h.meta?.provider ?? detectProvider(h.value).label;
31563
+ const uses = h.localUses > 0 ? `${h.useCount} uses (${h.localUses} local)` : `${h.useCount} uses`;
31564
+ return {
31565
+ key: h.value,
31566
+ label: h.value,
31567
+ detail: `${provider2} ${uses}`
31568
+ };
31569
+ });
31570
+ const result = await tuiSelect({
31571
+ items,
31572
+ activeKey: ctx.config.backendUrl,
31573
+ title: "Select Endpoint",
31574
+ rl: ctx.rl
31575
+ });
31576
+ if (result.confirmed && result.key) {
31577
+ await handleEndpoint(result.key, ctx, local);
31578
+ return;
31579
+ }
31580
+ renderInfo("Endpoint selection cancelled.");
31581
+ return;
31582
+ }
31299
31583
  const currentProvider = detectProvider(ctx.config.backendUrl);
31300
31584
  process.stdout.write(`
31301
31585
  ${c2.bold("Current endpoint:")}
@@ -31399,6 +31683,15 @@ async function handleEndpoint(arg, ctx, local = false) {
31399
31683
  }
31400
31684
  ctx.saveSettings(endpointSettings);
31401
31685
  }
31686
+ recordUsage("endpoint", normalizedUrl, {
31687
+ repoRoot: local ? ctx.repoRoot : void 0,
31688
+ local,
31689
+ meta: {
31690
+ provider: provider.label,
31691
+ backendType,
31692
+ ...apiKey ? { authHint: apiKey.slice(0, 4) + "..." } : {}
31693
+ }
31694
+ });
31402
31695
  process.stdout.write(`
31403
31696
  ${c2.green("\u2714")} Endpoint updated and saved${local ? " (project-local)" : ""}:
31404
31697
  `);
@@ -31715,6 +32008,14 @@ async function switchModel(query, ctx, local = false) {
31715
32008
  if (local) {
31716
32009
  renderInfo("Saved as project-local override.");
31717
32010
  }
32011
+ recordUsage("model", finalModel, {
32012
+ repoRoot: local ? ctx.repoRoot : void 0,
32013
+ local,
32014
+ meta: {
32015
+ endpoint: ctx.config.backendUrl,
32016
+ parameterSize: match.parameterSize ?? ""
32017
+ }
32018
+ });
31718
32019
  if (ctx.setContextWindowSize) {
31719
32020
  const ctxSize = await queryContextSize(ctx.config.backendUrl, finalModel, ctx.config.apiKey);
31720
32021
  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.8",
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",