diff --git a/registry/coder/modules/agentapi/README.md b/registry/coder/modules/agentapi/README.md index a2151f6c..cdec9590 100644 --- a/registry/coder/modules/agentapi/README.md +++ b/registry/coder/modules/agentapi/README.md @@ -1,6 +1,6 @@ --- display_name: AgentAPI -description: Building block for modules that need to run an AgentAPI server +description: Building block for modules that need to run an AgentAPI server. icon: ../../../../.icons/coder.svg verified: true tags: [internal, library] @@ -11,47 +11,47 @@ tags: [internal, library] > [!CAUTION] > We do not recommend using this module directly. Instead, please consider using one of our [Tasks-compatible AI agent modules](https://registry.coder.com/modules?search=tag%3Atasks). -The AgentAPI module is a building block for modules that need to run an AgentAPI server. It is intended primarily for internal use by Coder to create modules compatible with Tasks. +The AgentAPI module is a building block for modules that need to run an [AgentAPI](https://github.com/coder/agentapi) server. It is intended primarily for internal use by Coder to create modules compatible with [Tasks](https://coder.com/docs/ai-coder/tasks). ```tf module "agentapi" { source = "registry.coder.com/coder/agentapi/coder" - version = "2.4.0" + version = "2.5.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" + cli_app_slug = "goose-cli" module_directory = local.module_directory install_agentapi = var.install_agentapi } ``` -## Task log snapshot +## Features -Captures the last 10 messages from AgentAPI when a task workspace stops. This allows viewing conversation history while the task is paused. +- **Web and CLI apps**: creates `coder_app` resources for browser-based chat and terminal attachment +- **Task log snapshot**: captures the last 10 conversation messages when a workspace stops, enabling offline viewing while the task is paused +- **State persistence**: optionally saves and restores AgentAPI conversation state across workspace restarts (requires agentapi >= v0.12.0) +- **Script orchestration**: uses [coder-utils](https://registry.coder.com/modules/coder/coder-utils) for `coder exp sync` based script ordering so downstream modules can serialize their own scripts behind this module -To enable for task workspaces: +## Examples + +### Task log snapshot + +Enabled by default. Captures the last 10 messages from AgentAPI when a task workspace stops. ```tf module "agentapi" { # ... other config - task_log_snapshot = true # default: true + task_log_snapshot = true # default } ``` -## State Persistence +### State persistence -AgentAPI can save and restore conversation state across workspace restarts. -This is disabled by default and requires agentapi binary >= v0.12.0. - -State and PID files are stored in the `module_directory` alongside other module files (e.g. `$HOME/.coder-modules/coder/claude-code/agentapi-state.json`). - -To enable: +Disabled by default. Requires agentapi >= v0.12.0. ```tf module "agentapi" { @@ -60,16 +60,38 @@ module "agentapi" { } ``` -To override file paths: +Custom file paths: ```tf module "agentapi" { # ... other config - state_file_path = "/custom/path/state.json" - pid_file_path = "/custom/path/agentapi.pid" + enable_state_persistence = true + state_file_path = "/custom/path/state.json" + pid_file_path = "/custom/path/agentapi.pid" +} +``` + +### Script serialization + +The module outputs `scripts`, an ordered list of `coder exp sync` names. Downstream modules can use these to serialize their own `coder_script` resources behind the install pipeline: + +```tf +module "agentapi" { + source = "registry.coder.com/coder/agentapi/coder" + # ... +} + +output "scripts" { + value = module.agentapi.scripts } ``` ## 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). +For a complete example of how to build a module on top of AgentAPI, see the [Goose module](https://github.com/coder/registry/blob/main/registry/coder/modules/goose/main.tf). + +## Troubleshooting + +- Install logs are written to `~/.coder-modules/coder/agentapi/logs/install.log` +- AgentAPI server logs are written to `~/.coder-modules/coder/agentapi/agentapi-start.log` +- Check `agentapi --version` to verify the installed binary version diff --git a/registry/coder/modules/agentapi/main.test.ts b/registry/coder/modules/agentapi/main.test.ts index d529f85a..3d3037cf 100644 --- a/registry/coder/modules/agentapi/main.test.ts +++ b/registry/coder/modules/agentapi/main.test.ts @@ -67,6 +67,13 @@ const setup = async (props?: SetupProps): Promise<{ id: string }> => { skipAgentAPIMock: props?.skipAgentAPIMock, moduleDir: import.meta.dir, }); + // Mock `coder` CLI so `coder exp sync` calls from coder-utils wrappers + // succeed without a real control plane. + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/coder", + content: "#!/bin/bash\nexit 0\n", + }); await writeExecutable({ containerId: id, filePath: "/usr/bin/aiagent", diff --git a/registry/coder/modules/agentapi/test-util.ts b/registry/coder/modules/agentapi/test-util.ts index 85d1bddd..4e4a8de1 100644 --- a/registry/coder/modules/agentapi/test-util.ts +++ b/registry/coder/modules/agentapi/test-util.ts @@ -46,7 +46,25 @@ export const setupContainer = async ({ agent_id: "foo", ...vars, }); - const coderScript = findResourceInstance(state, "coder_script"); + // Find the run_on_start script. With coder-utils the install script lives + // inside a module and the shutdown script is a separate resource, so we + // pick the first coder_script that has run_on_start = true. + let coderScript: { script: string; [k: string]: unknown } | undefined; + for (const resource of state.resources) { + if (resource.type !== "coder_script") continue; + for (const instance of resource.instances) { + const attrs = instance.attributes as Record; + if (attrs.run_on_start === true) { + coderScript = attrs as { script: string; [k: string]: unknown }; + break; + } + } + if (coderScript) break; + } + if (!coderScript) { + // Fallback to original behavior for backwards compatibility. + coderScript = findResourceInstance(state, "coder_script"); + } const coderEnvVars = extractCoderEnvVars(state); const id = await runContainer(image ?? "codercom/enterprise-node:latest"); return {