diff --git a/registry/coder/modules/agentapi/README.md b/registry/coder/modules/agentapi/README.md index 22ce50fe8..f264028cc 100644 --- a/registry/coder/modules/agentapi/README.md +++ b/registry/coder/modules/agentapi/README.md @@ -16,36 +16,21 @@ The AgentAPI module is a building block for modules that need to run an AgentAPI ```tf module "agentapi" { source = "registry.coder.com/coder/agentapi/coder" - version = "2.3.0" + version = "3.0.0" 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 = "Goose" - cli_app_slug = "goose-cli" - cli_app_display_name = "Goose CLI" + web_app_display_name = "ClaudeCode" + cli_app_slug = "claude-cli" + cli_app_display_name = "Claude CLI" module_dir_name = local.module_dir_name install_agentapi = var.install_agentapi - pre_install_script = var.pre_install_script - post_install_script = var.post_install_script - start_script = local.start_script - install_script = <<-EOT - #!/bin/bash - set -o errexit - set -o pipefail - - echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh - chmod +x /tmp/install.sh - - ARG_PROVIDER='${var.goose_provider}' \ - ARG_MODEL='${var.goose_model}' \ - ARG_GOOSE_CONFIG="$(echo -n '${base64encode(local.combined_extensions)}' | base64 -d)" \ - ARG_INSTALL='${var.install_goose}' \ - ARG_GOOSE_VERSION='${var.goose_version}' \ - /tmp/install.sh - EOT + agent_name = "claude" + agentapi_term_width = 67 + agentapi_term_height = 1190 } ``` @@ -132,3 +117,21 @@ This ensures only the agent process is sandboxed while agentapi itself runs unre ## For module developers For a complete example of how to use this module, see the [Goose module](https://github.com/coder/registry/blob/main/registry/coder/modules/goose/main.tf). + +### agent-command.sh + +The calling module must create an executable script at `$HOME/{module_dir_name}/agent-command.sh` before this module's script runs. This script should contain the command to start your AI agent. + +Example: + +```bash +#!/bin/bash +module_path="$HOME/.my-module" + +cat > "$module_path/agent-command.sh" << EOF +#!/bin/bash +my-agent-command --my-agent-flags +EOF +``` + +The AgentAPI module will run this script with the agentapi server. diff --git a/registry/coder/modules/agentapi/main.test.ts b/registry/coder/modules/agentapi/main.test.ts index 39d10ca7a..1ebe569e7 100644 --- a/registry/coder/modules/agentapi/main.test.ts +++ b/registry/coder/modules/agentapi/main.test.ts @@ -6,7 +6,12 @@ import { setDefaultTimeout, beforeAll, } from "bun:test"; -import { execContainer, readFileContainer, runTerraformInit } from "~test"; +import { + execContainer, + readFileContainer, + runTerraformInit, + runTerraformApply, +} from "~test"; import { loadTestFile, writeExecutable, @@ -58,9 +63,13 @@ const setup = async (props?: SetupProps): Promise<{ id: string }> => { cli_app_display_name: "AgentAPI CLI", cli_app_slug: "agentapi-cli", agentapi_version: "latest", + agent_name: "claude", module_dir_name: moduleDirName, - start_script: await loadTestFile(import.meta.dir, "agentapi-start.sh"), folder: projectDir, + pre_install_script: "echo 'Pre-install'", + install_script: "echo 'Install'", + post_install_script: "echo 'Post-install'", + start_script: "echo 'Start'", ...props?.moduleVariables, }, registerCleanup, @@ -68,11 +77,23 @@ const setup = async (props?: SetupProps): Promise<{ id: string }> => { skipAgentAPIMock: props?.skipAgentAPIMock, moduleDir: import.meta.dir, }); + // Create the ai agent mock binary await writeExecutable({ containerId: id, filePath: "/usr/bin/aiagent", content: await loadTestFile(import.meta.dir, "ai-agent-mock.js"), }); + // Create the agent-command.sh script that the module expects + await execContainer(id, [ + "bash", + "-c", + `mkdir -p /home/coder/${moduleDirName}`, + ]); + await writeExecutable({ + containerId: id, + filePath: `/home/coder/${moduleDirName}/agent-command.sh`, + content: "#!/bin/bash\nexec aiagent", + }); return { id }; }; @@ -104,36 +125,6 @@ describe("agentapi", async () => { await expectAgentAPIStarted(id, 3827); }); - test("pre-post-install-scripts", async () => { - const { id } = await setup({ - moduleVariables: { - pre_install_script: `#!/bin/bash\necho "pre-install"`, - install_script: `#!/bin/bash\necho "install"`, - post_install_script: `#!/bin/bash\necho "post-install"`, - }, - }); - - await execModuleScript(id); - await expectAgentAPIStarted(id); - - const preInstallLog = await readFileContainer( - id, - `/home/coder/${moduleDirName}/pre_install.log`, - ); - const installLog = await readFileContainer( - id, - `/home/coder/${moduleDirName}/install.log`, - ); - const postInstallLog = await readFileContainer( - id, - `/home/coder/${moduleDirName}/post_install.log`, - ); - - expect(preInstallLog).toContain("pre-install"); - expect(installLog).toContain("install"); - expect(postInstallLog).toContain("post-install"); - }); - test("install-agentapi", async () => { const { id } = await setup({ skipAgentAPIMock: true }); @@ -160,12 +151,12 @@ describe("agentapi", async () => { expect(respModuleScript.exitCode).toBe(0); await expectAgentAPIStarted(id); - const agentApiStartLog = await readFileContainer( + const agentApiMockLog = await readFileContainer( id, - "/home/coder/test-agentapi-start.log", + "/home/coder/agentapi-mock.log", ); - expect(agentApiStartLog).toContain( - "Using AGENTAPI_CHAT_BASE_PATH: /@default/default.foo/apps/agentapi-web/chat", + expect(agentApiMockLog).toContain( + "AGENTAPI_CHAT_BASE_PATH: /@default/default.foo/apps/agentapi-web/chat", ); }); @@ -258,6 +249,38 @@ describe("agentapi", async () => { expect(agentApiStartLog).toContain("AGENTAPI_ALLOWED_HOSTS: *"); }); + test("enable-agentapi-false", async () => { + // Test that when enable_agentapi is false: + // 1. AgentAPI web app is not created + // 2. AgentAPI is not started + // 3. CLI app still works and uses agent-command.sh + const { id } = await setup({ + moduleVariables: { + enable_agentapi: "false", + cli_app: "true", + }, + }); + + const respModuleScript = await execModuleScript(id); + expect(respModuleScript.exitCode).toBe(0); + + // Verify agentapi is not running on the default port + const respCheck = await execContainer(id, [ + "bash", + "-c", + "curl -fs -o /dev/null http://localhost:3284/status || echo 'not running'", + ]); + expect(respCheck.stdout).toContain("not running"); + + // Verify agent-command.sh script exists and is executable + const respAgentCommand = await execContainer(id, [ + "bash", + "-c", + `test -x /home/coder/${moduleDirName}/agent-command.sh && echo 'exists'`, + ]); + expect(respAgentCommand.stdout).toContain("exists"); + }); + test("state-persistence-disabled", async () => { const { id } = await setup({ moduleVariables: { diff --git a/registry/coder/modules/agentapi/main.tf b/registry/coder/modules/agentapi/main.tf index 6f177036b..df1d35a93 100644 --- a/registry/coder/modules/agentapi/main.tf +++ b/registry/coder/modules/agentapi/main.tf @@ -110,6 +110,12 @@ variable "start_script" { description = "Script that starts AgentAPI." } +variable "enable_agentapi" { + type = bool + description = "Whether to enable AgentAPI. If false, AgentAPI will not be installed or started, and the web app will not be created." + default = true +} + variable "install_agentapi" { type = bool description = "Whether to install AgentAPI." @@ -128,6 +134,29 @@ variable "agentapi_port" { default = 3284 } +variable "agent_name" { + type = string + description = "The agent's name. This is used as server type for AgentAPI, passed using --agent flag." +} + +variable "agentapi_term_width" { + type = number + description = "The terminal width for AgentAPI." + default = 67 +} + +variable "agentapi_term_height" { + type = number + description = "The terminal height for AgentAPI." + default = 1190 +} + +variable "agentapi_initial_prompt" { + type = string + description = "Initial prompt for the agent. Recommended only if the agent doesn't support initial prompt in interaction mode." + default = null +} + variable "task_log_snapshot" { type = bool description = "Capture last 10 messages when workspace stops for offline viewing while task is paused." @@ -162,6 +191,21 @@ variable "agentapi_subdomain" { variable "module_dir_name" { type = string description = "Name of the subdirectory in the home directory for module files." + default = null + validation { + condition = var.module_dir_name == null || var.module_dir_path == null + error_message = "Cannot set both module_dir_name and module_dir_path. Please set only one of them to specify the module directory location." + } +} + +variable "module_dir_path" { + type = string + description = "Path to the module directory." + default = null + validation { + condition = var.module_dir_name == null || var.module_dir_path == null + error_message = "Cannot set both module_dir_name and module_dir_path. Please set only one of them to specify the module directory location." + } } variable "enable_boundary" { @@ -222,10 +266,7 @@ resource "coder_env" "boundary_config" { locals { # we always trim the slash for consistency workdir = trimsuffix(var.folder, "/") - encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : "" - encoded_install_script = var.install_script != null ? base64encode(var.install_script) : "" - encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : "" - agentapi_start_script_b64 = base64encode(var.start_script) + encoded_initial_prompt = var.agentapi_initial_prompt != null ? base64encode(var.agentapi_initial_prompt) : "" agentapi_wait_for_start_script_b64 = base64encode(file("${path.module}/scripts/agentapi-wait-for-start.sh")) // Chat base path is only set if not using a subdomain. // NOTE: @@ -238,50 +279,89 @@ locals { shutdown_script = file("${path.module}/scripts/agentapi-shutdown.sh") lib_script = file("${path.module}/scripts/lib.sh") boundary_script = file("${path.module}/scripts/boundary.sh") + + agentapi_main_script_name = "${var.agent_name}-main_script" + + module_dir_path = var.module_dir_path == null ? "$HOME/.coder-modules/coder/${var.module_dir_name}" : var.module_dir_path +} + +module "agent-helper" { + source = "registry.coder.com/coder/agent-helper/coder" + version = "1.0.0" + agent_id = var.agent_id + agent_name = var.agent_name + module_dir_name = var.module_dir_name + + pre_install_script = var.pre_install_script + install_script = var.install_script + post_install_script = var.post_install_script + start_script = var.start_script +} + +resource "coder_script" "boundary" { + count = var.enable_boundary ? 1 : 0 + agent_id = "" + display_name = "" + script = < /tmp/boundary.sh + chmod +x /tmp/boundary.sh + + ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \ + ARG_BOUNDARY_VERSION='${var.boundary_version}' \ + ARG_COMPILE_BOUNDARY_FROM_SOURCE='${var.compile_boundary_from_source}' \ + ARG_USE_BOUNDARY_DIRECTLY='${var.use_boundary_directly}' \ + /tmp/boundary.sh + EOT } resource "coder_script" "agentapi" { + count = var.enable_agentapi ? 1 : 0 agent_id = var.agent_id - display_name = "Install and start AgentAPI" + display_name = "Start AgentAPI" icon = var.web_app_icon script = <<-EOT #!/bin/bash set -o errexit set -o pipefail + trap 'coder exp sync complete ${local.agentapi_main_script_name}' EXIT + coder exp sync want ${local.agentapi_main_script_name} ${module.agent-helper.start_script_name} + coder exp sync start ${local.agentapi_main_script_name} + echo -n '${base64encode(local.main_script)}' | base64 -d > /tmp/main.sh chmod +x /tmp/main.sh echo -n '${base64encode(local.lib_script)}' | base64 -d > /tmp/agentapi-lib.sh - + echo -n '${base64encode(local.boundary_script)}' | base64 -d > /tmp/agentapi-boundary.sh chmod +x /tmp/agentapi-boundary.sh ARG_MODULE_DIR_NAME='${var.module_dir_name}' \ ARG_WORKDIR="$(echo -n '${base64encode(local.workdir)}' | base64 -d)" \ - ARG_PRE_INSTALL_SCRIPT="$(echo -n '${local.encoded_pre_install_script}' | base64 -d)" \ - ARG_INSTALL_SCRIPT="$(echo -n '${local.encoded_install_script}' | base64 -d)" \ ARG_INSTALL_AGENTAPI='${var.install_agentapi}' \ ARG_AGENTAPI_VERSION='${var.agentapi_version}' \ - ARG_START_SCRIPT="$(echo -n '${local.agentapi_start_script_b64}' | base64 -d)" \ ARG_WAIT_FOR_START_SCRIPT="$(echo -n '${local.agentapi_wait_for_start_script_b64}' | base64 -d)" \ - ARG_POST_INSTALL_SCRIPT="$(echo -n '${local.encoded_post_install_script}' | base64 -d)" \ ARG_AGENTAPI_PORT='${var.agentapi_port}' \ + ARG_AGENTAPI_SERVER_TYPE='${var.agent_name}' \ + ARG_AGENTAPI_TERM_WIDTH='${var.agentapi_term_width}' \ + ARG_AGENTAPI_TERM_HEIGHT='${var.agentapi_term_height}' \ + ARG_AGENTAPI_INITIAL_PROMPT="$(echo -n '${local.encoded_initial_prompt}' | base64 -d)" \ ARG_AGENTAPI_CHAT_BASE_PATH='${local.agentapi_chat_base_path}' \ ARG_TASK_ID='${try(data.coder_task.me.id, "")}' \ ARG_TASK_LOG_SNAPSHOT='${var.task_log_snapshot}' \ - ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \ - ARG_BOUNDARY_VERSION='${var.boundary_version}' \ - ARG_COMPILE_BOUNDARY_FROM_SOURCE='${var.compile_boundary_from_source}' \ - ARG_USE_BOUNDARY_DIRECTLY='${var.use_boundary_directly}' \ ARG_ENABLE_STATE_PERSISTENCE='${var.enable_state_persistence}' \ ARG_STATE_FILE_PATH='${var.state_file_path}' \ ARG_PID_FILE_PATH='${var.pid_file_path}' \ /tmp/main.sh EOT run_on_start = true + depends_on = [module.agent-helper] } resource "coder_script" "agentapi_shutdown" { + count = var.enable_agentapi ? 1 : 0 agent_id = var.agent_id display_name = "AgentAPI Shutdown" icon = var.web_app_icon @@ -305,6 +385,7 @@ resource "coder_script" "agentapi_shutdown" { } resource "coder_app" "agentapi_web" { + count = var.enable_agentapi ? 1 : 0 slug = var.web_app_slug display_name = var.web_app_display_name agent_id = var.agent_id @@ -320,7 +401,7 @@ resource "coder_app" "agentapi_web" { } } -resource "coder_app" "agentapi_cli" { +resource "coder_app" "agent_cli" { count = var.cli_app ? 1 : 0 slug = var.cli_app_slug @@ -333,7 +414,11 @@ resource "coder_app" "agentapi_cli" { export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 + %{if var.enable_agentapi~} agentapi attach + %{else} + ${local.module_dir_path}/agent-command.sh + %{endif} EOT icon = var.cli_app_icon order = var.cli_app_order @@ -341,5 +426,5 @@ resource "coder_app" "agentapi_cli" { } output "task_app_id" { - value = coder_app.agentapi_web.id + value = var.enable_agentapi ? coder_app.agentapi_web[0].id : null } diff --git a/registry/coder/modules/agentapi/main.tftest.hcl b/registry/coder/modules/agentapi/main.tftest.hcl new file mode 100644 index 000000000..d0ff778e0 --- /dev/null +++ b/registry/coder/modules/agentapi/main.tftest.hcl @@ -0,0 +1,213 @@ +# Test agentapi module with default settings (enable_agentapi = true) +run "test_agentapi_defaults" { + command = plan + + variables { + agent_id = "test-agent-id" + agent_name = "claude" + module_dir_name = ".agentapi-module" + web_app_display_name = "AgentAPI Web" + web_app_slug = "agentapi-web" + web_app_icon = "/icon/coder.svg" + cli_app_display_name = "AgentAPI CLI" + cli_app_slug = "agentapi-cli" + install_script = "echo 'install'" + start_script = "echo 'start'" + } + + assert { + condition = length(coder_script.agentapi) == 1 + error_message = "AgentAPI script should be created when enable_agentapi is true" + } + + assert { + condition = coder_script.agentapi[0].agent_id == "test-agent-id" + error_message = "AgentAPI script agent ID should match input" + } + + assert { + condition = coder_script.agentapi[0].display_name == "Start AgentAPI" + error_message = "AgentAPI script should have correct display name" + } + + assert { + condition = coder_script.agentapi[0].run_on_start == true + error_message = "AgentAPI script should run on start" + } + + assert { + condition = length(coder_script.agentapi_shutdown) == 1 + error_message = "AgentAPI shutdown script should be created when enable_agentapi is true" + } + + assert { + condition = coder_script.agentapi_shutdown[0].run_on_stop == true + error_message = "AgentAPI shutdown script should run on stop" + } + + assert { + condition = length(coder_app.agentapi_web) == 1 + error_message = "AgentAPI web app should be created when enable_agentapi is true" + } + + assert { + condition = coder_app.agentapi_web[0].slug == "agentapi-web" + error_message = "AgentAPI web app slug should match input" + } + + assert { + condition = coder_app.agentapi_web[0].subdomain == true + error_message = "AgentAPI web app should use subdomain by default" + } + + assert { + condition = length(coder_app.agent_cli) == 0 + error_message = "CLI app should not be created by default" + } +} + +# Test with enable_agentapi = false +run "test_agentapi_disabled" { + command = plan + + variables { + agent_id = "test-agent-id" + agent_name = "claude" + module_dir_name = ".agentapi-module" + web_app_display_name = "AgentAPI Web" + web_app_slug = "agentapi-web" + web_app_icon = "/icon/coder.svg" + cli_app_display_name = "AgentAPI CLI" + cli_app_slug = "agentapi-cli" + install_script = "echo 'install'" + start_script = "echo 'start'" + enable_agentapi = false + } + + assert { + condition = length(coder_script.agentapi) == 0 + error_message = "AgentAPI script should not be created when enable_agentapi is false" + } + + assert { + condition = length(coder_script.agentapi_shutdown) == 0 + error_message = "AgentAPI shutdown script should not be created when enable_agentapi is false" + } + + assert { + condition = length(coder_app.agentapi_web) == 0 + error_message = "AgentAPI web app should not be created when enable_agentapi is false" + } +} + +# Test with CLI app enabled +run "test_cli_app_enabled" { + command = plan + + variables { + agent_id = "test-agent-id" + agent_name = "claude" + module_dir_name = ".agentapi-module" + web_app_display_name = "AgentAPI Web" + web_app_slug = "agentapi-web" + web_app_icon = "/icon/coder.svg" + cli_app_display_name = "AgentAPI CLI" + cli_app_slug = "agentapi-cli" + install_script = "echo 'install'" + start_script = "echo 'start'" + cli_app = true + } + + assert { + condition = length(coder_app.agent_cli) == 1 + error_message = "CLI app should be created when cli_app is true" + } + + assert { + condition = coder_app.agent_cli[0].slug == "agentapi-cli" + error_message = "CLI app slug should match input" + } + + assert { + condition = coder_app.agent_cli[0].display_name == "AgentAPI CLI" + error_message = "CLI app display name should match input" + } +} + +# Test custom port +run "test_custom_port" { + command = plan + + variables { + agent_id = "test-agent-id" + agent_name = "claude" + module_dir_name = ".agentapi-module" + web_app_display_name = "AgentAPI Web" + web_app_slug = "agentapi-web" + web_app_icon = "/icon/coder.svg" + cli_app_display_name = "AgentAPI CLI" + cli_app_slug = "agentapi-cli" + install_script = "echo 'install'" + start_script = "echo 'start'" + agentapi_port = 4000 + } + + assert { + condition = coder_app.agentapi_web[0].url == "http://localhost:4000/" + error_message = "AgentAPI web app URL should use custom port" + } + + assert { + condition = one([for h in coder_app.agentapi_web[0].healthcheck : h.url]) == "http://localhost:4000/status" + error_message = "AgentAPI healthcheck URL should use custom port" + } +} + +# Test subdomain false validation rejects old versions +run "test_subdomain_false_rejects_old_version" { + command = plan + + variables { + agent_id = "test-agent-id" + agent_name = "claude" + module_dir_name = ".agentapi-module" + web_app_display_name = "AgentAPI Web" + web_app_slug = "agentapi-web" + web_app_icon = "/icon/coder.svg" + cli_app_display_name = "AgentAPI CLI" + cli_app_slug = "agentapi-cli" + install_script = "echo 'install'" + start_script = "echo 'start'" + agentapi_subdomain = false + agentapi_version = "v0.3.2" + } + + expect_failures = [ + var.agentapi_subdomain, + ] +} + +# Test subdomain false with valid version +run "test_subdomain_false_allows_valid_version" { + command = plan + + variables { + agent_id = "test-agent-id" + agent_name = "claude" + module_dir_name = ".agentapi-module" + web_app_display_name = "AgentAPI Web" + web_app_slug = "agentapi-web" + web_app_icon = "/icon/coder.svg" + cli_app_display_name = "AgentAPI CLI" + cli_app_slug = "agentapi-cli" + install_script = "echo 'install'" + start_script = "echo 'start'" + agentapi_subdomain = false + agentapi_version = "v0.3.3" + } + + assert { + condition = coder_app.agentapi_web[0].subdomain == false + error_message = "AgentAPI web app should not use subdomain" + } +} diff --git a/registry/coder/modules/agentapi/scripts/agentapi-wait-for-start.sh b/registry/coder/modules/agentapi/scripts/agentapi-wait-for-start.sh index 6ae5b14a3..084677fff 100644 --- a/registry/coder/modules/agentapi/scripts/agentapi-wait-for-start.sh +++ b/registry/coder/modules/agentapi/scripts/agentapi-wait-for-start.sh @@ -11,14 +11,14 @@ max_attempts=150 agentapi_started=false -echo "Waiting for agentapi server to start on port $port..." -for i in $(seq 1 "$max_attempts"); do +echo "Waiting for agentapi server to start on port ${port}..." +for i in $(seq 1 "${max_attempts}"); do for j in $(seq 1 3); do sleep 0.1 - if curl -fs -o /dev/null "http://localhost:$port/status"; then - echo "agentapi response received ($j/3)" + if curl -fs -o /dev/null "http://localhost:${port}/status"; then + echo "agentapi response received (${j}/3)" else - echo "agentapi server not responding ($i/$max_attempts)" + echo "agentapi server not responding (${i}/${max_attempts})" continue 2 fi done @@ -26,9 +26,9 @@ for i in $(seq 1 "$max_attempts"); do break done -if [ "$agentapi_started" != "true" ]; then - echo "Error: agentapi server did not start on port $port after $max_attempts attempts." +if [[ "${agentapi_started}" != "true" ]]; then + echo "Error: agentapi server did not start on port ${port} after ${max_attempts} attempts." exit 1 fi -echo "agentapi server started on port $port." +echo "agentapi server started on port ${port}." diff --git a/registry/coder/modules/agentapi/scripts/main.sh b/registry/coder/modules/agentapi/scripts/main.sh index b0afa24af..824441fb5 100644 --- a/registry/coder/modules/agentapi/scripts/main.sh +++ b/registry/coder/modules/agentapi/scripts/main.sh @@ -5,14 +5,14 @@ set -x set -o nounset MODULE_DIR_NAME="$ARG_MODULE_DIR_NAME" WORKDIR="$ARG_WORKDIR" -PRE_INSTALL_SCRIPT="$ARG_PRE_INSTALL_SCRIPT" -INSTALL_SCRIPT="$ARG_INSTALL_SCRIPT" INSTALL_AGENTAPI="$ARG_INSTALL_AGENTAPI" AGENTAPI_VERSION="$ARG_AGENTAPI_VERSION" -START_SCRIPT="$ARG_START_SCRIPT" WAIT_FOR_START_SCRIPT="$ARG_WAIT_FOR_START_SCRIPT" -POST_INSTALL_SCRIPT="$ARG_POST_INSTALL_SCRIPT" AGENTAPI_PORT="$ARG_AGENTAPI_PORT" +AGENTAPI_SERVER_TYPE="$ARG_AGENTAPI_SERVER_TYPE" +AGENTAPI_TERM_WIDTH="$ARG_AGENTAPI_TERM_WIDTH" +AGENTAPI_TERM_HEIGHT="$ARG_AGENTAPI_TERM_HEIGHT" +AGENTAPI_INITIAL_PROMPT="${ARG_AGENTAPI_INITIAL_PROMPT:-}" AGENTAPI_CHAT_BASE_PATH="${ARG_AGENTAPI_CHAT_BASE_PATH:-}" TASK_ID="${ARG_TASK_ID:-}" TASK_LOG_SNAPSHOT="${ARG_TASK_LOG_SNAPSHOT:-true}" @@ -48,17 +48,6 @@ if [ ! -d "${WORKDIR}" ]; then mkdir -p "${WORKDIR}" echo "Folder created successfully." fi -if [ -n "${PRE_INSTALL_SCRIPT}" ]; then - echo "Running pre-install script..." - echo -n "${PRE_INSTALL_SCRIPT}" > "$module_path/pre_install.sh" - chmod +x "$module_path/pre_install.sh" - "$module_path/pre_install.sh" 2>&1 | tee "$module_path/pre_install.log" -fi - -echo "Running install script..." -echo -n "${INSTALL_SCRIPT}" > "$module_path/install.sh" -chmod +x "$module_path/install.sh" -"$module_path/install.sh" 2>&1 | tee "$module_path/install.log" # Install AgentAPI if enabled if [ "${INSTALL_AGENTAPI}" = "true" ]; then @@ -96,18 +85,9 @@ if ! command_exists agentapi; then exit 1 fi -echo -n "${START_SCRIPT}" > "$module_path/scripts/agentapi-start.sh" echo -n "${WAIT_FOR_START_SCRIPT}" > "$module_path/scripts/agentapi-wait-for-start.sh" -chmod +x "$module_path/scripts/agentapi-start.sh" chmod +x "$module_path/scripts/agentapi-wait-for-start.sh" -if [ -n "${POST_INSTALL_SCRIPT}" ]; then - echo "Running post-install script..." - echo -n "${POST_INSTALL_SCRIPT}" > "$module_path/post_install.sh" - chmod +x "$module_path/post_install.sh" - "$module_path/post_install.sh" 2>&1 | tee "$module_path/post_install.log" -fi - export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 @@ -138,5 +118,20 @@ if [ "${ENABLE_STATE_PERSISTENCE}" = "true" ]; then echo "Warning: State persistence requires agentapi >= v0.12.0 (current: ${actual_version:-unknown}), skipping." fi fi -nohup "$module_path/scripts/agentapi-start.sh" true "${AGENTAPI_PORT}" &> "$module_path/agentapi-start.log" & + +# Build agentapi server command arguments +ARGS=( + "server" + "--type" "${AGENTAPI_SERVER_TYPE}" + "--port" "${AGENTAPI_PORT}" + "--term-width" "${AGENTAPI_TERM_WIDTH}" + "--term-height" "${AGENTAPI_TERM_HEIGHT}" +) +if [ -n "${AGENTAPI_INITIAL_PROMPT}" ]; then + ARGS+=("--initial-prompt" "${AGENTAPI_INITIAL_PROMPT}") +fi + +# Start agentapi server with the agent-command.sh script +nohup agentapi "${ARGS[@]}" -- "$module_path/agent-command.sh" &> "$module_path/agentapi-start.log" & + "$module_path/scripts/agentapi-wait-for-start.sh" "${AGENTAPI_PORT}" diff --git a/registry/coder/modules/agentapi/test-util.ts b/registry/coder/modules/agentapi/test-util.ts index 85d1bddd8..d9afb3367 100644 --- a/registry/coder/modules/agentapi/test-util.ts +++ b/registry/coder/modules/agentapi/test-util.ts @@ -115,6 +115,13 @@ export const setup = async ( }); props.registerCleanup(cleanup); await execContainer(id, ["bash", "-c", `mkdir -p '${projectDir}'`]); + // Add a mock coder CLI so that `coder exp sync` commands in the + // startup script succeed inside the test container. + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/coder", + content: "#!/bin/bash\nexit 0", + }); if (!props?.skipAgentAPIMock) { await writeExecutable({ containerId: id, diff --git a/registry/coder/modules/agentapi/testdata/agentapi-mock.js b/registry/coder/modules/agentapi/testdata/agentapi-mock.js index e2e2d560d..48614c225 100644 --- a/registry/coder/modules/agentapi/testdata/agentapi-mock.js +++ b/registry/coder/modules/agentapi/testdata/agentapi-mock.js @@ -14,7 +14,7 @@ if (args.includes("--version")) { console.log(`starting server on port ${port}`); fs.writeFileSync( "/home/coder/agentapi-mock.log", - `AGENTAPI_ALLOWED_HOSTS: ${process.env.AGENTAPI_ALLOWED_HOSTS}`, + `AGENTAPI_ALLOWED_HOSTS: ${process.env.AGENTAPI_ALLOWED_HOSTS}\nAGENTAPI_CHAT_BASE_PATH: ${process.env.AGENTAPI_CHAT_BASE_PATH || "not set"}`, ); // Log state persistence env vars.