fluxflow-cli 1.20.0 → 1.21.1

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 (3) hide show
  1. package/README.md +4 -1
  2. package/dist/fluxflow.js +470 -134
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -27,7 +27,10 @@ npm install -g fluxflow-cli
27
27
  fluxflow-cli
28
28
  ```
29
29
 
30
- *The agent will prompt you for your Gemini API Key on the first run and store it securely in an XOR-encrypted vault.* Free API Key recomemded to use Gemma 4 (Default Model).
30
+ *The agent will prompt you for your API Key on the first run and store it securely in an encrypted vault.* Choose from multiple supported AI providers:
31
+ - **Google GenAI** (Gemini & Gemma models)
32
+ - **DeepSeek** (Native DeepSeek API)
33
+ - **OpenRouter** (Access to hundreds of models; *Experimental*)
31
34
 
32
35
  ---
33
36
 
package/dist/fluxflow.js CHANGED
@@ -1176,7 +1176,7 @@ var init_main_tools = __esm({
1176
1176
  Access to internal tools. MUST use the exact syntax on a new line: [tool:functions.ToolName(args)]
1177
1177
  MANDATORY TOOL POLICY:
1178
1178
  - **MAX 3 TOOL CALLS PER TURN. Next Turn, verify results, plan next**
1179
- ${mode === "Flux" ? "- USE multiple search & replace on patch tool if editing same file/path with many edits\n" : ""}- Use contextually BEST tool, no brute force, no spamming
1179
+ ${mode === "Flux" ? "- USE multiple search & replace on patch tool if editing same file/path with many edits \u2190 **MANDATORY where possible**\n" : ""}- Use contextually BEST tool, no brute force, no spamming
1180
1180
  ${mode === "Flux" ? "- **File Tools >> Code in chat**\n" : ""}
1181
1181
  - COMMUNICATION TOOLS -
1182
1182
  1. [tool:functions.Ask(question="...", optionA="option::description", ...MAX 4)]. Ambiguity Resolution. Mandatory Triggers: Path Divergence, Security, Risk Mitigation. ask >> finish. Suggest best options; don't ask for preferences
@@ -1706,7 +1706,8 @@ function SettingsMenu({
1706
1706
  setInputConfig,
1707
1707
  saveSettings: saveSettings2,
1708
1708
  quotas,
1709
- setMessages
1709
+ setMessages,
1710
+ aiProvider
1710
1711
  }) {
1711
1712
  const [activeColumn, setActiveColumn] = useState3("categories");
1712
1713
  const [selectedCategoryIndex, setSelectedCategoryIndex] = useState3(0);
@@ -1740,6 +1741,7 @@ function SettingsMenu({
1740
1741
  ];
1741
1742
  case "other":
1742
1743
  return [
1744
+ { label: "Current Provider", value: "aiProvider", status: aiProvider },
1743
1745
  { label: "API Tier", value: "apiTier", status: apiTier }
1744
1746
  ];
1745
1747
  default:
@@ -1857,6 +1859,8 @@ function SettingsMenu({
1857
1859
  setEditValue(systemSettings.autoDisallowCommands || "");
1858
1860
  } else if (item.value === "apiTier") {
1859
1861
  setActiveView("apiTier");
1862
+ } else if (item.value === "aiProvider") {
1863
+ setActiveView("selectProvider");
1860
1864
  } else if (item.value === "autoDelete") {
1861
1865
  const options = ["1d", "7d", "30d"];
1862
1866
  const currentIndex = options.indexOf(systemSettings.autoDeleteHistory || "30d");
@@ -1948,7 +1952,7 @@ function SettingsMenu({
1948
1952
  },
1949
1953
  isSelected ? "\u276F " : " ",
1950
1954
  item.label
1951
- ), !isCommandListItem && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "gray", dimColor: true }, dots), /* @__PURE__ */ React6.createElement(Text6, { color: getStatusColor(item), bold: true }, "[ ", item.status, " ]"))), isCommandListItem && !isEditingThis && item.status !== "None" && /* @__PURE__ */ React6.createElement(Box6, { paddingX: 4, marginBottom: 1 }, /* @__PURE__ */ React6.createElement(Text6, { color: "gray", dimColor: true }, "\u21B3 ", item.status)), isEditingThis && /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", marginLeft: 4, marginBottom: 1 }, /* @__PURE__ */ React6.createElement(Box6, { paddingX: 1, borderStyle: "single", borderColor: "cyan", flexDirection: "row" }, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan", bold: true }, "> ", " "), /* @__PURE__ */ React6.createElement(
1955
+ ), !isCommandListItem && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "gray", dimColor: true }, dots), /* @__PURE__ */ React6.createElement(Text6, { color: getStatusColor(item), bold: true }, item.value === "aiProvider" ? item.status : `[ ${item.status} ]`))), isCommandListItem && !isEditingThis && item.status !== "None" && /* @__PURE__ */ React6.createElement(Box6, { paddingX: 4, marginBottom: 1 }, /* @__PURE__ */ React6.createElement(Text6, { color: "gray", dimColor: true }, "\u21B3 ", item.status)), isEditingThis && /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", marginLeft: 4, marginBottom: 1 }, /* @__PURE__ */ React6.createElement(Box6, { paddingX: 1, borderStyle: "single", borderColor: "cyan", flexDirection: "row" }, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan", bold: true }, "> ", " "), /* @__PURE__ */ React6.createElement(
1952
1956
  TextInput,
1953
1957
  {
1954
1958
  value: editValue,
@@ -2281,18 +2285,20 @@ var init_paths = __esm({
2281
2285
  var secrets_exports = {};
2282
2286
  __export(secrets_exports, {
2283
2287
  getAPIKey: () => getAPIKey,
2288
+ getProviderAPIKey: () => getProviderAPIKey,
2284
2289
  getSearchSecrets: () => getSearchSecrets,
2285
2290
  getSecret: () => getSecret,
2286
2291
  removeAPIKey: () => removeAPIKey,
2287
2292
  removeSecret: () => removeSecret,
2288
2293
  saveAPIKey: () => saveAPIKey,
2294
+ saveProviderAPIKey: () => saveProviderAPIKey,
2289
2295
  saveSearchId: () => saveSearchId,
2290
2296
  saveSearchKey: () => saveSearchKey,
2291
2297
  saveSecret: () => saveSecret
2292
2298
  });
2293
2299
  import fs3 from "fs-extra";
2294
2300
  import path3 from "path";
2295
- var SECRET_FILE, getAPIKey, getSecret, saveSecret, getSearchSecrets, saveAPIKey, saveSearchKey, saveSearchId, removeSecret, removeAPIKey;
2301
+ var SECRET_FILE, getAPIKey, getProviderAPIKey, saveProviderAPIKey, getSecret, saveSecret, getSearchSecrets, saveAPIKey, saveSearchKey, saveSearchId, removeSecret, removeAPIKey;
2296
2302
  var init_secrets = __esm({
2297
2303
  "src/utils/secrets.js"() {
2298
2304
  init_crypto();
@@ -2306,6 +2312,26 @@ var init_secrets = __esm({
2306
2312
  }
2307
2313
  return null;
2308
2314
  };
2315
+ getProviderAPIKey = async (provider) => {
2316
+ try {
2317
+ const secrets = readEncryptedJson(SECRET_FILE, {});
2318
+ if (provider === "Google") return secrets.GOOGLE_API_KEY || secrets.API_KEY || null;
2319
+ if (provider === "DeepSeek") return secrets.DEEPSEEK_API_KEY || null;
2320
+ if (provider === "OpenRouter") return secrets.OPENROUTER_API_KEY || null;
2321
+ } catch (e) {
2322
+ }
2323
+ return null;
2324
+ };
2325
+ saveProviderAPIKey = async (provider, key) => {
2326
+ if (provider === "Google") {
2327
+ await saveSecret("GOOGLE_API_KEY", key);
2328
+ await saveSecret("API_KEY", key);
2329
+ } else if (provider === "DeepSeek") {
2330
+ await saveSecret("DEEPSEEK_API_KEY", key);
2331
+ } else if (provider === "OpenRouter") {
2332
+ await saveSecret("OPENROUTER_API_KEY", key);
2333
+ }
2334
+ };
2309
2335
  getSecret = async (key) => {
2310
2336
  try {
2311
2337
  const secrets = readEncryptedJson(SECRET_FILE, {});
@@ -2465,7 +2491,7 @@ Identity: Flux Flow (by Kushal Roy Chowdhury). Conversational, Sassy${mode === "
2465
2491
  Mode: ${mode}${thinkingLevel !== "Fast" ? " (Thinking Mode)" : ""}. ${mode === "Flux" ? "Logical, Highly Detailed, Task-Driven. Prioritizes scalable file/folder structures, modular architecture, clean code abstractions, step-by-step execution. Industry standard latest coding practices/libraries, clean code, Double Check Imports, Client-Server Sync" : "Concise"}
2466
2492
 
2467
2493
  -- AGENT LOOP RULES (PRIORITY: HIGH) --
2468
- - **MANDATORY: MUST END WITH [turn: continue] to CONTINUE loop OR [turn: finish] to END loop**
2494
+ - **MANDATORY: MUST END WITH [turn: continue] to CONTINUE loop OR [turn: finish] to END loop** \u2190 IMPORTANT EVERY RESPONSE
2469
2495
  - Tool Called? No post tool chat until [turn: continue]
2470
2496
  - NEVER USE [turn: continue] [turn:finish] together
2471
2497
 
@@ -2480,7 +2506,7 @@ CRITICAL THINKING POLICY
2480
2506
  - ALWAYS use <think> ... </think> before responding, even with simple queries/greetings
2481
2507
  - ${thinkingLevel === "Low" || thinkingLevel === "Medium" || thinkingLevel === "Fast" ? "C" : "Interrogate approaches adversarially, but c"}ommit once best solution is determined through analysis. Avoid spiraling after reaching decision point
2482
2508
  ` : ""}` : ""}` : ``}
