@zds-ai/cli 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -229,6 +229,7 @@ export declare class GrokAgent extends EventEmitter {
229
229
  baseUrl: string;
230
230
  apiKeyEnvVar: string;
231
231
  model: string;
232
+ supportsVision: boolean;
232
233
  };
233
234
  /**
234
235
  * Restore session state from persistence
@@ -249,7 +250,14 @@ export declare class GrokAgent extends EventEmitter {
249
250
  baseUrl?: string;
250
251
  apiKeyEnvVar?: string;
251
252
  model?: string;
253
+ supportsVision?: boolean;
252
254
  }): Promise<void>;
255
+ /**
256
+ * Compact conversation context by keeping system prompt and last N messages
257
+ * Reduces context size when it grows too large for backend to handle
258
+ * @returns Number of messages removed
259
+ */
260
+ compactContext(keepLastMessages?: number): number;
253
261
  /**
254
262
  * Get all tool instances and their class names for display purposes
255
263
  */
@@ -5,6 +5,7 @@ import { ChatHistoryManager } from "../utils/chat-history-manager.js";
5
5
  import { logApiError } from "../utils/error-logger.js";
6
6
  import { parseImagesFromMessage, hasImageReferences } from "../utils/image-encoder.js";
7
7
  import { getTextContent } from "../utils/content-utils.js";
8
+ import { Variable } from "./prompt-variables.js";
8
9
  import fs from "fs";
9
10
  import { TextEditorTool, MorphEditorTool, ZshTool, ConfirmationTool, SearchTool, EnvTool, IntrospectTool, ClearCacheTool, CharacterTool, TaskTool, InternetTool, ImageTool, FileConversionTool, RestartTool } from "../tools/index.js";
10
11
  import { EventEmitter } from "events";
@@ -413,25 +414,26 @@ Current working directory: ${process.cwd()}`;
413
414
  }
414
415
  }
415
416
  }
417
+ // Clear one-shot variables
418
+ Variable.clearOneShot();
419
+ // Parse images once if present (for both text extraction and later assembly)
420
+ const parsed = hasImageReferences(messageToSend)
421
+ ? parseImagesFromMessage(messageToSend)
422
+ : { text: messageToSend, images: [] };
423
+ // Set USER:PROMPT variable (text only, images stripped)
424
+ Variable.set("USER:PROMPT", parsed.text);
425
+ // Assemble final message from variables
426
+ const assembledMessage = Variable.renderFull("USER");
416
427
  // Add user/system message to conversation
417
- // Check for image references and parse if present
418
428
  // Note: System messages can only have string content, so images are only supported for user messages
419
429
  const supportsVision = this.grokClient.getSupportsVision();
420
- let messageContent = messageToSend;
421
- if (messageType === "user" && hasImageReferences(messageToSend) && supportsVision) {
422
- // Parse images from message (only for user messages)
423
- const parsed = parseImagesFromMessage(messageToSend);
424
- if (parsed.images.length > 0) {
425
- // Construct content array with text and images
426
- messageContent = [
427
- { type: "text", text: parsed.text },
428
- ...parsed.images
429
- ];
430
- }
431
- else {
432
- // No valid images found (errors will be in parsed.text)
433
- messageContent = parsed.text;
434
- }
430
+ let messageContent = assembledMessage;
431
+ if (messageType === "user" && parsed.images.length > 0 && supportsVision) {
432
+ // Construct content array with assembled text and images
433
+ messageContent = [
434
+ { type: "text", text: assembledMessage },
435
+ ...parsed.images
436
+ ];
435
437
  }
436
438
  const userEntry = {
437
439
  type: messageType,
@@ -691,6 +693,22 @@ Current working directory: ${process.cwd()}`;
691
693
  return newEntries;
692
694
  }
693
695
  catch (error) {
696
+ // Check if context is too large (413 error when vision already disabled)
697
+ if (error.message && error.message.startsWith('CONTEXT_TOO_LARGE:')) {
698
+ const beforeCount = this.chatHistory.length;
699
+ this.compactContext(20);
700
+ const afterCount = this.chatHistory.length;
701
+ const removedCount = beforeCount - afterCount;
702
+ const compactEntry = {
703
+ type: "system",
704
+ content: `Context was too large for backend. Automatically compacted: removed ${removedCount} older messages, keeping last 20 messages. Please retry your request.`,
705
+ timestamp: new Date(),
706
+ };
707
+ this.chatHistory.push(compactEntry);
708
+ // Mark first message as processed
709
+ this.firstMessageProcessed = true;
710
+ return [userEntry, compactEntry];
711
+ }
694
712
  const errorEntry = {
695
713
  type: "assistant",
696
714
  content: `Sorry, I encountered an error: ${error.message}`,
@@ -862,25 +880,26 @@ Current working directory: ${process.cwd()}`;
862
880
  }
863
881
  }
864
882
  }
883
+ // Clear one-shot variables
884
+ Variable.clearOneShot();
885
+ // Parse images once if present (for both text extraction and later assembly)
886
+ const parsed = hasImageReferences(messageToSend)
887
+ ? parseImagesFromMessage(messageToSend)
888
+ : { text: messageToSend, images: [] };
889
+ // Set USER:PROMPT variable (text only, images stripped)
890
+ Variable.set("USER:PROMPT", parsed.text);
891
+ // Assemble final message from variables
892
+ const assembledMessage = Variable.renderFull("USER");
865
893
  // Add user/system message to both API conversation and chat history
866
- // Check for image references and parse if present
867
894
  // Note: System messages can only have string content, so images are only supported for user messages
868
895
  const supportsVision = this.grokClient.getSupportsVision();
869
- let messageContent = messageToSend;
870
- if (messageType === "user" && hasImageReferences(messageToSend) && supportsVision) {
871
- // Parse images from message (only for user messages)
872
- const parsed = parseImagesFromMessage(messageToSend);
873
- if (parsed.images.length > 0) {
874
- // Construct content array with text and images
875
- messageContent = [
876
- { type: "text", text: parsed.text },
877
- ...parsed.images
878
- ];
879
- }
880
- else {
881
- // No valid images found (errors will be in parsed.text)
882
- messageContent = parsed.text;
883
- }
896
+ let messageContent = assembledMessage;
897
+ if (messageType === "user" && parsed.images.length > 0 && supportsVision) {
898
+ // Construct content array with assembled text and images
899
+ messageContent = [
900
+ { type: "text", text: assembledMessage },
901
+ ...parsed.images
902
+ ];
884
903
  }
885
904
  const userEntry = {
886
905
  type: messageType,
@@ -894,7 +913,7 @@ Current working directory: ${process.cwd()}`;
894
913
  }
895
914
  else {
896
915
  // System messages must have string content only
897
- this.messages.push({ role: "system", content: typeof messageContent === "string" ? messageContent : messageToSend });
916
+ this.messages.push({ role: "system", content: typeof messageContent === "string" ? messageContent : assembledMessage });
898
917
  }
899
918
  await this.emitContextChange();
900
919
  // Yield user message so UI can display it immediately
@@ -1237,6 +1256,25 @@ Current working directory: ${process.cwd()}`;
1237
1256
  yield { type: "done" };
1238
1257
  return;
1239
1258
  }
1259
+ // Check if context is too large (413 error when vision already disabled)
1260
+ if (error.message && error.message.startsWith('CONTEXT_TOO_LARGE:')) {
1261
+ const beforeCount = this.chatHistory.length;
1262
+ this.compactContext(20);
1263
+ const afterCount = this.chatHistory.length;
1264
+ const removedCount = beforeCount - afterCount;
1265
+ const compactEntry = {
1266
+ type: "system",
1267
+ content: `Context was too large for backend. Automatically compacted: removed ${removedCount} older messages, keeping last 20 messages. Please retry your request.`,
1268
+ timestamp: new Date(),
1269
+ };
1270
+ this.chatHistory.push(compactEntry);
1271
+ yield {
1272
+ type: "content",
1273
+ content: getTextContent(compactEntry.content),
1274
+ };
1275
+ yield { type: "done" };
1276
+ return;
1277
+ }
1240
1278
  const errorEntry = {
1241
1279
  type: "assistant",
1242
1280
  content: `Sorry, I encountered an error: ${error.message}`,
@@ -1496,7 +1534,7 @@ Current working directory: ${process.cwd()}`;
1496
1534
  case "generateImage":
1497
1535
  return await this.imageTool.generateImage(args.prompt, args.negativePrompt, args.width, args.height, args.model, args.sampler, args.configScale, args.numSteps, args.nsfw, args.name, args.move, args.seed);
1498
1536
  case "captionImage":
1499
- return await this.imageTool.captionImage(args.filename, args.prompt);
1537
+ return await this.imageTool.captionImage(args.filename, args.backend);
1500
1538
  case "pngInfo":
1501
1539
  return await this.imageTool.pngInfo(args.filename);
1502
1540
  case "listImageModels":
@@ -2038,6 +2076,8 @@ Current working directory: ${process.cwd()}`;
2038
2076
  }
2039
2077
  setModel(model) {
2040
2078
  this.grokClient.setModel(model);
2079
+ // Reset supportsVision flag for new model
2080
+ this.grokClient.setSupportsVision(true);
2041
2081
  // Update token counter for new model
2042
2082
  this.tokenCounter.dispose();
2043
2083
  this.tokenCounter = createTokenCounter(model);
@@ -2412,6 +2452,7 @@ Current working directory: ${process.cwd()}`;
2412
2452
  baseUrl: this.grokClient.getBaseURL(),
2413
2453
  apiKeyEnvVar: this.apiKeyEnvVar,
2414
2454
  model: this.getCurrentModel(),
2455
+ supportsVision: this.grokClient.getSupportsVision(),
2415
2456
  };
2416
2457
  }
2417
2458
  /**
@@ -2441,6 +2482,10 @@ Current working directory: ${process.cwd()}`;
2441
2482
  const model = state.model || this.getCurrentModel();
2442
2483
  this.grokClient = new GrokClient(apiKey, model, state.baseUrl, state.backend);
2443
2484
  this.apiKeyEnvVar = state.apiKeyEnvVar;
2485
+ // Restore supportsVision flag if present
2486
+ if (state.supportsVision !== undefined) {
2487
+ this.grokClient.setSupportsVision(state.supportsVision);
2488
+ }
2444
2489
  // Reinitialize MCP servers when restoring session
2445
2490
  try {
2446
2491
  const config = loadMCPConfig();
@@ -2494,6 +2539,58 @@ Current working directory: ${process.cwd()}`;
2494
2539
  }
2495
2540
  }
2496
2541
  }
2542
+ /**
2543
+ * Compact conversation context by keeping system prompt and last N messages
2544
+ * Reduces context size when it grows too large for backend to handle
2545
+ * @returns Number of messages removed
2546
+ */
2547
+ compactContext(keepLastMessages = 20) {
2548
+ if (this.chatHistory.length <= keepLastMessages) {
2549
+ // Nothing to compact
2550
+ return 0;
2551
+ }
2552
+ const removedCount = this.chatHistory.length - keepLastMessages;
2553
+ const keptMessages = this.chatHistory.slice(-keepLastMessages);
2554
+ // Clear both arrays
2555
+ this.chatHistory = keptMessages;
2556
+ this.messages = [];
2557
+ // Add system message noting the compaction
2558
+ const compactionNote = {
2559
+ type: 'system',
2560
+ content: `Context compacted: removed ${removedCount} older messages, keeping last ${keepLastMessages} messages.`,
2561
+ timestamp: new Date()
2562
+ };
2563
+ this.chatHistory.push(compactionNote);
2564
+ // Rebuild this.messages from compacted chatHistory
2565
+ for (const entry of this.chatHistory) {
2566
+ if (entry.type === 'system') {
2567
+ this.messages.push({
2568
+ role: 'system',
2569
+ content: entry.content
2570
+ });
2571
+ }
2572
+ else if (entry.type === 'user') {
2573
+ this.messages.push({
2574
+ role: 'user',
2575
+ content: entry.content
2576
+ });
2577
+ }
2578
+ else if (entry.type === 'assistant') {
2579
+ this.messages.push({
2580
+ role: 'assistant',
2581
+ content: entry.content
2582
+ });
2583
+ }
2584
+ else if (entry.type === 'tool_result') {
2585
+ this.messages.push({
2586
+ role: 'tool',
2587
+ tool_call_id: entry.toolResult.output || '',
2588
+ content: JSON.stringify(entry.toolResult)
2589
+ });
2590
+ }
2591
+ }
2592
+ return removedCount;
2593
+ }
2497
2594
  /**
2498
2595
  * Get all tool instances and their class names for display purposes
2499
2596
  */