micro-models-agent 0.49.1 → 0.50.0

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 +784 -76
  2. package/package.json +2 -2
package/dist/main.js CHANGED
@@ -2676,6 +2676,8 @@ Fix the error and re-edit the file (a clean write clears the failure), or mark t
2676
2676
  "cli.cert_fixture_missing": "Fixture missing for {id}: {path}",
2677
2677
  "cli.cert_marks_hint": "Markers: ✔ certified on this provider, ○ certified on an older MMA version, · not certified",
2678
2678
  "cli.manage_providers": "Manage providers",
2679
+ "cli.provider_check": "Probe configured providers (free listModels call)",
2680
+ "repl.provider_down": '[WARN] Provider "{name}" is unreachable: {error}',
2679
2681
  "cli.list_providers": "List providers",
2680
2682
  "cli.current_provider": "Current provider:",
2681
2683
  "cli.base_url": "Base URL:",
@@ -2829,14 +2831,14 @@ Available commands:`,
2829
2831
  "repl.wizard": "Run setup wizard",
2830
2832
  "repl.wizard_usage": "Usage: /wizard",
2831
2833
  "repl.wizard_running": "Running setup wizard... (current config will be overwritten)",
2832
- "repl.provider_list": "Show/set provider",
2834
+ "repl.provider_list": "List configured providers or hot-swap the active one",
2833
2835
  "repl.provider_current": "Current provider",
2834
2836
  "repl.provider_set": "Provider set to: {name}",
2835
- "repl.provider_usage": "Usage: /provider list | /provider use <type>",
2836
- "repl.model_list": "Show/set model",
2837
+ "repl.provider_usage": "/provider list - show entries (* = active, prio = fallback order) | /provider use <name> - switch without restart",
2838
+ "repl.model_list": "List models of the active provider or switch the model",
2837
2839
  "repl.model_current": "Current model",
2838
2840
  "repl.model_set": "Model set to: {name}",
2839
- "repl.model_usage": "Usage: /model list | /model use <name>",
2841
+ "repl.model_usage": "/model list - fetch models from the active provider | /model use <name> - switch model",
2840
2842
  "repl.group.general": "General",
2841
2843
  "repl.group.agent": "Agent",
2842
2844
  "repl.group.session": "Sessions",
@@ -3047,7 +3049,12 @@ Apply a matching solution from these results. If none is relevant — do NOT rep
3047
3049
  "tools.hidden_header": "Additional tools (enable on demand via enable_tools or route to subagent tool_tags):",
3048
3050
  "tool.friendly.enable_tools": "Enable tools",
3049
3051
  "cli.provider_base_hint": "Base URL set to: {baseUrl}",
3052
+ "cli.add_provider": "Add a provider to the config",
3053
+ "cli.provider_no_url": 'No base URL for provider "{name}". Pass --url.',
3054
+ "cli.provider_added": "Provider added: {name}",
3055
+ "cli.provider_switch_hint": "Switch to it with: mma provider use <name>",
3050
3056
  "repl.cost": "Total cost: {cost}",
3057
+ "repl.cost_breakdown": "By provider: {breakdown}",
3051
3058
  "repl.tokens": "Tokens used: {tokens}",
3052
3059
  "repl.ctrl_c_interrupt": `
3053
3060
  [Ctrl+C] Stopping agent... (press again to force)`,
@@ -3374,6 +3381,8 @@ var init_ru = __esm(() => {
3374
3381
  "cli.cert_fixture_missing": "Фикстура отсутствует для {id}: {path}",
3375
3382
  "cli.cert_marks_hint": "Маркеры: ✔ сертифицировано на этом провайдере, ○ сертифицировано на старой версии MMA, · не сертифицировано",
3376
3383
  "cli.manage_providers": "Управление провайдерами",
3384
+ "cli.provider_check": "Проверить доступность провайдеров (бесплатный вызов listModels)",
3385
+ "repl.provider_down": "[ВНИМАНИЕ] Провайдер «{name}» недоступен: {error}",
3377
3386
  "cli.list_providers": "Список провайдеров",
3378
3387
  "cli.current_provider": "Текущий провайдер:",
3379
3388
  "cli.base_url": "Базовый URL:",
@@ -3527,14 +3536,14 @@ var init_ru = __esm(() => {
3527
3536
  "repl.wizard": "Запустить мастер настройки",
3528
3537
  "repl.wizard_usage": "Использование: /wizard",
3529
3538
  "repl.wizard_running": "Запуск мастера настройки... (текущая конфигурация будет перезаписана)",
3530
- "repl.provider_list": "Показать/сменить провайдер",
3539
+ "repl.provider_list": "Список настроенных провайдеров или горячая смена активного",
3531
3540
  "repl.provider_current": "Текущий провайдер",
3532
3541
  "repl.provider_set": "Провайдер установлен: {name}",
3533
- "repl.provider_usage": "Использование: /provider list | /provider use <тип>",
3534
- "repl.model_list": "Показать/сменить модель",
3542
+ "repl.provider_usage": "/provider list — записи (* = активный, prio = порядок fallback) | /provider use <имя> — смена без перезапуска",
3543
+ "repl.model_list": "Список моделей активного провайдера или смена модели",
3535
3544
  "repl.model_current": "Текущая модель",
3536
3545
  "repl.model_set": "Модель установлена: {name}",
3537
- "repl.model_usage": "Использование: /model list | /model use <имя>",
3546
+ "repl.model_usage": "/model list — получить модели активного провайдера | /model use <имя> — сменить модель",
3538
3547
  "repl.reload": "Перезагрузить агента с текущим конфигом",
3539
3548
  "repl.reload_usage": "Использование: /reload",
3540
3549
  "repl.reloading": "Перезагрузка агента...",
@@ -3752,7 +3761,12 @@ var init_ru = __esm(() => {
3752
3761
  "tools.hidden_header": "Дополнительные тулы (включите по требованию через enable_tools или маршрутизируйте через subagent tool_tags):",
3753
3762
  "tool.friendly.enable_tools": "Включить тулы",
3754
3763
  "cli.provider_base_hint": "Базовый URL установлен: {baseUrl}",
3764
+ "cli.add_provider": "Добавить провайдера в конфиг",
3765
+ "cli.provider_no_url": 'Нет базового URL для провайдера "{name}". Укажите --url.',
3766
+ "cli.provider_added": "Провайдер добавлен: {name}",
3767
+ "cli.provider_switch_hint": "Переключитесь на него: mma provider use <имя>",
3755
3768
  "repl.cost": "Итого потрачено: {cost}",
3769
+ "repl.cost_breakdown": "По провайдерам: {breakdown}",
3756
3770
  "repl.tokens": "Потрачено токенов: {tokens}",
3757
3771
  "repl.ctrl_c_interrupt": `