2483
- ${TOOL_PROTOCOL(mode, osDetected, isMultiModal, aiProvider)}
2509
+ ${TOOL_PROTOCOL(mode, osDetected, aiProvider.toLowerCase() === "deepseek" ? false : isMultiModal, aiProvider)}
2484
2510
  ${projectContextBlock}
2485
2511
  -- MEMORY RULES --
2486
2512
  - Memory: ${isMemoryEnabled ? "Subtly Personalize. Auto Saves" : "OFF. Decline Remembering Memories"}
@@ -4923,7 +4949,7 @@ var init_tools = __esm({
4923
4949
  import { GoogleGenAI, ThinkingLevel, HarmBlockThreshold, HarmCategory } from "@google/genai";
4924
4950
  import path16 from "path";
4925
4951
  import fs17 from "fs";
4926
- var client, TERMINATION_SIGNAL, stripAnsi2, fetchWithBackoff, getOpenRouterStream, signalTermination, TOOL_LABELS2, getToolDetail, runJanitorTask, getActiveToolContext, getContextSafeText, contextSafeReplace, getSanitizedText, detectToolCalls, initAI, consolidatePastMemories, getAIStream;
4952
+ var client, TERMINATION_SIGNAL, stripAnsi2, fetchWithBackoff, getDeepSeekStream, getOpenRouterStream, signalTermination, TOOL_LABELS2, getToolDetail, runJanitorTask, getActiveToolContext, getContextSafeText, contextSafeReplace, getSanitizedText, detectToolCalls, initAI, generateSimpleContent, consolidatePastMemories, getAIStream;
4927
4953
  var init_ai = __esm({
4928
4954
  async "src/utils/ai.js"() {
4929
4955
  await init_prompts();
@@ -4954,6 +4980,136 @@ var init_ai = __esm({
4954
4980
  }
4955
4981
  return fetch(url, options);
4956
4982
  };
4983
+ getDeepSeekStream = async function* (apiKey, model, contents, systemInstruction, thinkingLevel, mode, isMultiModal) {
4984
+ const messages = [];
4985
+ if (systemInstruction) {
4986
+ messages.push({ role: "system", content: systemInstruction });
4987
+ }
4988
+ for (const content of contents) {
4989
+ const role = content.role === "user" ? "user" : "assistant";
4990
+ const msgContent = [];
4991
+ if (Array.isArray(content.parts)) {
4992
+ for (const part of content.parts) {
4993
+ if (part.text) {
4994
+ msgContent.push({ type: "text", text: part.text });
4995
+ } else if (part.inlineData && isMultiModal) {
4996
+ const mimeType = part.inlineData.mimeType;
4997
+ const data = part.inlineData.data;
4998
+ const isImage = mimeType.startsWith("image/");
4999
+ if (isImage) {
5000
+ msgContent.push({
5001
+ type: "image_url",
5002
+ image_url: {
5003
+ url: `data:${mimeType};base64,${data}`
5004
+ }
5005
+ });
5006
+ }
5007
+ }
5008
+ }
5009
+ } else {
5010
+ const text = content.text || "";
5011
+ if (text) msgContent.push({ type: "text", text });
5012
+ }
5013
+ messages.push({
5014
+ role,
5015
+ content: msgContent.length === 1 && msgContent[0].type === "text" ? msgContent[0].text : msgContent
5016
+ });
5017
+ }
5018
+ const requestPayload = {
5019
+ model,
5020
+ messages,
5021
+ stream: true,
5022
+ stream_options: { include_usage: true },
5023
+ temperature: mode === "Flux" ? 1 : 1.4
5024
+ };
5025
+ if (thinkingLevel !== "Fast") {
5026
+ const reasoningEffortMap = {
5027
+ "Low": "high",
5028
+ "Medium": "high",
5029
+ "High": "high",
5030
+ "xHigh": "max"
5031
+ };
5032
+ requestPayload.reasoning_effort = reasoningEffortMap[thinkingLevel] || "high";
5033
+ requestPayload.extra_body = { thinking: { type: "enabled" } };
5034
+ } else {
5035
+ requestPayload.extra_body = { thinking: { type: "disabled" } };
5036
+ }
5037
+ const response = await fetchWithBackoff("https://api.deepseek.com/chat/completions", {
5038
+ method: "POST",
5039
+ headers: {
5040
+ "Authorization": `Bearer ${apiKey}`,
5041
+ "Content-Type": "application/json"
5042
+ },
5043
+ body: JSON.stringify(requestPayload)
5044
+ });
5045
+ if (!response.ok) {
5046
+ const errData = await response.json().catch(() => ({}));
5047
+ throw new Error(`DeepSeek Error (${response.status}): ${errData.error?.message || response.statusText}`);
5048
+ }
5049
+ const reader = response.body.getReader();
5050
+ const decoder = new TextDecoder();
5051
+ let buffer = "";
5052
+ let pendingParts = [];
5053
+ let latestUsageMetadata = null;
5054
+ let lastFlushTime = Date.now();
5055
+ let hasNewData = false;
5056
+ while (true) {
5057
+ const { done, value } = await reader.read();
5058
+ if (done) {
5059
+ if (hasNewData && (pendingParts.length > 0 || latestUsageMetadata)) {
5060
+ yield {
5061
+ candidates: pendingParts.length > 0 ? [{ content: { parts: pendingParts } }] : [],
5062
+ usageMetadata: latestUsageMetadata
5063
+ };
5064
+ }
5065
+ break;
5066
+ }
5067
+ buffer += decoder.decode(value, { stream: true });
5068
+ const lines = buffer.split("\n");
5069
+ buffer = lines.pop();
5070
+ for (const line of lines) {
5071
+ const cleanLine = line.trim();
5072
+ if (!cleanLine || !cleanLine.startsWith("data: ")) continue;
5073
+ if (cleanLine === "data: [DONE]") break;
5074
+ try {
5075
+ const json = JSON.parse(cleanLine.substring(6));
5076
+ const delta = json.choices?.[0]?.delta;
5077
+ const usage = json.usage;
5078
+ if (usage) {
5079
+ latestUsageMetadata = {
5080
+ totalTokenCount: usage.total_tokens || usage.prompt_tokens + usage.completion_tokens,
5081
+ promptTokenCount: usage.prompt_tokens || 0,
5082
+ candidatesTokenCount: usage.completion_tokens || 0,
5083
+ cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || 0,
5084
+ thoughtsTokenCount: usage.completion_tokens_details?.reasoning_tokens || 0
5085
+ };
5086
+ hasNewData = true;
5087
+ }
5088
+ if (delta) {
5089
+ const thought = delta.reasoning_content || null;
5090
+ if (thought) {
5091
+ pendingParts.push({ text: thought, thought: true });
5092
+ hasNewData = true;
5093
+ }
5094
+ if (delta.content) {
5095
+ pendingParts.push({ text: delta.content });
5096
+ hasNewData = true;
5097
+ }
5098
+ }
5099
+ } catch (e) {
5100
+ }
5101
+ }
5102
+ if (Date.now() - lastFlushTime >= 100 && hasNewData) {
5103
+ yield {
5104
+ candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
5105
+ usageMetadata: latestUsageMetadata
5106
+ };
5107
+ pendingParts = [];
5108
+ lastFlushTime = Date.now();
5109
+ hasNewData = false;
5110
+ }
5111
+ }
5112
+ };
4957
5113
  getOpenRouterStream = async function* (apiKey, model, contents, systemInstruction, thinkingLevel, mode, isMultiModal) {
4958
5114
  const messages = [];
4959
5115
  if (systemInstruction) {
@@ -5030,9 +5186,21 @@ var init_ai = __esm({
5030
5186
  const reader = response.body.getReader();
5031
5187
  const decoder = new TextDecoder();
5032
5188
  let buffer = "";
5189
+ let pendingParts = [];
5190
+ let latestUsageMetadata = null;
5191
+ let lastFlushTime = Date.now();
5192
+ let hasNewData = false;
5033
5193
  while (true) {
5034
5194
  const { done, value } = await reader.read();
5035
- if (done) break;
5195
+ if (done) {
5196
+ if (hasNewData && (pendingParts.length > 0 || latestUsageMetadata)) {
5197
+ yield {
5198
+ candidates: pendingParts.length > 0 ? [{ content: { parts: pendingParts } }] : [],
5199
+ usageMetadata: latestUsageMetadata
5200
+ };
5201
+ }
5202
+ break;
5203
+ }
5036
5204
  buffer += decoder.decode(value, { stream: true });
5037
5205
  const lines = buffer.split("\n");
5038
5206
  buffer = lines.pop();
@@ -5044,36 +5212,39 @@ var init_ai = __esm({
5044
5212
  const json = JSON.parse(cleanLine.substring(6));
5045
5213
  const delta = json.choices?.[0]?.delta;
5046
5214
  const usage = json.usage;
5047
- let usageMetadata = null;
5048
5215
  if (usage) {
5049
- usageMetadata = {
5216
+ latestUsageMetadata = {
5050
5217
  totalTokenCount: usage.total_tokens || usage.prompt_tokens + usage.completion_tokens,
5051
5218
  promptTokenCount: usage.prompt_tokens || 0,
5052
5219
  candidatesTokenCount: usage.completion_tokens || 0,
5053
5220
  cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || 0,
5054
5221
  thoughtsTokenCount: usage.completion_tokens_details?.reasoning_tokens || 0
5055
5222
  };
5223
+ hasNewData = true;
5056
5224
  }
5057
5225
  if (delta) {
5058
5226
  const thought = delta.reasoning || (delta.reasoning_details ? delta.reasoning_details.map((d) => d.text).join("") : null);
5059
- const parts = [];
5060
- if (thought) parts.push({ text: thought, thought: true });
5061
- if (delta.content) parts.push({ text: delta.content });
5062
- if (parts.length > 0 || usageMetadata) {
5063
- yield {
5064
- candidates: parts.length > 0 ? [{ content: { parts } }] : [],
5065
- usageMetadata
5066
- };
5227
+ if (thought) {
5228
+ pendingParts.push({ text: thought, thought: true });
5229
+ hasNewData = true;
5230
+ }
5231
+ if (delta.content) {
5232
+ pendingParts.push({ text: delta.content });
5233
+ hasNewData = true;
5067
5234
  }
5068
- } else if (usageMetadata) {
5069
- yield {
5070
- candidates: [],
5071
- usageMetadata
5072
- };
5073
5235
  }
5074
5236
  } catch (e) {
5075
5237
  }
5076
5238
  }
5239
+ if (Date.now() - lastFlushTime >= 100 && hasNewData) {
5240
+ yield {
5241
+ candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
5242
+ usageMetadata: latestUsageMetadata
5243
+ };
5244
+ pendingParts = [];
5245
+ lastFlushTime = Date.now();
5246
+ hasNewData = false;
5247
+ }
5077
5248
  }
5078
5249
  };
5079
5250
  signalTermination = () => {
@@ -5181,6 +5352,20 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
5181
5352
  const iterator2 = stream[Symbol.asyncIterator]();
5182
5353
  const firstResult2 = await iterator2.next();
5183
5354
  return { iterator: iterator2, firstResult: firstResult2 };
5355
+ } else if (aiProvider === "DeepSeek") {
5356
+ const stream = getDeepSeekStream(
5357
+ apiKey,
5358
+ "deepseek-chat",
5359
+ janitorContents,
5360
+ janitorPrompt,
5361
+ "Fast",
5362
+ // Janitor always minimal
5363
+ mode,
5364
+ false
5365
+ );
5366
+ const iterator2 = stream[Symbol.asyncIterator]();
5367
+ const firstResult2 = await iterator2.next();
5368
+ return { iterator: iterator2, firstResult: firstResult2 };
5184
5369
  } else {
5185
5370
  const stream = await client.models.generateContentStream({
5186
5371
  model: janitorModel || "gemma-4-26b-a4b-it",
@@ -5524,8 +5709,41 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
5524
5709
  client = new GoogleGenAI({ apiKey });
5525
5710
  return client;
5526
5711
  };
5712
+ generateSimpleContent = async (settings, model, contents, systemInstruction, thinkingLevel = "Fast") => {
5713
+ const { aiProvider = "Google", apiKey, mode } = settings;
5714
+ let fullText = "";
5715
+ let usageMetadata = null;
5716
+ let stream;
5717
+ if (aiProvider === "OpenRouter") {
5718
+ stream = getOpenRouterStream(apiKey, model, contents, systemInstruction, thinkingLevel, mode, false);
5719
+ } else if (aiProvider === "DeepSeek") {
5720
+ stream = getDeepSeekStream(apiKey, model, contents, systemInstruction, thinkingLevel, mode, false);
5721
+ } else {
5722
+ const genStream = await client.models.generateContentStream({
5723
+ model,
5724
+ contents,
5725
+ config: {
5726
+ systemInstruction,
5727
+ maxOutputTokens: 2048,
5728
+ temperature: 0.3,
5729
+ thinkingConfig: { includeThoughts: false, thinkingLevel: ThinkingLevel.MINIMAL }
5730
+ }
5731
+ });
5732
+ stream = genStream;
5733
+ }
5734
+ for await (const chunk of stream) {
5735
+ if (chunk.candidates?.[0]?.content?.parts) {
5736
+ for (const part of chunk.candidates[0].content.parts) {
5737
+ if (part.text && !part.thought) fullText += part.text;
5738
+ }
5739
+ }
5740
+ if (chunk.usageMetadata) usageMetadata = chunk.usageMetadata;
5741
+ }
5742
+ return { text: fullText, usageMetadata };
5743
+ };
5527
5744
  consolidatePastMemories = async (currentChatId, settings) => {
5528
5745
  try {
5746
+ const { aiProvider = "Google" } = settings;
5529
5747
  const tempStorage = readEncryptedJson(TEMP_MEM_FILE, {});
5530
5748
  const totalMemoriesCount = Object.values(tempStorage).flat().length;
5531
5749
  if (totalMemoriesCount <= 2) return;
@@ -5573,23 +5791,13 @@ ${newMemoryListStr}
5573
5791
  let attempts = 0;
5574
5792
  const maxAttempts = 3;
5575
5793
  let success = false;
5794
+ let targetModel = "gemini-3.1-flash-lite";
5795
+ if (aiProvider === "OpenRouter") targetModel = "google/gemma-4-26b-a4b-it:free";
5796
+ if (aiProvider === "DeepSeek") targetModel = "deepseek-v4-flash";
5576
5797
  while (attempts < maxAttempts && !success) {
5577
5798
  attempts++;
5578
5799
  try {
5579
- const response = await client.models.generateContent({
5580
- model: "gemini-3.1-flash-lite",
5581
- contents: prompt,
5582
- config: {
5583
- temperature: 0.3,
5584
- safetySettings: [
5585
- { category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
5586
- { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE },
5587
- { category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: HarmBlockThreshold.BLOCK_NONE },
5588
- { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: HarmBlockThreshold.BLOCK_NONE }
5589
- ],
5590
- thinkingConfig: { includeThoughts: false, thinkingLevel: ThinkingLevel.LOW }
5591
- }
5592
- });
5800
+ const response = await generateSimpleContent(settings, targetModel, prompt, null, "Fast");
5593
5801
  const responseText = response.text || "";
5594
5802
  const janitorToolCalls = detectToolCalls(responseText);
5595
5803
  if (janitorToolCalls.length === 0) {
@@ -5601,6 +5809,9 @@ ${newMemoryListStr}
5601
5809
  await dispatchTool(toolName, janitorToolCall.args, { chatId: currentChatId });
5602
5810
  }
5603
5811
  }
5812
+ if (response.usageMetadata) {
5813
+ await addToUsage("tokens", response.usageMetadata.totalTokenCount || 0);
5814
+ }
5604
5815
  success = true;
5605
5816
  } catch (err) {
5606
5817
  if (attempts >= maxAttempts) {
@@ -5653,66 +5864,22 @@ Provide a new consolidated summary of the entire session.` : `Here is the conver
5653
5864
  ${flattenedText2}
5654
5865
 
5655
5866
  Provide a consolidated summary of the entire session.`;
5867
+ let targetModel = "gemini-3.1-flash-lite";
5868
+ if (aiProvider === "OpenRouter") targetModel = "google/gemma-4-26b-a4b-it:free";
5869
+ if (aiProvider === "DeepSeek") targetModel = "deepseek-v4-flash";
5656
5870
  try {
5657
- const response = await client.models.generateContent({
5658
- model: "gemini-3.1-flash-lite",
5659
- contents: prompt,
5660
- config: {
5661
- systemInstruction,
5662
- maxOutputTokens: 4096,
5663
- temperature: 0.3,
5664
- safetySettings: [
5665
- { category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
5666
- { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE },
5667
- { category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: HarmBlockThreshold.BLOCK_NONE },
5668
- { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: HarmBlockThreshold.BLOCK_NONE }
5669
- ],
5670
- thinkingConfig: { includeThoughts: false, thinkingLevel: ThinkingLevel.MEDIUM }
5671
- }
5672
- });
5871
+ const response = await generateSimpleContent(settings, targetModel, prompt, systemInstruction, "Fast");
5673
5872
  return response.text || "";
5674
5873
  } catch (err) {
5675
- try {
5676
- const response = await client.models.generateContent({
5677
- model: "gemini-2.5-flash",
5678
- contents: prompt,
5679
- config: {
5680
- systemInstruction,
5681
- maxOutputTokens: 4096,
5682
- temperature: 0.3,
5683
- safetySettings: [
5684
- { category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
5685
- { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE },
5686
- { category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: HarmBlockThreshold.BLOCK_NONE },
5687
- { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: HarmBlockThreshold.BLOCK_NONE }
5688
- ],
5689
- thinkingConfig: { includeThoughts: false, thinkingBudget: 8192 }
5690
- }
5691
- });
5692
- return response.text || "";
5693
- } catch (e) {
5874
+ if (aiProvider === "Google") {
5694
5875
  try {
5695
- const response = await client.models.generateContent({
5696
- model: "gemini-2.5-flash-lite",
5697
- contents: prompt,
5698
- config: {
5699
- systemInstruction,
5700
- maxOutputTokens: 4096,
5701
- temperature: 0.3,
5702
- safetySettings: [
5703
- { category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
5704
- { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE },
5705
- { category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: HarmBlockThreshold.BLOCK_NONE },
5706
- { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: HarmBlockThreshold.BLOCK_NONE }
5707
- ],
5708
- thinkingConfig: { includeThoughts: false, thinkingBudget: 8192 }
5709
- }
5710
- });
5711
- return response.text || "";
5712
- } catch (e2) {
5876
+ const fallback = await generateSimpleContent(settings, "gemini-2.5-flash", prompt, systemInstruction, "Fast");
5877
+ return fallback.text || "";
5878
+ } catch (e) {
5713
5879
  return "";
5714
5880
  }
5715
5881
  }
5882
+ return "";
5716
5883
  }
5717
5884
  };
5718
5885
  const flattenedText = flattenContext(modifiedHistory);
@@ -6113,11 +6280,14 @@ ${thinkingLevel != "Fast" && aiProvider === "Google" ? `${modelName.toLowerCase(
6113
6280
  throw new Error("Error: Quota Exausted for Agent");
6114
6281
  }
6115
6282
  targetModel = modelName;
6283
+ if (aiProvider === "DeepSeek" && thinkingLevel === "Fast") {
6284
+ targetModel = "deepseek-chat";
6285
+ }
6116
6286
  if (retryCount === MAX_RETRIES - 1) {
6117
- targetModel = "gemini-3-flash-preview";
6287
+ targetModel = aiProvider === "DeepSeek" ? "deepseek-v4-flash" : "gemini-3-flash-preview";
6118
6288
  yield { type: "model_update", content: "Trying with fallback model" };
6119
6289
  } else if (retryCount === MAX_RETRIES) {
6120
- targetModel = "gemini-3.5-flash";
6290
+ targetModel = aiProvider === "DeepSeek" ? "deepseek-v4-pro" : "gemini-3.5-flash";
6121
6291
  yield { type: "model_update", content: "Trying with fallback model" };
6122
6292
  } else if (retryCount > 12 && retryCount < MAX_RETRIES - 2 && settings.apiKey !== "custom") {
6123
6293
  targetModel = "gemma-4-31b-it";
@@ -6154,6 +6324,16 @@ ${thinkingLevel != "Fast" && aiProvider === "Google" ? `${modelName.toLowerCase(
6154
6324
  mode,
6155
6325
  isMultiModal
6156
6326
  );
6327
+ } else if (aiProvider === "DeepSeek") {
6328
+ stream = getDeepSeekStream(
6329
+ settings.apiKey,
6330
+ targetModel,
6331
+ activeContents,
6332
+ currentSystemInstruction,
6333
+ thinkingLevel,
6334
+ mode,
6335
+ isMultiModal
6336
+ );
6157
6337
  } else {
6158
6338
  stream = await client.models.generateContentStream({
6159
6339
  model: targetModel || "gemma-4-31b-it",
@@ -6216,6 +6396,8 @@ ${thinkingLevel != "Fast" && aiProvider === "Google" ? `${modelName.toLowerCase(
6216
6396
  yield { type: "status", content: "Working..." };
6217
6397
  dedupeBuffer = "";
6218
6398
  isDedupeActive = accumulatedContext.length > 0;
6399
+ let pendingGoogleText = "";
6400
+ let lastGoogleFlushTime = Date.now();
6219
6401
  for await (const chunk of stream) {
6220
6402
  if (TERMINATION_SIGNAL) {
6221
6403
  yield { type: "status", content: "Termination Signal Received." };
@@ -6268,7 +6450,11 @@ ${thinkingLevel != "Fast" && aiProvider === "Google" ? `${modelName.toLowerCase(
6268
6450
  const dedupeClean = hasOpenThink ? cleanText.replace(/^\s*(?:<(think|thought)>|\[(think|thought)\])\s*/gi, "") : cleanText.replace(/^\s*(?:<(think|thought)>|\[(think|thought)\])[\s\S]*?(?:<\/(think|thought)>|\[\/(think|thought)\])\s*/gi, "").replace(/^\s*(?:<(think|thought)>|\[(think|thought)\])\s*/gi, "");
6269
6451
  if (dedupeClean) {
6270
6452
  turnText += dedupeClean;
6271
- yield { type: "text", content: dedupeClean };
6453
+ if (aiProvider === "Google") {
6454
+ pendingGoogleText += dedupeClean;
6455
+ } else {
6456
+ yield { type: "text", content: dedupeClean };
6457
+ }
6272
6458
  }
6273
6459
  }
6274
6460
  isDedupeActive = false;
@@ -6277,7 +6463,11 @@ ${thinkingLevel != "Fast" && aiProvider === "Google" ? `${modelName.toLowerCase(
6277
6463
  continue;
6278
6464
  } else {
6279
6465
  turnText += chunkText;
6280
- yield { type: "text", content: chunkText };
6466
+ if (aiProvider === "Google") {
6467
+ pendingGoogleText += chunkText;
6468
+ } else {
6469
+ yield { type: "text", content: chunkText };
6470
+ }
6281
6471
  }
6282
6472
  const signalSafeText3 = getSanitizedText(turnText);
6283
6473
  const toolContext = getActiveToolContext(turnText);
@@ -6758,6 +6948,11 @@ ${boxBottom}` };
6758
6948
  if (normToolName === "memory" && result.includes("SUCCESS")) yield { type: "memory_updated" };
6759
6949
  toolCallPointer++;
6760
6950
  }
6951
+ if (aiProvider === "Google" && pendingGoogleText && Date.now() - lastGoogleFlushTime >= 100) {
6952
+ yield { type: "text", content: pendingGoogleText };
6953
+ pendingGoogleText = "";
6954
+ lastGoogleFlushTime = Date.now();
6955
+ }
6761
6956
  }
6762
6957
  if (chunk.usageMetadata) {
6763
6958
  lastUsage = chunk.usageMetadata;
@@ -6772,7 +6967,11 @@ ${boxBottom}` };
6772
6967
  dedupeBuffer += "</think>";
6773
6968
  } else {
6774
6969
  turnText += "</think>";
6775
- yield { type: "text", content: "</think>" };
6970
+ if (aiProvider === "Google") {
6971
+ pendingGoogleText += "</think>";
6972
+ } else {
6973
+ yield { type: "text", content: "</think>" };
6974
+ }
6776
6975
  }
6777
6976
  }
6778
6977
  if (isDedupeActive && dedupeBuffer.length > 0) {
@@ -6790,12 +6989,20 @@ ${boxBottom}` };
6790
6989
  const dedupeClean = hasOpenThink ? cleanText.replace(/^\s*(?:<(think|thought)>|\[(think|thought)\])\s*/gi, "") : cleanText.replace(/^\s*(?:<(think|thought)>|\[(think|thought)\])[\s\S]*?(?:<\/(think|thought)>|\[\/(think|thought)\])\s*/gi, "").replace(/^\s*(?:<(think|thought)>|\[(think|thought)\])\s*/gi, "");
6791
6990
  if (dedupeClean) {
6792
6991
  turnText += dedupeClean;
6793
- yield { type: "text", content: dedupeClean };
6992
+ if (aiProvider === "Google") {
6993
+ pendingGoogleText += dedupeClean;
6994
+ } else {
6995
+ yield { type: "text", content: dedupeClean };
6996
+ }
6794
6997
  }
6795
6998
  }
6796
6999
  isDedupeActive = false;
6797
7000
  dedupeBuffer = "";
6798
7001
  }
7002
+ if (aiProvider === "Google" && pendingGoogleText) {
7003
+ yield { type: "text", content: pendingGoogleText };
7004
+ pendingGoogleText = "";
7005
+ }
6799
7006
  if (TERMINATION_SIGNAL) break;
6800
7007
  const signalSafeText2 = (turnText || "").trim();
6801
7008
  const hasFinish2 = /\[\s*(turn\s*:)?\s*finish\s*\]/i.test(signalSafeText2.toLowerCase());
@@ -6947,7 +7154,7 @@ Error Log can be found in ${path16.join(LOGS_DIR, "agent", "error.log")}`);
6947
7154
  const shouldContinue = toolCallPointer > 0;
6948
7155
  yield { type: "status", content: "Working..." };
6949
7156
  const cleanedTurnText = contextSafeReplace(turnText, /\[\s*(turn\s*:)?\s*(continue|finish)\s*\]/gi, "").trim();
6950
- let isActuallyFinished = hasFinish && !shouldContinue || !shouldContinue && !hasContinue;
7157
+ let isActuallyFinished = hasFinish || !shouldContinue;
6951
7158
  if (isActuallyFinished) {
6952
7159
  const fullAgentTextRaw = fullAgentResponseChunks.join("\n");
6953
7160
  const cleanedFullResponse = fullAgentTextRaw.replace(/(?:<think>|\[think\])[\s\S]*?(?:<\/think>|\[\/think\])/g, "").trim();
@@ -7663,44 +7870,63 @@ function App({ args = [] }) {
7663
7870
  const [tick, setTick] = useState10(0);
7664
7871
  const isFirstRender = useRef3(true);
7665
7872
  const isSecondRender = useRef3(true);
7873
+ const isThirdRender = useRef3(true);
7666
7874
  useEffect7(() => {
7875
+ if (!apiKey) return;
7667
7876
  if (isFirstRender.current) {
7668
7877
  isFirstRender.current = false;
7669
7878
  setTimeout(() => {
7670
7879
  isSecondRender.current = false;
7880
+ setTimeout(() => {
7881
+ isThirdRender.current = false;
7882
+ }, 1e3);
7671
7883
  }, 2e3);
7672
7884
  return;
7673
7885
  }
7674
7886
  if (isSecondRender.current) {
7675
7887
  return;
7676
7888
  }
7889
+ if (isThirdRender.current) {
7890
+ return;
7891
+ }
7677
7892
  const s = emojiSpace(2);
7893
+ let defaultModel = "";
7894
+ let modelDisplayName = "";
7678
7895
  if (apiTier === "Free") {
7679
- setActiveModel("gemma-4-31b-it");
7680
- saveSettings({ apiTier: "Free", activeModel: "gemma-4-31b-it" });
7681
- setMessages((prev) => {
7682
- setCompletedIndex(prev.length + 1);
7683
- return [...prev, {
7684
- id: "tier-switch-" + Date.now(),
7685
- role: "system",
7686
- text: `\u26A0\uFE0F${s}**[TIER LIMIT]** Auto-switched to Gemma (Free default).`,
7687
- isMeta: true
7688
- }];
7689
- });
7896
+ if (aiProvider === "Google") {
7897
+ defaultModel = "gemma-4-31b-it";
7898
+ modelDisplayName = "Gemma 4 (Free default)";
7899
+ } else if (aiProvider === "DeepSeek") {
7900
+ defaultModel = "deepseek-v4-flash";
7901
+ modelDisplayName = "DeepSeek Flash (Free default)";
7902
+ } else {
7903
+ defaultModel = "google/gemma-4-31b-it:free";
7904
+ modelDisplayName = "Gemma 4 (Free default)";
7905
+ }
7690
7906
  } else {
7691
- setActiveModel("gemini-3-flash-preview");
7692
- saveSettings({ apiTier: "Paid", activeModel: "gemini-3-flash-preview" });
7693
- setMessages((prev) => {
7694
- setCompletedIndex(prev.length + 1);
7695
- return [...prev, {
7696
- id: "tier-switch-" + Date.now(),
7697
- role: "system",
7698
- text: `\u26A0\uFE0F${s}**[TIER LIMIT]** Auto-switched to Gemini 3 Flash.`,
7699
- isMeta: true
7700
- }];
7701
- });
7907
+ if (aiProvider === "Google") {
7908
+ defaultModel = "gemini-3-flash-preview";
7909
+ modelDisplayName = "Gemini 3 Flash";
7910
+ } else if (aiProvider === "DeepSeek") {
7911
+ defaultModel = "deepseek-v4-flash";
7912
+ modelDisplayName = "DeepSeek Flash";
7913
+ } else {
7914
+ defaultModel = "deepseek/deepseek-v4-flash";
7915
+ modelDisplayName = "DeepSeek Flash";
7916
+ }
7702
7917
  }
7703
- }, [apiTier]);
7918
+ setActiveModel(defaultModel);
7919
+ saveSettings({ apiTier, activeModel: defaultModel });
7920
+ setMessages((prev) => {
7921
+ setCompletedIndex(prev.length + 1);
7922
+ return [...prev, {
7923
+ id: "tier-switch-" + Date.now(),
7924
+ role: "system",
7925
+ text: `\u26A0\uFE0F${s}**[TIER LIMIT]** Auto-switched to ${modelDisplayName}.`,
7926
+ isMeta: true
7927
+ }];
7928
+ });
7929
+ }, [apiTier, aiProvider, apiKey]);
7704
7930
  const terminalEnv = useMemo2(() => {
7705
7931
  const isIDE = process.env.TERM_PROGRAM === "vscode" || !!process.env.VSC_TERMINAL_URL || !!process.env.INTELLIJ_TERMINAL_COMMAND_BLOCKS;
7706
7932
  return {
@@ -8026,7 +8252,8 @@ function App({ args = [] }) {
8026
8252
  setSystemSettings(freshSettings);
8027
8253
  setProfileData(saved.profileData);
8028
8254
  setImageSettings(saved.imageSettings || { keyType: "Default", quality: "Low-High", apiKey: "" });
8029
- const key = await getAPIKey();
8255
+ const startupProvider = saved.aiProvider || "Google";
8256
+ const key = await getProviderAPIKey(startupProvider);
8030
8257
  if (key) {
8031
8258
  setApiKey(key);
8032
8259
  initAI(key);
@@ -8105,12 +8332,21 @@ function App({ args = [] }) {
8105
8332
  }, [mode, thinkingLevel, aiProvider, activeModel, showFullThinking, systemSettings, profileData, imageSettings, isInitializing, parsedArgs, apiTier]);
8106
8333
  const handleSetup = async (val) => {
8107
8334
  const key = val.trim();
8108
- const minLength = aiProvider === "OpenRouter" ? 10 : 30;
8335
+ let minLength = 30;
8336
+ if (aiProvider === "OpenRouter") minLength = 10;
8337
+ if (aiProvider === "DeepSeek") minLength = 20;
8109
8338
  if (key.length >= minLength) {
8110
- await saveAPIKey(key);
8339
+ await saveProviderAPIKey(aiProvider, key);
8111
8340
  setApiKey(key);
8112
8341
  initAI(key);
8113
- setMessages((prev) => [...prev, { role: "system", text: `\u2705 ${aiProvider} API Key saved successfully! Initialization complete.`, isMeta: true }]);
8342
+ let defaultModel = "gemma-4-31b-it";
8343
+ if (aiProvider === "OpenRouter") {
8344
+ defaultModel = "google/gemma-4-31b-it:free";
8345
+ } else if (aiProvider === "DeepSeek") {
8346
+ defaultModel = "deepseek-v4-flash";
8347
+ }
8348
+ setActiveModel(defaultModel);
8349
+ setMessages((prev) => [...prev, { role: "system", text: `\u2705 ${aiProvider} API Key saved successfully! Model set to ${defaultModel}. Initialization complete.`, isMeta: true }]);
8114
8350
  } else {
8115
8351
  setMessages((prev) => [...prev, { role: "system", text: `\u274C INVALID KEY: ${aiProvider} API keys must be at least ${minLength} characters.`, isMeta: true }]);
8116
8352
  setTempKey("");
@@ -8202,12 +8438,28 @@ function App({ args = [] }) {
8202
8438
  {
8203
8439
  cmd: "/thinking",
8204
8440
  desc: "Set AI reasoning depth",
8205
- subs: [
8206
- { cmd: "Fast", desc: "No Reasoning (Fastest)" },
8207
- { cmd: "Low", desc: "Quick Reasoning (Answers Quickly)" },
8208
- { cmd: "Medium", desc: "Balanced Reasoning (Decent Depth)" },
8209
- { cmd: "High", desc: "Deep Reasoning (Complex Problems)" },
8210
- { cmd: "xHigh", desc: "Extended Reasoning (Advanced Logic & Code)" }
8441
+ subs: aiProvider === "DeepSeek" ? [
8442
+ { cmd: "Fast", desc: "Fastest" },
8443
+ { cmd: "Standard", desc: "Standard Reasoning" },
8444
+ { cmd: "xHigh", desc: "Extended Reasoning" }
8445
+ ] : aiProvider === "OpenRouter" ? [
8446
+ { cmd: "Fast", desc: "Fastest" },
8447
+ { cmd: "Low", desc: "Quick Reasoning" },
8448
+ { cmd: "Medium", desc: "Balanced Reasoning" },
8449
+ { cmd: "High", desc: "Deep Reasoning" },
8450
+ { cmd: "xHigh", desc: "Extended Reasoning" }
8451
+ ] : activeModel && activeModel.toLowerCase().startsWith("gemini-3") ? [
8452
+ { cmd: "Fast", desc: "Fastest" },
8453
+ { cmd: "Low", desc: "Quick Reasoning" },
8454
+ { cmd: "Medium", desc: "Balanced Reasoning" },
8455
+ { cmd: "High", desc: "Deep Reasoning" }
8456
+ ] : [
8457
+ // Google General / Gemma
8458
+ { cmd: "Fast", desc: "Fastest" },
8459
+ { cmd: "Low", desc: "Quick Reasoning" },
8460
+ { cmd: "Medium", desc: "Balanced Reasoning" },
8461
+ { cmd: "High", desc: "Deep Reasoning" },
8462
+ { cmd: "xHigh", desc: "Extended Reasoning" }
8211
8463
  ]
8212
8464
  },
8213
8465
  {
@@ -8287,6 +8539,15 @@ function App({ args = [] }) {
8287
8539
  cmd: "moonshotai/kimi-k2.6",
8288
8540
  desc: "Multimodal"
8289
8541
  }
8542
+ ] : aiProvider === "DeepSeek" ? [
8543
+ {
8544
+ cmd: "deepseek-v4-flash",
8545
+ desc: "Fast & Efficient"
8546
+ },
8547
+ {
8548
+ cmd: "deepseek-v4-pro",
8549
+ desc: "High-Intelligence Reasoning"
8550
+ }
8290
8551
  ] : apiTier === "Free" ? [
8291
8552
  {
8292
8553
  cmd: "gemma-4-31b-it",
@@ -9455,7 +9716,61 @@ Selection: ${val}`,
9455
9716
  setInputConfig,
9456
9717
  saveSettings,
9457
9718
  quotas,
9458
- setMessages
9719
+ setMessages,
9720
+ aiProvider
9721
+ }
9722
+ );
9723
+ case "selectProvider":
9724
+ return /* @__PURE__ */ React13.createElement(
9725
+ CommandMenu,
9726
+ {
9727
+ title: "SELECT AI PROVIDER",
9728
+ items: [
9729
+ { label: "Google (Free/Paid)", value: "Google" },
9730
+ { label: "DeepSeek (Paid)", value: "DeepSeek" },
9731
+ { label: "OpenRouter (Free/Paid) [EXPERIMENTAL]", value: "OpenRouter" },
9732
+ { label: "Back", value: "settings" }
9733
+ ],
9734
+ onSelect: async (item) => {
9735
+ if (item.value === "settings" || item.value === "Back") {
9736
+ setActiveView("settings");
9737
+ return;
9738
+ }
9739
+ const selectedProvider = item.value;
9740
+ const key = await getProviderAPIKey(selectedProvider);
9741
+ if (key) {
9742
+ setAiProvider(selectedProvider);
9743
+ setApiKey(key);
9744
+ initAI(key);
9745
+ let defaultModel = "gemma-4-31b-it";
9746
+ if (selectedProvider === "OpenRouter") {
9747
+ defaultModel = "google/gemma-4-31b-it:free";
9748
+ } else if (selectedProvider === "DeepSeek") {
9749
+ defaultModel = "deepseek-v4-flash";
9750
+ }
9751
+ setActiveModel(defaultModel);
9752
+ saveSettings({ aiProvider: selectedProvider, activeModel: defaultModel, apiTier, quotas });
9753
+ setMessages((prev) => [
9754
+ ...prev,
9755
+ {
9756
+ role: "system",
9757
+ text: `\u2705 Switched to ${selectedProvider}! Key loaded from Vault. Model set to ${defaultModel}.`,
9758
+ isMeta: true
9759
+ }
9760
+ ]);
9761
+ setActiveView("settings");
9762
+ } else {
9763
+ setInputConfig({
9764
+ label: `Enter ${selectedProvider} API Key:`,
9765
+ key: "providerKey",
9766
+ provider: selectedProvider,
9767
+ value: "",
9768
+ returnView: "settings"
9769
+ });
9770
+ setActiveView("input");
9771
+ }
9772
+ },
9773
+ onClose: () => setActiveView("settings")
9459
9774
  }
9460
9775
  );
9461
9776
  case "apiTier":
@@ -9497,7 +9812,7 @@ Selection: ${val}`,
9497
9812
  {
9498
9813
  value: inputConfig?.value || "",
9499
9814
  onChange: (val) => setInputConfig((prev) => ({ ...prev, value: val })),
9500
- onSubmit: (val) => {
9815
+ onSubmit: async (val) => {
9501
9816
  const { key, subKey, next } = inputConfig;
9502
9817
  let newQuotas = { ...quotas };
9503
9818
  let newSettings = {};
@@ -9539,6 +9854,26 @@ Selection: ${val}`,
9539
9854
  return [...prev, { id: Date.now(), role: "system", text: `\u274C [IMAGE KEY ERROR] API key must start with sk_. Key strategy reset to Default.`, isMeta: true }];
9540
9855
  });
9541
9856
  }
9857
+ } else if (key === "providerKey") {
9858
+ const keyInput = val.trim();
9859
+ const prov = inputConfig.provider;
9860
+ await saveProviderAPIKey(prov, keyInput);
9861
+ setAiProvider(prov);
9862
+ setApiKey(keyInput);
9863
+ initAI(keyInput);
9864
+ let defaultModel = "gemma-4-31b-it";
9865
+ if (prov === "OpenRouter") {
9866
+ defaultModel = "google/gemma-4-31b-it:free";
9867
+ } else if (prov === "DeepSeek") {
9868
+ defaultModel = "deepseek-v4-flash";
9869
+ }
9870
+ setActiveModel(defaultModel);
9871
+ newSettings.aiProvider = prov;
9872
+ newSettings.activeModel = defaultModel;
9873
+ setMessages((prev) => {
9874
+ setCompletedIndex(prev.length + 1);
9875
+ return [...prev, { id: Date.now(), role: "system", text: `\u2705 ${prov} API Key saved successfully! Model set to ${defaultModel}.`, isMeta: true }];
9876
+ });
9542
9877
  }
9543
9878
  if (next) {
9544
9879
  setInputConfig(next(key === "quotas" ? newQuotas : val));
@@ -9937,8 +10272,9 @@ Selection: ${val}`,
9937
10272
  CommandMenu,
9938
10273
  {
9939
10274
  items: [
9940
- { label: "Google (Daily Free Quota)", value: "Google" },
9941
- { label: "OpenRouter (Daily Free Quota) [EXPERIMENTAL & UNSTABLE]", value: "OpenRouter" }
10275
+ { label: "Google (Free/Paid)", value: "Google" },
10276
+ { label: "DeepSeek (Paid)", value: "DeepSeek" },
10277
+ { label: "OpenRouter (Free/Paid) [EXPERIMENTAL]", value: "OpenRouter" }
9942
10278
  ],
9943
10279
  onSelect: (item) => {
9944
10280
  setAiProvider(item.value);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.20.0",
4
- "date": "2026-06-05",
3
+ "version": "1.21.1",
4
+ "date": "2026-06-06",
5
5
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
6
6
  "keywords": [
7
7
  "ai",