@cosmicstack/mercury-agent 1.1.3 → 1.1.5

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.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="docs/card-dark.png">
4
- <source media="(prefers-color-scheme: light)" srcset="docs/card-light.png">
5
- <img alt="Mercury — Soul-Driven AI Agent" src="docs/card-light.png" width="600">
3
+ <source media="(prefers-color-scheme: dark)" srcset="docs/img/card-dark.png">
4
+ <source media="(prefers-color-scheme: light)" srcset="docs/img/card-light.png">
5
+ <img alt="Mercury — Soul-Driven AI Agent" src="docs/img/card-light.png" width="600">
6
6
  </picture>
7
7
  </p>
8
8
 
package/dist/index.js CHANGED
@@ -88,9 +88,16 @@ function getDefaultConfig() {
88
88
  name: "ollamaLocal",
89
89
  apiKey: "",
90
90
  baseUrl: getEnv("OLLAMA_LOCAL_BASE_URL", "http://127.0.0.1:11434/api"),
91
- model: getEnv("OLLAMA_LOCAL_MODEL", "gpt-oss:20b"),
91
+ model: getEnv("OLLAMA_LOCAL_MODEL", ""),
92
92
  enabled: getEnvBool("OLLAMA_LOCAL_ENABLED", false)
93
93
  },
