From f58af906b0d36260985ce55a85dfbeb7262beadf Mon Sep 17 00:00:00 2001 From: emilyjiji <92887308+emilyjiji@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:02:47 -0400 Subject: [PATCH 1/3] Add Ollama provider (--use-ollama) + config keys; disable tools when unsupported --- src/common/Configuration/KnownSettings.cs | 97 +++++++++++-------- src/cycod/ChatClient/ChatClientFactory.cs | 50 +++++++++- src/cycod/ChatClient/FunctionCallingChat.cs | 18 ++-- .../CommandLine/CycoDevCommandLineOptions.cs | 32 ++++-- 4 files changed, 142 insertions(+), 55 deletions(-) diff --git a/src/common/Configuration/KnownSettings.cs b/src/common/Configuration/KnownSettings.cs index d5e2c9249..a050f0cba 100644 --- a/src/common/Configuration/KnownSettings.cs +++ b/src/common/Configuration/KnownSettings.cs @@ -13,13 +13,13 @@ public static class KnownSettings // Anthropic settings public const string AnthropicApiKey = "Anthropic.ApiKey"; public const string AnthropicModelName = "Anthropic.ModelName"; - + // AWS Bedrock settings public const string AWSBedrockAccessKey = "AWS.Bedrock.AccessKey"; public const string AWSBedrockSecretKey = "AWS.Bedrock.SecretKey"; public const string AWSBedrockRegion = "AWS.Bedrock.Region"; public const string AWSBedrockModelId = "AWS.Bedrock.ModelId"; - + // Google Gemini settings public const string GoogleGeminiApiKey = "Google.Gemini.ApiKey"; public const string GoogleGeminiModelId = "Google.Gemini.ModelId"; @@ -33,15 +33,19 @@ public static class KnownSettings public const string AzureOpenAIApiKey = "Azure.OpenAI.ApiKey"; public const string AzureOpenAIEndpoint = "Azure.OpenAI.Endpoint"; public const string AzureOpenAIChatDeployment = "Azure.OpenAI.ChatDeployment"; - + // OpenAI settings public const string OpenAIApiKey = "OpenAI.ApiKey"; public const string OpenAIEndpoint = "OpenAI.Endpoint"; public const string OpenAIChatModelName = "OpenAI.ChatModelName"; - + + //Ollama settings + public const string OllamaBaseUrl = "Ollama.BaseUrl"; + public const string OllamaModelId = "Ollama.ModelId"; + // GitHub settings public const string GitHubToken = "GitHub.Token"; - + // Copilot settings public const string CopilotModelName = "Copilot.ModelName"; public const string CopilotApiEndpoint = "Copilot.ApiEndpoint"; @@ -59,11 +63,11 @@ public static class KnownSettings public const string AppChatCompletionTimeout = "App.ChatCompletionTimeout"; public const string AppAutoApprove = "App.AutoApprove"; public const string AppAutoDeny = "App.AutoDeny"; - + #endregion - + #region Secret Settings - + /// /// Collection of known setting names that should be treated as secrets. /// @@ -91,11 +95,11 @@ public static class KnownSettings // GitHub secrets GitHubToken, }; - + #endregion - + #region Setting Format Mappings - + /// /// Mapping from dot notation to environment variable format. /// @@ -129,6 +133,10 @@ public static class KnownSettings { OpenAIApiKey, "OPENAI_API_KEY" }, { OpenAIEndpoint, "OPENAI_ENDPOINT" }, { OpenAIChatModelName, "OPENAI_CHAT_MODEL_NAME" }, + + //Ollama mappings + { OllamaBaseUrl, "OLLAMA_BASE_URL" }, + { OllamaModelId, "OLLAMA_MODEL_ID" }, // GitHub mappings { GitHubToken, "GITHUB_TOKEN" }, @@ -151,7 +159,7 @@ public static class KnownSettings { AppAutoApprove, "CYCOD_AUTO_APPROVE" }, { AppAutoDeny, "CYCOD_AUTO_DENY" } }; - + /// /// Mapping from dot notation to CLI option format. /// @@ -185,6 +193,10 @@ public static class KnownSettings { OpenAIApiKey, "--openai-api-key" }, { OpenAIEndpoint, "--openai-endpoint" }, { OpenAIChatModelName, "--openai-chat-model-name" }, + + // Ollama mappings + { OllamaBaseUrl, "--ollama-base-url" }, + { OllamaModelId, "--ollama-model-id" }, // GitHub mappings { GitHubToken, "--github-token" }, @@ -206,12 +218,12 @@ public static class KnownSettings { AppAutoApprove, "--auto-approve" }, { AppAutoDeny, "--auto-deny" } }; - + /// /// Mapping from environment variable format to dot notation. /// private static readonly Dictionary _envVarToDotMap; - + /// /// Mapping from CLI option format to dot notation. /// @@ -237,7 +249,7 @@ public static class KnownSettings AnthropicApiKey, AnthropicModelName }; - + /// /// Collection of settings for AWS Bedrock integration. /// @@ -248,7 +260,7 @@ public static class KnownSettings AWSBedrockRegion, AWSBedrockModelId }; - + /// /// Collection of settings for Google Gemini integration. /// @@ -257,7 +269,7 @@ public static class KnownSettings GoogleGeminiApiKey, GoogleGeminiModelId }; - + /// /// Collection of settings for Grok integration. /// @@ -277,7 +289,7 @@ public static class KnownSettings AzureOpenAIEndpoint, AzureOpenAIChatDeployment }; - + /// /// Collection of settings for OpenAI integration. /// @@ -287,7 +299,16 @@ public static class KnownSettings OpenAIEndpoint, OpenAIChatModelName }; - + + /// + /// Collection of settings for Ollama integration. + /// + public static readonly HashSet OllamaSettings = new(StringComparer.OrdinalIgnoreCase) + { + OllamaBaseUrl, + OllamaModelId + }; + /// /// Collection of settings for GitHub integration. /// @@ -295,7 +316,7 @@ public static class KnownSettings { GitHubToken }; - + /// /// Collection of settings for Copilot integration. /// @@ -306,7 +327,7 @@ public static class KnownSettings CopilotIntegrationId, CopilotEditorVersion }; - + /// /// Collection of settings for application configuration. /// @@ -323,16 +344,16 @@ public static class KnownSettings AppAutoApprove, AppAutoDeny }; - + #endregion - + static KnownSettings() { // Initialize reverse mappings _envVarToDotMap = _dotToEnvVarMap.ToDictionary(kvp => kvp.Value, kvp => kvp.Key, StringComparer.OrdinalIgnoreCase); _cliOptionToDotMap = _dotToCLIOptionMap.ToDictionary(kvp => kvp.Value, kvp => kvp.Key, StringComparer.OrdinalIgnoreCase); } - + /// /// Checks if the given key is a known setting. /// @@ -345,7 +366,7 @@ public static bool IsKnown(string key) if (_cliOptionToDotMap.ContainsKey(key)) return true; return false; } - + /// /// Checks if the given key is a secret value that should be obfuscated. /// @@ -356,7 +377,7 @@ public static bool IsSecret(string key) var dotNotationKey = ToDotNotation(key); return _secretSettings.Contains(dotNotationKey); } - + /// /// Gets all known setting names in dot notation format. /// @@ -376,7 +397,7 @@ public static bool IsMultiValue(string key) var dotNotationKey = ToDotNotation(key); return _multiValueSettings.Contains(dotNotationKey, StringComparer.OrdinalIgnoreCase); } - + /// /// Gets the canonical form of a known setting key. /// @@ -418,7 +439,7 @@ public static string ToEnvironmentVariable(string key) // Otherwise, just return it return key; } - + /// /// Converts a setting name to CLI option format. /// @@ -427,20 +448,20 @@ public static string ToEnvironmentVariable(string key) public static string ToCLIOption(string key) { if (IsCLIOptionFormat(key)) return key; - + // Try to map to a CLI option var dotNotationKey = ToDotNotation(key); if (_dotToCLIOptionMap.TryGetValue(dotNotationKey, out string? cliOption)) { return cliOption; } - + // Otherwise, use a general algorithm var parts = dotNotationKey.Split('.'); var kebabParts = parts.Select(p => ToKebabCase(p)); return "--" + string.Join("-", kebabParts).ToLowerInvariant(); } - + /// /// Converts a setting name to dot notation format. /// @@ -456,7 +477,7 @@ public static string ToDotNotation(string key) return dotNotation; } } - + // If it's a CLI option format if (IsCLIOptionFormat(key)) { @@ -464,7 +485,7 @@ public static string ToDotNotation(string key) { return dotNotation; } - + // Remove leading -- and convert kebab-case to PascalCase with dots var trimmed = key.TrimStart('-'); var parts = trimmed.Split('-'); @@ -475,13 +496,13 @@ public static string ToDotNotation(string key) parts[i] = char.ToUpper(parts[i][0]) + parts[i].Substring(1).ToLower(); } } - + return string.Join(".", parts); } return key; } - + /// /// Determines if the given key is in environment variable format. /// @@ -491,7 +512,7 @@ public static bool IsEnvironmentVariableFormat(string key) { return Regex.IsMatch(key, "^[A-Z0-9_]+$"); } - + /// /// Determines if the given key is in CLI option format. /// @@ -501,7 +522,7 @@ public static bool IsCLIOptionFormat(string key) { return key.StartsWith("--"); } - + /// /// Converts a string from PascalCase to kebab-case. /// @@ -511,7 +532,7 @@ private static string ToKebabCase(string input) { if (string.IsNullOrEmpty(input)) return input; - + // Insert a hyphen before each uppercase letter that follows a lowercase letter var result = Regex.Replace(input, "(? line.Trim())); diff --git a/src/cycod/ChatClient/FunctionCallingChat.cs b/src/cycod/ChatClient/FunctionCallingChat.cs index 732f8a975..e9280fa36 100644 --- a/src/cycod/ChatClient/FunctionCallingChat.cs +++ b/src/cycod/ChatClient/FunctionCallingChat.cs @@ -13,6 +13,8 @@ public FunctionCallingChat(IChatClient chatClient, string systemPrompt, Function ? chatClient.AsBuilder().UseFunctionInvocation().Build() : chatClient; + var toolsDisabled = (options?.ToolMode ?? ChatToolMode.Auto) == ChatToolMode.None; + var tools = _functionFactory.GetAITools().ToList(); ConsoleHelpers.WriteDebugLine($"FunctionCallingChat: Found {tools.Count} tools in FunctionFactory"); @@ -20,8 +22,8 @@ public FunctionCallingChat(IChatClient chatClient, string systemPrompt, Function _options = new ChatOptions() { ModelId = options?.ModelId, - ToolMode = options?.ToolMode, - Tools = tools, + ToolMode = toolsDisabled ? ChatToolMode.None : (options?.ToolMode ?? ChatToolMode.Auto), + Tools = toolsDisabled ? new List() : tools, MaxOutputTokens = maxOutputTokens.HasValue ? maxOutputTokens.Value : options?.MaxOutputTokens, @@ -51,7 +53,7 @@ public void AddUserMessage(string userMessage, int maxPromptTokenTarget = 0, int maxPromptTokenTarget: maxPromptTokenTarget, maxChatTokenTarget: maxChatTokenTarget); } - + public void AddUserMessages(IEnumerable userMessages, int maxPromptTokenTarget = 0, int maxChatTokenTarget = 0) { foreach (var userMessage in userMessages) @@ -109,12 +111,16 @@ public async Task CompleteChatStreamingAsync( messageCallback?.Invoke(_messages); var contentToReturn = string.Empty; + var toolsDisabled = (_options.ToolMode ?? ChatToolMode.Auto) == ChatToolMode.None; while (true) { var responseContent = string.Empty; await foreach (var update in _chatClient.GetStreamingResponseAsync(_messages, _options)) { - _functionCallDetector.CheckForFunctionCall(update); + if (!toolsDisabled) + { + _functionCallDetector.CheckForFunctionCall(update); + } var content = string.Join("", update.Contents .Where(c => c is TextContent) @@ -133,7 +139,7 @@ public async Task CompleteChatStreamingAsync( streamingCallback?.Invoke(update); } - if (TryCallFunctions(responseContent, approveFunctionCall, functionCallCallback, messageCallback)) + if (!toolsDisabled && TryCallFunctions(responseContent, approveFunctionCall, functionCallCallback, messageCallback)) { _functionCallDetector.Clear(); continue; @@ -150,7 +156,7 @@ private bool TryCallFunctions(string responseContent, Func Date: Mon, 25 Aug 2025 20:43:35 -0400 Subject: [PATCH 2/3] readme changes --- README.md | 2 + src/cycod/assets/help/configuration.txt | 1 + src/cycod/assets/help/options.txt | 5 ++ src/cycod/assets/help/provider.txt | 6 ++- src/cycod/assets/help/settings.txt | 2 + src/cycod/assets/help/use ollama.txt | 70 +++++++++++++++++++++++++ 6 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/cycod/assets/help/use ollama.txt diff --git a/README.md b/README.md index 99145552d..a9573a24b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ CycoD is a command-line interface (CLI) application that provides a chat-based i - Run shell commands (Bash, CMD, PowerShell) with persistent sessions - Manipulate files (view, create, edit, replace text) - Access date and time information + - **GUI Interactions** (Windows only): Show dialogs for user input, file selection, and notifications - **Comprehensive Configuration System**: - Multiple configuration scopes (global, user, local) - Profile support for different configurations @@ -177,6 +178,7 @@ For more detailed documentation, see: - [Getting Started](docs/getting-started.md) - [Command Line Options](docs/cli-options.md) - [Function Calling](docs/function-calling.md) +- [GUI Functions](docs/gui-functions.md) - Windows GUI interaction functions - [Creating Aliases](docs/aliases.md) - [Chat History](docs/chat-history.md) - [Slash Commands](#chat-commands) - Special commands during chat sessions diff --git a/src/cycod/assets/help/configuration.txt b/src/cycod/assets/help/configuration.txt index c5544dba9..ee6a88365 100644 --- a/src/cycod/assets/help/configuration.txt +++ b/src/cycod/assets/help/configuration.txt @@ -136,5 +136,6 @@ SEE ALSO cycod help use gemini cycod help use grok cycod help use openai + cycod help use ollama cycod help function calls cycod help timeouts \ No newline at end of file diff --git a/src/cycod/assets/help/options.txt b/src/cycod/assets/help/options.txt index f2efc67c1..318345ad7 100644 --- a/src/cycod/assets/help/options.txt +++ b/src/cycod/assets/help/options.txt @@ -52,6 +52,7 @@ USAGE: cycod [...] --use-gemini Prefer use of Google Gemini --use-grok Prefer use of Grok (x.ai) --use-openai Prefer use of OpenAI API as the chat provider + --use-ollama Prefer use of a local Ollama server (OpenAI-compatible) ANTHROPIC OPTIONS (see: cycod help use anthropic) --anthropic-api-key KEY Use a specific API key for Anthropic @@ -81,6 +82,10 @@ USAGE: cycod [...] --openai-api-key KEY Use a specific API key --openai-chat-model-name NAME Use a specific chat model (default: gpt-4o) + OLLAMA OPTIONS (see: cycod help use ollama) + --ollama-base-url URL Use a specific API base URL (default: http://localhost:11434/v1) + --ollama-model-id ID Use a specific model ID (e.g., llama3:8b, qwen2.5:0.5b) + COPILOT OPTIONS (see: cycod help use github copilot) --copilot-model-name NAME Use a specific model by name (default: claude-sonnet-4) --copilot-api-endpoint URL Use a specific API endpoint (default: https://api.githubcopilot.com) diff --git a/src/cycod/assets/help/provider.txt b/src/cycod/assets/help/provider.txt index 2c36eec9e..f66b1b4b1 100644 --- a/src/cycod/assets/help/provider.txt +++ b/src/cycod/assets/help/provider.txt @@ -9,6 +9,8 @@ CycoD supports multiple AI providers for chat functionality: - Google Gemini - Grok (x.ai) - OpenAI API +- GitHub Copilot +- Ollama (local OpenAI-compatible) ## Provider Selection Flags @@ -22,6 +24,7 @@ Use these flags to explicitly select which provider to use: --use-gemini Use Google Gemini --use-grok Use Grok (x.ai) --use-openai Use OpenAI API +--use-ollama Use local Ollama (OpenAI-compatible) ``` ## Named Profiles @@ -69,4 +72,5 @@ CYCOD_PREFERRED_PROVIDER=azure-openai cycod help use bedrock cycod help use gemini cycod help use grok - cycod help use openai \ No newline at end of file + cycod help use openai + cycod help use ollama \ No newline at end of file diff --git a/src/cycod/assets/help/settings.txt b/src/cycod/assets/help/settings.txt index de35f41dc..a0c34b325 100644 --- a/src/cycod/assets/help/settings.txt +++ b/src/cycod/assets/help/settings.txt @@ -238,6 +238,7 @@ USE APP SETTINGS cycod config set App.PreferredProvider gemini cycod config set App.PreferredProvider grok cycod config set App.PreferredProvider openai + cycod config set App.PreferredProvider ollama EXAMPLE 2: Set preferred AI provider via environment variable @@ -259,6 +260,7 @@ USE APP SETTINGS cycod chat --use-google cycod chat --use-grok cycod chat --use-openai + cycod chat --use-ollama SEE ALSO diff --git a/src/cycod/assets/help/use ollama.txt b/src/cycod/assets/help/use ollama.txt new file mode 100644 index 000000000..99d6dbfa6 --- /dev/null +++ b/src/cycod/assets/help/use ollama.txt @@ -0,0 +1,70 @@ +USE OLLAMA + + AUTHENTICATION + + No API key required for local Ollama. + + QUICK START + + EXAMPLE 1: Pull a tiny model locally (Ollama CLI) + ollama pull qwen2.5:0.5b + + EXAMPLE 2: Run a quick test with CycoD + cycod chat --use-ollama -q "hello" + + EXAMPLE 3: Point to a non-default server + cycod chat --use-ollama --ollama-base-url http://localhost:11434/v1 -q "hi" + + MODEL SELECTION + + EXAMPLE 1: Set model via config + cycod config set Ollama.ModelId qwen2.5:0.5b + cycod config set Ollama.ModelId llama3:8b --local + cycod config set Ollama.ModelId qwen2.5:7b --user + + EXAMPLE 2: Set model via environment variable + Set OLLAMA_MODEL_ID environment variable + + EXAMPLE 3: Supply model via command line + cycod chat --use-ollama --ollama-model-id qwen2.5:0.5b -q "hello" + + POPULAR MODELS + + EXAMPLE 1: Small (good for low-spec dev boxes) + ollama pull qwen2.5:0.5b + cycod chat --use-ollama --ollama-model-id qwen2.5:0.5b -q "hello" + + EXAMPLE 2: Mid-size (quality vs speed) + ollama pull llama3:8b + cycod chat --use-ollama --ollama-model-id llama3:8b -q "summarize why Rust uses a borrow checker" + + EXAMPLE 3: GPT-OSS (larger; needs more memory) + ollama pull gpt-oss:20b + cycod chat --use-ollama --ollama-model-id gpt-oss:20b -q "explain transformers in simple terms" + + API ENDPOINT (BASE URL) + + EXAMPLE 1: Set base URL via config + cycod config set Ollama.BaseUrl http://localhost:11434/v1 + cycod config set Ollama.BaseUrl http://127.0.0.1:11434/v1 --local + cycod config set Ollama.BaseUrl http://my-host:11434/v1 --user + + EXAMPLE 2: Set base URL via environment variable + Set OLLAMA_BASE_URL environment variable (default: http://localhost:11434/v1) + + EXAMPLE 3: Supply base URL via command line + cycod chat --use-ollama --ollama-base-url http://localhost:11434/v1 -q "hello" + + FUNCTION CALLING + + In this release, CycoD uses Ollama for chat completion only. + Function tools are not enabled with Ollama. + + TROUBLESHOOTING + + - model "X" not found + Run: ollama pull + + - out of memory / slow generation + Try a smaller model (e.g., qwen2.5:0.5b or llama3:8b), close other GPU apps, + or use a quantized/compact variant if available. From 08ec7bb190d9176bc5ab2bf08c98dc136818ce74 Mon Sep 17 00:00:00 2001 From: emilyjiji <92887308+emilyjiji@users.noreply.github.com> Date: Wed, 27 Aug 2025 22:40:56 -0400 Subject: [PATCH 3/3] revert unrelated code --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index a9573a24b..99145552d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ CycoD is a command-line interface (CLI) application that provides a chat-based i - Run shell commands (Bash, CMD, PowerShell) with persistent sessions - Manipulate files (view, create, edit, replace text) - Access date and time information - - **GUI Interactions** (Windows only): Show dialogs for user input, file selection, and notifications - **Comprehensive Configuration System**: - Multiple configuration scopes (global, user, local) - Profile support for different configurations @@ -178,7 +177,6 @@ For more detailed documentation, see: - [Getting Started](docs/getting-started.md) - [Command Line Options](docs/cli-options.md) - [Function Calling](docs/function-calling.md) -- [GUI Functions](docs/gui-functions.md) - Windows GUI interaction functions - [Creating Aliases](docs/aliases.md) - [Chat History](docs/chat-history.md) - [Slash Commands](#chat-commands) - Special commands during chat sessions