fluxflow-cli 1.20.0 → 1.21.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 (3) hide show
  1. package/README.md +4 -1
  2. package/dist/fluxflow.js +325 -126
  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:
@@ -1948,7 +1950,7 @@ function SettingsMenu({
1948
1950
  },
1949
1951
  isSelected ? "\u276F " : " ",
1950
1952
  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(
1953
+ ), !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
1954
  TextInput,
1953
1955
  {
1954
1956
  value: editValue,
@@ -2465,7 +2467,7 @@ Identity: Flux Flow (by Kushal Roy Chowdhury). Conversational, Sassy${mode === "
2465
2467
  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
2468
 
2467
2469
  -- AGENT LOOP RULES (PRIORITY: HIGH) --
2468
- - **MANDATORY: MUST END WITH [turn: continue] to CONTINUE loop OR [turn: finish] to END loop**
2470
+ - **MANDATORY: MUST END WITH [turn: continue] to CONTINUE loop OR [turn: finish] to END loop** \u2190 IMPORTANT EVERY RESPONSE
2469
2471
  - Tool Called? No post tool chat until [turn: continue]
2470
2472
  - NEVER USE [turn: continue] [turn:finish] together
2471
2473
 
@@ -2480,7 +2482,7 @@ CRITICAL THINKING POLICY
2480
2482
  - ALWAYS use <think> ... </think> before responding, even with simple queries/greetings
2481
2483
  - ${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
2484
  ` : ""}` : ""}` : ``}
2483
- ${TOOL_PROTOCOL(mode, osDetected, isMultiModal, aiProvider)}
2485
+ ${TOOL_PROTOCOL(mode, osDetected, aiProvider.toLowerCase() === "deepseek" ? false : isMultiModal, aiProvider)}
2484
2486
  ${projectContextBlock}
2485
2487
  -- MEMORY RULES --
2486
2488
  - Memory: ${isMemoryEnabled ? "Subtly Personalize. Auto Saves" : "OFF. Decline Remembering Memories"}
@@ -4923,7 +4925,7 @@ var init_tools = __esm({
4923
4925
  import { GoogleGenAI, ThinkingLevel, HarmBlockThreshold, HarmCategory } from "@google/genai";
4924
4926
  import path16 from "path";
4925
4927
  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;
4928
+ var client, TERMINATION_SIGNAL, stripAnsi2, fetchWithBackoff, getDeepSeekStream, getOpenRouterStream, signalTermination, TOOL_LABELS2, getToolDetail, runJanitorTask, getActiveToolContext, getContextSafeText, contextSafeReplace, getSanitizedText, detectToolCalls, initAI, generateSimpleContent, consolidatePastMemories, getAIStream;
4927
4929
  var init_ai = __esm({
4928
4930
  async "src/utils/ai.js"() {
4929
4931
  await init_prompts();
@@ -4954,6 +4956,136 @@ var init_ai = __esm({
4954
4956
  }
4955
4957
  return fetch(url, options);
4956
4958
  };
4959
+ getDeepSeekStream = async function* (apiKey, model, contents, systemInstruction, thinkingLevel, mode, isMultiModal) {
4960
+ const messages = [];
4961
+ if (systemInstruction) {
4962
+ messages.push({ role: "system", content: systemInstruction });
4963
+ }
4964
+ for (const content of contents) {
4965
+ const role = content.role === "user" ? "user" : "assistant";
4966
+ const msgContent = [];
4967
+ if (Array.isArray(content.parts)) {
4968
+ for (const part of content.parts) {
4969
+ if (part.text) {
4970
+ msgContent.push({ type: "text", text: part.text });
4971
+ } else if (part.inlineData && isMultiModal) {
4972
+ const mimeType = part.inlineData.mimeType;
4973
+ const data = part.inlineData.data;
4974
+ const isImage = mimeType.startsWith("image/");
4975
+ if (isImage) {
4976
+ msgContent.push({
4977
+ type: "image_url",
4978
+ image_url: {
4979
+ url: `data:${mimeType};base64,${data}`
4980
+ }
4981
+ });
4982
+ }
4983
+ }
4984
+ }
4985
+ } else {
4986
+ const text = content.text || "";
4987
+ if (text) msgContent.push({ type: "text", text });
4988
+ }
4989
+ messages.push({
4990
+ role,
4991
+ content: msgContent.length === 1 && msgContent[0].type === "text" ? msgContent[0].text : msgContent
4992
+ });
4993
+ }
4994
+ const requestPayload = {
4995
+ model,
4996
+ messages,
4997
+ stream: true,
4998
+ stream_options: { include_usage: true },
4999
+ temperature: mode === "Flux" ? 1 : 1.4
5000
+ };
5001
+ if (thinkingLevel !== "Fast") {
5002
+ const reasoningEffortMap = {
5003
+ "Low": "high",
5004
+ "Medium": "high",
5005
+ "High": "high",
5006
+ "xHigh": "max"
5007
+ };
5008
+ requestPayload.reasoning_effort = reasoningEffortMap[thinkingLevel] || "high";
5009
+ requestPayload.extra_body = { thinking: { type: "enabled" } };
5010
+ } else {
5011
+ requestPayload.extra_body = { thinking: { type: "disabled" } };
5012
+ }
5013
+ const response = await fetchWithBackoff("https://api.deepseek.com/chat/completions", {
5014
+ method: "POST",
5015
+ headers: {
5016
+ "Authorization": `Bearer ${apiKey}`,
5017
+ "Content-Type": "application/json"
5018
+ },
5019
+ body: JSON.stringify(requestPayload)
5020
+ });
5021
+ if (!response.ok) {
5022
+ const errData = await response.json().catch(() => ({}));
5023
+ throw new Error(`DeepSeek Error (${response.status}): ${errData.error?.message || response.statusText}`);
5024
+ }
5025
+ const reader = response.body.getReader();
5026
+ const decoder = new TextDecoder();
5027
+ let buffer = "";
5028
+ let pendingParts = [];
5029
+ let latestUsageMetadata = null;
5030
+ let lastFlushTime = Date.now();
5031
+ let hasNewData = false;
5032
+ while (true) {
5033
+ const { done, value } = await reader.read();
5034
+ if (done) {
5035
+ if (hasNewData && (pendingParts.length > 0 || latestUsageMetadata)) {
5036
+ yield {
5037
+ candidates: pendingParts.length > 0 ? [{ content: { parts: pendingParts } }] : [],
5038
+ usageMetadata: latestUsageMetadata
5039
+ };
5040
+ }
5041
+ break;
5042
+ }
5043
+ buffer += decoder.decode(value, { stream: true });
5044
+ const lines = buffer.split("\n");
5045
+ buffer = lines.pop();
5046
+ for (const line of lines) {
5047
+ const cleanLine = line.trim();
5048
+ if (!cleanLine || !cleanLine.startsWith("data: ")) continue;
5049
+ if (cleanLine === "data: [DONE]") break;
5050
+ try {
5051
+ const json = JSON.parse(cleanLine.substring(6));
5052
+ const delta = json.choices?.[0]?.delta;
5053
+ const usage = json.usage;
5054
+ if (usage) {
5055
+ latestUsageMetadata = {
5056
+ totalTokenCount: usage.total_tokens || usage.prompt_tokens + usage.completion_tokens,
5057
+ promptTokenCount: usage.prompt_tokens || 0,
5058
+ candidatesTokenCount: usage.completion_tokens || 0,
5059
+ cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || 0,
5060
+ thoughtsTokenCount: usage.completion_tokens_details?.reasoning_tokens || 0
5061
+ };
5062
+ hasNewData = true;
5063
+ }
5064
+ if (delta) {
5065
+ const thought = delta.reasoning_content || null;
5066
+ if (thought) {
5067
+ pendingParts.push({ text: thought, thought: true });
5068
+ hasNewData = true;
5069
+ }
5070
+ if (delta.content) {
5071
+ pendingParts.push({ text: delta.content });
5072
+ hasNewData = true;
5073
+ }
5074
+ }
5075
+ } catch (e) {
5076
+ }
5077
+ }
5078
+ if (Date.now() - lastFlushTime >= 350 && hasNewData) {
5079
+ yield {
5080
+ candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
5081
+ usageMetadata: latestUsageMetadata
5082
+ };
5083
+ pendingParts = [];
5084
+ lastFlushTime = Date.now();
5085
+ hasNewData = false;
5086
+ }
5087
+ }
5088
+ };
4957
5089
  getOpenRouterStream = async function* (apiKey, model, contents, systemInstruction, thinkingLevel, mode, isMultiModal) {
4958
5090
  const messages = [];
4959
5091
  if (systemInstruction) {
@@ -5030,9 +5162,21 @@ var init_ai = __esm({
5030
5162
  const reader = response.body.getReader();
5031
5163
  const decoder = new TextDecoder();
5032
5164
  let buffer = "";
5165
+ let pendingParts = [];
5166
+ let latestUsageMetadata = null;
5167
+ let lastFlushTime = Date.now();
5168
+ let hasNewData = false;
5033
5169
  while (true) {
5034
5170
  const { done, value } = await reader.read();
5035
- if (done) break;
5171
+ if (done) {
5172
+ if (hasNewData && (pendingParts.length > 0 || latestUsageMetadata)) {
5173
+ yield {
5174
+ candidates: pendingParts.length > 0 ? [{ content: { parts: pendingParts } }] : [],
5175
+ usageMetadata: latestUsageMetadata
5176
+ };
5177
+ }
5178
+ break;
5179
+ }
5036
5180
  buffer += decoder.decode(value, { stream: true });
5037
5181
  const lines = buffer.split("\n");
5038
5182
  buffer = lines.pop();
@@ -5044,36 +5188,39 @@ var init_ai = __esm({
5044
5188
  const json = JSON.parse(cleanLine.substring(6));
5045
5189
  const delta = json.choices?.[0]?.delta;
5046
5190
  const usage = json.usage;
5047
- let usageMetadata = null;
5048
5191
  if (usage) {
5049
- usageMetadata = {
5192
+ latestUsageMetadata = {
5050
5193
  totalTokenCount: usage.total_tokens || usage.prompt_tokens + usage.completion_tokens,
5051
5194
  promptTokenCount: usage.prompt_tokens || 0,
5052
5195
  candidatesTokenCount: usage.completion_tokens || 0,
5053
5196
  cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || 0,
5054
5197
  thoughtsTokenCount: usage.completion_tokens_details?.reasoning_tokens || 0
5055
5198
  };
5199
+ hasNewData = true;
5056
5200
  }
5057
5201
  if (delta) {
5058
5202
  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
- };
5203
+ if (thought) {
5204
+ pendingParts.push({ text: thought, thought: true });
5205
+ hasNewData = true;
5206
+ }
5207
+ if (delta.content) {
5208
+ pendingParts.push({ text: delta.content });
5209
+ hasNewData = true;
5067
5210
  }
5068
- } else if (usageMetadata) {
5069
- yield {
5070
- candidates: [],
5071
- usageMetadata
5072
- };
5073
5211
  }
5074
5212
  } catch (e) {
5075
5213
  }
5076
5214
  }
5215
+ if (Date.now() - lastFlushTime >= 350 && hasNewData) {
5216
+ yield {
5217
+ candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
5218
+ usageMetadata: latestUsageMetadata
5219
+ };
5220
+ pendingParts = [];
5221
+ lastFlushTime = Date.now();
5222
+ hasNewData = false;
5223
+ }
5077
5224
  }
5078
5225
  };
5079
5226
  signalTermination = () => {
@@ -5181,6 +5328,20 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
5181
5328
  const iterator2 = stream[Symbol.asyncIterator]();
5182
5329
  const firstResult2 = await iterator2.next();
5183
5330
  return { iterator: iterator2, firstResult: firstResult2 };
5331
+ } else if (aiProvider === "DeepSeek") {
5332
+ const stream = getDeepSeekStream(
5333
+ apiKey,
5334
+ "deepseek-chat",
5335
+ janitorContents,
5336
+ janitorPrompt,
5337
+ "Fast",
5338
+ // Janitor always minimal
5339
+ mode,
5340
+ false
5341
+ );
5342
+ const iterator2 = stream[Symbol.asyncIterator]();
5343
+ const firstResult2 = await iterator2.next();
5344
+ return { iterator: iterator2, firstResult: firstResult2 };
5184
5345
  } else {
5185
5346
  const stream = await client.models.generateContentStream({
5186
5347
  model: janitorModel || "gemma-4-26b-a4b-it",
@@ -5524,8 +5685,41 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
5524
5685
  client = new GoogleGenAI({ apiKey });
5525
5686
  return client;
5526
5687
  };
5688
+ generateSimpleContent = async (settings, model, contents, systemInstruction, thinkingLevel = "Fast") => {
5689
+ const { aiProvider = "Google", apiKey, mode } = settings;
5690
+ let fullText = "";
5691
+ let usageMetadata = null;
5692
+ let stream;
5693
+ if (aiProvider === "OpenRouter") {
5694
+ stream = getOpenRouterStream(apiKey, model, contents, systemInstruction, thinkingLevel, mode, false);
5695
+ } else if (aiProvider === "DeepSeek") {
5696
+ stream = getDeepSeekStream(apiKey, model, contents, systemInstruction, thinkingLevel, mode, false);
5697
+ } else {
5698
+ const genStream = await client.models.generateContentStream({
5699
+ model,
5700
+ contents,
5701
+ config: {
5702
+ systemInstruction,
5703
+ maxOutputTokens: 2048,
5704
+ temperature: 0.3,
5705
+ thinkingConfig: { includeThoughts: false, thinkingLevel: ThinkingLevel.MINIMAL }
5706
+ }
5707
+ });
5708
+ stream = genStream;
5709
+ }
5710
+ for await (const chunk of stream) {
5711
+ if (chunk.candidates?.[0]?.content?.parts) {
5712
+ for (const part of chunk.candidates[0].content.parts) {
5713
+ if (part.text && !part.thought) fullText += part.text;
5714
+ }
5715
+ }
5716
+ if (chunk.usageMetadata) usageMetadata = chunk.usageMetadata;
5717
+ }
5718
+ return { text: fullText, usageMetadata };
5719
+ };
5527
5720
  consolidatePastMemories = async (currentChatId, settings) => {
5528
5721
  try {
5722
+ const { aiProvider = "Google" } = settings;
5529
5723
  const tempStorage = readEncryptedJson(TEMP_MEM_FILE, {});
5530
5724
  const totalMemoriesCount = Object.values(tempStorage).flat().length;
5531
5725
  if (totalMemoriesCount <= 2) return;
@@ -5573,23 +5767,13 @@ ${newMemoryListStr}
5573
5767
  let attempts = 0;
5574
5768
  const maxAttempts = 3;
5575
5769
  let success = false;
5770
+ let targetModel = "gemini-3.1-flash-lite";
5771
+ if (aiProvider === "OpenRouter") targetModel = "google/gemma-4-26b-a4b-it:free";
5772
+ if (aiProvider === "DeepSeek") targetModel = "deepseek-v4-flash";
5576
5773
  while (attempts < maxAttempts && !success) {
5577
5774
  attempts++;
5578
5775
  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
- });
5776
+ const response = await generateSimpleContent(settings, targetModel, prompt, null, "Fast");
5593
5777
  const responseText = response.text || "";
5594
5778
  const janitorToolCalls = detectToolCalls(responseText);
5595
5779
  if (janitorToolCalls.length === 0) {
@@ -5601,6 +5785,9 @@ ${newMemoryListStr}
5601
5785
  await dispatchTool(toolName, janitorToolCall.args, { chatId: currentChatId });
5602
5786
  }
5603
5787
  }
5788
+ if (response.usageMetadata) {
5789
+ await addToUsage("tokens", response.usageMetadata.totalTokenCount || 0);
5790
+ }
5604
5791
  success = true;
5605
5792
  } catch (err) {
5606
5793
  if (attempts >= maxAttempts) {
@@ -5653,66 +5840,22 @@ Provide a new consolidated summary of the entire session.` : `Here is the conver
5653
5840
  ${flattenedText2}
5654
5841
 
5655
5842
  Provide a consolidated summary of the entire session.`;
5843
+ let targetModel = "gemini-3.1-flash-lite";
5844
+ if (aiProvider === "OpenRouter") targetModel = "google/gemma-4-26b-a4b-it:free";
5845
+ if (aiProvider === "DeepSeek") targetModel = "deepseek-v4-flash";
5656
5846
  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
- });
5847
+ const response = await generateSimpleContent(settings, targetModel, prompt, systemInstruction, "Fast");
5673
5848
  return response.text || "";
5674
5849
  } 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) {
5850
+ if (aiProvider === "Google") {
5694
5851
  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) {
5852
+ const fallback = await generateSimpleContent(settings, "gemini-2.5-flash", prompt, systemInstruction, "Fast");
5853
+ return fallback.text || "";
5854
+ } catch (e) {
5713
5855
  return "";
5714
5856
  }
5715
5857
  }
