@cosmicstack/mercury-agent 1.1.2 → 1.1.4

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/dist/index.js CHANGED
@@ -80,7 +80,7 @@ function getDefaultConfig() {
80
80
  ollamaCloud: {
81
81
  name: "ollamaCloud",
82
82
  apiKey: getEnv("OLLAMA_CLOUD_API_KEY", ""),
83
- baseUrl: getEnv("OLLAMA_CLOUD_BASE_URL", "https://ollama.com/api"),
83
+ baseUrl: getEnv("OLLAMA_CLOUD_BASE_URL", "https://ollama.com/v1"),
84
84
  model: getEnv("OLLAMA_CLOUD_MODEL", "gpt-oss:120b"),
85
85
  enabled: getEnvBool("OLLAMA_CLOUD_ENABLED", true)
86
86
  },
@@ -91,6 +91,13 @@ function getDefaultConfig() {
91
91
  model: getEnv("OLLAMA_LOCAL_MODEL", "gpt-oss:20b"),
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", ""),
@@ -145,7 +152,9 @@ function loadConfig() {
145
152
  const raw = readFileSync(CONFIG_PATH, "utf-8");
146
153
  const fileConfig = parseYaml(raw);
147
154
  const defaults = getDefaultConfig();
148
- return migrateLegacyTelegramAccess(deepMerge(defaults, fileConfig));
155
+ return migrateLegacyOllamaCloudBaseUrl(
156
+ migrateLegacyTelegramAccess(deepMerge(defaults, fileConfig))
157
+ );
149
158
  }
150
159
  return migrateLegacyTelegramAccess(getDefaultConfig());
151
160
  }
@@ -189,6 +198,12 @@ function isProviderConfigured(provider) {
189
198
  if (provider.name === "ollamaLocal") {
190
199
  return provider.baseUrl.length > 0 && provider.model.length > 0;
191
200
  }
201
+ if (provider.name === "ollamaCloud") {
202
+ return provider.apiKey.length > 0 && provider.baseUrl.length > 0;
203
+ }
204
+ if (provider.name === "openaiCompat") {
205
+ return provider.baseUrl.length > 0 && provider.model.length > 0;
206
+ }
192
207
  return provider.apiKey.length > 0;
193
208
  }
194
209
  function getTelegramApprovedUsers(config) {
@@ -330,6 +345,13 @@ function migrateLegacyTelegramAccess(config) {
330
345
  delete telegram.pairedUsername;
331
346
  return config;
332
347
  }
348
+ function migrateLegacyOllamaCloudBaseUrl(config) {
349
+ if (config.providers.ollamaCloud.baseUrl === "https://ollama.com/api") {
350
+ config.providers.ollamaCloud.baseUrl = "https://ollama.com/v1";
351
+ saveConfig(config);
352
+ }
353
+ return config;
354
+ }
333
355
 
334
356
  // src/utils/logger.ts
335
357
  import pino from "pino";
@@ -1531,15 +1553,15 @@ var OpenAICompatProvider = class extends BaseProvider {
1531
1553
  model;
1532
1554
  client;
1533
1555
  modelInstance;
1534
- constructor(config) {
1556
+ constructor(config, { useChatApi } = {}) {
1535
1557
  super(config);
1536
1558
  this.name = config.name;
1537
1559
  this.model = config.model;
1538
1560
  this.client = createOpenAI({
1539
- apiKey: config.apiKey,
1561
+ apiKey: config.apiKey || "no-key",
1540
1562
  baseURL: config.baseUrl
1541
1563
  });
1542
- this.modelInstance = this.client(config.model);
1564
+ this.modelInstance = useChatApi ? this.client.chat(config.model) : this.client(config.model);
1543
1565
  }
1544
1566
  async generateText(prompt, systemPrompt) {
1545
1567
  const result = await generateText({
@@ -1686,7 +1708,7 @@ var OllamaProvider = class extends BaseProvider {
1686
1708
  if (this.name === "ollamaLocal") {
1687
1709
  return this.config.baseUrl.length > 0 && this.config.model.length > 0;
1688
1710
  }
1689
- return this.config.apiKey.length > 0;
1711
+ return this.config.apiKey.length > 0 && this.config.baseUrl.length > 0;
1690
1712
  }
1691
1713
  getModelInstance() {
1692
1714
  return this.modelInstance;
@@ -1737,6 +1759,7 @@ var ProviderRegistry = class {
1737
1759
  config.providers.grok,
1738
1760
  config.providers.ollamaCloud,
1739
1761
  config.providers.ollamaLocal,
1762
+ config.providers.openaiCompat,
1740
1763
  config.providers.mimo,
1741
1764
  config.providers.mimoTokenPlan
1742
1765
  ];
@@ -1748,8 +1771,12 @@ var ProviderRegistry = class {
1748
1771
  provider = new AnthropicProvider(pc);
1749
1772
  } else if (pc.name === "deepseek") {
1750
1773
  provider = new DeepSeekProvider(pc);
1751
- } else if (pc.name === "ollamaCloud" || pc.name === "ollamaLocal") {
1774
+ } else if (pc.name === "ollamaLocal") {
1752
1775
  provider = new OllamaProvider(pc);
1776
+ } else if (pc.name === "ollamaCloud") {
1777
+ provider = new OpenAICompatProvider(pc, { useChatApi: true });
1778
+ } else if (pc.name === "openaiCompat") {
1779
+ provider = new OpenAICompatProvider(pc, { useChatApi: true });
1753
1780
  } else if (pc.name === "mimo" || pc.name === "mimoTokenPlan") {
1754
1781
  provider = new MiMoProvider(pc);
1755
1782
  } else {
@@ -8156,6 +8183,7 @@ var MIMO_PREFERRED_MODELS = [
8156
8183
  "mimo-v2-flash"
8157
8184
  ];
8158
8185
  var MIMO_TOKEN_PLAN_PREFERRED_MODELS = MIMO_PREFERRED_MODELS;
8186
+ var OPENAI_COMPAT_PREFERRED_MODELS = [];
8159
8187
  var ProviderModelFetchError = class extends Error {
8160
8188
  constructor(message) {
8161
8189
  super(message);
@@ -8211,6 +8239,7 @@ function chooseRecommendedModel(provider, models, currentModel) {
8211
8239
  grok: GROK_PREFERRED_MODELS,
8212
8240
  ollamaCloud: OLLAMA_CLOUD_PREFERRED_MODELS,
8213
8241
  ollamaLocal: OLLAMA_LOCAL_PREFERRED_MODELS,
8242
+ openaiCompat: OPENAI_COMPAT_PREFERRED_MODELS,
8214
8243
  mimo: MIMO_PREFERRED_MODELS,
8215
8244
  mimoTokenPlan: MIMO_TOKEN_PLAN_PREFERRED_MODELS
8216
8245
  };
@@ -8237,6 +8266,7 @@ function buildModelCatalog(provider, models, currentModel) {
8237
8266
  grok: GROK_PREFERRED_MODELS,
8238
8267
  ollamaCloud: OLLAMA_CLOUD_PREFERRED_MODELS,
8239
8268
  ollamaLocal: OLLAMA_LOCAL_PREFERRED_MODELS,
8269
+ openaiCompat: OPENAI_COMPAT_PREFERRED_MODELS,
8240
8270
  mimo: MIMO_PREFERRED_MODELS,
8241
8271
  mimoTokenPlan: MIMO_TOKEN_PLAN_PREFERRED_MODELS
8242
8272
  };
@@ -8248,19 +8278,32 @@ function buildModelCatalog(provider, models, currentModel) {
8248
8278
  };
8249
8279
  }
8250
8280
  async function fetchOpenAICompatModels(provider, config) {
8281
+ const headers = {};
8282
+ if (config.apiKey) {
8283
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
8284
+ }
8285
+ let errorMessage;
8286
+ if (provider === "grok") {
8287
+ errorMessage = "Mercury could not fetch models for this Grok key. Please re-enter it.";
8288
+ } else if (provider === "deepseek") {
8289
+ errorMessage = "Mercury could not fetch models for this DeepSeek key. Please re-enter it.";
8290
+ } else if (provider === "openaiCompat") {
8291
+ errorMessage = "Mercury could not fetch models from this server. Please check the base URL and try again.";
8292
+ } else {
8293
+ errorMessage = "Mercury could not fetch models for this OpenAI key. Please re-enter it.";
8294
+ }
8251
8295
  const data = await fetchJson(
8252
8296
  `${trimTrailingSlash(config.baseUrl)}/models`,
8253
- {
8254
- headers: {
8255
- Authorization: `Bearer ${config.apiKey}`
8256
- }
8257
- },
8258
- `Mercury could not fetch models for this ${provider === "grok" ? "Grok" : provider === "deepseek" ? "DeepSeek" : "OpenAI"} key. Please re-enter it.`
8297
+ { headers },
8298
+ errorMessage
8259
8299
  );
8260
8300
  const ids = (data.data ?? []).map((model) => model.id?.trim() ?? "").filter((id) => {
8261
8301
  if (provider === "deepseek") {
8262
8302
  return id.startsWith("deepseek-");
8263
8303
  }
8304
+ if (provider === "openaiCompat") {
8305
+ return id.length > 0;
8306
+ }
8264
8307
  return isOpenAIChatModel(id);
8265
8308
  });
8266
8309
  return buildModelCatalog(provider, ids, config.model);
@@ -8292,17 +8335,27 @@ async function fetchGrokModels(config) {
8292
8335
  const ids = (data.data ?? []).filter((model) => model.output_modalities?.includes("text") || model.output_modalities == null).map((model) => model.id?.trim() ?? "").filter((id) => id.startsWith("grok-"));
8293
8336
  return buildModelCatalog("grok", ids, config.model);
8294
8337
  }
8295
- async function fetchOllamaModels(provider, config) {
8296
- const headers = config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : void 0;
8338
+ async function fetchOllamaCloudModels(config) {
8297
8339
  const data = await fetchJson(
8298
- `${trimTrailingSlash(config.baseUrl)}/tags`,
8340
+ `${trimTrailingSlash(config.baseUrl)}/models`,
8299
8341
  {
8300
- headers
8342
+ headers: {
8343
+ Authorization: `Bearer ${config.apiKey}`
8344
+ }
8301
8345
  },
8302
- provider === "ollamaCloud" ? "Mercury could not fetch models for this Ollama Cloud key. Please re-enter it." : "Mercury could not fetch models from this Ollama Local server. Please check the base URL and try again."
8346
+ "Mercury could not fetch models for this Ollama Cloud key. Please re-enter it."
8347
+ );
8348
+ const ids = (data.data ?? []).map((model) => model.id?.trim() ?? "").filter(Boolean);
8349
+ return buildModelCatalog("ollamaCloud", ids, config.model);
8350
+ }
8351
+ async function fetchOllamaLocalModels(config) {
8352
+ const data = await fetchJson(
8353
+ `${trimTrailingSlash(config.baseUrl)}/tags`,
8354
+ {},
8355
+ "Mercury could not fetch models from this Ollama Local server. Please check the base URL and try again."
8303
8356
  );
8304
8357
  const ids = (data.models ?? []).map((model) => model.model?.trim() || model.name?.trim() || "").filter(Boolean);
8305
- return buildModelCatalog(provider, ids, config.model);
8358
+ return buildModelCatalog("ollamaLocal", ids, config.model);
8306
8359
  }
8307
8360
  async function fetchMiMoModels(config) {
8308
8361
  const data = await fetchJson(
@@ -8343,8 +8396,14 @@ async function fetchProviderModelCatalog(provider, config) {
8343
8396
  if (provider === "grok") {
8344
8397
  return fetchGrokModels(config);
8345
8398
  }
8346
- if (provider === "ollamaCloud" || provider === "ollamaLocal") {
8347
- return fetchOllamaModels(provider, config);
8399
+ if (provider === "ollamaCloud") {
8400
+ return fetchOllamaCloudModels(config);
8401
+ }
8402
+ if (provider === "ollamaLocal") {
8403
+ return fetchOllamaLocalModels(config);
8404
+ }
8405
+ if (provider === "openaiCompat") {
8406
+ return fetchOpenAICompatModels(provider, config);
8348
8407
  }
8349
8408
  if (provider === "mimo") {
8350
8409
  return fetchMiMoModels(config);
@@ -8410,6 +8469,7 @@ var PROVIDER_OPTIONS = [
8410
8469
  { key: "grok", label: "Grok (xAI)" },
8411
8470
  { key: "ollamaCloud", label: "Ollama Cloud" },
8412
8471
  { key: "ollamaLocal", label: "Ollama Local" },
8472
+ { key: "openaiCompat", label: "OpenAI Compilations" },
8413
8473
  { key: "mimo", label: "MiMo (Xiaomi)" },
8414
8474
  { key: "mimoTokenPlan", label: "MiMo Token Plan (Xiaomi)" }
8415
8475
  ];
@@ -8635,6 +8695,40 @@ async function promptOllamaLocalModelSelection(config) {
8635
8695
  }
8636
8696
  }
8637
8697
  }
8698
+ async function promptOpenAICompatSetup(config, isReconfig) {
8699
+ const existingConfig = config.providers.openaiCompat;
8700
+ const baseUrl = await promptValidatedValue(
8701
+ chalk7.white(` Server base URL${isReconfig && existingConfig.baseUrl ? ` [${existingConfig.baseUrl}]` : ""}: `),
8702
+ validateBaseUrl,
8703
+ existingConfig.baseUrl
8704
+ );
8705
+ if (!baseUrl) return { skipped: true };
8706
+ 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): ");
8707
+ const apiKey = await ask(apiKeyPrompt);
8708
+ const resolvedApiKey = apiKey || existingConfig.apiKey || "";
8709
+ console.log(chalk7.dim(" Fetching models from server..."));
8710
+ try {
8711
+ const catalog = await fetchProviderModelCatalog("openaiCompat", {
8712
+ ...existingConfig,
8713
+ baseUrl,
8714
+ apiKey: resolvedApiKey
8715
+ });
8716
+ const model = await chooseProviderModel(
8717
+ "OpenAI Compilations",
8718
+ catalog.recommendedModel,
8719
+ catalog.models
8720
+ );
8721
+ return { baseUrl, apiKey: resolvedApiKey, model, skipped: false };
8722
+ } catch {
8723
+ console.log(chalk7.yellow(" Could not fetch models from this server. You can enter the model name manually."));
8724
+ const model = await promptValidatedValue(
8725
+ chalk7.white(" Model name: "),
8726
+ validateModelName
8727
+ );
8728
+ if (!model) return { baseUrl, apiKey: resolvedApiKey, model: existingConfig.model, skipped: false };
8729
+ return { baseUrl, apiKey: resolvedApiKey, model, skipped: false };
8730
+ }
8731
+ }
8638
8732
  async function promptValidatedValue(prompt, validator, existingValue, options) {
8639
8733
  while (true) {
8640
8734
  const value = await ask(prompt);
@@ -8868,6 +8962,18 @@ async function configure(existingConfig) {
8868
8962
  }
8869
8963
  continue;
8870
8964
  }
8965
+ if (provider === "openaiCompat") {
8966
+ const result = await promptOpenAICompatSetup(config, isReconfig);
8967
+ if (!result.skipped && result.baseUrl && result.model) {
8968
+ config.providers.openaiCompat.baseUrl = result.baseUrl;
8969
+ config.providers.openaiCompat.model = result.model;
8970
+ config.providers.openaiCompat.enabled = true;
8971
+ if (result.apiKey) {
8972
+ config.providers.openaiCompat.apiKey = result.apiKey;
8973
+ }
8974
+ }
8975
+ continue;
8976
+ }
8871
8977
  if (provider === "mimo") {
8872
8978
  const mask = isReconfig && config.providers.mimo.apiKey ? ` [${maskKey(config.providers.mimo.apiKey)}]` : "";
8873
8979
  const result = await promptApiKeyWithModelSelection(
@@ -9054,16 +9160,21 @@ async function runAgent(isDaemon = false) {
9054
9160
  process.exit(1);
9055
9161
  }
9056
9162
  const available = providers.listAvailable();
9057
- const providerLabels = available.map((provider) => getProviderLabel(provider));
9058
- const providerModels = available.map((provider) => {
9059
- const key = provider;
9060
- return `${getProviderLabel(key)}: ${config.providers[key].model}`;
9061
- });
9163
+ const defaultProvider = config.providers.default;
9164
+ const defaultModel = config.providers[defaultProvider]?.model ?? "unknown";
9062
9165
  if (!isDaemon) {
9063
- console.log(chalk7.dim(` Providers: ${providerLabels.join(", ")}`));
9064
- console.log(chalk7.dim(` Models: ${providerModels.join(" | ")}`));
9166
+ const providerSummary = available.map((provider) => {
9167
+ const key = provider;
9168
+ const label = getProviderLabel(key);
9169
+ const model = config.providers[key]?.model ?? "?";
9170
+ const marker = key === defaultProvider ? " \u2190 default" : "";
9171
+ return `${label}: ${model}${marker}`;
9172
+ });
9173
+ console.log("");
9174
+ console.log(chalk7.bgMagenta.black.bold(` \u26A1 ${getProviderLabel(defaultProvider)} \xB7 ${defaultModel} `));
9175
+ console.log(chalk7.dim(` Providers: ${providerSummary.join(" \xB7 ")}`));
9065
9176
  } else {
9066
- logger.info({ providers: available }, "Providers loaded");
9177
+ logger.info({ providers: available, default: defaultProvider }, "Providers loaded");
9067
9178
  }
9068
9179
  const skillLoader = new SkillLoader();
9069
9180
  const skills = skillLoader.discover();