Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Graceful error handling and fallback mechanisms
- State management for condensed conversations in chat history

## [0.3.4] - 2025-08-17

### Improved
- **Conversation Condensing**: Complete replacement of conversation history with condensed summary
- Set `maxMessagesToKeep` to 0 for complete history replacement
- Removed `condense_conversation` tool from available tools list
- Completely replace chat history with just the condensed summary
- Improved conditional logic in `condenseConversation` utility function

### Added
- **Comprehensive Test Suite**: Added test script for automatic condensing verification
- `test/automatic-condense-test.js`: Full test suite for condensing functionality
- Verification of token efficiency and conversation state management
- Testing of edge cases and error conditions

### Technical Improvements
- Enhanced token efficiency by completely removing all previous messages
- Cleaner state management with simplified conversation history handling
- Better performance through reduced memory footprint after condensing
## [0.4.0] - 2025-08-18

### Refactored
- **LLM Configuration Handling**: Simplified LLM config management by removing complex deriveLLMConfigFromClient method
- **Condense Function**: Improved condenseConversation to accept LLMClient directly instead of LLMConfig
- **Code Cleanup**: Removed unused imports (LLMProvider, BatchResult) to clean up dependencies

### Added
- **Condense Threshold Setting**: Added configurable condense threshold setting to UI with percentage values
## [0.1.0] - 2025-01-25

### Added
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@graphteon/juriko-cli",
"version": "0.3.3",
"version": "0.4.0",
"description": "JURIKO - An open-source AI agent that brings the power of AI directly into your terminal.",
"main": "dist/index.js",
"bin": {
Expand Down
97 changes: 17 additions & 80 deletions src/agent/multi-llm-agent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LLMClient } from "../llm/client";
import { LLMMessage, LLMToolCall, LLMConfig, LLMProvider } from "../llm/types";
import { LLMMessage, LLMToolCall, LLMConfig } from "../llm/types";
import { TextEditorTool, BashTool, TodoTool, ConfirmationTool, CondenseTool } from "../tools";
import { ToolResult } from "../types";
import { EventEmitter } from "events";
Expand All @@ -15,7 +15,7 @@ import {
import { parseToolCallArguments, validateArgumentTypes } from "../utils/argument-parser";
import { SystemPromptBuilder, PromptOptions } from "./prompts/system-prompt-builder";
import { ResponseFormatter, ResponseStyle } from "../utils/response-formatter";
import { BatchToolExecutor, BatchResult } from "../tools/batch-executor";
import { BatchToolExecutor } from "../tools/batch-executor";
import { getEffectiveSettings } from "../utils/user-settings";

export interface ChatEntry {
Expand Down Expand Up @@ -176,23 +176,6 @@ const MULTI_LLM_TOOLS = [
}
}
},
{
type: "function" as const,
function: {
name: "condense_conversation",
description: "Condense the conversation to reduce token usage while preserving important context",
parameters: {
type: "object" as const,
properties: {
context: {
type: "string",
description: "Optional context for the condensing operation"
}
},
required: []
}
}
}
];

