levix-bot 2.2.1 → 2.3.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.
@@ -48,7 +48,10 @@ const settings = require("../config/settings.cjs");
48
48
  const memory = require("../utils/memory.cjs");
49
49
  const secrets = require("../config/secrets.cjs");
50
50
  const { DATA_DIR } = require("../config/paths.cjs");
51
- const { PERSONA_FILE } = require("../services/aiAgent.cjs");
51
+ const {
52
+ PERSONA_FILE,
53
+ activeProviderKeySetting,
54
+ } = require("../services/aiAgent.cjs");
52
55
  const { deleteScheduledJob, retryScheduledJob } = require("../../scheduler.cjs");
53
56
  const { describeScheduledJob } = require("../utils/recurrence.cjs");
54
57
 
@@ -140,7 +143,9 @@ router.get("/stats", (req, res) => {
140
143
 
141
144
  prefix: runtimeConfig.getPrefix(),
142
145
  commandCount: getCommandCatalog().length,
143
- aiConfigured: !!settings.get("gemini_api_key"),
146
+ // The active provider's key — "AI key: Not set" must mean what the
147
+ // bot will actually use, not whatever gemini_api_key happens to hold.
148
+ aiConfigured: !!settings.get(activeProviderKeySetting()),
144
149
 
145
150
  uptime: process.uptime(),
146
151
  memory: process.memoryUsage(),
@@ -22,6 +22,10 @@ const aiIdentity = require("../config/ai-identity.cjs");
22
22
  const settings = require("../config/settings.cjs");
23
23
  const memory = require("../utils/memory.cjs");
24
24
  const { toolDeclarations, describeCall, runTool } = require("./aiTools.cjs");
25
+ const {
26
+ runProviderAgent,
27
+ activeProviderKeySetting,
28
+ } = require("./aiProviders.cjs");
25
29
 
26
30
  // Model, key, tool budget and history length all come from config/settings.cjs
27
31
  // (what the dashboard saved, else the default) and are read per call, so changing
@@ -36,13 +40,18 @@ const { toolDeclarations, describeCall, runTool } = require("./aiTools.cjs");
36
40
  // client whose model and per-request configuration are passed at call time —
37
41
  // which suits Levix, because the model and the tool set are settings that may
38
42
  // change between one message and the next.
39
- let clientCache = { key: null, client: null };
43
+ let clientCache = { key: null, baseUrl: null, client: null };
40
44
 
41
45
  function geminiClient() {
42
46
  const key = settings.get("gemini_api_key");
43
47
  if (!key) return null;
44
- if (clientCache.key !== key) {
45
- clientCache = { key, client: new GoogleGenAI({ apiKey: key }) };
48
+ const baseUrl = String(settings.get("gemini_base_url")).replace(/\/+$/, "");
49
+ if (clientCache.key !== key || clientCache.baseUrl !== baseUrl) {
50
+ clientCache = {
51
+ key,
52
+ baseUrl,
53
+ client: new GoogleGenAI({ apiKey: key, httpOptions: { baseUrl } }),
54
+ };
46
55
  }
47
56
  return clientCache.client;
48
57
  }
@@ -225,7 +234,9 @@ function withTimeout(promise, ms, label) {
225
234
  }
226
235
 
227
236
  function isAgentEnabled() {
228
- return Boolean(geminiClient()) && settings.get("ai_agent");
237
+ // The key that matters is the active provider's: a panel switch from gemini
238
+ // to openai must not keep the agent gated on (or running on) a Gemini key.
239
+ return Boolean(settings.get(activeProviderKeySetting())) && settings.get("ai_agent");
229
240
  }
230
241
 
231
242
  // ===========================================================================
@@ -247,7 +258,7 @@ function isAgentEnabled() {
247
258
 
248
259
  const GOOGLE_SEARCH_TOOL = Object.freeze({ googleSearch: {} });
249
260
 
250
- /** Gemini-only. Nothing here is offered to Groq or any other provider. */
261
+ /** Gemini-only. The openai and anthropic providers are never offered it. */
251
262
  function googleSearchEnabled() {
252
263
  return settings.get("ai_google_search") === true;
253
264
  }
@@ -368,6 +379,10 @@ function formatSources(sources) {
368
379
  /**
369
380
  * Run one agent turn.
370
381
  *
382
+ * Dispatches on the `ai_provider` setting: gemini runs the native loop below
383
+ * (built-in Google Search, Files uploads); openai and anthropic run the same
384
+ * loop over their own wire formats in aiProviders.cjs.
385
+ *
371
386
  * @param {object} options
372
387
  * @param {Array} options.parts - the Gemini parts for this message
373
388
  * @param {Array} [options.history]- prior conversation
@@ -384,6 +399,20 @@ async function runAgent({
384
399
  useTools = true,
385
400
  maxSteps = null,
386
401
  } = {}) {
402
+ const provider = settings.get("ai_provider");
403
+ if (provider !== "gemini") {
404
+ const result = await runProviderAgent(provider, {
405
+ parts,
406
+ history: trimHistory(history),
407
+ systemInstruction: buildSystemInstruction(context),
408
+ status,
409
+ context,
410
+ useTools,
411
+ maxSteps,
412
+ });
413
+ return { ...result, history: trimHistory(result.history) };
414
+ }
415
+
387
416
  const genAI = geminiClient();
388
417
  if (!genAI) throw new Error("GEMINI_API_KEY غير معرف");
389
418
 
@@ -540,6 +569,7 @@ module.exports = {
540
569
  buildSystemInstruction,
541
570
  loadPersona,
542
571
  isAgentEnabled,
572
+ activeProviderKeySetting,
543
573
  isFileReferenceError,
544
574
  sanitizeHistoryForFiles,
545
575
  trimHistory,