3758
3772
  [Ctrl+C] Остановка агента... (ещё раз — принудительно)`,
@@ -4757,6 +4771,26 @@ var init_app_logger = __esm(() => {
4757
4771
  };
4758
4772
  });
4759
4773
 
4774
+ // src/modules/providers/registry.ts
4775
+ class ProviderRegistry {
4776
+ specs = new Map;
4777
+ register(spec) {
4778
+ this.specs.set(spec.type, spec);
4779
+ }
4780
+ get(type) {
4781
+ return this.specs.get(type);
4782
+ }
4783
+ has(type) {
4784
+ return this.specs.has(type);
4785
+ }
4786
+ list() {
4787
+ return [...this.specs.values()];
4788
+ }
4789
+ types() {
4790
+ return [...this.specs.keys()];
4791
+ }
4792
+ }
4793
+
4760
4794
  // node_modules/base64-js/index.js
4761
4795
  var require_base64_js = __commonJS((exports) => {
4762
4796
  exports.byteLength = byteLength;
@@ -5646,13 +5680,13 @@ class OpenAICompatProvider {
5646
5680
  headers
5647
5681
  });
5648
5682
  if (!response.ok) {
5649
- return [];
5683
+ throw new Error(`HTTP ${response.status} from ${url}`);
5650
5684
  }
5651
5685
  const data = await response.json();
5652
5686
  const models = (data.data || data || []).map((m) => m.id || m.name || m.model || "").filter(Boolean);
5653
5687
  return models;
5654
- } catch {
5655
- return [];
5688
+ } catch (err) {
5689
+ throw err instanceof Error ? err : new Error(String(err));
5656
5690
  }
5657
5691
  }
5658
5692
  async fetchWithRetry(url, init) {
@@ -5681,12 +5715,16 @@ class OpenAICompatProvider {
5681
5715
  }
5682
5716
  }
5683
5717
  if (lastStatus === 429) {
5684
- throw new Error(t("error.llm_429", {
5718
+ const e429 = new Error(t("error.llm_429", {
5685
5719
  model: this.model,
5686
5720
  baseUrl: this.config.baseUrl
5687
5721
  }));
5722
+ e429.llmStatus = lastStatus;
5723
+ throw e429;
5688
5724
  }
5689
- throw new Error(t("error.llm_retries"));
5725
+ const eRetry = new Error(t("error.llm_retries"));
5726
+ eRetry.llmStatus = lastStatus;
5727
+ throw eRetry;
5690
5728
  }
5691
5729
  isRetryable(status) {
5692
5730
  return status === 429 || status >= 500;
@@ -5717,6 +5755,399 @@ var init_openai_compat = __esm(() => {
5717
5755
  init_rate_limiter();
5718
5756
  });
5719
5757
 
5758
+ // src/modules/providers/presets.ts
5759
+ function openaiCompat(opts) {
5760
+ const retry = opts.retry ?? {
5761
+ maxRetries: 3,
5762
+ baseDelay: 1000,
5763
+ maxDelay: 30000
5764
+ };
5765
+ const rateLimits = opts.rateLimits;
5766
+ return new OpenAICompatProvider({
5767
+ model: opts.model,
5768
+ baseUrl: opts.baseUrl,
5769
+ apiKey: opts.apiKey,
5770
+ contextWindow: opts.contextWindow,
5771
+ retry,
5772
+ rateLimits
5773
+ });
5774
+ }
5775
+ var OPENAI_COMPAT, OPENROUTER, OPENAI, ANTHROPIC, OPENCODE_ZEN, OPENCODE_GO, BUILTIN_PROVIDERS, HOSTED_BASE_URLS;
5776
+ var init_presets = __esm(() => {
5777
+ init_openai_compat();
5778
+ OPENAI_COMPAT = {
5779
+ type: "openai-compat",
5780
+ label: "OpenAI-compatible (LM Studio, Ollama, vLLM, etc.)",
5781
+ baseUrl: "",
5782
+ capabilities: {
5783
+ streaming: true,
5784
+ tools: true,
5785
+ vision: false,
5786
+ reasoningEffort: false,
5787
+ listModels: true,
5788
+ requiresKey: false,
5789
+ auth: "bearer"
5790
+ },
5791
+ create: openaiCompat
5792
+ };
5793
+ OPENROUTER = {
5794
+ type: "openrouter",
5795
+ label: "OpenRouter (openrouter.ai)",
5796
+ baseUrl: "https://openrouter.ai/api/v1",
5797
+ capabilities: {
5798
+ streaming: true,
5799
+ tools: true,
5800
+ vision: true,
5801
+ reasoningEffort: false,
5802
+ listModels: true,
5803
+ requiresKey: true,
5804
+ auth: "bearer"
5805
+ },
5806
+ create: openaiCompat
5807
+ };
5808
+ OPENAI = {
5809
+ type: "openai",
5810
+ label: "OpenAI API",
5811
+ baseUrl: "https://api.openai.com/v1",
5812
+ capabilities: {
5813
+ streaming: true,
5814
+ tools: true,
5815
+ vision: true,
5816
+ reasoningEffort: true,
5817
+ listModels: true,
5818
+ requiresKey: true,
5819
+ auth: "bearer"
5820
+ },
5821
+ create: openaiCompat
5822
+ };
5823
+ ANTHROPIC = {
5824
+ type: "anthropic",
5825
+ label: "Anthropic Claude",
5826
+ baseUrl: "https://api.anthropic.com",
5827
+ capabilities: {
5828
+ streaming: false,
5829
+ tools: false,
5830
+ vision: true,
5831
+ reasoningEffort: false,
5832
+ listModels: false,
5833
+ requiresKey: true,
5834
+ auth: "header"
5835
+ },
5836
+ create: openaiCompat
5837
+ };
5838
+ OPENCODE_ZEN = {
5839
+ type: "opencode-zen",
5840
+ label: "OpenCode Zen (opencode.ai/zen)",
5841
+ baseUrl: "https://opencode.ai/zen/v1",
5842
+ capabilities: {
5843
+ streaming: true,
5844
+ tools: true,
5845
+ vision: false,
5846
+ reasoningEffort: false,
5847
+ listModels: true,
5848
+ requiresKey: false,
5849
+ auth: "bearer"
5850
+ },
5851
+ create: openaiCompat
5852
+ };
5853
+ OPENCODE_GO = {
5854
+ type: "opencode-go",
5855
+ label: "OpenCode Go (opencode.ai/go)",
5856
+ baseUrl: "https://opencode.ai/zen/go/v1",
5857
+ capabilities: {
5858
+ streaming: true,
5859
+ tools: true,
5860
+ vision: false,
5861
+ reasoningEffort: false,
5862
+ listModels: true,
5863
+ requiresKey: false,
5864
+ auth: "bearer"
5865
+ },
5866
+ create: openaiCompat
5867
+ };
5868
+ BUILTIN_PROVIDERS = [
5869
+ OPENAI_COMPAT,
5870
+ OPENROUTER,
5871
+ OPENAI,
5872
+ ANTHROPIC,
5873
+ OPENCODE_ZEN,
5874
+ OPENCODE_GO
5875
+ ];
5876
+ HOSTED_BASE_URLS = {};
5877
+ for (const spec of BUILTIN_PROVIDERS) {
5878
+ if (spec.baseUrl)
5879
+ HOSTED_BASE_URLS[spec.type] = spec.baseUrl;
5880
+ }
5881
+ });
5882
+
5883
+ // src/modules/providers/create.ts
5884
+ var exports_create = {};
5885
+ __export(exports_create, {
5886
+ defaultRegistry: () => defaultRegistry,
5887
+ createProvider: () => createProvider
5888
+ });
5889
+ function createProvider(type, opts, registry = defaultRegistry()) {
5890
+ const spec = registry.get(type);
5891
+ if (!spec) {
5892
+ return BUILTIN_PROVIDERS[0].create(opts);
5893
+ }
5894
+ return spec.create(opts);
5895
+ }
5896
+ function defaultRegistry() {
5897
+ const registry = new ProviderRegistry;
5898
+ for (const spec of BUILTIN_PROVIDERS) {
5899
+ registry.register(spec);
5900
+ }
5901
+ return registry;
5902
+ }
5903
+ var init_create = __esm(() => {
5904
+ init_presets();
5905
+ });
5906
+
5907
+ // src/modules/providers/manager.ts
5908
+ class ProviderManager {
5909
+ entries;
5910
+ activeProviderName;
5911
+ model;
5912
+ opts;
5913
+ registry;
5914
+ cache = new Map;
5915
+ constructor(config, opts) {
5916
+ this.opts = opts;
5917
+ this.registry = opts.registry ?? defaultRegistry();
5918
+ this.entries = this.normalizeEntries(config);
5919
+ this.activeProviderName = this.resolveActive(config);
5920
+ this.model = "";
5921
+ }
5922
+ get activeModel() {
5923
+ return this.model;
5924
+ }
5925
+ get activeName() {
5926
+ return this.activeProviderName;
5927
+ }
5928
+ get active() {
5929
+ return this.build(this.activeProviderName);
5930
+ }
5931
+ get activeEntry() {
5932
+ const entry = this.entryByName(this.activeProviderName);
5933
+ if (!entry)
5934
+ throw new Error(`Unknown active provider "${this.activeProviderName}"`);
5935
+ return entry;
5936
+ }
5937
+ list() {
5938
+ return this.entries.map((e) => {
5939
+ const preset = this.registry.get(e.type);
5940
+ return { ...e, label: e.label || preset?.label || e.type };
5941
+ }).map((e, i) => ({ e, i, p: e.priority ?? Number.MAX_SAFE_INTEGER })).sort((a, b) => a.p - b.p || a.i - b.i).map(({ e }) => e);
5942
+ }
5943
+ listNames() {
5944
+ return this.list().map((e) => e.label);
5945
+ }
5946
+ pickFor(needs) {
5947
+ const globalWindow = this.opts.contextWindow;
5948
+ if (!needs.vision && !needs.tools && needs.minContext === undefined) {
5949
+ return this.activeProviderName;
5950
+ }
5951
+ for (const e of this.list()) {
5952
+ const caps = this.registry.get(e.type)?.capabilities;
5953
+ if (needs.vision && !caps?.vision)
5954
+ continue;
5955
+ if (needs.tools && !caps?.tools)
5956
+ continue;
5957
+ if (needs.minContext !== undefined && (e.contextWindow ?? globalWindow) < needs.minContext) {
5958
+ continue;
5959
+ }
5960
+ return e.label;
5961
+ }
5962
+ return;
5963
+ }
5964
+ switch(name, model) {
5965
+ if (!this.entryByName(name)) {
5966
+ throw new Error(`Unknown provider "${name}"`);
5967
+ }
5968
+ this.activeProviderName = name;
5969
+ if (model !== undefined)
5970
+ this.model = model;
5971
+ return this.active;
5972
+ }
5973
+ setModel(model) {
5974
+ this.model = model;
5975
+ return this.active;
5976
+ }
5977
+ toConfig() {
5978
+ const active = this.activeEntry;
5979
+ return {
5980
+ active: this.activeProviderName,
5981
+ entries: this.entries,
5982
+ fallback: false,
5983
+ type: active.type,
5984
+ baseUrl: active.baseUrl,
5985
+ apiKey: active.apiKey
5986
+ };
5987
+ }
5988
+ normalizeEntries(config) {
5989
+ if (Array.isArray(config.entries) && config.entries.length > 0) {
5990
+ return config.entries.map((e) => ({ ...e, label: e.label || e.type }));
5991
+ }
5992
+ return [
5993
+ {
5994
+ type: config.type ?? "openai-compat",
5995
+ label: config.type ?? "openai-compat",
5996
+ baseUrl: config.baseUrl ?? "",
5997
+ apiKey: config.apiKey
5998
+ }
5999
+ ];
6000
+ }
6001
+ resolveActive(config) {
6002
+ if (config.active && this.entryByName(config.active))
6003
+ return config.active;
6004
+ const first = this.entries[0];
6005
+ return first ? first.label ?? "openai-compat" : "openai-compat";
6006
+ }
6007
+ entryByName(name) {
6008
+ return this.entries.find((e) => e.label === name || e.type === name);
6009
+ }
6010
+ build(name) {
6011
+ const entry = this.entryByName(name);
6012
+ if (!entry)
6013
+ throw new Error(`Unknown provider "${name}"`);
6014
+ const key = `${name}|${this.model}`;
6015
+ const cached = this.cache.get(key);
6016
+ if (cached)
6017
+ return cached;
6018
+ const provider = createProvider(entry.type, {
6019
+ model: this.model,
6020
+ baseUrl: entry.baseUrl,
6021
+ apiKey: entry.apiKey,
6022
+ contextWindow: entry.contextWindow ?? this.opts.contextWindow,
6023
+ retry: entry.retry ?? this.opts.retry,
6024
+ rateLimits: entry.rateLimits ?? this.opts.rateLimits
6025
+ }, this.registry);
6026
+ this.cache.set(key, provider);
6027
+ return provider;
6028
+ }
6029
+ resetCache() {
6030
+ this.cache.clear();
6031
+ }
6032
+ }
6033
+ var init_manager = __esm(() => {
6034
+ init_create();
6035
+ init_presets();
6036
+ });
6037
+
6038
+ // src/modules/providers/health.ts
6039
+ async function probeProviders(targets, opts) {
6040
+ const timeoutMs = opts?.timeoutMs ?? 5000;
6041
+ const results = await Promise.all(targets.map((t2) => probeOne(t2, timeoutMs)));
6042
+ return results;
6043
+ }
6044
+ async function probeOne(target, timeoutMs) {
6045
+ const start = Date.now();
6046
+ try {
6047
+ const models = await withTimeout(target.listModels(), timeoutMs);
6048
+ return { name: target.name, ok: true, ms: Date.now() - start, models: models.length };
6049
+ } catch (err) {
6050
+ const message = err instanceof Error ? err.message : String(err);
6051
+ return {
6052
+ name: target.name,
6053
+ ok: false,
6054
+ ms: Date.now() - start,
6055
+ models: 0,
6056
+ error: message
6057
+ };
6058
+ }
6059
+ }
6060
+ function withTimeout(promise, ms) {
6061
+ return new Promise((resolve, reject) => {
6062
+ const timer = setTimeout(() => reject(new Error(`timeout after ${ms}ms`)), ms);
6063
+ promise.then((v) => {
6064
+ clearTimeout(timer);
6065
+ resolve(v);
6066
+ }, (e) => {
6067
+ clearTimeout(timer);
6068
+ reject(e);
6069
+ });
6070
+ });
6071
+ }
6072
+ function targetsFromProviders(entries) {
6073
+ return entries.map(({ name, provider }) => ({
6074
+ name,
6075
+ listModels: () => provider.listModels()
6076
+ }));
6077
+ }
6078
+
6079
+ // src/modules/providers/index.ts
6080
+ var init_providers = __esm(() => {
6081
+ init_create();
6082
+ init_manager();
6083
+ init_presets();
6084
+ });
6085
+
6086
+ // src/modules/providers/fallback.ts
6087
+ function isFailoverError(err) {
6088
+ const e = err;
6089
+ if (e?.name === "AbortError")
6090
+ return false;
6091
+ if (typeof e?.llmStatus === "number")
6092
+ return e.llmStatus === 429 || e.llmStatus >= 500;
6093
+ const normCode = (e?.code ?? "").replace(/[-_]/g, "").toLowerCase();
6094
+ if (/^abort/.test(normCode))
6095
+ return false;
6096
+ if (/conn(refused|reset|timedout)|enotfound|eai_again|namenotresolved/.test(normCode)) {
6097
+ return true;
6098
+ }
6099
+ return /fetch failed|unable to connect|network|econnrefused|enotfound|etimedout|socket hang up|connection refused|connection reset/i.test(e?.message ?? "");
6100
+ }
6101
+
6102
+ class FallbackProvider {
6103
+ source;
6104
+ onFailover;
6105
+ constructor(source, onFailover) {
6106
+ this.source = source;
6107
+ this.onFailover = onFailover;
6108
+ }
6109
+ get model() {
6110
+ return this.source.active.model;
6111
+ }
6112
+ get contextWindow() {
6113
+ return this.source.active.contextWindow;
6114
+ }
6115
+ async* chat(messages, tools, signal, options) {
6116
+ const names = this.source.listNames();
6117
+ if (names.length <= 1) {
6118
+ yield* this.source.active.chat(messages, tools, signal, options);
6119
+ return;
6120
+ }
6121
+ let emitted = false;
6122
+ let idx = Math.max(0, names.indexOf(this.source.activeName));
6123
+ let lastError;
6124
+ for (let attempt = 0;attempt < names.length; attempt++) {
6125
+ const name = names[idx];
6126
+ const provider = name === this.source.activeName ? this.source.active : this.source.switch(name);
6127
+ try {
6128
+ for await (const chunk of provider.chat(messages, tools, signal, options)) {
6129
+ emitted = true;
6130
+ yield chunk;
6131
+ }
6132
+ return;
6133
+ } catch (err) {
6134
+ if (emitted || !isFailoverError(err))
6135
+ throw err;
6136
+ lastError = err;
6137
+ idx = (idx + 1) % names.length;
6138
+ this.onFailover?.(name, names[idx], err);
6139
+ }
6140
+ }
6141
+ throw lastError;
6142
+ }
6143
+ countTokens(text) {
6144
+ return this.source.active.countTokens(text);
6145
+ }
6146
+ async listModels() {
6147
+ return this.source.active.listModels();
6148
+ }
6149
+ }
6150
+
5720
6151
  // src/llm/model-loader.ts
5721
6152
  class ModelLoader {
5722
6153
  baseUrl;
@@ -6018,6 +6449,16 @@ function safeResolvePath(baseDir, userPath) {
6018
6449
  return asIs;
6019
6450
  }
6020
6451
  const norm = normalize(userPath);
6452
+ if (isAbsolute(norm)) {
6453
+ if (existsSync7(norm) || existsSync7(dirname2(norm)))
6454
+ return norm;
6455
+ const stripped2 = norm.replace(/^[/\\]/, "");
6456
+ const relativeCandidate = resolve(baseDir, stripped2);
6457
+ if (existsSync7(relativeCandidate) || existsSync7(dirname2(relativeCandidate))) {
6458
+ return relativeCandidate;
6459
+ }
6460
+ return norm;
6461
+ }
6021
6462
  const stripped = norm.replace(/^[/\\]/, "");
6022
6463
  const resolved = resolve(baseDir, stripped);
6023
6464
  if (existsSync7(resolved) || existsSync7(dirname2(resolved)))
@@ -8636,9 +9077,11 @@ import { readFileSync as readFileSync9, existsSync as existsSync18 } from "fs";
8636
9077
 
8637
9078
  class SessionLogger {
8638
9079
  session;
9080
+ getCaller;
8639
9081
  logger;
8640
- constructor(session, logger) {
9082
+ constructor(session, logger, getCaller) {
8641
9083
  this.session = session;
9084
+ this.getCaller = getCaller;
8642
9085
  this.logger = logger;
8643
9086
  this.bindSessionDir();
8644
9087
  }
@@ -8677,12 +9120,15 @@ class SessionLogger {
8677
9120
  });
8678
9121
  }
8679
9122
  logAssistant(content, reasoning, toolCalls, iteration) {
9123
+ const caller = this.getCaller?.();
8680
9124
  if (reasoning) {
8681
9125
  this.session?.appendLog({
8682
9126
  ts: new Date().toISOString(),
8683
9127
  type: "reasoning",
8684
9128
  content: reasoning,
8685
- iteration
9129
+ iteration,
9130
+ provider: caller?.provider,
9131
+ model: caller?.model
8686
9132
  });
8687
9133
  }
8688
9134
  this.session?.appendLog({
@@ -8696,14 +9142,19 @@ class SessionLogger {
8696
9142
  arguments: tc.arguments
8697
9143
  }))
8698
9144
  } : {},
8699
- iteration
9145
+ iteration,
9146
+ provider: caller?.provider,
9147
+ model: caller?.model
8700
9148
  });
8701
9149
  }
8702
9150
  saveAssistantMessage(content) {
9151
+ const caller = this.getCaller?.();
8703
9152
  this.session?.appendMessage({
8704
9153
  role: "assistant",
8705
9154
  content: content.slice(0, 500),
8706
- timestamp: new Date().toISOString()
9155
+ timestamp: new Date().toISOString(),
9156
+ provider: caller?.provider,
9157
+ model: caller?.model
8707
9158
  });
8708
9159
  }
8709
9160
  logToolDefs(toolCount, toolNames, iteration) {
@@ -8726,11 +9177,14 @@ class SessionLogger {
8726
9177
  });
8727
9178
  }
8728
9179
  logToolResult(call, result, duration, iteration) {
9180
+ const caller = this.getCaller?.();
8729
9181
  this.session?.appendMessage({
8730
9182
  role: "tool",
8731
9183
  content: sanitizeLogMessage(result.output.slice(0, 500)),
8732
9184
  name: call.name,
8733
- timestamp: new Date().toISOString()
9185
+ timestamp: new Date().toISOString(),
9186
+ provider: caller?.provider,
9187
+ model: caller?.model
8734
9188
  });
8735
9189
  this.session?.appendLog({
8736
9190
  ts: new Date().toISOString(),
@@ -8741,7 +9195,9 @@ class SessionLogger {
8741
9195
  content: sanitizeLogMessage(result.output.slice(0, 1000)),
8742
9196
  diff: result.diff,
8743
9197
  duration,
8744
- iteration
9198
+ iteration,
9199
+ provider: caller?.provider,
9200
+ model: caller?.model
8745
9201
  });
8746
9202
  }
8747
9203
  logCompaction(info) {
@@ -8768,6 +9224,8 @@ class SessionLogger {
8768
9224
  type: "context",
8769
9225
  content: info.kind === "start" ? "context snapshot at session start" : `context at iteration ${info.iteration}`,
8770
9226
  iteration: info.iteration,
9227
+ provider: info.provider,
9228
+ model: info.model,
8771
9229
  window: info.window,
8772
9230
  systemBudget: info.systemBudget,
8773
9231
  reserveBudget: info.reserveBudget,
@@ -8787,6 +9245,8 @@ class SessionLogger {
8787
9245
  ts: new Date().toISOString(),
8788
9246
  type: "llm_usage",
8789
9247
  iteration,
9248
+ provider: usage.provider,
9249
+ model: usage.model,
8790
9250
  promptTokens: usage.promptTokens,
8791
9251
  completionTokens: usage.completionTokens,
8792
9252
  totalTokens: usage.totalTokens,
@@ -9622,18 +10082,20 @@ class OrchestratorClient {
9622
10082
  this.config = config;
9623
10083
  if (config.model) {
9624
10084
  if (config.provider) {
9625
- this.provider = new OpenAICompatProvider({
10085
+ this.provider = createProvider(config.provider.type ?? "openai-compat", {
9626
10086
  model: config.model,
9627
10087
  baseUrl: config.provider.baseUrl || "http://localhost:1234/v1",
9628
10088
  apiKey: config.provider.apiKey,
10089
+ contextWindow: 32768,
9629
10090
  retry: config.retry
9630
10091
  });
9631
10092
  } else if (defaultProvider) {
9632
10093
  this.provider = defaultProvider;
9633
10094
  } else {
9634
- this.provider = new OpenAICompatProvider({
10095
+ this.provider = createProvider("openai-compat", {
9635
10096
  model: config.model,
9636
10097
  baseUrl: "http://localhost:1234/v1",
10098
+ contextWindow: 32768,
9637
10099
  retry: config.retry
9638
10100
  });
9639
10101
  }
@@ -9800,7 +10262,7 @@ Respond with JSON only:
9800
10262
 
9801
10263
  Max 3 re-plan cycles. After 3 cycles, return partial result.`;