export class MultiLLMAgent extends EventEmitter {
Expand Down Expand Up @@ -220,8 +203,8 @@ export class MultiLLMAgent extends EventEmitter {
this.confirmationTool = new ConfirmationTool();
this.condenseTool = new CondenseTool();

// Initialize LLM config for condensing - use provided config or derive from current client
this.llmConfig = llmConfig || this.deriveLLMConfigFromClient();
// Store provided LLM config if any (for backward compatibility)
this.llmConfig = llmConfig || { provider: 'openai', model: 'gpt-4', apiKey: '', baseURL: undefined };

// Initialize token counter with the current model for accurate counting
this.tokenCounter = createTokenCounter(this.llmClient.getCurrentModel());
Expand Down Expand Up @@ -408,47 +391,7 @@ export class MultiLLMAgent extends EventEmitter {
}
}

private deriveLLMConfigFromClient(): LLMConfig {
// Extract the current configuration from the LLM client
const currentModel = this.llmClient.getCurrentModel();

// Determine provider based on model name
let provider: LLMProvider = 'openai'; // default fallback
let apiKey = '';
let baseURL: string | undefined;

// Check for Anthropic models
if (currentModel.includes('claude')) {
provider = 'anthropic';
apiKey = process.env.ANTHROPIC_API_KEY || '';
baseURL = process.env.ANTHROPIC_BASE_URL;
}
// Check for Grok models
else if (currentModel.includes('grok')) {
provider = 'grok';
apiKey = process.env.GROK_API_KEY || '';
baseURL = process.env.GROK_BASE_URL || 'https://api.x.ai/v1';
}
// Check for local models
else if (currentModel === 'custom-model' || process.env.LOCAL_LLM_BASE_URL) {
provider = 'local';
apiKey = process.env.LOCAL_LLM_API_KEY || 'local-key';
baseURL = process.env.LOCAL_LLM_BASE_URL || 'http://localhost:1234/v1';
}
// Default to OpenAI
else {
provider = 'openai';
apiKey = process.env.OPENAI_API_KEY || '';
baseURL = process.env.OPENAI_BASE_URL;
}

return {
provider,
model: currentModel,
apiKey,
baseURL
};
}


private async initializeMCP(): Promise<void> {
try {
Expand Down Expand Up @@ -1156,11 +1099,11 @@ export class MultiLLMAgent extends EventEmitter {

const condenseResult = await condenseConversation(
jurikoMessages,
this.llmConfig,
this.llmClient,
this.tokenCounter,
currentTokens,
{
maxMessagesToKeep: 3,
maxMessagesToKeep: 0, // Don't keep any old messages, replace everything with summary
isAutomaticTrigger,
systemPrompt: (() => {
const systemMsg = this.messages.find(m => m.role === 'system');
Expand All @@ -1173,24 +1116,20 @@ export class MultiLLMAgent extends EventEmitter {
);

if (!condenseResult.error) {
// Convert back to LLMMessage[] and update the messages
// Completely replace messages with condensed version
this.messages = condenseResult.messages.map(msg => ({
role: msg.role as 'system' | 'user' | 'assistant' | 'tool',
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
tool_calls: (msg as any).tool_calls,
tool_call_id: (msg as any).tool_call_id
}));

// Update chat history to reflect the condensing
const condensedEntry: ChatEntry = {
type: "assistant",
content: `📝 **Conversation Summary**\n\n${condenseResult.summary}`,
// Completely replace chat history with just the condensed summary
this.chatHistory = [{
type: "user",
content: `Previous conversation summary:\n\n${condenseResult.summary}`,
timestamp: new Date(),
};

// Replace older entries with the summary, keep recent ones
const recentEntries = this.chatHistory.slice(-6); // Keep last 6 entries
this.chatHistory = [condensedEntry, ...recentEntries];
}];
}

return condenseResult;
Expand Down Expand Up @@ -1242,8 +1181,6 @@ export class MultiLLMAgent extends EventEmitter {
case "update_todo_list":
return await this.todoTool.updateTodoList(args.updates);

case "condense_conversation":
return await this.condenseTool.condenseConversation(args.context);

default:
// Check if it's an MCP tool
Expand Down Expand Up @@ -1285,8 +1222,8 @@ export class MultiLLMAgent extends EventEmitter {
// Update token counter for new model
this.tokenCounter.dispose();
this.tokenCounter = createTokenCounter(model);
// Update LLM config for condensing to match new model
this.llmConfig = this.deriveLLMConfigFromClient();
// Update LLM config for backward compatibility
this.llmConfig = { provider: 'openai', model: model, apiKey: '', baseURL: undefined };
}

getLLMClient(): LLMClient {
Expand All @@ -1295,8 +1232,8 @@ export class MultiLLMAgent extends EventEmitter {

setLLMClient(client: LLMClient): void {
this.llmClient = client;
// Update LLM config for condensing to match new client
this.llmConfig = this.deriveLLMConfigFromClient();
// Update LLM config for backward compatibility
this.llmConfig = { provider: 'openai', model: client.getCurrentModel(), apiKey: '', baseURL: undefined };
}

abortCurrentOperation(): void {
Expand Down
30 changes: 24 additions & 6 deletions src/ui/components/settings-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useState, useEffect } from 'react';
import { Box, Text, Newline, useInput } from 'ink';
import {
getEffectiveSettings,
saveResponseStyle,
saveBetaFeatures,
import {
getEffectiveSettings,
saveResponseStyle,
saveBetaFeatures,
saveSecurityLevel,
resetAllSettings
saveCondenseThreshold,
resetAllSettings
} from '../../utils/user-settings';

interface SettingsMenuProps {
Expand All @@ -31,6 +32,7 @@ export const SettingsMenu: React.FC<SettingsMenuProps> = ({ onClose }) => {
{ key: 'enableBatching', label: 'Multi-Tool Batching (BETA)', type: 'toggle' as const },
{ key: 'enableCodeReferences', label: 'Code References (BETA)', type: 'toggle' as const },
{ key: 'securityLevel', label: 'Security Level', type: 'select' as const },
{ key: 'condenseThreshold', label: 'Auto-Condense Threshold', type: 'number' as const },
{ key: 'reset', label: 'Reset to Defaults', type: 'action' as const },
{ key: 'close', label: 'Close Settings', type: 'action' as const },
];
Expand Down Expand Up @@ -67,6 +69,9 @@ export const SettingsMenu: React.FC<SettingsMenuProps> = ({ onClose }) => {
case 'securityLevel':
await saveSecurityLevel(value);
break;
case 'condenseThreshold':
await saveCondenseThreshold(value);
break;
}

setSaveStatus('✅ Settings saved successfully!');
Expand Down Expand Up @@ -130,6 +135,16 @@ export const SettingsMenu: React.FC<SettingsMenuProps> = ({ onClose }) => {
setSettings(newSettings);
await saveIndividualSetting('securityLevel', levels[nextIndex]);
}
} else if (selectedItem.type === 'number') {
if (selectedItem.key === 'condenseThreshold') {
// Cycle through common threshold values: 50%, 60%, 70%, 75%, 80%, 85%, 90%
const thresholds = [50, 60, 70, 75, 80, 85, 90];
const currentIndex = thresholds.indexOf(settings.condenseThreshold);
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % thresholds.length;
const newSettings = { ...settings, condenseThreshold: thresholds[nextIndex] };
setSettings(newSettings);
await saveIndividualSetting('condenseThreshold', thresholds[nextIndex]);
}
}
} else if (key.escape) {
onClose();
Expand Down Expand Up @@ -163,6 +178,8 @@ export const SettingsMenu: React.FC<SettingsMenuProps> = ({ onClose }) => {
return settings.enableCodeReferences ? '[ON]' : '[OFF]';
case 'securityLevel':
return `[${settings.securityLevel}]`;
case 'condenseThreshold':
return `[${settings.condenseThreshold}%]`;
default:
return '';
}
Expand Down Expand Up @@ -213,9 +230,10 @@ export const SettingsMenu: React.FC<SettingsMenuProps> = ({ onClose }) => {
<Text color="gray">Navigation:</Text>
<Text color="gray"> ↑/↓ - Navigate ENTER - Toggle/Change ESC - Close</Text>
<Newline />
<Text color="yellow">Beta Features:</Text>
<Text color="yellow">Features:</Text>
<Text color="gray"> • Multi-Tool Batching: Execute multiple tools in parallel</Text>
<Text color="gray"> • Code References: Clickable file links in VSCode</Text>
<Text color="gray"> • Auto-Condense: Automatically condense conversation at threshold</Text>
<Newline />
<Text color="cyan">Settings saved to: ~/.juriko/user-settings.json</Text>
</Box>
Expand Down
15 changes: 8 additions & 7 deletions src/utils/condense.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JurikoMessage } from "../juriko/client";
import { LLMClient } from "../llm/client";
import { LLMConfig, LLMMessage } from "../llm/types";
import { LLMMessage } from "../llm/types";
import { TokenCounter } from "./token-counter";
import { getCondenseThresholdWithEnv } from "./user-settings";

Expand All @@ -25,7 +25,7 @@ export interface CondenseOptions {
*/
export async function condenseConversation(
messages: JurikoMessage[],
llmConfig: LLMConfig,
llmClient: LLMClient,
tokenCounter: TokenCounter,
prevContextTokens: number,
options: CondenseOptions = {}
Expand Down Expand Up @@ -100,8 +100,7 @@ export async function condenseConversation(
"\n\nOutput only the summary of the conversation so far, without any additional commentary or explanation."
};

// Create LLM client for condensing
const llmClient = new LLMClient(llmConfig);
// Use the provided LLM client directly

// Prepare messages for condensing (exclude system message from the conversation to be summarized)
const conversationMessages = messages.filter(m => m.role !== 'system');
Expand Down Expand Up @@ -160,9 +159,11 @@ export async function condenseConversation(
content: `Previous conversation summary:\n\n${summary}`
});

// Add the most recent messages to maintain context
const recentMessages = messages.slice(-maxMessagesToKeep);
condensedMessages.push(...recentMessages);
// Only add recent messages if maxMessagesToKeep > 0
if (maxMessagesToKeep > 0) {
const recentMessages = messages.slice(-maxMessagesToKeep);
condensedMessages.push(...recentMessages);
}

// Calculate new token count
const newContextTokens = tokenCounter.countMessageTokens(condensedMessages as any);
Expand Down
Loading
Loading