@zds-ai/cli 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/agent/grok-agent.d.ts +15 -1
- package/dist/agent/grok-agent.js +176 -111
- package/dist/agent/grok-agent.js.map +1 -1
- package/dist/bin/generate_image_sd.sh +252 -0
- package/dist/grok/client.d.ts +3 -0
- package/dist/grok/client.js +62 -1
- package/dist/grok/client.js.map +1 -1
- package/dist/grok/tools.js +12 -0
- package/dist/grok/tools.js.map +1 -1
- package/dist/hooks/use-input-handler.d.ts +2 -0
- package/dist/hooks/use-input-handler.js +70 -44
- package/dist/hooks/use-input-handler.js.map +1 -1
- package/dist/index.js +97 -7
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.js +3 -4
- package/dist/mcp/client.js.map +1 -1
- package/dist/tools/file-conversion-tool.js +4 -11
- package/dist/tools/file-conversion-tool.js.map +1 -1
- package/dist/tools/image-tool.d.ts +6 -1
- package/dist/tools/image-tool.js +53 -3
- package/dist/tools/image-tool.js.map +1 -1
- package/dist/ui/components/chat-history.js +25 -12
- package/dist/ui/components/chat-history.js.map +1 -1
- package/dist/ui/components/chat-interface.js +16 -9
- package/dist/ui/components/chat-interface.js.map +1 -1
- package/dist/ui/components/rephrase-menu.d.ts +8 -0
- package/dist/ui/components/rephrase-menu.js +25 -0
- package/dist/ui/components/rephrase-menu.js.map +1 -0
- package/dist/utils/chat-history-manager.js +16 -6
- package/dist/utils/chat-history-manager.js.map +1 -1
- package/dist/utils/content-utils.d.ts +5 -0
- package/dist/utils/content-utils.js +15 -0
- package/dist/utils/content-utils.js.map +1 -0
- package/dist/utils/image-encoder.d.ts +35 -0
- package/dist/utils/image-encoder.js +133 -0
- package/dist/utils/image-encoder.js.map +1 -0
- package/dist/utils/rephrase-handler.d.ts +15 -0
- package/dist/utils/rephrase-handler.js +106 -0
- package/dist/utils/rephrase-handler.js.map +1 -0
- package/dist/utils/slash-commands.js +1 -23
- package/dist/utils/slash-commands.js.map +1 -1
- package/dist/utils/token-counter.js +17 -2
- package/dist/utils/token-counter.js.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -339,6 +339,38 @@ Add to `~/.grok/user-settings.json`:
|
|
|
339
339
|
|
|
340
340
|
**Model Priority**: `--model` flag > `GROK_MODEL` environment variable > user default model > system default (grok-code-fast-1)
|
|
341
341
|
|
|
342
|
+
### Image Support
|
|
343
|
+
|
|
344
|
+
zai-cli supports sending images to vision-capable AI models. Use the `@` prefix to reference image files in your messages:
|
|
345
|
+
|
|
346
|
+
```sh
|
|
347
|
+
# Absolute path
|
|
348
|
+
zai-cli --prompt "What's in this image? @/Users/joseph/photos/image.jpg"
|
|
349
|
+
|
|
350
|
+
# Relative path
|
|
351
|
+
zai-cli --prompt "Analyze @./screenshot.png"
|
|
352
|
+
|
|
353
|
+
# Tilde expansion
|
|
354
|
+
zai-cli --prompt "Describe @~/Pictures/photo.jpg"
|
|
355
|
+
|
|
356
|
+
# Paths with spaces (quoted)
|
|
357
|
+
zai-cli --prompt 'Compare these images: @"~/My Pictures/photo1.jpg" @"~/My Pictures/photo2.jpg"'
|
|
358
|
+
|
|
359
|
+
# Paths with spaces (escaped)
|
|
360
|
+
zai-cli --prompt "What's here? @/Users/joseph/My\ Documents/image.png"
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Supported Image Formats**: .jpg, .jpeg, .png, .gif, .webp, .bmp
|
|
364
|
+
|
|
365
|
+
**Vision-Capable Models**: Image support works with vision models like:
|
|
366
|
+
- `grok-4-1-fast-reasoning`
|
|
367
|
+
- `grok-vision-beta`
|
|
368
|
+
- Other vision-enabled models (via custom base URLs)
|
|
369
|
+
|
|
370
|
+
**Automatic Fallback**: If you send an image to a model that doesn't support vision, zai-cli will automatically detect the error and retry with text-only content.
|
|
371
|
+
|
|
372
|
+
**Interactive Mode**: The `@` syntax works in both interactive and headless (`--prompt`) modes.
|
|
373
|
+
|
|
342
374
|
### Command Line Options
|
|
343
375
|
|
|
344
376
|
```sh
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { GrokMessage, GrokToolCall } from "../grok/client.js";
|
|
2
|
+
import type { ChatCompletionContentPart } from "openai/resources/chat/completions.js";
|
|
2
3
|
import { ToolResult } from "../types/index.js";
|
|
3
4
|
import { EventEmitter } from "events";
|
|
4
5
|
export interface ChatEntry {
|
|
5
6
|
type: "user" | "assistant" | "tool_result" | "tool_call" | "system";
|
|
6
|
-
content?: string;
|
|
7
|
+
content?: string | ChatCompletionContentPart[];
|
|
7
8
|
timestamp: Date;
|
|
8
9
|
tool_calls?: GrokToolCall[];
|
|
9
10
|
toolCall?: GrokToolCall;
|
|
@@ -15,6 +16,10 @@ export interface ChatEntry {
|
|
|
15
16
|
};
|
|
16
17
|
isStreaming?: boolean;
|
|
17
18
|
preserveFormatting?: boolean;
|
|
19
|
+
metadata?: {
|
|
20
|
+
rephrased_note?: string;
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
};
|
|
18
23
|
}
|
|
19
24
|
export interface StreamingChunk {
|
|
20
25
|
type: "content" | "tool_calls" | "tool_result" | "done" | "token_count" | "user_message";
|
|
@@ -62,6 +67,7 @@ export declare class GrokAgent extends EventEmitter {
|
|
|
62
67
|
private activeTaskColor;
|
|
63
68
|
private apiKeyEnvVar;
|
|
64
69
|
private pendingContextEditSession;
|
|
70
|
+
private rephraseState;
|
|
65
71
|
constructor(apiKey: string, baseURL?: string, model?: string, maxToolRounds?: number, debugLogFile?: string, startupHookOutput?: string, temperature?: number, maxTokens?: number);
|
|
66
72
|
private startupHookOutput?;
|
|
67
73
|
private systemPrompt;
|
|
@@ -125,6 +131,14 @@ export declare class GrokAgent extends EventEmitter {
|
|
|
125
131
|
contextFilePath: string;
|
|
126
132
|
} | null;
|
|
127
133
|
clearPendingContextEditSession(): void;
|
|
134
|
+
setRephraseState(originalAssistantMessageIndex: number, rephraseRequestIndex: number, newResponseIndex: number, messageType: "user" | "system"): void;
|
|
135
|
+
getRephraseState(): {
|
|
136
|
+
originalAssistantMessageIndex: number;
|
|
137
|
+
rephraseRequestIndex: number;
|
|
138
|
+
newResponseIndex: number;
|
|
139
|
+
messageType: "user" | "system";
|
|
140
|
+
} | null;
|
|
141
|
+
clearRephraseState(): void;
|
|
128
142
|
setPersona(persona: string, color?: string): Promise<{
|
|
129
143
|
success: boolean;
|
|
130
144
|
error?: string;
|
package/dist/agent/grok-agent.js
CHANGED
|
@@ -3,6 +3,8 @@ import { getAllGrokTools, getMCPManager, initializeMCPServers, } from "../grok/t
|
|
|
3
3
|
import { loadMCPConfig } from "../mcp/config.js";
|
|
4
4
|
import { ChatHistoryManager } from "../utils/chat-history-manager.js";
|
|
5
5
|
import { logApiError } from "../utils/error-logger.js";
|
|
6
|
+
import { parseImagesFromMessage, hasImageReferences } from "../utils/image-encoder.js";
|
|
7
|
+
import { getTextContent } from "../utils/content-utils.js";
|
|
6
8
|
import fs from "fs";
|
|
7
9
|
import { TextEditorTool, MorphEditorTool, ZshTool, ConfirmationTool, SearchTool, EnvTool, IntrospectTool, ClearCacheTool, CharacterTool, TaskTool, InternetTool, ImageTool, FileConversionTool, RestartTool } from "../tools/index.js";
|
|
8
10
|
import { EventEmitter } from "events";
|
|
@@ -106,6 +108,7 @@ export class GrokAgent extends EventEmitter {
|
|
|
106
108
|
activeTaskColor = "white";
|
|
107
109
|
apiKeyEnvVar = "GROK_API_KEY";
|
|
108
110
|
pendingContextEditSession = null;
|
|
111
|
+
rephraseState = null;
|
|
109
112
|
constructor(apiKey, baseURL, model, maxToolRounds, debugLogFile, startupHookOutput, temperature, maxTokens) {
|
|
110
113
|
super();
|
|
111
114
|
const manager = getSettingsManager();
|
|
@@ -229,21 +232,24 @@ Current working directory: ${process.cwd()}`;
|
|
|
229
232
|
switch (entry.type) {
|
|
230
233
|
case "system":
|
|
231
234
|
// All system messages from chatHistory go into conversation (persona, mood, etc.)
|
|
235
|
+
// System messages must always be strings
|
|
232
236
|
historyMessages.push({
|
|
233
237
|
role: "system",
|
|
234
|
-
content: entry.content,
|
|
238
|
+
content: getTextContent(entry.content),
|
|
235
239
|
});
|
|
236
240
|
break;
|
|
237
241
|
case "user":
|
|
242
|
+
// User messages can have images (content arrays)
|
|
238
243
|
historyMessages.push({
|
|
239
244
|
role: "user",
|
|
240
|
-
content: entry.content,
|
|
245
|
+
content: entry.content || "",
|
|
241
246
|
});
|
|
242
247
|
break;
|
|
243
248
|
case "assistant":
|
|
249
|
+
// Assistant messages are always text (no images in responses)
|
|
244
250
|
const assistantMessage = {
|
|
245
251
|
role: "assistant",
|
|
246
|
-
content: entry.content || "", // Ensure content is never null/undefined
|
|
252
|
+
content: getTextContent(entry.content) || "", // Ensure content is never null/undefined
|
|
247
253
|
};
|
|
248
254
|
if (entry.tool_calls && entry.tool_calls.length > 0) {
|
|
249
255
|
// For assistant messages with tool calls, collect the tool results that correspond to them
|
|
@@ -344,6 +350,39 @@ Current working directory: ${process.cwd()}`;
|
|
|
344
350
|
return false;
|
|
345
351
|
}
|
|
346
352
|
async processUserMessage(message) {
|
|
353
|
+
// Detect rephrase commands
|
|
354
|
+
let isRephraseCommand = false;
|
|
355
|
+
let isSystemRephrase = false;
|
|
356
|
+
let messageToSend = message;
|
|
357
|
+
let messageType = "user";
|
|
358
|
+
if (message.startsWith("/system rephrase")) {
|
|
359
|
+
isRephraseCommand = true;
|
|
360
|
+
isSystemRephrase = true;
|
|
361
|
+
messageToSend = message.substring(8).trim(); // Strip "/system " (8 chars including space)
|
|
362
|
+
messageType = "system";
|
|
363
|
+
}
|
|
364
|
+
else if (message.startsWith("/rephrase")) {
|
|
365
|
+
isRephraseCommand = true;
|
|
366
|
+
messageToSend = message; // Keep full text including "/rephrase"
|
|
367
|
+
messageType = "user";
|
|
368
|
+
}
|
|
369
|
+
// If this is a rephrase command, find the last assistant message
|
|
370
|
+
if (isRephraseCommand) {
|
|
371
|
+
// Find index of last assistant message in chatHistory
|
|
372
|
+
let lastAssistantIndex = -1;
|
|
373
|
+
for (let i = this.chatHistory.length - 1; i >= 0; i--) {
|
|
374
|
+
if (this.chatHistory[i].type === "assistant") {
|
|
375
|
+
lastAssistantIndex = i;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (lastAssistantIndex === -1) {
|
|
380
|
+
throw new Error("No previous assistant message to rephrase");
|
|
381
|
+
}
|
|
382
|
+
// Store rephrase state (will be updated with newResponseIndex after response)
|
|
383
|
+
// For now, just mark that we're in rephrase mode
|
|
384
|
+
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType);
|
|
385
|
+
}
|
|
347
386
|
// Before adding the new user message, check if there are incomplete tool calls
|
|
348
387
|
// from a previous interrupted turn. This prevents malformed message sequences
|
|
349
388
|
// that cause Ollama 500 errors.
|
|
@@ -374,14 +413,40 @@ Current working directory: ${process.cwd()}`;
|
|
|
374
413
|
}
|
|
375
414
|
}
|
|
376
415
|
}
|
|
377
|
-
// Add user message to conversation
|
|
416
|
+
// Add user/system message to conversation
|
|
417
|
+
// Check for image references and parse if present
|
|
418
|
+
// Note: System messages can only have string content, so images are only supported for user messages
|
|
419
|
+
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
|
+
}
|
|
435
|
+
}
|
|
378
436
|
const userEntry = {
|
|
379
|
-
type:
|
|
380
|
-
content:
|
|
437
|
+
type: messageType,
|
|
438
|
+
content: messageContent,
|
|
381
439
|
timestamp: new Date(),
|
|
382
440
|
};
|
|
383
441
|
this.chatHistory.push(userEntry);
|
|
384
|
-
|
|
442
|
+
// Push to messages array with proper typing based on role
|
|
443
|
+
if (messageType === "user") {
|
|
444
|
+
this.messages.push({ role: "user", content: messageContent });
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
// System messages must have string content only
|
|
448
|
+
this.messages.push({ role: "system", content: typeof messageContent === "string" ? messageContent : messageToSend });
|
|
449
|
+
}
|
|
385
450
|
await this.emitContextChange();
|
|
386
451
|
const newEntries = [userEntry];
|
|
387
452
|
const maxToolRounds = this.maxToolRounds; // Prevent infinite loops
|
|
@@ -529,10 +594,11 @@ Current working directory: ${process.cwd()}`;
|
|
|
529
594
|
// Collect system messages that appeared after this assistant message
|
|
530
595
|
for (let i = assistantIndex + 1; i < this.chatHistory.length; i++) {
|
|
531
596
|
const entry = this.chatHistory[i];
|
|
532
|
-
|
|
597
|
+
const content = getTextContent(entry.content);
|
|
598
|
+
if (entry.type === 'system' && content && content.trim()) {
|
|
533
599
|
this.messages.push({
|
|
534
600
|
role: 'system',
|
|
535
|
-
content:
|
|
601
|
+
content: content
|
|
536
602
|
});
|
|
537
603
|
}
|
|
538
604
|
// Stop if we hit another assistant or user message (next turn)
|
|
@@ -573,6 +639,11 @@ Current working directory: ${process.cwd()}`;
|
|
|
573
639
|
content: trimmedContent,
|
|
574
640
|
});
|
|
575
641
|
newEntries.push(responseEntry);
|
|
642
|
+
// Update rephrase state with the new response index
|
|
643
|
+
if (this.rephraseState && this.rephraseState.newResponseIndex === -1) {
|
|
644
|
+
const newResponseIndex = this.chatHistory.length - 1;
|
|
645
|
+
this.setRephraseState(this.rephraseState.originalAssistantMessageIndex, this.rephraseState.rephraseRequestIndex, newResponseIndex, this.rephraseState.messageType);
|
|
646
|
+
}
|
|
576
647
|
}
|
|
577
648
|
// TODO: HACK - This is a temporary fix to prevent duplicate responses.
|
|
578
649
|
// We need a proper way for the bot to signal task completion, such as:
|
|
@@ -728,6 +799,39 @@ Current working directory: ${process.cwd()}`;
|
|
|
728
799
|
async *processUserMessageStream(message) {
|
|
729
800
|
// Create new abort controller for this request
|
|
730
801
|
this.abortController = new AbortController();
|
|
802
|
+
// Detect rephrase commands
|
|
803
|
+
let isRephraseCommand = false;
|
|
804
|
+
let isSystemRephrase = false;
|
|
805
|
+
let messageToSend = message;
|
|
806
|
+
let messageType = "user";
|
|
807
|
+
if (message.startsWith("/system rephrase")) {
|
|
808
|
+
isRephraseCommand = true;
|
|
809
|
+
isSystemRephrase = true;
|
|
810
|
+
messageToSend = message.substring(8).trim(); // Strip "/system " (8 chars including space)
|
|
811
|
+
messageType = "system";
|
|
812
|
+
}
|
|
813
|
+
else if (message.startsWith("/rephrase")) {
|
|
814
|
+
isRephraseCommand = true;
|
|
815
|
+
messageToSend = message; // Keep full text including "/rephrase"
|
|
816
|
+
messageType = "user";
|
|
817
|
+
}
|
|
818
|
+
// If this is a rephrase command, find the last assistant message
|
|
819
|
+
if (isRephraseCommand) {
|
|
820
|
+
// Find index of last assistant message in chatHistory
|
|
821
|
+
let lastAssistantIndex = -1;
|
|
822
|
+
for (let i = this.chatHistory.length - 1; i >= 0; i--) {
|
|
823
|
+
if (this.chatHistory[i].type === "assistant") {
|
|
824
|
+
lastAssistantIndex = i;
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (lastAssistantIndex === -1) {
|
|
829
|
+
throw new Error("No previous assistant message to rephrase");
|
|
830
|
+
}
|
|
831
|
+
// Store rephrase state (will be updated with newResponseIndex after response)
|
|
832
|
+
// For now, just mark that we're in rephrase mode
|
|
833
|
+
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType);
|
|
834
|
+
}
|
|
731
835
|
// Before adding the new user message, check if there are incomplete tool calls
|
|
732
836
|
// from a previous interrupted turn. This prevents malformed message sequences
|
|
733
837
|
// that cause Ollama 500 errors.
|
|
@@ -758,14 +862,40 @@ Current working directory: ${process.cwd()}`;
|
|
|
758
862
|
}
|
|
759
863
|
}
|
|
760
864
|
}
|
|
761
|
-
// Add user message to both API conversation and chat history
|
|
865
|
+
// Add user/system message to both API conversation and chat history
|
|
866
|
+
// Check for image references and parse if present
|
|
867
|
+
// Note: System messages can only have string content, so images are only supported for user messages
|
|
868
|
+
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
|
+
}
|
|
884
|
+
}
|
|
762
885
|
const userEntry = {
|
|
763
|
-
type:
|
|
764
|
-
content:
|
|
886
|
+
type: messageType,
|
|
887
|
+
content: messageContent,
|
|
765
888
|
timestamp: new Date(),
|
|
766
889
|
};
|
|
767
890
|
this.chatHistory.push(userEntry);
|
|
768
|
-
|
|
891
|
+
// Push to messages array with proper typing based on role
|
|
892
|
+
if (messageType === "user") {
|
|
893
|
+
this.messages.push({ role: "user", content: messageContent });
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
// System messages must have string content only
|
|
897
|
+
this.messages.push({ role: "system", content: typeof messageContent === "string" ? messageContent : messageToSend });
|
|
898
|
+
}
|
|
769
899
|
await this.emitContextChange();
|
|
770
900
|
// Yield user message so UI can display it immediately
|
|
771
901
|
yield {
|
|
@@ -934,6 +1064,11 @@ Current working directory: ${process.cwd()}`;
|
|
|
934
1064
|
};
|
|
935
1065
|
this.chatHistory.push(assistantEntry);
|
|
936
1066
|
await this.emitContextChange();
|
|
1067
|
+
// Update rephrase state if this is a final response (no tool calls)
|
|
1068
|
+
if (this.rephraseState && this.rephraseState.newResponseIndex === -1 && (!accumulatedMessage.tool_calls || accumulatedMessage.tool_calls.length === 0)) {
|
|
1069
|
+
const newResponseIndex = this.chatHistory.length - 1;
|
|
1070
|
+
this.setRephraseState(this.rephraseState.originalAssistantMessageIndex, this.rephraseState.rephraseRequestIndex, newResponseIndex, this.rephraseState.messageType);
|
|
1071
|
+
}
|
|
937
1072
|
// Handle tool calls if present
|
|
938
1073
|
if (accumulatedMessage.tool_calls?.length > 0) {
|
|
939
1074
|
toolRounds++;
|
|
@@ -1048,10 +1183,11 @@ Current working directory: ${process.cwd()}`;
|
|
|
1048
1183
|
// Collect system messages that appeared after this assistant message
|
|
1049
1184
|
for (let i = assistantIndex + 1; i < this.chatHistory.length; i++) {
|
|
1050
1185
|
const entry = this.chatHistory[i];
|
|
1051
|
-
|
|
1186
|
+
const content = getTextContent(entry.content);
|
|
1187
|
+
if (entry.type === 'system' && content && content.trim()) {
|
|
1052
1188
|
this.messages.push({
|
|
1053
1189
|
role: 'system',
|
|
1054
|
-
content:
|
|
1190
|
+
content: content
|
|
1055
1191
|
});
|
|
1056
1192
|
}
|
|
1057
1193
|
// Stop if we hit another assistant or user message (next turn)
|
|
@@ -1109,7 +1245,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
1109
1245
|
this.chatHistory.push(errorEntry);
|
|
1110
1246
|
yield {
|
|
1111
1247
|
type: "content",
|
|
1112
|
-
content: errorEntry.content,
|
|
1248
|
+
content: getTextContent(errorEntry.content),
|
|
1113
1249
|
};
|
|
1114
1250
|
// Mark first message as processed even on error
|
|
1115
1251
|
this.firstMessageProcessed = true;
|
|
@@ -1245,18 +1381,9 @@ Current working directory: ${process.cwd()}`;
|
|
|
1245
1381
|
// Validate tool arguments against schema
|
|
1246
1382
|
const validationError = await this.validateToolArguments(toolCall.function.name, args);
|
|
1247
1383
|
if (validationError) {
|
|
1248
|
-
//
|
|
1249
|
-
const
|
|
1250
|
-
console.warn(`[VALIDATION ERROR] ${
|
|
1251
|
-
this.messages.push({
|
|
1252
|
-
role: 'system',
|
|
1253
|
-
content: systemMsg
|
|
1254
|
-
});
|
|
1255
|
-
this.chatHistory.push({
|
|
1256
|
-
type: 'system',
|
|
1257
|
-
content: systemMsg,
|
|
1258
|
-
timestamp: new Date()
|
|
1259
|
-
});
|
|
1384
|
+
// Validation failed - return error
|
|
1385
|
+
const errorMsg = `Tool call validation failed: ${validationError}. Please try again with correct parameters.`;
|
|
1386
|
+
console.warn(`[VALIDATION ERROR] ${errorMsg}`);
|
|
1260
1387
|
return {
|
|
1261
1388
|
success: false,
|
|
1262
1389
|
error: validationError
|
|
@@ -1372,6 +1499,8 @@ Current working directory: ${process.cwd()}`;
|
|
|
1372
1499
|
return await this.imageTool.captionImage(args.filename, args.prompt);
|
|
1373
1500
|
case "pngInfo":
|
|
1374
1501
|
return await this.imageTool.pngInfo(args.filename);
|
|
1502
|
+
case "listImageModels":
|
|
1503
|
+
return await this.imageTool.listImageModels();
|
|
1375
1504
|
case "readXlsx":
|
|
1376
1505
|
return await this.fileConversionTool.readXlsx(args.filename, args.sheetName, args.outputFormat, args.output);
|
|
1377
1506
|
case "listXlsxSheets":
|
|
@@ -1493,17 +1622,17 @@ Current working directory: ${process.cwd()}`;
|
|
|
1493
1622
|
const timestamp = entry.timestamp.toLocaleTimeString();
|
|
1494
1623
|
if (entry.type === 'user') {
|
|
1495
1624
|
lines.push(`(${msgNum}) ${userName} (user) - ${timestamp}`);
|
|
1496
|
-
lines.push(entry.content || "");
|
|
1625
|
+
lines.push(getTextContent(entry.content) || "");
|
|
1497
1626
|
lines.push("");
|
|
1498
1627
|
}
|
|
1499
1628
|
else if (entry.type === 'assistant') {
|
|
1500
1629
|
lines.push(`(${msgNum}) ${agentName} (assistant) - ${timestamp}`);
|
|
1501
|
-
lines.push(entry.content || "");
|
|
1630
|
+
lines.push(getTextContent(entry.content) || "");
|
|
1502
1631
|
lines.push("");
|
|
1503
1632
|
}
|
|
1504
1633
|
else if (entry.type === 'system') {
|
|
1505
1634
|
lines.push(`(${msgNum}) System (system) - ${timestamp}`);
|
|
1506
|
-
lines.push(entry.content || "");
|
|
1635
|
+
lines.push(getTextContent(entry.content) || "");
|
|
1507
1636
|
lines.push("");
|
|
1508
1637
|
}
|
|
1509
1638
|
else if (entry.type === 'tool_call') {
|
|
@@ -1518,7 +1647,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
1518
1647
|
const toolCall = entry.toolCall;
|
|
1519
1648
|
const toolName = toolCall?.function?.name || "unknown";
|
|
1520
1649
|
lines.push(`(${msgNum}) System (tool_result: ${toolName}) - ${timestamp}`);
|
|
1521
|
-
lines.push(entry.content || "");
|
|
1650
|
+
lines.push(getTextContent(entry.content) || "");
|
|
1522
1651
|
lines.push("");
|
|
1523
1652
|
}
|
|
1524
1653
|
});
|
|
@@ -1554,6 +1683,15 @@ Current working directory: ${process.cwd()}`;
|
|
|
1554
1683
|
clearPendingContextEditSession() {
|
|
1555
1684
|
this.pendingContextEditSession = null;
|
|
1556
1685
|
}
|
|
1686
|
+
setRephraseState(originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType) {
|
|
1687
|
+
this.rephraseState = { originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType };
|
|
1688
|
+
}
|
|
1689
|
+
getRephraseState() {
|
|
1690
|
+
return this.rephraseState;
|
|
1691
|
+
}
|
|
1692
|
+
clearRephraseState() {
|
|
1693
|
+
this.rephraseState = null;
|
|
1694
|
+
}
|
|
1557
1695
|
async setPersona(persona, color) {
|
|
1558
1696
|
// Execute hook if configured
|
|
1559
1697
|
const settings = getSettingsManager();
|
|
@@ -1561,12 +1699,6 @@ Current working directory: ${process.cwd()}`;
|
|
|
1561
1699
|
const hookMandatory = settings.isPersonaHookMandatory();
|
|
1562
1700
|
if (!hookPath && hookMandatory) {
|
|
1563
1701
|
const reason = "Persona hook is mandatory but not configured";
|
|
1564
|
-
// Note: Don't add to this.messages during tool execution - only chatHistory
|
|
1565
|
-
this.chatHistory.push({
|
|
1566
|
-
type: 'system',
|
|
1567
|
-
content: `Failed to change persona to "${persona}": ${reason}`,
|
|
1568
|
-
timestamp: new Date()
|
|
1569
|
-
});
|
|
1570
1702
|
return {
|
|
1571
1703
|
success: false,
|
|
1572
1704
|
error: reason
|
|
@@ -1584,24 +1716,13 @@ Current working directory: ${process.cwd()}`;
|
|
|
1584
1716
|
// Even in rejection, we process commands (might have MODEL change)
|
|
1585
1717
|
await this.processHookResult(hookResult);
|
|
1586
1718
|
// Note: We ignore the return value here since we're already rejecting the persona
|
|
1587
|
-
// Note: Don't add to this.messages during tool execution - only chatHistory
|
|
1588
|
-
this.chatHistory.push({
|
|
1589
|
-
type: 'system',
|
|
1590
|
-
content: `Failed to change persona to "${persona}": ${reason}`,
|
|
1591
|
-
timestamp: new Date()
|
|
1592
|
-
});
|
|
1593
1719
|
return {
|
|
1594
1720
|
success: false,
|
|
1595
1721
|
error: reason
|
|
1596
1722
|
};
|
|
1597
1723
|
}
|
|
1598
1724
|
if (hookResult.timedOut) {
|
|
1599
|
-
//
|
|
1600
|
-
this.chatHistory.push({
|
|
1601
|
-
type: 'system',
|
|
1602
|
-
content: `Persona hook timed out (auto-approved)`,
|
|
1603
|
-
timestamp: new Date()
|
|
1604
|
-
});
|
|
1725
|
+
// Hook timed out but was auto-approved
|
|
1605
1726
|
}
|
|
1606
1727
|
// Process hook commands (ENV, MODEL, SYSTEM)
|
|
1607
1728
|
const result = await this.processHookResult(hookResult, 'ZDS_AI_AGENT_PERSONA');
|
|
@@ -1622,25 +1743,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
1622
1743
|
this.persona = persona;
|
|
1623
1744
|
this.personaColor = color || "white";
|
|
1624
1745
|
process.env.ZDS_AI_AGENT_PERSONA = persona;
|
|
1625
|
-
//
|
|
1626
|
-
let systemContent;
|
|
1627
|
-
if (oldPersona) {
|
|
1628
|
-
const oldColorStr = oldColor && oldColor !== "white" ? ` (${oldColor})` : "";
|
|
1629
|
-
const newColorStr = this.personaColor && this.personaColor !== "white" ? ` (${this.personaColor})` : "";
|
|
1630
|
-
systemContent = `Assistant changed the persona from "${oldPersona}"${oldColorStr} to "${this.persona}"${newColorStr}`;
|
|
1631
|
-
}
|
|
1632
|
-
else {
|
|
1633
|
-
const colorStr = this.personaColor && this.personaColor !== "white" ? ` (${this.personaColor})` : "";
|
|
1634
|
-
systemContent = `Assistant set the persona to "${this.persona}"${colorStr}`;
|
|
1635
|
-
}
|
|
1636
|
-
// Note: Don't add to this.messages during tool execution - only chatHistory
|
|
1637
|
-
// System messages added during tool execution create invalid message sequences
|
|
1638
|
-
// because they get inserted between tool_calls and tool_results
|
|
1639
|
-
this.chatHistory.push({
|
|
1640
|
-
type: 'system',
|
|
1641
|
-
content: systemContent,
|
|
1642
|
-
timestamp: new Date()
|
|
1643
|
-
});
|
|
1746
|
+
// Persona hook generates success message - no need for redundant CLI message
|
|
1644
1747
|
this.emit('personaChange', {
|
|
1645
1748
|
persona: this.persona,
|
|
1646
1749
|
color: this.personaColor
|
|
@@ -1654,12 +1757,6 @@ Current working directory: ${process.cwd()}`;
|
|
|
1654
1757
|
const hookMandatory = settings.isMoodHookMandatory();
|
|
1655
1758
|
if (!hookPath && hookMandatory) {
|
|
1656
1759
|
const reason = "Mood hook is mandatory but not configured";
|
|
1657
|
-
// Note: Don't add to this.messages during tool execution - only chatHistory
|
|
1658
|
-
this.chatHistory.push({
|
|
1659
|
-
type: 'system',
|
|
1660
|
-
content: `Failed to change mood to "${mood}": ${reason}`,
|
|
1661
|
-
timestamp: new Date()
|
|
1662
|
-
});
|
|
1663
1760
|
return {
|
|
1664
1761
|
success: false,
|
|
1665
1762
|
error: reason
|
|
@@ -1675,24 +1772,13 @@ Current working directory: ${process.cwd()}`;
|
|
|
1675
1772
|
const reason = hookResult.reason || "Hook rejected mood change";
|
|
1676
1773
|
// Process rejection commands (MODEL, SYSTEM)
|
|
1677
1774
|
await this.processHookResult(hookResult);
|
|
1678
|
-
// Note: Don't add to this.messages during tool execution - only chatHistory
|
|
1679
|
-
this.chatHistory.push({
|
|
1680
|
-
type: 'system',
|
|
1681
|
-
content: `Failed to change mood to "${mood}": ${reason}`,
|
|
1682
|
-
timestamp: new Date()
|
|
1683
|
-
});
|
|
1684
1775
|
return {
|
|
1685
1776
|
success: false,
|
|
1686
1777
|
error: reason
|
|
1687
1778
|
};
|
|
1688
1779
|
}
|
|
1689
1780
|
if (hookResult.timedOut) {
|
|
1690
|
-
//
|
|
1691
|
-
this.chatHistory.push({
|
|
1692
|
-
type: 'system',
|
|
1693
|
-
content: `Mood hook timed out (auto-approved)`,
|
|
1694
|
-
timestamp: new Date()
|
|
1695
|
-
});
|
|
1781
|
+
// Hook timed out but was auto-approved
|
|
1696
1782
|
}
|
|
1697
1783
|
// Process hook commands (ENV, MODEL, SYSTEM)
|
|
1698
1784
|
const result = await this.processHookResult(hookResult, 'ZDS_AI_AGENT_MOOD');
|
|
@@ -1760,20 +1846,13 @@ Current working directory: ${process.cwd()}`;
|
|
|
1760
1846
|
await this.processHookResult(hookResult);
|
|
1761
1847
|
if (!hookResult.approved) {
|
|
1762
1848
|
const reason = hookResult.reason || "Hook rejected task start";
|
|
1763
|
-
this.messages.push({
|
|
1764
|
-
role: 'system',
|
|
1765
|
-
content: `Failed to start task "${activeTask}": ${reason}`
|
|
1766
|
-
});
|
|
1767
1849
|
return {
|
|
1768
1850
|
success: false,
|
|
1769
1851
|
error: reason
|
|
1770
1852
|
};
|
|
1771
1853
|
}
|
|
1772
1854
|
if (hookResult.timedOut) {
|
|
1773
|
-
|
|
1774
|
-
role: 'system',
|
|
1775
|
-
content: `Task start hook timed out (auto-approved)`
|
|
1776
|
-
});
|
|
1855
|
+
// Hook timed out but was auto-approved
|
|
1777
1856
|
}
|
|
1778
1857
|
}
|
|
1779
1858
|
// Set the task
|
|
@@ -1815,20 +1894,13 @@ Current working directory: ${process.cwd()}`;
|
|
|
1815
1894
|
await this.processHookResult(hookResult);
|
|
1816
1895
|
if (!hookResult.approved) {
|
|
1817
1896
|
const reason = hookResult.reason || "Hook rejected task status transition";
|
|
1818
|
-
this.messages.push({
|
|
1819
|
-
role: 'system',
|
|
1820
|
-
content: `Failed to transition task "${this.activeTask}" from ${this.activeTaskAction} to ${action}: ${reason}`
|
|
1821
|
-
});
|
|
1822
1897
|
return {
|
|
1823
1898
|
success: false,
|
|
1824
1899
|
error: reason
|
|
1825
1900
|
};
|
|
1826
1901
|
}
|
|
1827
1902
|
if (hookResult.timedOut) {
|
|
1828
|
-
|
|
1829
|
-
role: 'system',
|
|
1830
|
-
content: `Task transition hook timed out (auto-approved)`
|
|
1831
|
-
});
|
|
1903
|
+
// Hook timed out but was auto-approved
|
|
1832
1904
|
}
|
|
1833
1905
|
}
|
|
1834
1906
|
// Store old action for system message
|
|
@@ -1874,20 +1946,13 @@ Current working directory: ${process.cwd()}`;
|
|
|
1874
1946
|
await this.processHookResult(hookResult);
|
|
1875
1947
|
if (!hookResult.approved) {
|
|
1876
1948
|
const hookReason = hookResult.reason || "Hook rejected task stop";
|
|
1877
|
-
this.messages.push({
|
|
1878
|
-
role: 'system',
|
|
1879
|
-
content: `Failed to stop task "${this.activeTask}": ${hookReason}`
|
|
1880
|
-
});
|
|
1881
1949
|
return {
|
|
1882
1950
|
success: false,
|
|
1883
1951
|
error: hookReason
|
|
1884
1952
|
};
|
|
1885
1953
|
}
|
|
1886
1954
|
if (hookResult.timedOut) {
|
|
1887
|
-
|
|
1888
|
-
role: 'system',
|
|
1889
|
-
content: `Task stop hook timed out (auto-approved)`
|
|
1890
|
-
});
|
|
1955
|
+
// Hook timed out but was auto-approved
|
|
1891
1956
|
}
|
|
1892
1957
|
}
|
|
1893
1958
|
// Calculate remaining time to meet 3-second minimum
|