9802
10264
  var init_orchestrator = __esm(() => {
9803
- init_openai_compat();
10265
+ init_create();
9804
10266
  init_esm();
9805
10267
  });
9806
10268
 
@@ -11849,6 +12311,7 @@ class CostTracker {
11849
12311
  model;
11850
12312
  _total = 0;
11851
12313
  _known = false;
12314
+ byProvider = new Map;
11852
12315
  constructor(model, config) {
11853
12316
  this.model = model;
11854
12317
  this.config = config;
@@ -11856,16 +12319,36 @@ class CostTracker {
11856
12319
  setModel(model) {
11857
12320
  this.model = model;
11858
12321
  }
11859
- record(promptTokens, completionTokens) {
12322
+ record(promptTokens, completionTokens, provider) {
12323
+ const key = provider || "unknown";
11860
12324
  const cost = calculateCost(this.model, promptTokens, completionTokens, this.config);
11861
12325
  if (cost === undefined)
11862
12326
  return;
11863
12327
  this._known = true;
11864
12328
  this._total += cost;
12329
+ let entry = this.byProvider.get(key);
12330
+ if (!entry) {
12331
+ entry = { total: 0, known: false };
12332
+ this.byProvider.set(key, entry);
12333
+ }
12334
+ entry.known = true;
12335
+ entry.total += cost;
11865
12336
  }
11866
12337
  get total() {
11867
12338
  return this._known ? this._total : undefined;
11868
12339
  }
12340
+ totalFor(provider) {
12341
+ const entry = this.byProvider.get(provider);
12342
+ return entry && entry.known ? entry.total : undefined;
12343
+ }
12344
+ breakdown() {
12345
+ const entries = [];
12346
+ for (const [provider, entry] of this.byProvider) {
12347
+ if (entry.known)
12348
+ entries.push({ provider, cost: entry.total });
12349
+ }
12350
+ return entries.sort((a, b) => b.cost - a.cost);
12351
+ }
11869
12352
  get formatted() {
11870
12353
  return this._known ? formatCost(this._total) : undefined;
11871
12354
  }
@@ -11908,6 +12391,12 @@ class Agent {
11908
12391
  get contextManager() {
11909
12392
  return this.deps.contextManager;
11910
12393
  }
12394
+ callerProvenance() {
12395
+ return {
12396
+ provider: this.deps.config.provider?.type || "unknown",
12397
+ model: this.deps.config.model
12398
+ };
12399
+ }
11911
12400
  getModule(name) {
11912
12401
  return this.deps.moduleRegistry?.get(name);
11913
12402
  }
@@ -12022,7 +12511,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12022
12511
  sessionManager,
12023
12512
  baseDir
12024
12513
  } = this.deps;
12025
- const slog = new SessionLogger(sessionManager, logger);
12514
+ const slog = new SessionLogger(sessionManager, logger, () => this.callerProvenance());
12026
12515
  if (sessionManager && !sessionManager.getActive()) {
12027
12516
  sessionManager.create();
12028
12517
  logger.debug(`Session started: ${sessionManager.getActive()}`);
@@ -12076,7 +12565,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12076
12565
  sessionManager,
12077
12566
  baseDir
12078
12567
  } = this.deps;
12079
- const slog = new SessionLogger(sessionManager, logger);
12568
+ const slog = new SessionLogger(sessionManager, logger, () => this.callerProvenance());
12080
12569
  this.abortController = new AbortController;
12081
12570
  let iteration = 0;
12082
12571
  let lastText = "";
@@ -12255,7 +12744,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12255
12744
  source,
12256
12745
  durationMs: Date.now() - llmStart
12257
12746
  });
12258
- this.costTracker.record(prompt, completion);
12747
+ this.costTracker.record(prompt, completion, this.callerProvenance().provider);
12259
12748
  }
12260
12749
  if (this.shutdownRequested) {
12261
12750
  break;
@@ -12608,6 +13097,7 @@ ${warnLine}
12608
13097
  completionTokens: usageTokens.completion,
12609
13098
  totalTokens: usageTokens.total,
12610
13099
  totalCost: this.costTracker.total,
13100
+ costBreakdown: this.costTracker.breakdown(),
12611
13101
  compactionCount: contextManager.getCompactionCount(),
12612
13102
  contextQuality: contextManager.getQuality()
12613
13103
  };
@@ -12623,6 +13113,7 @@ ${warnLine}
12623
13113
  completionTokens: usageTokens.completion,
12624
13114
  totalTokens: usageTokens.total,
12625
13115
  totalCost: this.costTracker.total,
13116
+ costBreakdown: this.costTracker.breakdown(),
12626
13117
  compactionCount: contextManager.getCompactionCount(),
12627
13118
  contextQuality: contextManager.getQuality()
12628
13119
  };
