micro-models-agent 0.24.1 → 0.24.2

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/main.js +111 -28
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2306,6 +2306,7 @@ var init_en = __esm(() => {
2306
2306
  "tool.memory_error": "Memory error: {error}",
2307
2307
  "tool.screenshot_unavailable": "[Screenshot captured — image not available for text-only model]",
2308
2308
  "tool.timeout": "Tool {name} timed out after {seconds} seconds",
2309
+ "tool.aborted": "Tool {name} was interrupted by user",
2309
2310
  "tool.interactive_disabled": "Interactive tool is disabled in exit-on-complete mode. Proceed without asking the user.",
2310
2311
  "proc.started": `Started background process {id} (PID {pid}).
2311
2312
  Command: {command}`,
@@ -2850,6 +2851,7 @@ var init_ru = __esm(() => {
2850
2851
  "tool.memory_error": "Ошибка памяти: {error}",
2851
2852
  "tool.screenshot_unavailable": "[Скриншот сделан — изображение недоступно для текстовой модели]",
2852
2853
  "tool.timeout": "Инструмент {name} превысил таймаут ({seconds} сек)",
2854
+ "tool.aborted": "Инструмент {name} прерван пользователем",
2853
2855
  "tool.interactive_disabled": "Интерактивный инструмент отключён в режиме exit-on-complete. Продолжай без вопроса пользователю.",
2854
2856
  "proc.started": `Фоновый процесс запущен: {id} (PID {pid}).
2855
2857
  Команда: {command}`,
@@ -4291,6 +4293,11 @@ function getMessageText(content) {
4291
4293
  }
4292
4294
 
4293
4295
  // src/llm/token-counter.ts
4296
+ var exports_token_counter = {};
4297
+ __export(exports_token_counter, {
4298
+ TokenCounter: () => TokenCounter
4299
+ });
4300
+
4294
4301
  class TokenCounter {
4295
4302
  encoder;
4296
4303
  constructor(model = "gpt-4o") {
@@ -4957,7 +4964,7 @@ class ToolExecutor {
4957
4964
  this.ctx = ctx;
4958
4965
  this.pluginManager = pluginManager;
4959
4966
  }
4960
- async execute(call) {
4967
+ async execute(call, signal) {
4961
4968
  const tool = this.registry.get(call.name);
4962
4969
  if (!tool) {
4963
4970
  return {
@@ -4999,10 +5006,25 @@ class ToolExecutor {
4999
5006
  })));
5000
5007
  }, TOOL_EXECUTION_TIMEOUT_MS);
5001
5008
  });
5002
- result = await Promise.race([
5009
+ const racePromises = [
5003
5010
  tool.handler(this.ctx, call.arguments),
5004
5011
  timeoutPromise
5005
- ]);
5012
+ ];
5013
+ if (signal) {
5014
+ const abortPromise = new Promise((_, reject) => {
5015
+ const onAbort = () => {
5016
+ killByCallId(call.id);
5017
+ reject(new Error(t("tool.aborted", { name: call.name })));
5018
+ };
5019
+ if (signal.aborted) {
5020
+ onAbort();
5021
+ } else {
5022
+ signal.addEventListener("abort", onAbort, { once: true });
5023
+ }
5024
+ });
5025
+ racePromises.push(abortPromise);
5026
+ }
5027
+ result = await Promise.race(racePromises);
5006
5028
  }
5007
5029
  result.toolCallId = call.id;
5008
5030
  } catch (e) {
@@ -5033,6 +5055,9 @@ class ToolExecutor {
5033
5055
  setScope(scope) {
5034
5056
  this.ctx.scope = scope;
5035
5057
  }
5058
+ updateProvider(provider) {
5059
+ this.ctx.llmProvider = provider;
5060
+ }
5036
5061
  }
5037
5062
  var TOOL_EXECUTION_TIMEOUT_MS = 60000;
5038
5063
  var init_executor = __esm(() => {
@@ -9040,6 +9065,7 @@ class Agent {
9040
9065
  deps;
9041
9066
  systemPromptAdded = false;
9042
9067
  shutdownRequested = false;
9068
+ abortController = null;
9043
9069
  constructor(deps) {
9044
9070
  this.deps = deps;
9045
9071
  }
@@ -9155,6 +9181,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9155
9181
  baseDir
9156
9182
  } = this.deps;
9157
9183
  const slog = new SessionLogger(sessionManager);
9184
+ this.abortController = new AbortController;
9158
9185
  let iteration = 0;
9159
9186
  let lastText = "";
9160
9187
  let hallucinationRetries = 0;
@@ -9168,6 +9195,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9168
9195
  const MAX_AUDIT_RETRIES = 3;
9169
9196
  while (iteration < config.maxToolIterations && !this.shutdownRequested) {
9170
9197
  iteration++;
9198
+ contextManager.noteIteration();
9171
9199
  pluginManager.runOnBeforeThink({
9172
9200
  iteration,
9173
9201
  logger,
@@ -9200,6 +9228,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9200
9228
  this.emitPhase(iteration, "thinking", onPhase);
9201
9229
  try {
9202
9230
  for await (const chunk of llmProvider.chat(history, allTools)) {
9231
+ if (this.shutdownRequested)
9232
+ break;
9203
9233
  if (chunk.type === "text" && chunk.content) {
9204
9234
  if (emittedReasoning && !textContent) {
9205
9235
  onMeta?.(`
@@ -9303,7 +9333,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9303
9333
  onTool?.({ type: "start", tool: call.name, args: call.arguments });
9304
9334
  slog.logToolCall(call, iteration);
9305
9335
  const tokensBeforeTool = contextManager.getEstimatedTokens();
9306
- const result = await toolExecutor.execute(call);
9336
+ const result = await toolExecutor.execute(call, this.abortController?.signal);
9307
9337
  const duration = Date.now() - startTime;
9308
9338
  if (!result.success)
9309
9339
  anyToolFailed = true;
@@ -9322,15 +9352,6 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9322
9352
  ` + result.diff + `
9323
9353
  `);
9324
9354
  }
9325
- const tokensAfterTool = contextManager.getEstimatedTokens();
9326
- onTool?.({
9327
- type: "end",
9328
- tool: call.name,
9329
- args: call.arguments,
9330
- duration,
9331
- error: !result.success,
9332
- ctxDelta: tokensAfterTool - tokensBeforeTool
9333
- });
9334
9355
  const currentTokens2 = contextManager.getEstimatedTokens();
9335
9356
  const budget3 = contextManager.getBudget();
9336
9357
  const truncatedOutput = this.truncateToolOutput(result.output, budget3, currentTokens2);
@@ -9342,6 +9363,15 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9342
9363
  success: result.success,
9343
9364
  arguments: call.arguments
9344
9365
  });
9366
+ const tokensAfterTool = contextManager.getEstimatedTokens();
9367
+ onTool?.({
9368
+ type: "end",
9369
+ tool: call.name,
9370
+ args: call.arguments,
9371
+ duration,
9372
+ error: !result.success,
9373
+ ctxDelta: tokensAfterTool - tokensBeforeTool
9374
+ });
9345
9375
  summaries.push(`[Tool: ${call.name} (${JSON.stringify(call.arguments)}) → ${truncatedOutput.slice(0, 200)}]`);
9346
9376
  if (config.session.autoSave) {
9347
9377
  slog.logToolResult(call, result, duration, iteration);
@@ -9512,6 +9542,23 @@ ${taskReminder}</system-summary>`
9512
9542
  this.deps.contextManager.clear();
9513
9543
  this.systemPromptAdded = false;
9514
9544
  }
9545
+ async reconfigure(config) {
9546
+ const { OpenAICompatProvider: OpenAICompatProvider2 } = await Promise.resolve().then(() => (init_openai_compat(), exports_openai_compat));
9547
+ const { TokenCounter: TokenCounter2 } = await Promise.resolve().then(() => (init_token_counter(), exports_token_counter));
9548
+ const newProvider = new OpenAICompatProvider2({
9549
+ model: config.model,
9550
+ baseUrl: config.provider.baseUrl,
9551
+ apiKey: config.provider.apiKey,
9552
+ contextWindow: config.contextWindow,
9553
+ retry: config.retry,
9554
+ rateLimits: config.security?.rateLimits
9555
+ });
9556
+ this.deps.llmProvider = newProvider;
9557
+ this.deps.toolExecutor.updateProvider(newProvider);
9558
+ const newTokenCounter = new TokenCounter2(config.model);
9559
+ this.deps.contextManager.resize(config.contextWindow, config.contextBudget, newTokenCounter);
9560
+ this.deps.config = config;
9561
+ }
9515
9562
  setContext(messages) {
9516
9563
  const { contextManager } = this.deps;
9517
9564
  contextManager.clear();
@@ -9530,6 +9577,7 @@ ${taskReminder}</system-summary>`
9530
9577
  }
9531
9578
  shutdown() {
9532
9579
  this.shutdownRequested = true;
9580
+ this.abortController?.abort();
9533
9581
  const { pluginManager, logger, sessionManager, contextManager } = this.deps;
9534
9582
  contextManager.onCompact = null;
9535
9583
  const killed = processRegistry.killAll();
@@ -9635,10 +9683,18 @@ class ContextManager {
9635
9683
  getIterationsSinceCompaction() {
9636
9684
  return this.iterationsSinceCompaction;
9637
9685
  }
9686
+ noteIteration() {
9687
+ this.iterationsSinceCompaction++;
9688
+ }
9638
9689
  getQuality() {
9639
- const freshness = 1 - this.iterationsSinceCompaction / COMPACTION_INTERVAL;
9640
- const depth = 1 / (1 + this.compactionCount);
9641
- return Math.round(freshness * depth * 100);
9690
+ const usedTokens = this.getEstimatedTokens();
9691
+ const tokenLoad = Math.max(0, 1 - usedTokens / this.budget.history);
9692
+ const compactionLoss = Math.max(0, 1 - this.compactionCount * 0.15);
9693
+ const msgCount = this.messages.length || 1;
9694
+ const errorDensity = Math.max(0, 1 - Math.min(1, this.errorFacts.length / msgCount));
9695
+ const freshness = Math.max(0, 1 - this.iterationsSinceCompaction / COMPACTION_INTERVAL);
9696
+ const score = tokenLoad * 0.4 + compactionLoss * 0.25 + errorDensity * 0.2 + freshness * 0.15;
9697
+ return Math.round(Math.min(100, Math.max(0, score * 100)));
9642
9698
  }
9643
9699
  addMessage(msg) {
9644
9700
  if (msg.role === "user" && this.pendingImageParts.length > 0) {
@@ -9653,7 +9709,6 @@ class ContextManager {
9653
9709
  this.pendingImageParts = [];
9654
9710
  }
9655
9711
  this.messages.push(msg);
9656
- this.iterationsSinceCompaction++;
9657
9712
  const tokens = this.getEstimatedTokens();
9658
9713
  if (tokens > this.peakTokens)
9659
9714
  this.peakTokens = tokens;
@@ -9820,6 +9875,13 @@ ${lines.join(`
9820
9875
  getEstimatedTokens() {
9821
9876
  return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0);
9822
9877
  }
9878
+ resize(contextWindow, contextBudget, tokenCounter) {
9879
+ this.contextWindow = contextWindow;
9880
+ this.budget = this.calculateBudget(contextWindow, contextBudget);
9881
+ if (tokenCounter !== undefined) {
9882
+ this.tokenCounter = tokenCounter ?? null;
9883
+ }
9884
+ }
9823
9885
  }
9824
9886
  var COMPACTION_INTERVAL = 15, KEEP_LAST_N = 6;
9825
9887
  var init_manager = () => {};
@@ -10354,7 +10416,10 @@ var init_web_search = __esm(() => {
10354
10416
  type: "object",
10355
10417
  properties: {
10356
10418
  query: { type: "string", description: "Search query" },
10357
- numResults: { type: "number", description: "Number of results (default 5)" }
10419
+ numResults: {
10420
+ type: "number",
10421
+ description: "Number of results (default 5)"
10422
+ }
10358
10423
  },
10359
10424
  required: ["query"]
10360
10425
  },
@@ -10372,27 +10437,41 @@ var init_web_search = __esm(() => {
10372
10437
  };
10373
10438
  }
10374
10439
  try {
10375
- const response = await fetch(url, { signal: AbortSignal.timeout(securityConfig?.requestTimeout || 1e4) });
10440
+ const response = await fetch(url, {
10441
+ signal: AbortSignal.timeout(securityConfig?.requestTimeout || 1e4)
10442
+ });
10376
10443
  const html = await response.text();
10377
10444
  const results = [];
10378
- const snippetRegex = /<a[^>]+class="result__a"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
10445
+ const snippetRegex = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
10379
10446
  let match;
10380
10447
  let count = 0;
10381
10448
  while ((match = snippetRegex.exec(html)) !== null && count < numResults) {
10382
- const title = match[1].replace(/<[^>]+>/g, "").trim();
10383
- const snippet = match[2].replace(/<[^>]+>/g, "").trim();
10384
- results.push(`${title}: ${snippet}`);
10449
+ const href = match[1].trim();
10450
+ const title = match[2].replace(/<[^>]+>/g, "").trim();
10451
+ const snippet = match[3].replace(/<[^>]+>/g, "").trim();
10452
+ results.push(`${count + 1}. ${title}
10453
+ URL: ${href}
10454
+ ${snippet}`);
10385
10455
  count++;
10386
10456
  }
10387
10457
  logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Results: ${results.length}`);
10388
10458
  if (results.length === 0) {
10389
10459
  return { success: true, output: t("tool.no_results", { query }) };
10390
10460
  }
10391
- return { success: true, output: t("tool.search_results", { query, results: results.join(`
10392
- `) }) };
10461
+ return {
10462
+ success: true,
10463
+ output: t("tool.search_results", {
10464
+ query,
10465
+ results: results.join(`
10466
+ `)
10467
+ })
10468
+ };
10393
10469
  } catch (err) {
10394
10470
  logNetworkRequest(ctx.sessionId, sanitizeUrl(url), false, `Error: ${err.message}`);
10395
- return { success: false, output: t("error.search_failed", { message: err.message }) };
10471
+ return {
10472
+ success: false,
10473
+ output: t("error.search_failed", { message: err.message })
10474
+ };
10396
10475
  }
10397
10476
  }
10398
10477
  };
@@ -24150,6 +24229,7 @@ function registerMmaCommands(ctx) {
24150
24229
  ctx.config.maxToolIterations = answers.maxToolIterations;
24151
24230
  ctx.config.locale = answers.locale;
24152
24231
  saveConfig(ctx.config, configPath);
24232
+ await ctx.agent.reconfigure(ctx.config);
24153
24233
  console.log(pc.green(t("cli.config_saved")));
24154
24234
  });
24155
24235
  }
@@ -24180,7 +24260,7 @@ Excluded blocks: ${info.excluded.length}`));
24180
24260
  name: "provider",
24181
24261
  description: t("repl.provider_list"),
24182
24262
  usage: t("repl.provider_usage"),
24183
- action: (args) => {
24263
+ action: async (args) => {
24184
24264
  const subcmd = args[0];
24185
24265
  if (!subcmd || subcmd === "list") {
24186
24266
  console.log(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
@@ -24196,6 +24276,7 @@ Excluded blocks: ${info.excluded.length}`));
24196
24276
  ctx.config.provider.type = name;
24197
24277
  const configPath = join35(homedir16(), ".mma", "config.json");
24198
24278
  saveConfig(ctx.config, configPath);
24279
+ await ctx.agent.reconfigure(ctx.config);
24199
24280
  console.log(pc.green(t("repl.provider_set", { name })));
24200
24281
  return;
24201
24282
  }
@@ -24251,6 +24332,7 @@ Excluded blocks: ${info.excluded.length}`));
24251
24332
  ctx.config.model = name;
24252
24333
  const configPath = join35(homedir16(), ".mma", "config.json");
24253
24334
  saveConfig(ctx.config, configPath);
24335
+ await ctx.agent.reconfigure(ctx.config);
24254
24336
  console.log(pc.green(t("repl.model_set", { name })));
24255
24337
  return;
24256
24338
  }
@@ -24261,7 +24343,7 @@ Excluded blocks: ${info.excluded.length}`));
24261
24343
  name: "context",
24262
24344
  description: t("cli.manage_context"),
24263
24345
  usage: "/context <size>",
24264
- action: (args) => {
24346
+ action: async (args) => {
24265
24347
  if (args.length === 0) {
24266
24348
  console.log(`/context ${t("repl.context")} ${ctx.config.contextWindow}`);
24267
24349
  console.log(`Usage: /context <size> (min 1024)`);
@@ -24275,6 +24357,7 @@ Excluded blocks: ${info.excluded.length}`));
24275
24357
  ctx.config.contextWindow = size;
24276
24358
  const configPath = join35(homedir16(), ".mma", "config.json");
24277
24359
  saveConfig(ctx.config, configPath);
24360
+ await ctx.agent.reconfigure(ctx.config);
24278
24361
  console.log(pc.green(t("cli.context_set", { size })));
24279
24362
  }
24280
24363
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.24.1",
3
+ "version": "0.24.2",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {