Skip to content
Closed
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
16 changes: 15 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ This is a cycod CLI application written in C#. It consists of multiple component

## Code Style
- Follow Microsoft C# coding guidelines
- Match the order of method implementations with the order they are referenced or called
- Place private fields at the end of class definitions, not the beginning
- Name async methods with an "Async" suffix (e.g., `ProcessDataAsync`)
- Use XML documentation comments for public members
- Prefer explicit types over var when the type is not obvious
- Avoid magic strings and numbers
- Use meaningful variable and method names
- Prefer concise code (e.g., ternary operators for simple conditionals)

## PR Instructions
- Don't create or submit PRs unless explicitly requested to do so
- Don't stage changes with `git add` unless explicitly requested by reviewers
- Reviewers use staging status to track which changes they have reviewed
- Run tests before submitting PRs
- Keep changes focused and small when possible
- Follow semantic versioning
Expand All @@ -31,9 +37,17 @@ This is a cycod CLI application written in C#. It consists of multiple component
- Don't expose sensitive information in error messages

## Code Organization
- Reuse existing utility classes whenever possible
- Keep implementation order consistent with reference/calling order throughout the codebase
- Reuse existing utility/helper classes whenever possible
- Check FileHelpers.cs for file-related operations before creating custom implementations
- When extending functionality, consider adding to existing helper classes rather than creating duplicates
- Follow local documentation style when modifying existing files
- For new files, use XML documentation for public members and classes
- When creating help files, follow the existing style and conventions:
- Use spaces between words in help topic names (e.g., "chat history" not "chat-history")
- Don't include short options (-f, -o) in help documentation unless they're supported in code
- Format examples with proper spacing and follow the established pattern
- Modify help files directly, don't create separate "revised" versions

## Key Helper Classes
- **FileHelpers**: Core utility for file operations including reading, writing, finding files, path manipulations
Expand Down
16 changes: 12 additions & 4 deletions src/common/Configuration/KnownSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public static class KnownSettings
public const string AppPreferredProvider = "App.PreferredProvider";
public const string AppAutoSaveChatHistory = "App.AutoSaveChatHistory";
public const string AppAutoSaveTrajectory = "App.AutoSaveTrajectory";
public const string AppCompactionMode = "App.CompactionMode";
public const string AppPreserveMessages = "App.PreserveMessages";
public const string AppChatCompletionTimeout = "App.ChatCompletionTimeout";
public const string AppAutoApprove = "App.AutoApprove";
public const string AppAutoDeny = "App.AutoDeny";
Expand Down Expand Up @@ -149,7 +151,9 @@ public static class KnownSettings
{ AppAutoSaveTrajectory, "CYCOD_AUTO_SAVE_TRAJECTORY" },
{ AppChatCompletionTimeout, "CYCOD_CHAT_COMPLETION_TIMEOUT" },
{ AppAutoApprove, "CYCOD_AUTO_APPROVE" },
{ AppAutoDeny, "CYCOD_AUTO_DENY" }
{ AppAutoDeny, "CYCOD_AUTO_DENY" },
{ AppCompactionMode, "CYCOD_COMPACTION_MODE" },
{ AppPreserveMessages, "CYCOD_PRESERVE_MESSAGES" }
};

/// <summary>
Expand Down Expand Up @@ -199,12 +203,14 @@ public static class KnownSettings
{ AppMaxPromptTokens, "--max-prompt-tokens" },
{ AppMaxOutputTokens, "--max-output-tokens" },
{ AppMaxToolTokens, "--max-tool-tokens" },
{ AppMaxChatTokens, "--max-chat-tokens" },
{ AppMaxChatTokens, "--max-tokens" },
{ AppAutoSaveChatHistory, "--auto-save-chat-history" },
{ AppAutoSaveTrajectory, "--auto-save-trajectory" },
{ AppChatCompletionTimeout, "--chat-completion-timeout" },
{ AppAutoApprove, "--auto-approve" },
{ AppAutoDeny, "--auto-deny" }
{ AppAutoDeny, "--auto-deny" },
{ AppCompactionMode, "--compact" },
{ AppPreserveMessages, "--preserve-messages" }
};