@@ -12649,6 +13140,45 @@ ${warnLine}
12649
13140
  this.deps.config = config;
12650
13141
  this.costTracker.setModel(config.model);
12651
13142
  }
13143
+ setProvider(name, model) {
13144
+ const manager = new ProviderManager(this.deps.config.provider, {
13145
+ contextWindow: this.deps.config.contextWindow,
13146
+ retry: this.deps.config.retry,
13147
+ rateLimits: this.deps.config.security?.rateLimits
13148
+ });
13149
+ manager.switch(name, model);
13150
+ const providerCfg = manager.toConfig();
13151
+ this.deps.config.provider = providerCfg;
13152
+ this.deps.llmProvider = manager.active;
13153
+ this.deps.toolExecutor.updateProvider(manager.active);
13154
+ this.deps.toolExecutor.ctx.llmProvider = manager.active;
13155
+ }
13156
+ listProviders() {
13157
+ const manager = new ProviderManager(this.deps.config.provider, {
13158
+ contextWindow: this.deps.config.contextWindow,
13159
+ retry: this.deps.config.retry,
13160
+ rateLimits: this.deps.config.security?.rateLimits
13161
+ });
13162
+ const activeName = this.deps.config.provider.active;
13163
+ return manager.list().map((e) => ({
13164
+ ...e,
13165
+ active: e.label === activeName || e.type === activeName
13166
+ }));
13167
+ }
13168
+ getProviderFor(name) {
13169
+ try {
13170
+ const manager = new ProviderManager(this.deps.config.provider, {
13171
+ contextWindow: this.deps.config.contextWindow,
13172
+ retry: this.deps.config.retry,
13173
+ rateLimits: this.deps.config.security?.rateLimits
13174
+ });
13175
+ manager.setModel(this.deps.config.model);
13176
+ manager.switch(name);
13177
+ return manager.active;
13178
+ } catch {
13179
+ return;
13180
+ }
13181
+ }
12652
13182
  setContext(messages) {
12653
13183
  const { contextManager } = this.deps;
12654
13184
  contextManager.clear();
@@ -12683,6 +13213,7 @@ ${warnLine}
12683
13213
  }
12684
13214
  var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000, QUALITY_TRIGGER_THRESHOLD = 40, FORCED_COMPACTION_COOLDOWN = 3;
12685
13215
  var init_agent = __esm(() => {
13216
+ init_manager();
12686
13217
  init_i18n();
12687
13218
  init_colors();
12688
13219
  init_prompt_builder();
@@ -13158,7 +13689,7 @@ ${lines.join(`
13158
13689
  }
13159
13690
  }
13160
13691
  var COMPACTION_INTERVAL = 15, KEEP_LAST_N = 6;
13161
- var init_manager = __esm(() => {
13692
+ var init_manager2 = __esm(() => {
13162
13693
  init_fact_extractor();
13163
13694
  });
13164
13695
 
@@ -13828,7 +14359,7 @@ class PluginManager {
13828
14359
  return chunk;
13829
14360
  }
13830
14361
  }
13831
- var init_manager2 = __esm(() => {
14362
+ var init_manager3 = __esm(() => {
13832
14363
  init_checker();
13833
14364
  });
13834
14365
 
@@ -13848,9 +14379,9 @@ Workflow rules:
13848
14379
  var init_subagent = __esm(() => {
13849
14380
  init_i18n();
13850
14381
  init_agent();
13851
- init_manager();
13852
- init_detector();
13853
14382
  init_manager2();
14383
+ init_detector();
14384
+ init_manager3();
13854
14385
  init_audit_log();
13855
14386
  init_session_isolation();
13856
14387
  init_store();
@@ -14958,8 +15489,8 @@ var init_pipeline_run = __esm(() => {
14958
15489
  init_engine();
14959
15490
  init_parser();
14960
15491
  init_agent();
14961
- init_manager();
14962
15492
  init_manager2();
15493
+ init_manager3();
14963
15494
  init_detector();
14964
15495
  init_audit_log();
14965
15496
  init_session_isolation();
@@ -20016,7 +20547,7 @@ class SessionManager {
20016
20547
  }
20017
20548
  }
20018
20549
  }
20019
- var init_manager3 = __esm(() => {
20550
+ var init_manager4 = __esm(() => {
20020
20551
  init_i18n();
20021
20552
  init_session_isolation();
20022
20553
  });
@@ -20052,7 +20583,7 @@ var init_module2 = __esm(() => {
20052
20583
  // src/modules/session/index.ts
20053
20584
  var init_session2 = __esm(() => {
20054
20585
  init_store3();
20055
- init_manager3();
20586
+ init_manager4();
20056
20587
  init_module2();
20057
20588
  });
20058
20589
 
@@ -22274,14 +22805,18 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
22274
22805
  const profile = new UserProfile(join39(dir));
22275
22806
  profile.load() || profile.collect();
22276
22807
  profile.save();
22277
- const llmProvider = new OpenAICompatProvider({
22278
- model: config.model,
22279
- baseUrl: config.provider.baseUrl,
22280
- apiKey: config.provider.apiKey,
22808
+ const providerManager = new ProviderManager(config.provider, {
22281
22809
  contextWindow: config.contextWindow,
22282
22810
  retry: config.retry,
22283
22811
  rateLimits: config.security?.rateLimits
22284
22812
  });
22813
+ providerManager.setModel(config.model);
22814
+ let llmProvider = providerManager.active;
22815
+ if (config.provider.fallback && providerManager.listNames().length > 1) {
22816
+ llmProvider = new FallbackProvider(providerManager, (from, to, error) => {
22817
+ logger.warn(`provider failover: ${from} -> ${to} (${error.message.slice(0, 120)})`);
22818
+ });
22819
+ }
22285
22820
  const baseDir = projectDir ? resolve23(projectDir) : process.cwd();
22286
22821
  const envReport = collectEnvironment({
22287
22822
  configDir: dir,
@@ -22572,15 +23107,15 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
22572
23107
  var init_bootstrap = __esm(() => {
22573
23108
  init_config2();
22574
23109
  init_app_logger();
22575
- init_openai_compat();
23110
+ init_providers();
22576
23111
  init_tools();
22577
23112
  init_hidden_tools_block();
22578
23113
  init_executor();
22579
- init_manager2();
23114
+ init_manager3();
22580
23115
  init_loader();
22581
23116
  init_lint_on_write();
22582
23117
  init_notify();
22583
- init_manager();
23118
+ init_manager2();
22584
23119
  init_token_counter();
22585
23120
  init_detector();
22586
23121
  init_module();
@@ -23324,35 +23859,24 @@ async function runSetup(externalRl) {
23324
23859
  console.log(l);
23325
23860
  return answers;
23326
23861
  }
23327
- var PROVIDER_TYPES, KNOWN_PORTS, HOSTED_BASE_URLS;
23862
+ var PROVIDER_TYPES, KNOWN_PORTS;
23328
23863
  var init_setup = __esm(() => {
23329
23864
  init_colors();
23330
23865
  init_i18n();
23331
23866
  init_spinner();
23867
+ init_presets();
23332
23868
  init_box();
23333
23869
  init_table();
23334
- PROVIDER_TYPES = [
23335
- {
23336
- value: "openai-compat",
23337
- label: "OpenAI-compatible (LM Studio, Ollama, vLLM, etc.)"
23338
- },
23339
- { value: "openai", label: "OpenAI API" },
23340
- { value: "anthropic", label: "Anthropic Claude" },
23341
- { value: "opencode-zen", label: "OpenCode Zen (opencode.ai/zen)" },
23342
- { value: "opencode-go", label: "OpenCode Go (opencode.ai/go)" }
23343
- ];
23870
+ PROVIDER_TYPES = BUILTIN_PROVIDERS.map((spec) => ({
23871
+ value: spec.type,
23872
+ label: spec.label
23873
+ }));
23344
23874
  KNOWN_PORTS = [
23345
23875
  { port: 1234, label: "LM Studio" },
23346
23876
  { port: 11434, label: "Ollama" },
23347
23877
  { port: 8080, label: "vLLM / custom" },
23348
23878
  { port: 4891, label: "GPT4All" }
23349
23879
  ];
23350
- HOSTED_BASE_URLS = {
23351
- openai: "https://api.openai.com/v1",
23352
- anthropic: "https://api.anthropic.com",
23353
- "opencode-zen": "https://opencode.ai/zen/v1",
23354
- "opencode-go": "https://opencode.ai/zen/go/v1"
23355
- };
23356
23880
  });
23357
23881
 
23358
23882
  // src/modules/certification/manifest.ts
@@ -31176,7 +31700,13 @@ function registerMmaCommands(ctx) {
31176
31700
  const sys = Math.floor(ctxW * cfg.contextBudget.systemPrompt);
31177
31701
  const res = Math.floor(ctxW * cfg.contextBudget.responseReserve);
31178
31702
  console.log(`${t("repl.model")} ${cfg.model}`);
31179
- console.log(`${t("repl.provider")} ${cfg.provider.type} → ${cfg.provider.baseUrl}`);
31703
+ const providers = ctx.agent.listProviders();
31704
+ const active = providers.find((p) => p.active);
31705
+ if (active) {
31706
+ console.log(`${t("repl.provider")} ${active.label} (${pc2.dim(active.type)}) → ${pc2.dim(active.baseUrl)}`);
31707
+ } else {
31708
+ console.log(`${t("repl.provider")} ${cfg.provider.type} → ${cfg.provider.baseUrl}`);
31709
+ }
31180
31710
  console.log(`${t("repl.context")} ${ctxW} (sys:${sys} res:${res} hist:${ctxW - sys - res})`);
31181
31711
  console.log(`${t("repl.max_iters")} ${cfg.maxToolIterations}`);
31182
31712
  console.log(`${t("repl.stuck_thresh")} ${cfg.stuckThreshold}`);
@@ -31204,8 +31734,14 @@ function registerMmaCommands(ctx) {
31204
31734
  description: t("repl.status"),
31205
31735
  usage: t("repl.status_usage"),
31206
31736
  action: () => {
31737
+ const providers = ctx.agent.listProviders();
31738
+ const active = providers.find((p) => p.active);
31207
31739
  console.log(`${t("repl.model")} ${ctx.config.model}`);
31208
- console.log(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
31740
+ if (active) {
31741
+ console.log(`${t("repl.provider")} ${active.label} (${pc2.dim(active.type)}) @ ${pc2.dim(active.baseUrl)}`);
31742
+ } else {
31743
+ console.log(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
31744
+ }
31209
31745
  const meta = ctx.sessionManager?.getActiveMeta();
31210
31746
  if (meta) {
31211
31747
  console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} messages`);
@@ -31264,8 +31800,17 @@ Excluded blocks: ${info.excluded.length}`));
31264
31800
  action: async (args) => {
31265
31801
  const subcmd = args[0];
31266
31802
  if (!subcmd || subcmd === "list") {
31267
- console.log(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
31268
- console.log(` ${ctx.config.provider.baseUrl}`);
31803
+ const providers = ctx.agent.listProviders();
31804
+ if (providers.length === 0) {
31805
+ console.log(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
31806
+ console.log(` ${ctx.config.provider.baseUrl}`);
31807
+ } else {
31808
+ console.log(t("repl.provider_current"));
31809
+ for (const p of providers) {
31810
+ const marker = p.active ? pc2.green("* ") : " ";
31811
+ console.log(` ${marker}${p.label} (${pc2.dim(p.type)}) ${pc2.dim(p.baseUrl)}`);
31812
+ }
31813
+ }
31269
31814
  return;
31270
31815
  }
31271
31816
  if (subcmd === "use") {
@@ -31274,11 +31819,13 @@ Excluded blocks: ${info.excluded.length}`));
31274
31819
  console.log(t("repl.provider_usage"));
31275
31820
  return;
31276
31821
  }