94
+ openaiCompat: {
95
+ name: "openaiCompat",
96
+ apiKey: getEnv("OPENAI_COMPAT_API_KEY", ""),
97
+ baseUrl: getEnv("OPENAI_COMPAT_BASE_URL", ""),
98
+ model: getEnv("OPENAI_COMPAT_MODEL", ""),
99
+ enabled: getEnvBool("OPENAI_COMPAT_ENABLED", false)
100
+ },
94
101
  mimo: {
95
102
  name: "mimo",
96
103
  apiKey: getEnv("MIMO_API_KEY", ""),
@@ -194,6 +201,9 @@ function isProviderConfigured(provider) {
194
201
  if (provider.name === "ollamaCloud") {
195
202
  return provider.apiKey.length > 0 && provider.baseUrl.length > 0;
196
203
  }
204
+ if (provider.name === "openaiCompat") {
205
+ return provider.baseUrl.length > 0 && provider.model.length > 0;
206
+ }
197
207
  return provider.apiKey.length > 0;
198
208
  }
199
209
  function getTelegramApprovedUsers(config) {
@@ -1548,7 +1558,7 @@ var OpenAICompatProvider = class extends BaseProvider {
1548
1558
  this.name = config.name;
1549
1559
  this.model = config.model;
1550
1560
  this.client = createOpenAI({
1551
- apiKey: config.apiKey,
1561
+ apiKey: config.apiKey || "no-key",
1552
1562
  baseURL: config.baseUrl
1553
1563
  });
1554
1564
  this.modelInstance = useChatApi ? this.client.chat(config.model) : this.client(config.model);
@@ -1749,6 +1759,7 @@ var ProviderRegistry = class {
1749
1759
  config.providers.grok,
1750
1760
  config.providers.ollamaCloud,
1751
1761
  config.providers.ollamaLocal,
1762
+ config.providers.openaiCompat,
1752
1763
  config.providers.mimo,
1753
1764
  config.providers.mimoTokenPlan
1754
1765
  ];
@@ -1764,6 +1775,8 @@ var ProviderRegistry = class {
1764
1775
  provider = new OllamaProvider(pc);
1765
1776
  } else if (pc.name === "ollamaCloud") {
1766
1777
  provider = new OpenAICompatProvider(pc, { useChatApi: true });
1778
+ } else if (pc.name === "openaiCompat") {
1779
+ provider = new OpenAICompatProvider(pc, { useChatApi: true });
1767
1780
  } else if (pc.name === "mimo" || pc.name === "mimoTokenPlan") {
1768
1781
  provider = new MiMoProvider(pc);
1769
1782
  } else {
@@ -8159,8 +8172,16 @@ var OLLAMA_CLOUD_PREFERRED_MODELS = [
8159
8172
  "gpt-oss:20b"
8160
8173
  ];
8161
8174
  var OLLAMA_LOCAL_PREFERRED_MODELS = [
8162
- "gpt-oss:20b",
8163
- "gpt-oss:120b"
8175
+ "llama3.2",
8176
+ "llama3.1",
8177
+ "llama3",
8178
+ "mistral",
8179
+ "codellama",
8180
+ "gemma2",
8181
+ "phi3",
8182
+ "qwen2",
8183
+ "deepseek-r1",
8184
+ "deepseek-coder-v2"
8164
8185
  ];
8165
8186
  var MIMO_PREFERRED_MODELS = [
8166
8187
  "mimo-v2.5-pro",
@@ -8170,6 +8191,7 @@ var MIMO_PREFERRED_MODELS = [
8170
8191
  "mimo-v2-flash"
8171
8192
  ];
8172
8193
  var MIMO_TOKEN_PLAN_PREFERRED_MODELS = MIMO_PREFERRED_MODELS;
8194
+ var OPENAI_COMPAT_PREFERRED_MODELS = [];
8173
8195
  var ProviderModelFetchError = class extends Error {
8174
8196
  constructor(message) {
8175
8197
  super(message);
@@ -8225,6 +8247,7 @@ function chooseRecommendedModel(provider, models, currentModel) {
8225
8247
  grok: GROK_PREFERRED_MODELS,
8226
8248
  ollamaCloud: OLLAMA_CLOUD_PREFERRED_MODELS,
8227
8249
  ollamaLocal: OLLAMA_LOCAL_PREFERRED_MODELS,
8250
+ openaiCompat: OPENAI_COMPAT_PREFERRED_MODELS,
8228
8251
  mimo: MIMO_PREFERRED_MODELS,
8229
8252
  mimoTokenPlan: MIMO_TOKEN_PLAN_PREFERRED_MODELS
8230
8253
  };
@@ -8251,6 +8274,7 @@ function buildModelCatalog(provider, models, currentModel) {
8251
8274
  grok: GROK_PREFERRED_MODELS,
8252
8275
  ollamaCloud: OLLAMA_CLOUD_PREFERRED_MODELS,
8253
8276
  ollamaLocal: OLLAMA_LOCAL_PREFERRED_MODELS,
8277
+ openaiCompat: OPENAI_COMPAT_PREFERRED_MODELS,
8254
8278
  mimo: MIMO_PREFERRED_MODELS,
8255
8279
  mimoTokenPlan: MIMO_TOKEN_PLAN_PREFERRED_MODELS
8256
8280
  };
@@ -8262,19 +8286,32 @@ function buildModelCatalog(provider, models, currentModel) {
8262
8286
  };
8263
8287
  }
8264
8288
  async function fetchOpenAICompatModels(provider, config) {
8289
+ const headers = {};
8290
+ if (config.apiKey) {
8291
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
8292
+ }
8293
+ let errorMessage;
8294
+ if (provider === "grok") {
8295
+ errorMessage = "Mercury could not fetch models for this Grok key. Please re-enter it.";
8296
+ } else if (provider === "deepseek") {
8297
+ errorMessage = "Mercury could not fetch models for this DeepSeek key. Please re-enter it.";
8298
+ } else if (provider === "openaiCompat") {
8299
+ errorMessage = "Mercury could not fetch models from this server. Please check the base URL and try again.";
8300
+ } else {
8301
+ errorMessage = "Mercury could not fetch models for this OpenAI key. Please re-enter it.";
8302
+ }
8265
8303
  const data = await fetchJson(
8266
8304
  `${trimTrailingSlash(config.baseUrl)}/models`,
8267
- {
8268
- headers: {
8269
- Authorization: `Bearer ${config.apiKey}`
8270
- }
8271
- },
8272
- `Mercury could not fetch models for this ${provider === "grok" ? "Grok" : provider === "deepseek" ? "DeepSeek" : "OpenAI"} key. Please re-enter it.`
8305
+ { headers },
8306
+ errorMessage
8273
8307
  );
8274
8308
  const ids = (data.data ?? []).map((model) => model.id?.trim() ?? "").filter((id) => {
8275
8309
  if (provider === "deepseek") {
8276
8310
  return id.startsWith("deepseek-");
8277
8311
  }
8312
+ if (provider === "openaiCompat") {
8313
+ return id.length > 0;
8314
+ }
8278
8315
  return isOpenAIChatModel(id);
8279
8316
  });
8280
8317
  return buildModelCatalog(provider, ids, config.model);
@@ -8373,6 +8410,9 @@ async function fetchProviderModelCatalog(provider, config) {
8373
8410
  if (provider === "ollamaLocal") {
8374
8411
  return fetchOllamaLocalModels(config);
8375
8412
  }
8413
+ if (provider === "openaiCompat") {
8414
+ return fetchOpenAICompatModels(provider, config);
8415
+ }
8376
8416
  if (provider === "mimo") {
8377
8417
  return fetchMiMoModels(config);
8378
8418
  }
@@ -8437,6 +8477,7 @@ var PROVIDER_OPTIONS = [
8437
8477
  { key: "grok", label: "Grok (xAI)" },
8438
8478
  { key: "ollamaCloud", label: "Ollama Cloud" },
8439
8479
  { key: "ollamaLocal", label: "Ollama Local" },
8480
+ { key: "openaiCompat", label: "OpenAI Compilations" },
8440
8481
  { key: "mimo", label: "MiMo (Xiaomi)" },
8441
8482
  { key: "mimoTokenPlan", label: "MiMo Token Plan (Xiaomi)" }
8442
8483
  ];
@@ -8631,35 +8672,106 @@ async function promptApiKeyWithModelSelection(config, provider, providerLabel, p
8631
8672
  );
8632
8673
  return { apiKey: value, model, skipped: false };
8633
8674
  } catch (error) {
8634
- const message = error instanceof ProviderModelFetchError ? error.message : `Mercury could not fetch models for ${providerLabel}. Please re-enter the key.`;
8635
- console.log(chalk7.red(` ${message}`));
8675
+ const message = error instanceof ProviderModelFetchError ? error.message : `Mercury could not fetch models for ${providerLabel}.`;
8676
+ console.log(chalk7.yellow(` ${message}`));
8677
+ console.log(chalk7.dim(" The API key looks valid but Mercury could not reach the provider."));
8678
+ console.log(chalk7.dim(` You can enter a model name manually, or skip ${providerLabel} for now.`));
8679
+ const manualModel = await ask(chalk7.white(` ${providerLabel} model name (Enter to skip ${providerLabel} for now): `));
8680
+ if (!manualModel) {
8681
+ if (isReconfig && existingConfig.apiKey) {
8682
+ return { apiKey: existingConfig.apiKey, model: existingConfig.model, skipped: true };
8683
+ }
8684
+ return { skipped: true };
8685
+ }
8686
+ const modelError = validateModelName(manualModel);
8687
+ if (modelError) {
8688
+ console.log(chalk7.red(` ${modelError}`));
8689
+ continue;
8690
+ }
8691
+ return { apiKey: value, model: manualModel, skipped: false };
8636
8692
  }
8637
8693
  }
8638
8694
  }
8639
- async function promptOllamaLocalModelSelection(config) {
8695
+ async function promptOllamaLocalModelSelection(config, isReconfig) {
8640
8696
  const existingConfig = config.providers.ollamaLocal;
8641
- while (true) {
8642
- const baseUrl = await promptValidatedValue(
8643
- chalk7.white(` Ollama Local base URL [${existingConfig.baseUrl}]: `),
8644
- validateBaseUrl,
8645
- existingConfig.baseUrl
8697
+ const baseUrlPrompt = isReconfig && existingConfig.baseUrl ? chalk7.white(` Ollama Local base URL [${existingConfig.baseUrl}]: `) : chalk7.white(' Ollama Local base URL (Enter to skip, or "none" to skip): ');
8698
+ const baseUrlInput = await ask(baseUrlPrompt);
8699
+ if (!baseUrlInput || baseUrlInput.toLowerCase() === "none") {
8700
+ if (isReconfig && existingConfig.baseUrl) {
8701
+ return { baseUrl: existingConfig.baseUrl, model: existingConfig.model, skipped: true };
8702
+ }
8703
+ return { skipped: true };
8704
+ }
8705
+ const baseUrlError = validateBaseUrl(baseUrlInput);
8706
+ if (baseUrlError) {
8707
+ console.log(chalk7.red(` ${baseUrlError}`));
8708
+ if (isReconfig && existingConfig.baseUrl) {
8709
+ return { baseUrl: existingConfig.baseUrl, model: existingConfig.model, skipped: true };
8710
+ }
8711
+ return { skipped: true };
8712
+ }
8713
+ const baseUrl = baseUrlInput;
8714
+ console.log(chalk7.dim(" Fetching Ollama Local models..."));
8715
+ try {
8716
+ const catalog = await fetchProviderModelCatalog("ollamaLocal", {
8717
+ ...existingConfig,
8718
+ baseUrl
8719
+ });
8720
+ const model = await chooseProviderModel(
8721
+ "Ollama Local",
8722
+ catalog.recommendedModel,
8723
+ catalog.models
8646
8724
  );
8647
- console.log(chalk7.dim(" Fetching Ollama Local models..."));
8648
- try {
8649
- const catalog = await fetchProviderModelCatalog("ollamaLocal", {
8650
- ...existingConfig,
8651
- baseUrl
8652
- });
8653
- const model = await chooseProviderModel(
8654
- "Ollama Local",
8655
- catalog.recommendedModel,
8656
- catalog.models
8657
- );
8658
- return { baseUrl, model, skipped: false };
8659
- } catch (error) {
8660
- const message = error instanceof ProviderModelFetchError ? error.message : "Mercury could not fetch Ollama Local models. Please check the base URL and try again.";
8661
- console.log(chalk7.red(` ${message}`));
8725
+ return { baseUrl, model, skipped: false };
8726
+ } catch (error) {
8727
+ const message = error instanceof ProviderModelFetchError ? error.message : "Mercury could not fetch Ollama Local models.";
8728
+ console.log(chalk7.yellow(` ${message}`));
8729
+ console.log(chalk7.dim(" Make sure Ollama is running locally, or enter the model name manually."));
8730
+ console.log(chalk7.dim(" You can run `mercury doctor` later to configure Ollama after starting it."));
8731
+ const manualModel = await ask(chalk7.white(` Ollama Local model name (Enter to skip Ollama Local for now): `));
8732
+ if (!manualModel) {
8733
+ return { skipped: true };
8734
+ }
8735
+ const modelError = validateModelName(manualModel);
8736
+ if (modelError) {
8737
+ console.log(chalk7.red(` ${modelError}`));
8738
+ return { skipped: true };
8662
8739
  }
8740
+ return { baseUrl, model: manualModel, skipped: false };
8741
+ }
8742
+ }
8743
+ async function promptOpenAICompatSetup(config, isReconfig) {
8744
+ const existingConfig = config.providers.openaiCompat;
8745
+ const baseUrl = await promptValidatedValue(
8746
+ chalk7.white(` Server base URL${isReconfig && existingConfig.baseUrl ? ` [${existingConfig.baseUrl}]` : ""}: `),
8747
+ validateBaseUrl,
8748
+ existingConfig.baseUrl
8749
+ );
8750
+ if (!baseUrl) return { skipped: true };
8751
+ const apiKeyPrompt = isReconfig && existingConfig.apiKey ? chalk7.white(` API key (optional, press Enter to keep current) [${maskKey(existingConfig.apiKey)}]: `) : chalk7.white(" API key (optional, press Enter to skip): ");
8752
+ const apiKey = await ask(apiKeyPrompt);
8753
+ const resolvedApiKey = apiKey || existingConfig.apiKey || "";
8754
+ console.log(chalk7.dim(" Fetching models from server..."));
8755
+ try {
8756
+ const catalog = await fetchProviderModelCatalog("openaiCompat", {
8757
+ ...existingConfig,
8758
+ baseUrl,
8759
+ apiKey: resolvedApiKey
8760
+ });
8761
+ const model = await chooseProviderModel(
8762
+ "OpenAI Compilations",
8763
+ catalog.recommendedModel,
8764
+ catalog.models
8765
+ );
8766
+ return { baseUrl, apiKey: resolvedApiKey, model, skipped: false };
8767
+ } catch {
8768
+ console.log(chalk7.yellow(" Could not fetch models from this server. You can enter the model name manually."));
8769
+ const model = await promptValidatedValue(
8770
+ chalk7.white(" Model name: "),
8771
+ validateModelName
8772
+ );
8773
+ if (!model) return { baseUrl, apiKey: resolvedApiKey, model: existingConfig.model, skipped: false };
8774
+ return { baseUrl, apiKey: resolvedApiKey, model, skipped: false };
8663
8775
  }
8664
8776
  }
8665
8777
  async function promptValidatedValue(prompt, validator, existingValue, options) {
@@ -8799,7 +8911,8 @@ async function configure(existingConfig) {
8799
8911
  if (isReconfig) {
8800
8912
  console.log(chalk7.dim(" Choose which providers to configure now. Existing values are shown where available."));
8801
8913
  } else {
8802
- console.log(chalk7.dim(" Choose one or more providers. Press Enter to configure DeepSeek by default."));
8914
+ console.log(chalk7.dim(" Choose one or more providers. You can skip any provider by pressing Enter."));
8915
+ console.log(chalk7.dim(" Press Enter to configure DeepSeek by default (free at platform.deepseek.com)."));
8803
8916
  }
8804
8917
  console.log("");
8805
8918
  while (true) {
@@ -8887,7 +9000,7 @@ async function configure(existingConfig) {
8887
9000
  continue;
8888
9001
  }
8889
9002
  if (provider === "ollamaLocal") {
8890
- const result = await promptOllamaLocalModelSelection(config);
9003
+ const result = await promptOllamaLocalModelSelection(config, isReconfig);
8891
9004
  if (!result.skipped && result.baseUrl && result.model) {
8892
9005
  config.providers.ollamaLocal.baseUrl = result.baseUrl;
8893
9006
  config.providers.ollamaLocal.model = result.model;
@@ -8895,6 +9008,18 @@ async function configure(existingConfig) {
8895
9008
  }
8896
9009
  continue;
8897
9010
  }
9011
+ if (provider === "openaiCompat") {
9012
+ const result = await promptOpenAICompatSetup(config, isReconfig);
9013
+ if (!result.skipped && result.baseUrl && result.model) {
9014
+ config.providers.openaiCompat.baseUrl = result.baseUrl;
9015
+ config.providers.openaiCompat.model = result.model;
9016
+ config.providers.openaiCompat.enabled = true;
9017
+ if (result.apiKey) {
9018
+ config.providers.openaiCompat.apiKey = result.apiKey;
9019
+ }
9020
+ }
9021
+ continue;
9022
+ }
8898
9023
  if (provider === "mimo") {
8899
9024
  const mask = isReconfig && config.providers.mimo.apiKey ? ` [${maskKey(config.providers.mimo.apiKey)}]` : "";
8900
9025
  const result = await promptApiKeyWithModelSelection(
@@ -8929,8 +9054,25 @@ async function configure(existingConfig) {
8929
9054
  }
8930
9055
  const configuredProviders = getConfiguredProviderNames(config);
8931
9056
  if (configuredProviders.length === 0) {
8932
- console.log(chalk7.red(" You need to configure at least one LLM provider to continue."));
8933
- console.log(chalk7.dim(" Let\u2019s try that step again."));
9057
+ console.log("");
9058
+ console.log(chalk7.yellow(" No LLM providers were configured."));
9059
+ console.log(chalk7.dim(" Mercury needs at least one provider to work."));
9060
+ console.log(chalk7.dim(" DeepSeek offers a free API key at platform.deepseek.com"));
9061
+ console.log("");
9062
+ console.log(chalk7.white(" Options:"));
9063
+ console.log(chalk7.white(" 1. Try again \u2014 choose a provider and enter an API key"));
9064
+ console.log(chalk7.white(" 2. Skip for now \u2014 you can run `mercury doctor` later"));
9065
+ console.log("");
9066
+ const skipChoice = await ask(chalk7.white(' Press Enter to try again, or type "skip" to exit setup: '));
9067
+ if (skipChoice.toLowerCase() === "skip") {
9068
+ saveConfig(config);
9069
+ const home2 = getMercuryHome();
9070
+ console.log("");
9071
+ console.log(chalk7.green(` \u2713 Config saved to ${home2}/mercury.yaml`));
9072
+ console.log(chalk7.yellow(" No providers configured yet. Run `mercury doctor` when ready."));
9073
+ console.log("");
9074
+ process.exit(0);
9075
+ }
8934
9076
  console.log("");
8935
9077
  continue;
8936
9078
  }
@@ -9081,16 +9223,21 @@ async function runAgent(isDaemon = false) {
9081
9223
  process.exit(1);
9082
9224
  }
9083
9225
  const available = providers.listAvailable();
9084
- const providerLabels = available.map((provider) => getProviderLabel(provider));
9085
- const providerModels = available.map((provider) => {
9086
- const key = provider;
9087
- return `${getProviderLabel(key)}: ${config.providers[key].model}`;
9088
- });
9226
+ const defaultProvider = config.providers.default;
9227
+ const defaultModel = config.providers[defaultProvider]?.model ?? "unknown";
9089
9228
  if (!isDaemon) {
9090
- console.log(chalk7.dim(` Providers: ${providerLabels.join(", ")}`));
9091
- console.log(chalk7.dim(` Models: ${providerModels.join(" | ")}`));
9229
+ const providerSummary = available.map((provider) => {
9230
+ const key = provider;
9231
+ const label = getProviderLabel(key);
9232
+ const model = config.providers[key]?.model ?? "?";
9233
+ const marker = key === defaultProvider ? " \u2190 default" : "";
9234
+ return `${label}: ${model}${marker}`;
9235
+ });
9236
+ console.log("");
9237
+ console.log(chalk7.bgMagenta.black.bold(` \u26A1 ${getProviderLabel(defaultProvider)} \xB7 ${defaultModel} `));
9238
+ console.log(chalk7.dim(` Providers: ${providerSummary.join(" \xB7 ")}`));
9092
9239
  } else {
9093
- logger.info({ providers: available }, "Providers loaded");
9240
+ logger.info({ providers: available, default: defaultProvider }, "Providers loaded");
9094
9241
  }
9095
9242
  const skillLoader = new SkillLoader();
9096
9243
  const skills = skillLoader.discover();