/// <summary>
Expand Down Expand Up @@ -321,7 +327,9 @@ public static class KnownSettings
AppAutoSaveTrajectory,
AppChatCompletionTimeout,
AppAutoApprove,
AppAutoDeny
AppAutoDeny,
AppCompactionMode,
AppPreserveMessages
};

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ private async Task<PersistentShellCommandResult> RunCommandInternalAsync(string
isSyntaxError
);
}
catch (TimeoutException timeoutEx)
catch (TimeoutException)
{
// Attempt to kill the shell process since it's in a bad state
try
Expand Down
99 changes: 98 additions & 1 deletion src/cycod/CommandLine/CycoDevCommandLineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ public static bool Parse(string[] args, out CommandLineOptions? options, out Com
override protected string PeekCommandName(string[] args, int i)
{
var name1 = GetInputOptionArgs(i, args, max: 1).FirstOrDefault();
var name2 = GetInputOptionArgs(i + 1, args, max: 1).FirstOrDefault();

return name1 switch
{
"chat" => "chat",
"chat" => name2 == "compact" ? "chat compact" : "chat",
_ => base.PeekCommandName(args, i)
};
}
Expand All @@ -58,6 +60,7 @@ override protected bool CheckPartialCommandNeedsHelp(string commandName)
return commandName switch
{
"chat" => new ChatCommand(),
"chat compact" => new ChatCompactCommand(),
"github login" => new GitHubLoginCommand(),
"github models" => new GitHubModelsCommand(),
"config list" => new ConfigListCommand(),
Expand Down Expand Up @@ -85,6 +88,7 @@ override protected bool CheckPartialCommandNeedsHelp(string commandName)
override protected bool TryParseOtherCommandOptions(Command? command, string[] args, ref int i, string arg)
{
return TryParseChatCommandOptions(command as ChatCommand, args, ref i, arg) ||
TryParseChatCompactCommandOptions(command as ChatCompactCommand, args, ref i, arg) ||
TryParseGitHubLoginCommandOptions(command as GitHubLoginCommand, args, ref i, arg) ||
TryParseConfigCommandOptions(command as ConfigBaseCommand, args, ref i, arg) ||
TryParseAliasCommandOptions(command as AliasBaseCommand, args, ref i, arg) ||
Expand Down Expand Up @@ -200,6 +204,11 @@ override protected bool TryParseOtherCommandArg(Command? command, string arg)
mcpAddCommand.Name = arg;
parsedOption = true;
}
else if (command is ChatCompactCommand chatCompactCommand && chatCompactCommand.File == null)
{
chatCompactCommand.File = new FileInfo(arg);
parsedOption = true;
}
else if (command is McpRemoveCommand mcpRemoveCommand && string.IsNullOrEmpty(mcpRemoveCommand.Name))
{
mcpRemoveCommand.Name = arg;
Expand Down Expand Up @@ -394,6 +403,8 @@ private bool TryParseMcpCommandOptions(McpBaseCommand? command, string[] args, r
return parsed;
}



private bool TryParseChatCommandOptions(ChatCommand? command, string[] args, ref int i, string arg)
{
bool parsed = true;
Expand Down Expand Up @@ -582,6 +593,31 @@ private bool TryParseChatCommandOptions(ChatCommand? command, string[] args, ref
command.OutputTrajectory = outputTrajectory;
i += max1Arg.Count();
}
else if (arg == "--compact")
{
// Check if there's a mode specified (full, simple, none)
var max1Arg = GetInputOptionArgs(i + 1, args, max: 1);
var modeValue = max1Arg.FirstOrDefault();

// Set the compaction mode in configuration
if (modeValue == null || modeValue.StartsWith("-"))
{
// Default to "full" if no mode specified
ConfigStore.Instance.SetFromCommandLine(KnownSettings.AppCompactionMode, "full");
i += 0; // No argument consumed
}
else
{
// Use the specified mode
ConfigStore.Instance.SetFromCommandLine(KnownSettings.AppCompactionMode, modeValue.ToLowerInvariant());
i += max1Arg.Count();
}
}
else if (arg == "--no-compact")
{
// Explicitly disable compaction
ConfigStore.Instance.SetFromCommandLine(KnownSettings.AppCompactionMode, "none");
}
else if (arg == "--use-anthropic")
{
ConfigStore.Instance.SetFromCommandLine(KnownSettings.AppPreferredProvider, "anthropic");
Expand Down Expand Up @@ -653,6 +689,67 @@ private bool TryParseChatCommandOptions(ChatCommand? command, string[] args, ref
return parsed;
}

private bool TryParseChatCompactCommandOptions(ChatCompactCommand? command, string[] args, ref int i, string arg)
{
var parsedOption = false;

if (command == null) return parsedOption;

// Parse file option
if (arg == "--file")
{
var max1Arg = GetInputOptionArgs(i + 1, args, max: 1);
var fileValue = max1Arg.FirstOrDefault();
if (fileValue != null)
{
command.File = new FileInfo(fileValue);
i += max1Arg.Count();
parsedOption = true;
}
}
// Parse output file option
else if (arg == "--output")
{
var max1Arg = GetInputOptionArgs(i + 1, args, max: 1);
var outputValue = max1Arg.FirstOrDefault();
if (outputValue != null)
{
command.OutputFile = outputValue;
i += max1Arg.Count();
parsedOption = true;
}
}
// Parse compaction mode options
else if (arg == "--compact")
{
// Check if there's a mode specified (full, simple, none)
var max1Arg = GetInputOptionArgs(i + 1, args, max: 1);
var modeValue = max1Arg.FirstOrDefault();

// If the next argument looks like another option or is missing, use "full" as default
if (modeValue == null)
{
command.CompactionMode = "full";
parsedOption = true;
}
else
{
// Get the specified mode
command.CompactionMode = modeValue.ToLowerInvariant();
i += max1Arg.Count();
parsedOption = true;
}
}
// Handle explicit --no-compact
else if (arg == "--no-compact")
{
command.CompactionMode = "none";
parsedOption = true;
}

return parsedOption;
}

private bool TryParseGitHubLoginCommandOptions(GitHubLoginCommand? command, string[] args, ref int i, string arg)
{
if (command == null)
Expand Down
30 changes: 26 additions & 4 deletions src/cycod/CommandLineCommands/ChatCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public override ChatCommand Clone()
clone.InputInstructions = new List<string>(this.InputInstructions);
clone.UseTemplates = this.UseTemplates;

// Copy compaction settings
clone._compactionMode = this._compactionMode;
clone._preserveMessages = this._preserveMessages;

// Deep copy variables dictionary
clone.Variables = new Dictionary<string, string>(this.Variables);

Expand Down Expand Up @@ -67,6 +71,11 @@ public override async Task<object> ExecuteAsync(bool interactive)
MaxToolTokenTarget = ConfigStore.Instance.GetFromAnyScope(KnownSettings.AppMaxToolTokens).AsInt(DefaultMaxToolTokenTarget);
MaxChatTokenTarget = ConfigStore.Instance.GetFromAnyScope(KnownSettings.AppMaxChatTokens).AsInt(DefaultMaxChatTokenTarget);

// Initialize compaction settings
string compactionMode = ConfigStore.Instance.GetFromAnyScope(KnownSettings.AppCompactionMode).AsString() ?? "";
_compactionMode = ChatHistoryCompactionHelper.ParseCompactionMode(compactionMode);
_preserveMessages = ConfigStore.Instance.GetFromAnyScope(KnownSettings.AppPreserveMessages).AsInt(ChatHistoryCompactionHelper.DefaultPreserveMessages);

// Ground the filenames (in case they're templatized, or auto-save is enabled).
InputChatHistory = ChatHistoryFileHelpers.GroundInputChatHistoryFileName(InputChatHistory, LoadMostRecentChatHistory)?.ReplaceValues(_namedValues);
AutoSaveOutputChatHistory = ChatHistoryFileHelpers.GroundAutoSaveChatHistoryFileName()?.ReplaceValues(_namedValues);
Expand Down Expand Up @@ -148,7 +157,7 @@ public override async Task<object> ExecuteAsync(bool interactive)
ImagePatterns.Clear();

var response = await CompleteChatStreamingAsync(chat, giveAssistant, imageFiles,
(messages) => HandleUpdateMessages(messages),
(messages) => HandleUpdateMessagesAsync(messages),
(update) => HandleStreamingChatCompletionUpdate(update),
(name, args) => HandleFunctionCallApproval(factory, name, args!),
(name, args, result) => HandleFunctionCallCompleted(name, args, result));
Expand Down Expand Up @@ -501,10 +510,18 @@ private async Task<string> CompleteChatStreamingAsync(
return MultilineInputHelper.ReadMultilineInput(firstLine);
}

private void HandleUpdateMessages(IList<ChatMessage> messages)
private async void HandleUpdateMessagesAsync(IList<ChatMessage> messages)
{
messages.TryTrimToTarget(MaxPromptTokenTarget, MaxToolTokenTarget, MaxChatTokenTarget);

// Try trim with compaction as needed - only using compaction mode and preserve messages
await ChatHistoryCompactionHelper.TryTrimToTargetWithCompactionAsync(
messages,
MaxPromptTokenTarget,
MaxToolTokenTarget,
MaxChatTokenTarget,
_compactionMode,
_preserveMessages);

// Handle all the file saving operations
TrySaveChatHistoryToFile(messages, AutoSaveOutputChatHistory);
if (OutputChatHistory != AutoSaveOutputChatHistory)
{
Expand All @@ -516,6 +533,7 @@ private void HandleUpdateMessages(IList<ChatMessage> messages)
_trajectoryFile.AppendMessage(lastMessage);
}


private void TrySaveChatHistoryToFile(IList<ChatMessage> messages, string? filePath)
{
if (filePath == null) return;
Expand Down Expand Up @@ -967,4 +985,8 @@ private void AddAutoDenyToolDefaults()

private HashSet<string> _approvedFunctionCallNames = new HashSet<string>();
private HashSet<string> _deniedFunctionCallNames = new HashSet<string>();

// Compaction settings to be initialized during ExecuteAsync
private ChatHistoryCompactionHelper.CompactionMode _compactionMode;
private int _preserveMessages;
}
Loading