31277
- ctx.config.provider.type = name;
31278
- const configPath = join47(homedir17(), ".mma", "config.json");
31279
- saveConfig(ctx.config, configPath);
31280
- await ctx.agent.reconfigure(ctx.config);
31281
- console.log(pc2.green(t("repl.provider_set", { name })));
31822
+ try {
31823
+ await ctx.agent.setProvider(name);
31824
+ ctx.refreshModelCache();
31825
+ console.log(pc2.green(t("repl.provider_set", { name })));
31826
+ } catch (e) {
31827
+ console.log(pc2.red(e.message));
31828
+ }
31282
31829
  return;
31283
31830
  }
31284
31831
  console.log(t("repl.provider_usage"));
@@ -31306,6 +31853,7 @@ Excluded blocks: ${info.excluded.length}`));
31306
31853
  const models = await provider.listModels();
31307
31854
  s.stop();
31308
31855
  if (models.length > 0) {
31856
+ ctx.refreshModelCache();
31309
31857
  console.log(t("cli.available_models"));
31310
31858
  const { getCertMark: getCertMark2 } = await Promise.resolve().then(() => (init_manifest(), exports_manifest));
31311
31859
  for (const m of models) {
@@ -31777,6 +32325,7 @@ init_bootstrap();
31777
32325
  init_config2();
31778
32326
  init_setup();
31779
32327
  init_i18n();
32328
+ init_colors();
31780
32329
  import { join as join46 } from "path";
31781
32330
  import { homedir as homedir16 } from "os";
31782
32331
 
@@ -32413,7 +32962,7 @@ function createPluginCommand(program2) {
32413
32962
  }
32414
32963
 
32415
32964
  // src/cli/commands.ts
32416
- init_setup();
32965
+ init_presets();
32417
32966
  init_version();
32418
32967
  var version = readMmaVersion();
32419
32968
  function createProgram() {
@@ -32496,8 +33045,8 @@ function createProgram() {
32496
33045
  model.command("list").description(t("cli.list_models")).action(async () => {
32497
33046
  const { config } = await bootstrap();
32498
33047
  console.log(t("cli.current_model"), config.model);
32499
- const { OpenAICompatProvider: OpenAICompatProvider2 } = await Promise.resolve().then(() => (init_openai_compat(), exports_openai_compat));
32500
- const provider2 = new OpenAICompatProvider2({
33048
+ const { createProvider: createProvider2 } = await Promise.resolve().then(() => (init_create(), exports_create));
33049
+ const provider2 = createProvider2(config.provider.type, {
32501
33050
  model: config.model,
32502
33051
  baseUrl: config.provider.baseUrl,
32503
33052
  apiKey: config.provider.apiKey,
@@ -32576,14 +33125,51 @@ function createProgram() {
32576
33125
  console.log(t("cli.context_set", { size: contextWindow }));
32577
33126
  });
32578
33127
  const provider = program2.command("provider").description(t("cli.manage_providers"));
33128
+ provider.command("check").description(t("cli.provider_check")).option("--timeout <ms>", "Per-provider probe timeout in ms").action(async (opts) => {
33129
+ const { config, agent } = await bootstrap();
33130
+ const providers = agent.listProviders();
33131
+ const targets = targetsFromProviders(providers.length > 0 ? providers.map((p) => ({ name: p.label, provider: agent.getProviderFor(p.label) })).filter((x) => Boolean(x.provider)) : [
33132
+ {
33133
+ name: config.provider.type,
33134
+ provider: agent.getProviderFor(config.provider.active || config.provider.type)
33135
+ }
33136
+ ].filter((x) => Boolean(x.provider)));
33137
+ const results = await probeProviders(targets, {
33138
+ timeoutMs: opts.timeout ? Number(opts.timeout) : 5000
33139
+ });
33140
+ for (const r of results) {
33141
+ const mark = r.ok ? pc2.green("✓") : pc2.red("✗");
33142
+ const detail = r.ok ? pc2.dim(`${r.ms}ms, ${r.models} models`) : pc2.red(r.error ?? "failed");
33143
+ console.log(` ${mark} ${r.name.padEnd(16)} ${detail}`);
33144
+ }
33145
+ });
32579
33146
  provider.command("list").description(t("cli.list_providers")).action(async () => {
32580
- const { config } = await bootstrap();
33147
+ const { config, agent } = await bootstrap();
33148
+ const providers = agent.listProviders();
33149
+ if (providers.length > 0) {
33150
+ console.log(t("cli.current_provider"));
33151
+ for (const p of providers) {
33152
+ const marker = p.active ? pc2.green("* ") : " ";
33153
+ const prio = p.priority !== undefined ? ` ${pc2.dim(`prio=${p.priority}`)}` : "";
33154
+ console.log(` ${marker}${p.label} (${pc2.dim(p.type)}) ${pc2.dim(p.baseUrl)}${prio}`);
33155
+ }
33156
+ return;
33157
+ }
32581
33158
  console.log(t("cli.current_provider"), config.provider.type);
32582
33159
  console.log(t("cli.base_url"), config.provider.baseUrl);
32583
33160
  });
32584
33161
  provider.command("use").argument("<name>", "Provider name").description(t("cli.set_provider")).action(async (name) => {
32585
33162
  const configPath = join46(homedir16(), ".mma", "config.json");
32586
- const { config } = await bootstrap();
33163
+ const { config, agent } = await bootstrap();
33164
+ if (config.provider.entries && config.provider.entries.length > 0) {
33165
+ try {
33166
+ await agent.setProvider(name);
33167
+ console.log(t("cli.provider_set", { name }));
33168
+ } catch (e) {
33169
+ console.log(pc2.red(e.message));
33170
+ }
33171
+ return;
33172
+ }
32587
33173
  config.provider.type = name;
32588
33174
  const baseUrl = HOSTED_BASE_URLS[name];
32589
33175
  if (baseUrl) {
@@ -32595,6 +33181,43 @@ function createProgram() {
32595
33181
  console.log(t("cli.provider_base_hint", { baseUrl }));
32596
33182
  }
32597
33183
  });
33184
+ provider.command("add").argument("<name>", "Provider type or label").option("--url <url>", "Base URL").option("--key <key>", "API key").option("--priority <n>", "Fallback priority (lower = tried first)").option("--context-window <n>", "Context window override for this entry").option("--rpm <n>", "Max requests per minute for this entry").option("--parallel <n>", "Max parallel tasks for this entry").description(t("cli.add_provider")).action(async (name, opts) => {
33185
+ const configPath = join46(homedir16(), ".mma", "config.json");
33186
+ const { config } = await bootstrap();
33187
+ const entries = Array.isArray(config.provider.entries) ? config.provider.entries : [];
33188
+ if (entries.length === 0) {
33189
+ entries.push({
33190
+ type: config.provider.type,
33191
+ label: config.provider.type,
33192
+ baseUrl: config.provider.baseUrl,
33193
+ apiKey: config.provider.apiKey
33194
+ });
33195
+ }
33196
+ const baseUrl = opts.url || HOSTED_BASE_URLS[name] || HOSTED_BASE_URLS[`opencode-${name}`] || "";
33197
+ if (!baseUrl) {
33198
+ console.log(pc2.red(t("cli.provider_no_url", { name })));
33199
+ return;
33200
+ }
33201
+ entries.push({
33202
+ type: name,
33203
+ label: name,
33204
+ baseUrl,
33205
+ apiKey: opts.key,
33206
+ ...opts.priority !== undefined ? { priority: Number(opts.priority) } : {},
33207
+ ...opts.contextWindow !== undefined ? { contextWindow: Number(opts.contextWindow) } : {},
33208
+ ...opts.rpm !== undefined || opts.parallel !== undefined ? {
33209
+ rateLimits: {
33210
+ maxRequestsPerMinute: Number(opts.rpm ?? 0) || 60,
33211
+ maxParallelTasks: Number(opts.parallel ?? 0) || 1
33212
+ }
33213
+ } : {}
33214
+ });
33215
+ config.provider.entries = entries;
33216
+ config.provider.active = config.provider.active || config.provider.type;
33217
+ saveConfig(config, configPath);
33218
+ console.log(pc2.green(t("cli.provider_added", { name })));
33219
+ console.log(t("cli.provider_switch_hint"));
33220
+ });
32598
33221
  const session2 = program2.command("session").description(t("cli.manage_sessions"));
32599
33222
  session2.command("list").description(t("cli.list_sessions")).action(async () => {
32600
33223
  const { sessionManager } = await bootstrap();
@@ -33431,6 +34054,34 @@ class SubcommandProvider {
33431
34054
  return this.subcommands.filter((s) => s.startsWith(ctx.partial));
33432
34055
  }
33433
34056
  }
34057
+
34058
+ class ModelArgProvider {
34059
+ name = "model-args";
34060
+ getModels;
34061
+ constructor(getModels) {
34062
+ this.getModels = getModels;
34063
+ }
34064
+ match(ctx) {
34065
+ return ctx.tokenIndex === 2 && ctx.tokens[0] === "/model" && ctx.tokens[1] === "use";
34066
+ }
34067
+ complete(ctx) {
34068
+ return this.getModels().filter((m) => m.startsWith(ctx.partial));
34069
+ }
34070
+ }
34071
+ class ProviderArgProvider {
34072
+ name = "provider-args";
34073
+ providers;
34074
+ constructor(providers) {
34075
+ this.providers = providers;
34076
+ }
34077
+ match(ctx) {
34078
+ return ctx.tokenIndex === 2 && ctx.tokens[0] === "/provider" && ctx.tokens[1] === "use";
34079
+ }
34080
+ complete(ctx) {
34081
+ return this.providers.filter((p) => p.startsWith(ctx.partial));
34082
+ }
34083
+ }
34084
+
33434
34085
  class SessionNameProvider {
33435
34086
  name = "session-names";
33436
34087
  getNames;
@@ -33697,6 +34348,9 @@ class FormattingStream {
33697
34348
  return result;
33698
34349
  }
33699
34350
  }
34351
+ function formatWarning(text) {
34352
+ return pc2.yellow(pc2.bold(t("ui.warning_prefix")) + text);
34353
+ }
33700
34354
 
33701
34355
  // src/ui/renderer.ts
33702
34356
  init_spinner();
@@ -33843,8 +34497,9 @@ ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}
33843
34497
  `);