5858
+ return "";
5716
5859
  }
5717
5860
  };
5718
5861
  const flattenedText = flattenContext(modifiedHistory);
@@ -6113,11 +6256,14 @@ ${thinkingLevel != "Fast" && aiProvider === "Google" ? `${modelName.toLowerCase(
6113
6256
  throw new Error("Error: Quota Exausted for Agent");
6114
6257
  }
6115
6258
  targetModel = modelName;
6259
+ if (aiProvider === "DeepSeek" && thinkingLevel === "Fast") {
6260
+ targetModel = "deepseek-chat";
6261
+ }
6116
6262
  if (retryCount === MAX_RETRIES - 1) {
6117
- targetModel = "gemini-3-flash-preview";
6263
+ targetModel = aiProvider === "DeepSeek" ? "deepseek-v4-flash" : "gemini-3-flash-preview";
6118
6264
  yield { type: "model_update", content: "Trying with fallback model" };
6119
6265
  } else if (retryCount === MAX_RETRIES) {
6120
- targetModel = "gemini-3.5-flash";
6266
+ targetModel = aiProvider === "DeepSeek" ? "deepseek-v4-pro" : "gemini-3.5-flash";
6121
6267
  yield { type: "model_update", content: "Trying with fallback model" };
6122
6268
  } else if (retryCount > 12 && retryCount < MAX_RETRIES - 2 && settings.apiKey !== "custom") {
6123
6269
  targetModel = "gemma-4-31b-it";
@@ -6154,6 +6300,16 @@ ${thinkingLevel != "Fast" && aiProvider === "Google" ? `${modelName.toLowerCase(
6154
6300
  mode,
6155
6301
  isMultiModal
6156
6302
  );
6303
+ } else if (aiProvider === "DeepSeek") {
6304
+ stream = getDeepSeekStream(
6305
+ settings.apiKey,
6306
+ targetModel,
6307
+ activeContents,
6308
+ currentSystemInstruction,
6309
+ thinkingLevel,
6310
+ mode,
6311
+ isMultiModal
6312
+ );
6157
6313
  } else {
6158
6314
  stream = await client.models.generateContentStream({
6159
6315
  model: targetModel || "gemma-4-31b-it",
@@ -6947,7 +7103,7 @@ Error Log can be found in ${path16.join(LOGS_DIR, "agent", "error.log")}`);
6947
7103
  const shouldContinue = toolCallPointer > 0;
6948
7104
  yield { type: "status", content: "Working..." };
6949
7105
  const cleanedTurnText = contextSafeReplace(turnText, /\[\s*(turn\s*:)?\s*(continue|finish)\s*\]/gi, "").trim();
6950
- let isActuallyFinished = hasFinish && !shouldContinue || !shouldContinue && !hasContinue;
7106
+ let isActuallyFinished = hasFinish || !shouldContinue;
6951
7107
  if (isActuallyFinished) {
6952
7108
  const fullAgentTextRaw = fullAgentResponseChunks.join("\n");
6953
7109
  const cleanedFullResponse = fullAgentTextRaw.replace(/(?:<think>|\[think\])[\s\S]*?(?:<\/think>|\[\/think\])/g, "").trim();
@@ -7663,44 +7819,63 @@ function App({ args = [] }) {
7663
7819
  const [tick, setTick] = useState10(0);
7664
7820
  const isFirstRender = useRef3(true);
7665
7821
  const isSecondRender = useRef3(true);
7822
+ const isThirdRender = useRef3(true);
7666
7823
  useEffect7(() => {
7824
+ if (!apiKey) return;
7667
7825
  if (isFirstRender.current) {
7668
7826
  isFirstRender.current = false;
7669
7827
  setTimeout(() => {
7670
7828
  isSecondRender.current = false;
7829
+ setTimeout(() => {
7830
+ isThirdRender.current = false;
7831
+ }, 1e3);
7671
7832
  }, 2e3);
7672
7833
  return;
7673
7834
  }
7674
7835
  if (isSecondRender.current) {
7675
7836
  return;
7676
7837
  }
7838
+ if (isThirdRender.current) {
7839
+ return;
7840
+ }
7677
7841
  const s = emojiSpace(2);
7842
+ let defaultModel = "";
7843
+ let modelDisplayName = "";
7678
7844
  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
- });
7845
+ if (aiProvider === "Google") {
7846
+ defaultModel = "gemma-4-31b-it";
7847
+ modelDisplayName = "Gemma 4 (Free default)";
7848
+ } else if (aiProvider === "DeepSeek") {
7849
+ defaultModel = "deepseek-v4-flash";
7850
+ modelDisplayName = "DeepSeek Flash (Free default)";
7851
+ } else {
7852
+ defaultModel = "google/gemma-4-31b-it:free";
7853
+ modelDisplayName = "Gemma 4 (Free default)";
7854
+ }
7690
7855
  } 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
- });
7856
+ if (aiProvider === "Google") {
7857
+ defaultModel = "gemini-3-flash-preview";
7858
+ modelDisplayName = "Gemini 3 Flash";
7859
+ } else if (aiProvider === "DeepSeek") {
7860
+ defaultModel = "deepseek-v4-flash";
7861
+ modelDisplayName = "DeepSeek Flash";
7862
+ } else {
7863
+ defaultModel = "deepseek/deepseek-v4-flash";
7864
+ modelDisplayName = "DeepSeek Flash";
7865
+ }
7702
7866
  }
7703
- }, [apiTier]);
7867
+ setActiveModel(defaultModel);
7868
+ saveSettings({ apiTier, activeModel: defaultModel });
7869
+ setMessages((prev) => {
7870
+ setCompletedIndex(prev.length + 1);
7871
+ return [...prev, {
7872
+ id: "tier-switch-" + Date.now(),
7873
+ role: "system",
7874
+ text: `\u26A0\uFE0F${s}**[TIER LIMIT]** Auto-switched to ${modelDisplayName}.`,
7875
+ isMeta: true
7876
+ }];
7877
+ });
7878
+ }, [apiTier, aiProvider, apiKey]);
7704
7879
  const terminalEnv = useMemo2(() => {
7705
7880
  const isIDE = process.env.TERM_PROGRAM === "vscode" || !!process.env.VSC_TERMINAL_URL || !!process.env.INTELLIJ_TERMINAL_COMMAND_BLOCKS;
7706
7881
  return {
@@ -8105,12 +8280,21 @@ function App({ args = [] }) {
8105
8280
  }, [mode, thinkingLevel, aiProvider, activeModel, showFullThinking, systemSettings, profileData, imageSettings, isInitializing, parsedArgs, apiTier]);
8106
8281
  const handleSetup = async (val) => {
8107
8282
  const key = val.trim();
8108
- const minLength = aiProvider === "OpenRouter" ? 10 : 30;
8283
+ let minLength = 30;
8284
+ if (aiProvider === "OpenRouter") minLength = 10;
8285
+ if (aiProvider === "DeepSeek") minLength = 20;
8109
8286
  if (key.length >= minLength) {
8110
8287
  await saveAPIKey(key);
8111
8288
  setApiKey(key);
8112
8289
  initAI(key);
8113
- setMessages((prev) => [...prev, { role: "system", text: `\u2705 ${aiProvider} API Key saved successfully! Initialization complete.`, isMeta: true }]);
8290
+ let defaultModel = "gemma-4-31b-it";
8291
+ if (aiProvider === "OpenRouter") {
8292
+ defaultModel = "google/gemma-4-31b-it:free";
8293
+ } else if (aiProvider === "DeepSeek") {
8294
+ defaultModel = "deepseek-v4-flash";
8295
+ }
8296
+ setActiveModel(defaultModel);
8297
+ setMessages((prev) => [...prev, { role: "system", text: `\u2705 ${aiProvider} API Key saved successfully! Model set to ${defaultModel}. Initialization complete.`, isMeta: true }]);
8114
8298
  } else {
8115
8299
  setMessages((prev) => [...prev, { role: "system", text: `\u274C INVALID KEY: ${aiProvider} API keys must be at least ${minLength} characters.`, isMeta: true }]);
8116
8300
  setTempKey("");
@@ -8202,12 +8386,16 @@ function App({ args = [] }) {
8202
8386
  {
8203
8387
  cmd: "/thinking",
8204
8388
  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)" }
8389
+ subs: aiProvider === "DeepSeek" ? [
8390
+ { cmd: "Fast", desc: "Fastest" },
8391
+ { cmd: "Standard", desc: "Standard Reasoning" },
8392
+ { cmd: "xHigh", desc: "Extended Reasoning" }
8393
+ ] : [
8394
+ { cmd: "Fast", desc: "Fastest" },
8395
+ { cmd: "Low", desc: "Quick Reasoning" },
8396
+ { cmd: "Medium", desc: "Balanced Reasoning" },
8397
+ { cmd: "High", desc: "Deep Reasoning" },
8398
+ { cmd: "xHigh", desc: "Extended Reasoning" }
8211
8399
  ]
8212
8400
  },
8213
8401
  {
@@ -8287,6 +8475,15 @@ function App({ args = [] }) {
8287
8475
  cmd: "moonshotai/kimi-k2.6",
8288
8476
  desc: "Multimodal"
8289
8477
  }
8478
+ ] : aiProvider === "DeepSeek" ? [
8479
+ {
8480
+ cmd: "deepseek-v4-flash",
8481
+ desc: "Fast & Efficient"
8482
+ },
8483
+ {
8484
+ cmd: "deepseek-v4-pro",
8485
+ desc: "High-Intelligence Reasoning"
8486
+ }
8290
8487
  ] : apiTier === "Free" ? [
8291
8488
  {
8292
8489
  cmd: "gemma-4-31b-it",
@@ -9455,7 +9652,8 @@ Selection: ${val}`,
9455
9652
  setInputConfig,
9456
9653
  saveSettings,
9457
9654
  quotas,
9458
- setMessages
9655
+ setMessages,
9656
+ aiProvider
9459
9657
  }
9460
9658
  );
9461
9659
  case "apiTier":
@@ -9937,8 +10135,9 @@ Selection: ${val}`,
9937
10135
  CommandMenu,
9938
10136
  {
9939
10137
  items: [
9940
- { label: "Google (Daily Free Quota)", value: "Google" },
9941
- { label: "OpenRouter (Daily Free Quota) [EXPERIMENTAL & UNSTABLE]", value: "OpenRouter" }
10138
+ { label: "Google (Free/Paid)", value: "Google" },
10139
+ { label: "DeepSeek (Paid)", value: "DeepSeek" },
10140
+ { label: "OpenRouter (Free/Paid) [EXPERIMENTAL]", value: "OpenRouter" }
9942
10141
  ],
9943
10142
  onSelect: (item) => {
9944
10143
  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.0",
4
+ "date": "2026-06-06",
5
5
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
6
6
  "keywords": [
7
7
  "ai",