diff --git a/registry/coder/modules/aider/README.md b/registry/coder/modules/aider/README.md index c93fb89cd..5f2bd3e23 100644 --- a/registry/coder/modules/aider/README.md +++ b/registry/coder/modules/aider/README.md @@ -8,76 +8,55 @@ tags: [agent, ai, aider] # Aider -Run [Aider](https://aider.chat) AI pair programming in your workspace. This module installs Aider and provides a persistent session using screen or tmux. +Run [Aider](https://aider.chat) AI pair programming in your workspace. This module installs Aider with AgentAPI for seamless Coder Tasks Support. ```tf module "aider" { - source = "registry.coder.com/coder/aider/coder" - version = "1.1.2" - agent_id = coder_agent.example.id + source = "registry.coder.com/coder/aider/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + ai_api_key = var.api_key + install_aider = true + ai_provider = "google" + ai_model = "gemini" + install_agentapi = true } ``` -## Features +## Prerequisites -- **Interactive Parameter Selection**: Choose your AI provider, model, and configuration options when creating the workspace -- **Multiple AI Providers**: Supports Anthropic (Claude), OpenAI, DeepSeek, GROQ, and OpenRouter -- **Persistent Sessions**: Uses screen (default) or tmux to keep Aider running in the background -- **Optional Dependencies**: Install Playwright for web page scraping and PortAudio for voice coding -- **Project Integration**: Works with any project directory, including Git repositories -- **Browser UI**: Use Aider in your browser with a modern web interface instead of the terminal -- **Non-Interactive Mode**: Automatically processes tasks when provided via the `task_prompt` variable +- Include the [Coder Login](https://registry.coder.com/modules/coder-login/coder) module in your template +- pipx is automatically installed if not already available -## Module Parameters - -> [!NOTE] -> The `use_screen` and `use_tmux` parameters cannot both be enabled at the same time. By default, `use_screen` is set to `true` and `use_tmux` is set to `false`. - -## Usage Examples - -### Basic setup with API key +## Usage Example ```tf -variable "anthropic_api_key" { - type = string - description = "Anthropic API key" - sensitive = true -} +data "coder_parameter" "ai_prompt" { + name = "AI Prompt" + description = "Write an initial prompt for Aider to work on." + type = "string" + default = "" + mutable = true -module "aider" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/aider/coder" - version = "1.1.2" - agent_id = coder_agent.example.id - ai_api_key = var.anthropic_api_key } -``` - -This basic setup will: - -- Install Aider in the workspace -- Create a persistent screen session named "aider" -- Configure Aider to use Anthropic Claude 3.7 Sonnet model -- Enable task reporting (configures Aider to report tasks to Coder MCP) -### Using OpenAI with tmux - -```tf -variable "openai_api_key" { +variable "gemini_api_key" { type = string - description = "OpenAI API key" + description = "Gemini API key" sensitive = true } module "aider" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/aider/coder" - version = "1.1.2" - agent_id = coder_agent.example.id - use_tmux = true - ai_provider = "openai" - ai_model = "4o" # Uses Aider's built-in alias for gpt-4o - ai_api_key = var.openai_api_key + source = "registry.coder.com/coder/aider/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + ai_api_key = var.gemini_api_key + install_aider = true + ai_provider = "google" + ai_model = "gemini" + install_agentapi = true + task_prompt = data.coder_parameter.ai_prompt.value + system_prompt = "..." } ``` @@ -93,7 +72,7 @@ variable "custom_api_key" { module "aider" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/aider/coder" - version = "1.1.2" + version = "1.0.0" agent_id = coder_agent.example.id ai_provider = "custom" custom_env_var_name = "MY_CUSTOM_API_KEY" @@ -102,165 +81,6 @@ module "aider" { } ``` -### Adding Custom Extensions (Experimental) - -You can extend Aider's capabilities by adding custom extensions: - -```tf -module "aider" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/aider/coder" - version = "1.1.2" - agent_id = coder_agent.example.id - ai_api_key = var.anthropic_api_key - - experiment_pre_install_script = <<-EOT - pip install some-custom-dependency - EOT - - experiment_additional_extensions = <<-EOT - custom-extension: - args: [] - cmd: custom-extension-command - description: A custom extension for Aider - enabled: true - envs: {} - name: custom-extension - timeout: 300 - type: stdio - EOT -} -``` - -Note: The indentation in the heredoc is preserved, so you can write the YAML naturally. - -## Task Reporting (Experimental) - -> This functionality is in early access as of Coder v2.21 and is still evolving. -> For now, we recommend testing it in a demo or staging environment, -> rather than deploying to production -> -> Learn more in [the Coder documentation](https://coder.com/docs/tutorials/ai-agents) -> -> Join our [Discord channel](https://discord.gg/coder) or -> [contact us](https://coder.com/contact) to get help or share feedback. - -Your workspace must have either `screen` or `tmux` installed to use this. - -Task reporting is **enabled by default** in this module, allowing you to: - -- Send an initial prompt to Aider during workspace creation -- Monitor task progress in the Coder UI -- Use the `coder_parameter` resource to collect prompts from users - -### Setting up Task Reporting - -To use task reporting effectively: - -1. Add the Coder Login module to your template -2. Configure the necessary variables to pass the task prompt -3. Optionally add a coder_parameter to collect prompts from users - -Here's a complete example: - -```tf -module "coder-login" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/modules/coder-login/coder" - version = "1.0.15" - agent_id = coder_agent.example.id -} - -variable "anthropic_api_key" { - type = string - description = "Anthropic API key" - sensitive = true -} - -data "coder_parameter" "ai_prompt" { - type = "string" - name = "AI Prompt" - default = "" - description = "Write a prompt for Aider" - mutable = true - ephemeral = true -} - -module "aider" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/aider/coder" - version = "1.1.2" - agent_id = coder_agent.example.id - ai_api_key = var.anthropic_api_key - task_prompt = data.coder_parameter.ai_prompt.value - - # Optionally customize the system prompt - system_prompt = <<-EOT -You are a helpful Coding assistant. Aim to autonomously investigate -and solve issues the user gives you and test your work, whenever possible. -Avoid shortcuts like mocking tests. When you get stuck, you can ask the user -but opt for autonomy. -YOU MUST REPORT ALL TASKS TO CODER. -When reporting tasks, you MUST follow these EXACT instructions: -- IMMEDIATELY report status after receiving ANY user message. -- Be granular. If you are investigating with multiple steps, report each step to coder. -Task state MUST be one of the following: -- Use "state": "working" when actively processing WITHOUT needing additional user input. -- Use "state": "complete" only when finished with a task. -- Use "state": "failure" when you need ANY user input, lack sufficient details, or encounter blockers. -Task summaries MUST: -- Include specifics about what you're doing. -- Include clear and actionable steps for the user. -- Be less than 160 characters in length. - EOT -} -``` - -When a task prompt is provided via the `task_prompt` variable, the module automatically: - -1. Combines the system prompt with the task prompt into a single message in the format: - -``` -SYSTEM PROMPT: -[system_prompt content] - -This is your current task: [task_prompt] -``` - -2. Executes the task during workspace creation using the `--message` and `--yes-always` flags -3. Logs task output to `$HOME/.aider.log` for reference - -If you want to disable task reporting, set `experiment_report_tasks = false` in your module configuration. - -## Using Aider in Your Workspace - -After the workspace starts, Aider will be installed and configured according to your parameters. A persistent session will automatically be started during workspace creation. - -### Session Options - -You can run Aider in three different ways: - -1. **Direct Mode**: Aider starts directly in the specified folder when you click the app button - -- Simple setup without persistent context -- Suitable for quick coding sessions - -2. **Screen Mode** (Default): Run Aider in a screen session that persists across connections - -- Session name: "aider" (or configured via `session_name`) - -3. **Tmux Mode**: Run Aider in a tmux session instead of screen - -- Set `use_tmux = true` to enable -- Session name: "aider" (or configured via `session_name`) -- Configures tmux with mouse support for shared sessions - -Persistent sessions (screen/tmux) allow you to: - -- Disconnect and reconnect without losing context -- Run Aider in the background while doing other work -- Switch between terminal and browser interfaces - ### Available AI Providers and Models Aider supports various providers and models, and this module integrates directly with Aider's built-in model aliases: @@ -280,10 +100,16 @@ For a complete and up-to-date list of supported aliases and models, please refer ## Troubleshooting -If you encounter issues: +- If `aider` is not found, ensure `install_aider = true` and your API key is valid +- Logs are written under `/home/coder/.aider-module/` (`install.log`, `agentapi-start.log`) for debugging +- If AgentAPI fails to start, verify that your container has network access and executable permissions for the scripts + +> [!IMPORTANT] +> For using **Coder Tasks** with Aider, make sure to pass the `AI Prompt` parameter and set `ai_api_key`. +> This ensures task reporting and status updates work seamlessly. -1. **Screen/Tmux issues**: If you can't reconnect to your session, check if the session exists with `screen -list` or `tmux list-sessions` -2. **API key issues**: Ensure you've entered the correct API key for your selected provider -3. **Browser mode issues**: If the browser interface doesn't open, check that you're accessing it from a machine that can reach your Coder workspace +## References -For more information on using Aider, see the [Aider documentation](https://aider.chat/docs/). +- [Aider Documentation](https://aider.chat/docs) +- [AgentAPI Documentation](https://github.com/coder/agentapi) +- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents) diff --git a/registry/coder/modules/aider/main.test.ts b/registry/coder/modules/aider/main.test.ts index c25513a56..ea722724f 100644 --- a/registry/coder/modules/aider/main.test.ts +++ b/registry/coder/modules/aider/main.test.ts @@ -1,107 +1,164 @@ -import { describe, expect, it } from "bun:test"; import { - findResourceInstance, - runTerraformApply, - runTerraformInit, - testRequiredVariables, -} from "~test"; - -describe("aider", async () => { - await runTerraformInit(import.meta.dir); + test, + afterEach, + describe, + setDefaultTimeout, + beforeAll, + expect, +} from "bun:test"; +import { execContainer, readFileContainer, runTerraformInit } from "~test"; +import { + loadTestFile, + writeExecutable, + setup as setupUtil, + execModuleScript, + expectAgentAPIStarted, +} from "../../../coder/modules/agentapi/test-util"; + +let cleanupFunctions: (() => Promise)[] = []; +const registerCleanup = (cleanup: () => Promise) => { + cleanupFunctions.push(cleanup); +}; +afterEach(async () => { + const cleanupFnsCopy = cleanupFunctions.slice().reverse(); + cleanupFunctions = []; + for (const cleanup of cleanupFnsCopy) { + try { + await cleanup(); + } catch (error) { + console.error("Error during cleanup:", error); + } + } +}); - testRequiredVariables(import.meta.dir, { - agent_id: "foo", +interface SetupProps { + skipAgentAPIMock?: boolean; + skipAiderMock?: boolean; + moduleVariables?: Record; + agentapiMockScript?: string; +} + +const setup = async (props?: SetupProps): Promise<{ id: string }> => { + const projectDir = "/home/coder/project"; + const { id } = await setupUtil({ + moduleDir: import.meta.dir, + moduleVariables: { + install_aider: props?.skipAiderMock ? "true" : "false", + install_agentapi: props?.skipAgentAPIMock ? "true" : "false", + aider_model: "test-model", + ...props?.moduleVariables, + }, + registerCleanup, + projectDir, + skipAgentAPIMock: props?.skipAgentAPIMock, + agentapiMockScript: props?.agentapiMockScript, }); - it("configures task prompt correctly", async () => { - const testPrompt = "Add a hello world function"; - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - task_prompt: testPrompt, + // Place the Aider mock CLI binary inside the container + if (!props?.skipAiderMock) { + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/aider", + content: await loadTestFile(`${import.meta.dir}`, "aider-mock.sh"), }); + } - const instance = findResourceInstance(state, "coder_script"); - expect(instance.script).toContain( - `This is your current task: ${testPrompt}`, - ); - expect(instance.script).toContain("aider --architect --yes-always"); - }); + return { id }; +}; - it("handles custom system prompt", async () => { - const customPrompt = "Report all tasks with state: working"; - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - system_prompt: customPrompt, - }); +setDefaultTimeout(60 * 5000); - const instance = findResourceInstance(state, "coder_script"); - expect(instance.script).toContain(customPrompt); +describe("Aider", async () => { + beforeAll(async () => { + await runTerraformInit(import.meta.dir); }); - it("handles pre and post install scripts", async () => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - experiment_pre_install_script: "echo 'Pre-install script executed'", - experiment_post_install_script: "echo 'Post-install script executed'", - }); - - const instance = findResourceInstance(state, "coder_script"); - - expect(instance.script).toContain("Running pre-install script"); - expect(instance.script).toContain("Running post-install script"); - expect(instance.script).toContain("base64 -d > /tmp/pre_install.sh"); - expect(instance.script).toContain("base64 -d > /tmp/post_install.sh"); + test("happy-path", async () => { + const { id } = await setup(); + await execModuleScript(id); + await expectAgentAPIStarted(id); }); - it("validates that use_screen and use_tmux cannot both be true", async () => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - use_screen: true, - use_tmux: true, + test("api-key", async () => { + const apiKey = "test-api-key-123"; + const { id } = await setup({ + moduleVariables: { + ai_api_key: apiKey, + }, }); - - const instance = findResourceInstance(state, "coder_script"); - - expect(instance.script).toContain( - "Error: Both use_screen and use_tmux cannot be enabled at the same time", + await execModuleScript(id); + const resp = await readFileContainer( + id, + "/home/coder/.aider-module/agentapi-start.log", ); - expect(instance.script).toContain("exit 1"); + expect(resp).toContain("API key provided !"); }); - it("configures Aider with known provider and model", async () => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - ai_provider: "anthropic", - ai_model: "sonnet", - ai_api_key: "test-anthropic-key", + test("custom-folder", async () => { + const folder = "/tmp/aider-test"; + const { id } = await setup({ + moduleVariables: { + folder, + }, }); + await execModuleScript(id); + const resp = await readFileContainer( + id, + "/home/coder/.aider-module/install.log", + ); + expect(resp).toContain(folder); + }); - const instance = findResourceInstance(state, "coder_script"); - expect(instance.script).toContain( - 'export ANTHROPIC_API_KEY=\\"test-anthropic-key\\"', + test("pre-post-install-scripts", async () => { + const { id } = await setup({ + moduleVariables: { + experiment_pre_install_script: "#!/bin/bash\necho 'pre-install-script'", + experiment_post_install_script: + "#!/bin/bash\necho 'post-install-script'", + }, + }); + await execModuleScript(id); + const preLog = await readFileContainer( + id, + "/home/coder/.aider-module/pre_install.log", ); - expect(instance.script).toContain("--model sonnet"); - expect(instance.script).toContain( - "Starting Aider using anthropic provider and model: sonnet", + expect(preLog).toContain("pre-install-script"); + const postLog = await readFileContainer( + id, + "/home/coder/.aider-module/post_install.log", ); + expect(postLog).toContain("post-install-script"); }); - it("handles custom provider with custom env var and API key", async () => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - ai_provider: "custom", - custom_env_var_name: "MY_CUSTOM_API_KEY", - ai_model: "custom-model", - ai_api_key: "test-custom-key", + test("system-prompt", async () => { + const system_prompt = "this is a system prompt for Aider"; + const { id } = await setup({ + moduleVariables: { + system_prompt, + }, }); - - const instance = findResourceInstance(state, "coder_script"); - expect(instance.script).toContain( - 'export MY_CUSTOM_API_KEY=\\"test-custom-key\\"', + await execModuleScript(id); + const resp = await readFileContainer( + id, + "/home/coder/.aider-module/SYSTEM_PROMPT.md", ); - expect(instance.script).toContain("--model custom-model"); - expect(instance.script).toContain( - "Starting Aider using custom provider and model: custom-model", + expect(resp).toContain(system_prompt); + }); + + test("task-prompt", async () => { + const prompt = "this is a task prompt for Aider"; + const apiKey = "test-api-key-123"; + const { id } = await setup({ + moduleVariables: { + ai_api_key: apiKey, + task_prompt: prompt, + }, + }); + await execModuleScript(id); + const resp = await readFileContainer( + id, + "/home/coder/.aider-module/agentapi-start.log", ); + expect(resp).toContain(`Aider task prompt provided : ${prompt}`); }); }); diff --git a/registry/coder/modules/aider/main.tf b/registry/coder/modules/aider/main.tf index e1f2eccdf..9773980db 100644 --- a/registry/coder/modules/aider/main.tf +++ b/registry/coder/modules/aider/main.tf @@ -48,30 +48,6 @@ variable "install_aider" { default = true } -variable "aider_version" { - type = string - description = "The version of Aider to install." - default = "latest" -} - -variable "use_screen" { - type = bool - description = "Whether to use screen for running Aider in the background" - default = true -} - -variable "use_tmux" { - type = bool - description = "Whether to use tmux instead of screen for running Aider in the background" - default = false -} - -variable "session_name" { - type = string - description = "Name for the persistent session (screen or tmux)" - default = "aider" -} - variable "experiment_report_tasks" { type = bool description = "Whether to enable task reporting." @@ -82,23 +58,22 @@ variable "system_prompt" { type = string description = "System prompt for instructing Aider on task reporting and behavior" default = <<-EOT -You are a helpful Coding assistant. Aim to autonomously investigate -and solve issues the user gives you and test your work, whenever possible. -Avoid shortcuts like mocking tests. When you get stuck, you can ask the user -but opt for autonomy. -YOU MUST REPORT ALL TASKS TO CODER. -When reporting tasks, you MUST follow these EXACT instructions: -- IMMEDIATELY report status after receiving ANY user message. -- Be granular. If you are investigating with multiple steps, report each step to coder. -Task state MUST be one of the following: -- Use "state": "working" when actively processing WITHOUT needing additional user input. -- Use "state": "complete" only when finished with a task. -- Use "state": "failure" when you need ANY user input, lack sufficient details, or encounter blockers. -Task summaries MUST: -- Include specifics about what you're doing. -- Include clear and actionable steps for the user. -- Be less than 160 characters in length. -EOT + You are a helpful Coding assistant. Aim to autonomously investigate + and solve issues the user gives you and test your work, whenever possible. + Avoid shortcuts like mocking tests. When you get stuck, you can ask the user + but opt for autonomy. + YOU MUST REPORT ALL TASKS TO CODER. + When reporting tasks, you MUST follow these EXACT instructions: + - IMMEDIATELY report status after receiving ANY user message. + - Be granular. If you are investigating with multiple steps, report each step to coder. + Task state MUST be one of the following: + - Use "state": "working" when actively processing WITHOUT needing additional user input. + - Use "state": "complete" only when finished with a task. + - Use "state": "failure" when you need ANY user input, lack sufficient details, or encounter blockers. + Task summaries MUST: + - Include clear and actionable steps for the user. + - Be less than 160 characters in length. + EOT } variable "task_prompt" { @@ -107,6 +82,12 @@ variable "task_prompt" { default = "" } +variable "aider_prompt" { + type = bool + description = "This prompt will be sent to Aider and should run only once, and AgentAPI will be disabled." + default = false +} + variable "experiment_pre_install_script" { type = string description = "Custom script to run before installing Aider." @@ -128,7 +109,7 @@ variable "experiment_additional_extensions" { variable "ai_provider" { type = string description = "AI provider to use with Aider (openai, anthropic, azure, google, etc.)" - default = "anthropic" + default = "google" validation { condition = contains(["openai", "anthropic", "azure", "google", "cohere", "mistral", "ollama", "custom"], var.ai_provider) error_message = "ai_provider must be one of: openai, anthropic, azure, google, cohere, mistral, ollama, custom" @@ -138,7 +119,7 @@ variable "ai_provider" { variable "ai_model" { type = string description = "AI model to use with Aider. Can use Aider's built-in aliases like '4o' (gpt-4o), 'sonnet' (claude-3-7-sonnet), 'opus' (claude-3-opus), etc." - default = "sonnet" + default = "gemini" } variable "ai_api_key" { @@ -154,55 +135,90 @@ variable "custom_env_var_name" { default = "" } -locals { - base_extensions = <<-EOT -coder: - args: - - exp - - mcp - - server - cmd: coder - description: Report ALL tasks and statuses (in progress, done, failed) you are working on. - enabled: true - envs: - CODER_MCP_APP_STATUS_SLUG: aider - name: Coder - timeout: 3000 - type: stdio -developer: - display_name: Developer - enabled: true - name: developer - timeout: 300 - type: builtin -EOT - - formatted_base = " ${replace(trimspace(local.base_extensions), "\n", "\n ")}" - additional_extensions = var.experiment_additional_extensions != null ? "\n ${replace(trimspace(var.experiment_additional_extensions), "\n", "\n ")}" : "" +variable "install_agentapi" { + type = bool + description = "Whether to install AgentAPI." + default = true +} + +variable "agentapi_version" { + type = string + description = "The version of AgentAPI to install." + default = "v0.3.0" +} - combined_extensions = <<-EOT -extensions: -${local.formatted_base}${local.additional_extensions} -EOT +variable "base_aider_config" { + type = string + description = <<-EOT + Base Aider configuration in yaml format. Will be stored in .aider.conf.yml file. + + options include: + read: + - CONVENTIONS.md + - anotherfile.txt + - thirdfile.py + model: xxx + ##Specify the OpenAI API key + openai-api-key: xxx + ## (deprecated, use --set-env OPENAI_API_TYPE=) + openai-api-type: xxx + ## (deprecated, use --set-env OPENAI_API_VERSION=) + openai-api-version: xxx + ## (deprecated, use --set-env OPENAI_API_DEPLOYMENT_ID=) + openai-api-deployment-id: xxx + ## Set an environment variable (to control API settings, can be used multiple times) + set-env: xxx + ## Specify multiple values like this: + set-env: + - xxx + - yyy + - zzz + + Reference : https://aider.chat/docs/config/aider_conf.html + EOT + default = null +} - encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : "" - encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : "" - # Combine system prompt and task prompt for aider - combined_prompt = trimspace(<<-EOT -SYSTEM PROMPT: -${var.system_prompt} +locals { + app_slug = "aider" + coder_mcp = <<-EOT + coder: + args: + - exp + - mcp + - server + cmd: coder + description: Report ALL tasks and statuses (in progress, done, failed) you are working on. + enabled: true + envs: + - CODER_MCP_APP_STATUS_SLUG: aider + - CODER_MCP_AI_AGENTAPI_URL: "http://localhost:3284" + name: Coder + timeout: 3000 + type: stdio + developer: + display_name: Developer + enabled: true + name: developer + timeout: 300 + type: builtin + EOT -This is your current task: ${var.task_prompt} -EOT - ) + formatted_base = "\n ${replace(trimspace(local.coder_mcp), "\n", "\n ")}" + additional_extensions = var.experiment_additional_extensions != null ? "\n ${replace(trimspace(var.experiment_additional_extensions), "\n", "\n ")}" : "" + base_aider_config = var.base_aider_config != null ? "${replace(trimspace(var.base_aider_config), "\n", "\n ")}" : "" + combined_extensions = <<-EOT + extensions: + ${local.base_aider_config}${local.formatted_base}${local.additional_extensions} + EOT # Map providers to their environment variable names provider_env_vars = { openai = "OPENAI_API_KEY" anthropic = "ANTHROPIC_API_KEY" azure = "AZURE_OPENAI_API_KEY" - google = "GOOGLE_API_KEY" + google = "GEMINI_API_KEY" cohere = "COHERE_API_KEY" mistral = "MISTRAL_API_KEY" ollama = "OLLAMA_HOST" @@ -214,296 +230,59 @@ EOT # Model flag for aider command model_flag = var.ai_provider == "ollama" ? "--ollama-model" : "--model" + + install_script = file("${path.module}/scripts/install.sh") + start_script = file("${path.module}/scripts/start.sh") + module_dir_name = ".aider-module" } -# Install and Initialize Aider -resource "coder_script" "aider" { - agent_id = var.agent_id - display_name = "Aider" - icon = var.icon - script = <<-EOT +module "agentapi" { + source = "registry.coder.com/coder/agentapi/coder" + version = "1.0.1" + + agent_id = var.agent_id + web_app_slug = local.app_slug + web_app_order = var.order + web_app_group = var.group + web_app_icon = var.icon + web_app_display_name = "Aider" + cli_app_slug = "${local.app_slug}-cli" + cli_app_display_name = "Aider CLI" + module_dir_name = local.module_dir_name + install_agentapi = var.install_agentapi + agentapi_version = var.agentapi_version + pre_install_script = var.experiment_pre_install_script + post_install_script = var.experiment_post_install_script + start_script = <<-EOT #!/bin/bash - set -e - - command_exists() { - command -v "$1" >/dev/null 2>&1 - } - - echo "Setting up Aider AI pair programming..." - - if [ "${var.use_screen}" = "true" ] && [ "${var.use_tmux}" = "true" ]; then - echo "Error: Both use_screen and use_tmux cannot be enabled at the same time." - exit 1 - fi - - mkdir -p "${var.folder}" - - if [ "$(uname)" = "Linux" ]; then - echo "Checking dependencies for Linux..." - - if [ "${var.use_tmux}" = "true" ]; then - if ! command_exists tmux; then - echo "Installing tmux for persistent sessions..." - if command -v apt-get >/dev/null 2>&1; then - if command -v sudo >/dev/null 2>&1; then - sudo apt-get update -qq - sudo apt-get install -y -qq tmux - else - apt-get update -qq || echo "Warning: Cannot update package lists without sudo privileges" - apt-get install -y -qq tmux || echo "Warning: Cannot install tmux without sudo privileges" - fi - elif command -v dnf >/dev/null 2>&1; then - if command -v sudo >/dev/null 2>&1; then - sudo dnf install -y -q tmux - else - dnf install -y -q tmux || echo "Warning: Cannot install tmux without sudo privileges" - fi - else - echo "Warning: Unable to install tmux on this system. Neither apt-get nor dnf found." - fi - else - echo "tmux is already installed, skipping installation." - fi - elif [ "${var.use_screen}" = "true" ]; then - if ! command_exists screen; then - echo "Installing screen for persistent sessions..." - if command -v apt-get >/dev/null 2>&1; then - if command -v sudo >/dev/null 2>&1; then - sudo apt-get update -qq - sudo apt-get install -y -qq screen - else - apt-get update -qq || echo "Warning: Cannot update package lists without sudo privileges" - apt-get install -y -qq screen || echo "Warning: Cannot install screen without sudo privileges" - fi - elif command -v dnf >/dev/null 2>&1; then - if command -v sudo >/dev/null 2>&1; then - sudo dnf install -y -q screen - else - dnf install -y -q screen || echo "Warning: Cannot install screen without sudo privileges" - fi - else - echo "Warning: Unable to install screen on this system. Neither apt-get nor dnf found." - fi - else - echo "screen is already installed, skipping installation." - fi - fi - else - echo "This module currently only supports Linux workspaces." - exit 1 - fi - - if [ -n "${local.encoded_pre_install_script}" ]; then - echo "Running pre-install script..." - echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh - chmod +x /tmp/pre_install.sh - /tmp/pre_install.sh - fi - - if [ "${var.install_aider}" = "true" ]; then - echo "Installing Aider..." - - if ! command_exists python3 || ! command_exists pip3; then - echo "Installing Python dependencies required for Aider..." - if command -v apt-get >/dev/null 2>&1; then - if command -v sudo >/dev/null 2>&1; then - sudo apt-get update -qq - sudo apt-get install -y -qq python3-pip python3-venv - else - apt-get update -qq || echo "Warning: Cannot update package lists without sudo privileges" - apt-get install -y -qq python3-pip python3-venv || echo "Warning: Cannot install Python packages without sudo privileges" - fi - elif command -v dnf >/dev/null 2>&1; then - if command -v sudo >/dev/null 2>&1; then - sudo dnf install -y -q python3-pip python3-virtualenv - else - dnf install -y -q python3-pip python3-virtualenv || echo "Warning: Cannot install Python packages without sudo privileges" - fi - else - echo "Warning: Unable to install Python on this system. Neither apt-get nor dnf found." - fi - else - echo "Python is already installed, skipping installation." - fi - - if ! command_exists aider; then - curl -LsSf https://aider.chat/install.sh | sh - fi - - if [ -f "$HOME/.bashrc" ]; then - if ! grep -q 'export PATH="$HOME/bin:$PATH"' "$HOME/.bashrc"; then - echo 'export PATH="$HOME/bin:$PATH"' >> "$HOME/.bashrc" - fi - fi - - if [ -f "$HOME/.zshrc" ]; then - if ! grep -q 'export PATH="$HOME/bin:$PATH"' "$HOME/.zshrc"; then - echo 'export PATH="$HOME/bin:$PATH"' >> "$HOME/.zshrc" - fi - fi - - fi - - if [ -n "${local.encoded_post_install_script}" ]; then - echo "Running post-install script..." - echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh - chmod +x /tmp/post_install.sh - /tmp/post_install.sh - fi - - if [ "${var.experiment_report_tasks}" = "true" ]; then - echo "Configuring Aider to report tasks via Coder MCP..." - - mkdir -p "$HOME/.config/aider" - - cat > "$HOME/.config/aider/config.yml" << EOL -${trimspace(local.combined_extensions)} -EOL - echo "Added Coder MCP extension to Aider config.yml" - fi - - echo "Starting persistent Aider session..." - - touch "$HOME/.aider.log" - - export LANG=en_US.UTF-8 - export LC_ALL=en_US.UTF-8 - - export PATH="$HOME/bin:$PATH" - - if [ "${var.use_tmux}" = "true" ]; then - if [ -n "${var.task_prompt}" ]; then - echo "Running Aider with message in tmux session..." - - # Configure tmux for shared sessions - if [ ! -f "$HOME/.tmux.conf" ]; then - echo "Creating ~/.tmux.conf with shared session settings..." - echo "set -g mouse on" > "$HOME/.tmux.conf" - fi - - if ! grep -q "^set -g mouse on$" "$HOME/.tmux.conf"; then - echo "Adding 'set -g mouse on' to ~/.tmux.conf..." - echo "set -g mouse on" >> "$HOME/.tmux.conf" - fi - - echo "Starting Aider using ${var.ai_provider} provider and model: ${var.ai_model}" - tmux new-session -d -s ${var.session_name} -c ${var.folder} "export ${local.env_var_name}=\"${var.ai_api_key}\"; aider --architect --yes-always ${local.model_flag} ${var.ai_model} --message \"${local.combined_prompt}\"" - echo "Aider task started in tmux session '${var.session_name}'. Check the UI for progress." - else - # Configure tmux for shared sessions - if [ ! -f "$HOME/.tmux.conf" ]; then - echo "Creating ~/.tmux.conf with shared session settings..." - echo "set -g mouse on" > "$HOME/.tmux.conf" - fi - - if ! grep -q "^set -g mouse on$" "$HOME/.tmux.conf"; then - echo "Adding 'set -g mouse on' to ~/.tmux.conf..." - echo "set -g mouse on" >> "$HOME/.tmux.conf" - fi - - echo "Starting Aider using ${var.ai_provider} provider and model: ${var.ai_model}" - tmux new-session -d -s ${var.session_name} -c ${var.folder} "export ${local.env_var_name}=\"${var.ai_api_key}\"; aider --architect --yes-always ${local.model_flag} ${var.ai_model} --message \"${var.system_prompt}\"" - echo "Tmux session '${var.session_name}' started. Access it by clicking the Aider button." - fi - else - if [ -n "${var.task_prompt}" ]; then - echo "Running Aider with message in screen session..." - - if [ ! -f "$HOME/.screenrc" ]; then - echo "Creating ~/.screenrc and adding multiuser settings..." - echo -e "multiuser on\nacladd $(whoami)" > "$HOME/.screenrc" - fi - - if ! grep -q "^multiuser on$" "$HOME/.screenrc"; then - echo "Adding 'multiuser on' to ~/.screenrc..." - echo "multiuser on" >> "$HOME/.screenrc" - fi - - if ! grep -q "^acladd $(whoami)$" "$HOME/.screenrc"; then - echo "Adding 'acladd $(whoami)' to ~/.screenrc..." - echo "acladd $(whoami)" >> "$HOME/.screenrc" - fi - - echo "Starting Aider using ${var.ai_provider} provider and model: ${var.ai_model}" - screen -U -dmS ${var.session_name} bash -c " - cd ${var.folder} - export PATH=\"$HOME/bin:$HOME/.local/bin:$PATH\" - export ${local.env_var_name}=\"${var.ai_api_key}\" - aider --architect --yes-always ${local.model_flag} ${var.ai_model} --message \"${local.combined_prompt}\" - /bin/bash - " - - echo "Aider task started in screen session '${var.session_name}'. Check the UI for progress." - else - - if [ ! -f "$HOME/.screenrc" ]; then - echo "Creating ~/.screenrc and adding multiuser settings..." - echo -e "multiuser on\nacladd $(whoami)" > "$HOME/.screenrc" - fi - - if ! grep -q "^multiuser on$" "$HOME/.screenrc"; then - echo "Adding 'multiuser on' to ~/.screenrc..." - echo "multiuser on" >> "$HOME/.screenrc" - fi - - if ! grep -q "^acladd $(whoami)$" "$HOME/.screenrc"; then - echo "Adding 'acladd $(whoami)' to ~/.screenrc..." - echo "acladd $(whoami)" >> "$HOME/.screenrc" - fi - - echo "Starting Aider using ${var.ai_provider} provider and model: ${var.ai_model}" - screen -U -dmS ${var.session_name} bash -c " - cd ${var.folder} - export PATH=\"$HOME/bin:$HOME/.local/bin:$PATH\" - export ${local.env_var_name}=\"${var.ai_api_key}\" - aider --architect --yes-always ${local.model_flag} ${var.ai_model} --message \"${local.combined_prompt}\" - /bin/bash - " - echo "Screen session '${var.session_name}' started. Access it by clicking the Aider button." - fi - fi - - echo "Aider setup complete!" + set -o errexit + set -o pipefail + + echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh + chmod +x /tmp/start.sh + AIDER_START_DIRECTORY='${var.folder}' \ + ARG_API_KEY='${var.ai_api_key}' \ + ARG_AI_MODULE='${var.ai_model}' \ + ARG_AI_PROVIDER='${var.ai_provider}' \ + ARG_ENV_API_NAME_HOLDER='${local.env_var_name}' \ + ARG_TASK_PROMPT='${base64encode(var.task_prompt)}' \ + AIDER_PROMPT='${var.aider_prompt}' \ + /tmp/start.sh EOT - run_on_start = true -} -# Aider CLI app -resource "coder_app" "aider_cli" { - agent_id = var.agent_id - slug = "aider" - display_name = "Aider" - icon = var.icon - command = <<-EOT + install_script = <<-EOT #!/bin/bash - set -e - - export PATH="$HOME/bin:$HOME/.local/bin:$PATH" - - export LANG=en_US.UTF-8 - export LC_ALL=en_US.UTF-8 - - if [ "${var.use_tmux}" = "true" ]; then - if tmux has-session -t ${var.session_name} 2>/dev/null; then - echo "Attaching to existing Aider tmux session..." - tmux attach-session -t ${var.session_name} - else - echo "Starting new Aider tmux session..." - tmux new-session -s ${var.session_name} -c ${var.folder} "export ${local.env_var_name}=\"${var.ai_api_key}\"; aider ${local.model_flag} ${var.ai_model} --message \"${local.combined_prompt}\"; exec bash" - fi - elif [ "${var.use_screen}" = "true" ]; then - if ! screen -list | grep -q "${var.session_name}"; then - echo "Error: No existing Aider session found. Please wait for the script to start it." - exit 1 - fi - screen -xRR ${var.session_name} - else - cd "${var.folder}" - echo "Starting Aider directly..." - export ${local.env_var_name}="${var.ai_api_key}" - aider ${local.model_flag} ${var.ai_model} --message "${local.combined_prompt}" - fi + set -o errexit + set -o pipefail + + echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh + chmod +x /tmp/install.sh + AIDER_START_DIRECTORY='${var.folder}' \ + ARG_INSTALL_AIDER='${var.install_aider}' \ + AIDER_SYSTEM_PROMPT='${var.system_prompt}' \ + ARG_IMPLEMENT_MCP='${var.experiment_report_tasks}' \ + ARG_AIDER_CONFIG="$(echo -n '${base64encode(trimspace(local.combined_extensions))}' | base64 -d)" \ + /tmp/install.sh EOT - order = var.order - group = var.group } + diff --git a/registry/coder/modules/aider/scripts/install.sh b/registry/coder/modules/aider/scripts/install.sh new file mode 100644 index 000000000..178b28342 --- /dev/null +++ b/registry/coder/modules/aider/scripts/install.sh @@ -0,0 +1,57 @@ +#!/bin/bash +set -euo pipefail + +# Function to check if a command exists +command_exists() { + command -v "$1" > /dev/null 2>&1 +} + +echo "--------------------------------" +echo "Install flag: $ARG_INSTALL_AIDER" +echo "Workspace: $AIDER_START_DIRECTORY" +echo "--------------------------------" + +function install_aider() { + echo "pipx installing..." + sudo apt-get install -y pipx + echo "pipx installed!" + pipx ensurepath + echo $PATH + mkdir -p "$AIDER_START_DIRECTORY/.local/bin" + export PATH="$HOME/.local/bin:$AIDER_START_DIRECTORY/.local/bin:$PATH" # ensure in current shell too + + if ! command_exists aider; then + echo "Installing Aider via pipx..." + pipx install --force aider-install + aider-install + fi + echo "Aider installed: $(aider --version || echo 'check failed the Aider module insatllation failed')" +} + +function setup_system_prompt() { + if [ -n "${AIDER_SYSTEM_PROMPT:-}" ]; then + echo "Setting Aider system prompt..." + mkdir -p "$HOME/.aider-module" + echo "$AIDER_SYSTEM_PROMPT" > "$HOME/.aider-module/SYSTEM_PROMPT.md" + echo "System prompt saved to $HOME/.aider-module/SYSTEM_PROMPT.md" + else + echo "No system prompt provided for Aider." + fi +} + +function configure_aider_settings() { + if [ "${ARG_IMPLEMENT_MCP}" = "true" ]; then + echo "Configuring Aider to report tasks via Coder MCP..." + + mkdir -p "$HOME/.config/aider" + + echo "$ARG_AIDER_CONFIG" > "$HOME/.config/aider/.aider.conf.yml" + echo "Added Coder MCP extension to Aider config.yml" + else + printf "MCP Server not Implemented" + fi +} + +install_aider +setup_system_prompt +configure_aider_settings diff --git a/registry/coder/modules/aider/scripts/start.sh b/registry/coder/modules/aider/scripts/start.sh new file mode 100644 index 000000000..ef5e47e41 --- /dev/null +++ b/registry/coder/modules/aider/scripts/start.sh @@ -0,0 +1,34 @@ +#!/bin/bash +set -euo pipefail + +# Ensure pipx-installed apps are in PATH +export PATH="$HOME/.local/bin:$PATH" + +echo "--------------------------------" +echo "Provider: $ARG_AI_PROVIDER" +echo "Module: $ARG_AI_MODULE" +echo "--------------------------------" + +ARG_TASK_PROMPT=$(echo -n "${ARG_TASK_PROMPT:-}" | base64 -d) + +if [ -n "$ARG_API_KEY" ]; then + printf "API key provided !\n" + export $ARG_ENV_API_NAME_HOLDER=$ARG_API_KEY +else + printf "API key not provided\n" +fi + +if [[ "${AIDER_PROMPT}" == "true" && -n "${ARG_TASK_PROMPT:-}" ]]; then + printf "Aider start only with this prompt : $ARG_TASK_PROMPT" + mkdir -p $HOME/.aider-module/ + echo aider --model $ARG_AI_MODULE --yes-always --message "$ARG_TASK_PROMPT" > $HOME/.aider-module/aider_output.txt + +elif [ -n "${ARG_TASK_PROMPT:-}" ]; then + printf "Aider task prompt provided : $ARG_TASK_PROMPT" + PROMPT="Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $ARG_TASK_PROMPT" + + agentapi server --term-width=67 --term-height=1190 -- aider --model $ARG_AI_MODULE --yes-always --message "$ARG_TASK_PROMPT" +else + printf "No task prompt given.\n" + agentapi server --term-width=67 --term-height=1190 -- aider --model $ARG_AI_MODULE --yes-always +fi diff --git a/registry/coder/modules/aider/testdata/aider-mock.sh b/registry/coder/modules/aider/testdata/aider-mock.sh new file mode 100644 index 000000000..e021b2d2d --- /dev/null +++ b/registry/coder/modules/aider/testdata/aider-mock.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +if [[ "$1" == "--version" ]]; then + echo "HELLO: $(bash -c env)" + echo "aider version v0.86.0" + exit 0 +fi + +set -e + +while true; do + echo "$(date) - aider-agent-mock" + sleep 15 +done