33844
34498
  }
33845
34499
  }
33846
- toolEnd(_tool, duration, error, ctxDelta, costUsd) {
34500
+ toolEnd(_tool, duration, error, ctxDelta, costUsd, provider, model) {
33847
34501
  this.spinner.stop();
34502
+ const prov = provider && model ? `${pc2.dim(`${provider}·${model}`)}` : undefined;
33848
34503
  if (!this.rich) {
33849
34504
  const parts = [];
33850
34505
  if (ctxDelta !== undefined && ctxDelta !== 0) {
@@ -33854,6 +34509,8 @@ ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}
33854
34509
  if (costUsd !== undefined && costUsd > 0) {
33855
34510
  parts.push(`${pc2.dim("cost")} ${pc2.yellow(formatUsd(costUsd))}`);
33856
34511
  }
34512
+ if (prov)
34513
+ parts.push(prov);
33857
34514
  if (parts.length > 0)
33858
34515
  this.out.write(`${parts.join(" ")}
33859
34516
  `);
@@ -33871,6 +34528,9 @@ ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}
33871
34528
  if (costUsd !== undefined && costUsd > 0) {
33872
34529
  footer += ` ${pc2.dim("cost")} ${pc2.yellow(formatUsd(costUsd))}`;
33873
34530
  }
34531
+ if (prov) {
34532
+ footer += ` ${pc2.dim("via")} ${prov}`;
34533
+ }
33874
34534
  if (this.toolStyle === "inline") {
33875
34535
  this.out.write(`${GUTTER}${footer}
33876
34536
  `);
@@ -33961,7 +34621,7 @@ async function probeLspServers(config, baseDir, deps = {}) {
33961
34621
  const ok = [];
33962
34622
  const failed = [];
33963
34623
  const deadline = Date.now() + deadlineMs;
33964
- const probeOne = async (language, server) => {
34624
+ const probeOne2 = async (language, server) => {
33965
34625
  const projectRoot = findProjectRoot(baseDir, baseDir, server.workspaceMarkers ?? []);
33966
34626
  const serverTimeout = Math.min(server.timeout ?? timeoutCap, timeoutCap);
33967
34627
  const started = Date.now();
@@ -33996,7 +34656,7 @@ async function probeLspServers(config, baseDir, deps = {}) {
33996
34656
  break;
33997
34657
  }
33998
34658
  const wave = servers.slice(i, i + maxParallel);
33999
- await Promise.all(wave.map(({ language, config: server }) => probeOne(language, server)));
34659
+ await Promise.all(wave.map(({ language, config: server }) => probeOne2(language, server)));
34000
34660
  }
34001
34661
  return { ok, failed, noneApplicable: false };
34002
34662
  }
@@ -34086,6 +34746,14 @@ function stepContextForTool(plan, tool, args) {
34086
34746
 
34087
34747
  // src/cli/repl.ts
34088
34748
  init_prices();
34749
+
34750
+ // src/ui/output.ts
34751
+ function writeWarning(text) {
34752
+ process.stdout.write(formatWarning(text) + `
34753
+ `);
34754
+ }
34755
+
34756
+ // src/cli/repl.ts
34089
34757
  function formatContextBar(used, limit, compactions, quality) {
34090
34758
  const pct = Math.min(100, Math.round(used / limit * 100));
34091
34759
  const barLen = 20;
@@ -34105,6 +34773,7 @@ function formatContextBar(used, limit, compactions, quality) {
34105
34773
 
34106
34774
  class Repl {
34107
34775
  completer = new Completer;
34776
+ modelCache = [];
34108
34777
  running = false;
34109
34778
  agentRunning = false;
34110
34779
  inputLocked = false;
@@ -34200,6 +34869,28 @@ class Repl {
34200
34869
  this.completer.registerProvider(new SubcommandProvider("skill", ["list", "loaded", "load", "unload", "search"]));
34201
34870
  this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
34202
34871
  }
34872
+ this.completer.registerProvider(new SubcommandProvider("provider", ["list", "use"]));
34873
+ this.completer.registerProvider(new SubcommandProvider("model", ["list", "use"]));
34874
+ const entryLabels = this.config.provider?.entries?.map((e) => e.label || e.type) ?? [];
34875
+ if (entryLabels.length > 0) {
34876
+ this.completer.registerProvider(new ProviderArgProvider(entryLabels));
34877
+ }
34878
+ this.completer.registerProvider(new ModelArgProvider(() => this.modelCache));
34879
+ this.refreshModelCache();
34880
+ }
34881
+ refreshModelCache() {
34882
+ (async () => {
34883
+ try {
34884
+ const name = this.config.provider.active || this.config.provider.entries?.[0]?.label || this.config.provider.type;
34885
+ const provider = this.agent.getProviderFor(name);
34886
+ if (!provider)
34887
+ return;
34888
+ const models = await provider.listModels();
34889
+ if (models.length > 0) {
34890
+ this.modelCache = models.sort();
34891
+ }
34892
+ } catch {}
34893
+ })();
34203
34894
  }
34204
34895
  setupListeners() {
34205
34896
  let multiLineBuffer = "";
@@ -34383,7 +35074,7 @@ ${t("image.clipboard_empty")}`));
34383
35074
  if (ev.type === "start") {
34384
35075
  renderer.toolStart(ev.tool, ev.args, stepContextForTool(this.execModule?.getActivePlan() ?? null, ev.tool, ev.args), ev.icon);
34385
35076
  } else {
34386
- renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta, ev.costUsd);
35077
+ renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta, ev.costUsd, ev.provider, ev.model);
34387
35078
  if (ev.tool === "plan" || ev.tool === "todo") {
34388
35079
  this.renderPlan(renderer);
34389
35080
  }
@@ -34582,6 +35273,18 @@ ${t("image.clipboard_empty")}`));
34582
35273
  this.updateLspRow(headerWidth, lspSummary);
34583
35274
  }).catch(() => {});
34584
35275
  }
35276
+ if ((this.config.provider.entries?.length ?? 0) > 1) {
35277
+ probeProviders(targetsFromProviders((this.config.provider.entries ?? []).map((e) => ({
35278
+ name: e.label || e.type,
35279
+ provider: this.agent.getProviderFor(e.label || e.type)
35280
+ })).filter((x) => Boolean(x.provider))), { timeoutMs: 5000 }).then((results) => {
35281
+ for (const r of results) {
35282
+ if (!r.ok && r.name !== this.config.provider.active) {
35283
+ writeWarning(t("repl.provider_down", { name: r.name, error: r.error ?? "" }));
35284
+ }
35285
+ }
35286
+ }).catch(() => {});
35287
+ }
34585
35288
  }
34586
35289
  updateLspRow(headerWidth, summary) {
34587
35290
  if (this.agentRunning || !process.stdout.isTTY)
@@ -34644,6 +35347,10 @@ function printRunResult(result, flush) {
34644
35347
  if (result.success) {
34645
35348
  if (result.totalCost !== undefined && result.totalCost > 0) {
34646
35349
  console.log(pc2.dim(`${t("repl.cost", { cost: formatCost(result.totalCost) })}`));
35350
+ if (result.costBreakdown && result.costBreakdown.length > 1) {
35351
+ const parts = result.costBreakdown.map((e) => `${e.provider} ${formatCost(e.cost)}`);
35352
+ console.log(pc2.dim(` ${t("repl.cost_breakdown", { breakdown: parts.join(", ") })}`));
35353
+ }
34647
35354
  } else if (result.totalTokens !== undefined && result.totalTokens > 0) {
34648
35355
  console.log(pc2.dim(`${t("repl.tokens", { tokens: result.totalTokens })}`));
34649
35356
  }
@@ -34858,7 +35565,8 @@ async function main() {
34858
35565
  promptTokens: result2.promptTokens ?? null,
34859
35566
  completionTokens: result2.completionTokens ?? null,
34860
35567
  totalTokens: result2.totalTokens ?? null,
34861
- totalCost: result2.totalCost ?? null
35568
+ totalCost: result2.totalCost ?? null,
35569
+ costBreakdown: result2.costBreakdown ?? []
34862
35570
  }, null, 2));
34863
35571
  process.stdout.write(`
34864
35572
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.49.1",
3
+ "version": "0.50.0",
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": {
@@ -43,6 +43,6 @@
43
43
  "@types/node": "^22.20.1",
44
44
  "prettier": "^3.9.6",
45
45
  "typescript": "^5.9.3",
46
- "vitest": "^4.1.10"
46
+ "vitest": "^4.1.11"
47
47
  }
48
48
  }